Bug 1438678 - Pass early prefs via shared memory instead of the command line. r=aklotz,jld,glandium. draft
authorNicholas Nethercote <nnethercote@mozilla.com>
Fri, 16 Feb 2018 17:54:21 +1100
changeset 757694 01027a1bf952d8ed43c50490b1892cf6b32971a8
parent 757692 11e054d0b64941177165273ba5f4319d813ff3a9
push id99836
push usernnethercote@mozilla.com
push dateWed, 21 Feb 2018 05:34:51 +0000
reviewersaklotz, jld, glandium
bugs1438678, 1436911
milestone60.0a1
Bug 1438678 - Pass early prefs via shared memory instead of the command line. r=aklotz,jld,glandium. This patch replaces the large -intPrefs/-boolPrefs/-stringPrefs flags with -prefsHandle (on Windows) and -prefsLen (on all platforms), which describe a short-lived, anonymous, shared memory mapping that is used to pass the early prefs. Removing the bloat from the command line is nice, but more important is the fact that this will let us pass more prefs at content process start-up, which will allow us to remove the early/late prefs split (bug 1436911). Although this mechanism is only used for prefs, it's conceivable that it could be used for other data that must be received very early by children, and for which the command line isn't ideal. MozReview-Commit-ID: 2F80HG1pQ3u * * * [mq]: shmem-redo MozReview-Commit-ID: c6jrLBaUNP
dom/ipc/ContentParent.cpp
dom/ipc/ContentPrefs.h
dom/ipc/ContentProcess.cpp
ipc/glue/GeckoChildProcessHost.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/DebugOnly.h"
 
 #include "base/basictypes.h"
+#include "base/shared_memory.h"
 
 #include "ContentParent.h"
 #include "TabParent.h"
 
 #if defined(ANDROID) || defined(LINUX)
 # include <sys/time.h>
 # include <sys/resource.h>
 #endif
@@ -1996,20 +1997,20 @@ ContentParent::LaunchSubprocess(ProcessP
   SprintfLiteral(idStr, "%" PRId64, static_cast<uint64_t>(mChildID));
   extraArgs.push_back(idStr);
   extraArgs.push_back(IsForBrowser() ? "-isForBrowser" : "-notForBrowser");
 
   nsAutoCStringN<1024> boolPrefs;
   nsAutoCStringN<1024> intPrefs;
   nsAutoCStringN<1024> stringPrefs;
 
-  size_t prefsLen;
-  ContentPrefs::GetEarlyPrefs(&prefsLen);
-
-  for (unsigned int i = 0; i < prefsLen; i++) {
+  size_t numEarlyPrefs;
+  ContentPrefs::GetEarlyPrefs(&numEarlyPrefs);
+
+  for (unsigned int i = 0; i < numEarlyPrefs; i++) {
     const char* prefName = ContentPrefs::GetEarlyPref(i);
     MOZ_ASSERT_IF(i > 0,
                   strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0);
 
     if (!Preferences::MustSendToContentProcesses(prefName)) {
       continue;
     }
 
@@ -2029,42 +2030,79 @@ ContentParent::LaunchSubprocess(ProcessP
     case nsIPrefBranch::PREF_INVALID:
       break;
     default:
       printf("preference type: %x\n", Preferences::GetType(prefName));
       MOZ_CRASH();
     }
   }
 
-  nsCString schedulerPrefs = Scheduler::GetPrefs();
-
-  // Only do these ones if they're non-empty.
-  if (!intPrefs.IsEmpty()) {
-    extraArgs.push_back("-intPrefs");
-    extraArgs.push_back(intPrefs.get());
-  }
-  if (!boolPrefs.IsEmpty()) {
-    extraArgs.push_back("-boolPrefs");
-    extraArgs.push_back(boolPrefs.get());
-  }
-  if (!stringPrefs.IsEmpty()) {
-    extraArgs.push_back("-stringPrefs");
-    extraArgs.push_back(stringPrefs.get());
-  }
+  // Prefs information is passed via anonymous shared memory to avoid bloating
+  // the command line.
+
+  // +2 for two newlines and +1 for the terminating null.
+  size_t prefsLen =
+    intPrefs.Length() + boolPrefs.Length() + stringPrefs.Length() + 3;
+
+  base::SharedMemory shm;
+  if (!shm.Create("", /* read_only */ false, /* open_existing */ false,
+                  prefsLen)) {
+    printf_stderr("failed to create shared memory in the parent\n");//njn:tmp
+    NS_ERROR("failed to create shared memory in the parent");
+    MarkAsDead();
+    return false;
+  }
+  if (!shm.Map(prefsLen)) {
+    printf_stderr("failed to map shared memory in the parent\n");//njn:tmp
+    NS_ERROR("failed to map shared memory in the parent");
+    MarkAsDead();
+    return false;
+  }
+
+  auto p = static_cast<char*>(shm.memory());
+  memcpy(p, intPrefs.get(), intPrefs.Length());
+  p += intPrefs.Length();
+  *p++ = '\n';
+  memcpy(p, boolPrefs.get(), boolPrefs.Length());
+  p += boolPrefs.Length();
+  *p++ = '\n';
+  memcpy(p, stringPrefs.get(), stringPrefs.Length());
+  p += stringPrefs.Length();
+  *p = '\0';
+
+#if defined(XP_UNIX)
+  // On Unix we set up a remapping to a hard-coded fd in the child.
+  mSubprocess->AddFdToRemap(shm.handle().fd, kPrefsSharedMemoryFileDesc);
+#elif defined(XP_WIN)
+  // Mark the handle as shareable, and pass it via the command line.
+  HANDLE h = shm.handle();
+  mSubprocess->AddHandleToShare(h);
+  extraArgs.push_back("-prefsHandle");
+  extraArgs.push_back(
+    nsPrintfCString("%zu", reinterpret_cast<uintptr_t>(h)).get());
+#else
+# error "Unsupported platform"
+#endif
+
+  extraArgs.push_back("-prefsLen");
+  extraArgs.push_back(nsPrintfCString("%zu", prefsLen).get());
 
   // Scheduler prefs need to be handled differently because the scheduler needs
   // to start up in the content process before the normal preferences service.
+  nsCString schedulerPrefs = Scheduler::GetPrefs();
   extraArgs.push_back("-schedulerPrefs");
   extraArgs.push_back(schedulerPrefs.get());
 
   if (gSafeMode) {
     extraArgs.push_back("-safeMode");
   }
 
   if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
+    printf_stderr("failed to launch child in the parent\n");//njn:tmp
+    NS_ERROR("failed to launch child in the parent");
     MarkAsDead();
     return false;
   }
 
   base::ProcessId procId = base::GetProcId(mSubprocess->GetChildProcessHandle());
 
   Open(mSubprocess->GetChannel(), procId);
 
--- a/dom/ipc/ContentPrefs.h
+++ b/dom/ipc/ContentPrefs.h
@@ -7,16 +7,22 @@
 #ifndef mozilla_dom_ContentPrefs_h
 #define mozilla_dom_ContentPrefs_h
 
 // See the comment in ContentPrefs.cpp for more information.
 
 namespace mozilla {
 namespace dom {
 
+#ifdef XP_UNIX
+// File descriptors 0..7 are used for other purposes. We use 8 for the shared
+// memory used to pass prefs to content processes.
+static const int kPrefsSharedMemoryFileDesc = 8;
+#endif
+
 class ContentPrefs {
 public:
   static const char** GetEarlyPrefs(size_t* aCount);
   static const char* GetEarlyPref(size_t aIndex);
 
 private:
   static const char* gEarlyPrefs[];
 };
--- a/dom/ipc/ContentProcess.cpp
+++ b/dom/ipc/ContentProcess.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ipc/IOThreadChild.h"
 
 #include "ContentProcess.h"
 #include "ContentPrefs.h"
+#include "base/shared_memory.h"
 #include "mozilla/Scheduler.h"
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
 #include <stdlib.h>
 #endif
 
 #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
 #include "mozilla/Preferences.h"
@@ -83,31 +84,38 @@ SetUpSandboxEnvironment()
 
 bool
 ContentProcess::Init(int aArgc, char* aArgv[])
 {
   // If passed in grab the application path for xpcom init
   bool foundAppdir = false;
   bool foundChildID = false;
   bool foundIsForBrowser = false;
-  bool foundIntPrefs = false;
-  bool foundBoolPrefs = false;
-  bool foundStringPrefs = false;
+  bool foundPrefsHandle = false;
+  bool foundPrefsLen = false;
   bool foundSchedulerPrefs = false;
 
   uint64_t childID;
   bool isForBrowser;
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
   // If passed in grab the profile path for sandboxing
   bool foundProfile = false;
   nsCOMPtr<nsIFile> profileDir;
 #endif
 
   char* schedulerPrefs = nullptr;
+  base::SharedMemoryHandle prefsHandle;
+#ifdef XP_UNIX
+  // On Unix we use a hard-coded fd.
+  prefsHandle = base::FileDescriptor(kPrefsSharedMemoryFileDesc,
+                                     /* auto_close */ true);
+  foundPrefsHandle = true;
+#endif
+  size_t prefsLen = 0;
   InfallibleTArray<Pref> prefsArray;
   for (int idx = aArgc; idx > 0; idx--) {
     if (!aArgv[idx]) {
       continue;
     }
 
     if (!strcmp(aArgv[idx], "-appdir")) {
       MOZ_ASSERT(!foundAppdir);
@@ -129,64 +137,31 @@ ContentProcess::Init(int aArgc, char* aA
       }
     } else if (!strcmp(aArgv[idx], "-isForBrowser") || !strcmp(aArgv[idx], "-notForBrowser")) {
       MOZ_ASSERT(!foundIsForBrowser);
       if (foundIsForBrowser) {
         continue;
       }
       isForBrowser = strcmp(aArgv[idx], "-notForBrowser");
       foundIsForBrowser = true;
-    } else if (!strcmp(aArgv[idx], "-intPrefs")) {
-      char* str = aArgv[idx + 1];
-      while (*str) {
-        int32_t index = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ':');
-        str++;
-        MaybePrefValue value(PrefValue(static_cast<int32_t>(strtol(str, &str, 10))));
-        MOZ_ASSERT(str[0] == '|');
-        str++;
-        // XXX: we assume these values as default values, which may not be
-        // true. We also assume they are unlocked. Fortunately, these prefs
-        // get reset properly by the first IPC message.
-        Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
-                  /* isLocked */ false, value, MaybePrefValue());
-        prefsArray.AppendElement(pref);
-      }
-      foundIntPrefs = true;
-    } else if (!strcmp(aArgv[idx], "-boolPrefs")) {
+#if defined(XP_WIN)
+    } else if (!strcmp(aArgv[idx], "-prefsHandle")) {
       char* str = aArgv[idx + 1];
-      while (*str) {
-        int32_t index = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ':');
-        str++;
-        MaybePrefValue value(PrefValue(!!strtol(str, &str, 10)));
-        MOZ_ASSERT(str[0] == '|');
-        str++;
-        Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
-                  /* isLocked */ false, value, MaybePrefValue());
-        prefsArray.AppendElement(pref);
-      }
-      foundBoolPrefs = true;
-    } else if (!strcmp(aArgv[idx], "-stringPrefs")) {
+      MOZ_ASSERT(str[0] != '\0');
+      uintptr_t h = strtoll(str, &str, 10);
+      prefsHandle = reinterpret_cast<HANDLE>(h);
+      MOZ_ASSERT(str[0] == '\0');
+      foundPrefsHandle = true;
+#endif
+    } else if (!strcmp(aArgv[idx], "-prefsLen")) {
       char* str = aArgv[idx + 1];
-      while (*str) {
-        int32_t index = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ':');
-        str++;
-        int32_t length = strtol(str, &str, 10);
-        MOZ_ASSERT(str[0] == ';');
-        str++;
-        MaybePrefValue value(PrefValue(nsCString(str, length)));
-        Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
-                  /* isLocked */ false, value, MaybePrefValue());
-        prefsArray.AppendElement(pref);
-        str += length + 1;
-        MOZ_ASSERT(*(str - 1) == '|');
-      }
-      foundStringPrefs = true;
+      MOZ_ASSERT(str[0] != '\0');
+      prefsLen = strtol(str, &str, 10);
+      MOZ_ASSERT(str[0] == '\0');
+      foundPrefsLen = true;
     } else if (!strcmp(aArgv[idx], "-schedulerPrefs")) {
       schedulerPrefs = aArgv[idx + 1];
       foundSchedulerPrefs = true;
     } else if (!strcmp(aArgv[idx], "-safeMode")) {
       gSafeMode = true;
     }
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
@@ -204,30 +179,97 @@ ContentProcess::Init(int aArgc, char* aA
       }
       foundProfile = true;
     }
 #endif /* XP_MACOSX && MOZ_CONTENT_SANDBOX */
 
     bool allFound = foundAppdir
                  && foundChildID
                  && foundIsForBrowser
-                 && foundIntPrefs
-                 && foundBoolPrefs
-                 && foundStringPrefs
+                 && foundPrefsHandle
+                 && foundPrefsLen
                  && foundSchedulerPrefs;
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
     allFound &= foundProfile;
 #endif
 
     if (allFound) {
       break;
     }
   }
 
+  // Get data from the shared memory.
+  base::SharedMemory shm;
+  if (!shm.SetHandle(prefsHandle, /* read_only */ true)) {
+    printf_stderr("failed to open shared memory in the child\n");//njn:tmp
+    NS_ERROR("failed to open shared memory in the child");
+    return false;
+  }
+  if (!shm.Map(prefsLen)) {
+    printf_stderr("failed to map shared memory in the child\n");//njn:tmp
+    NS_ERROR("failed to map shared memory in the child");
+    return false;
+  }
+
+  // Get the int prefs.
+  auto str = static_cast<char*>(shm.memory());
+#ifdef DEBUG
+  char* str0 = str;
+#endif
+  while (*str != '\n') {
+    int32_t index = strtol(str, &str, 10);
+    MOZ_ASSERT(str[0] == ':');
+    str++;
+    MaybePrefValue value(PrefValue(static_cast<int32_t>(strtol(str, &str, 10))));
+    MOZ_ASSERT(str[0] == '|');
+    str++;
+    // XXX: we assume these values are default values, which may not be true.
+    // We also assume they are unlocked. Fortunately, these prefs get reset
+    // properly by the first IPC message.
+    Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
+              /* isLocked */ false, value, MaybePrefValue());
+    prefsArray.AppendElement(pref);
+  }
+  str++;
+
+  // Get the bool prefs.
+  while (*str != '\n') {
+    int32_t index = strtol(str, &str, 10);
+    MOZ_ASSERT(str[0] == ':');
+    str++;
+    MaybePrefValue value(PrefValue(!!strtol(str, &str, 10)));
+    MOZ_ASSERT(str[0] == '|');
+    str++;
+    Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
+              /* isLocked */ false, value, MaybePrefValue());
+    prefsArray.AppendElement(pref);
+  }
+  str++;
+
+  // Get the string prefs.
+  while (*str) {
+    int32_t index = strtol(str, &str, 10);
+    MOZ_ASSERT(str[0] == ':');
+    str++;
+    int32_t length = strtol(str, &str, 10);
+    MOZ_ASSERT(str[0] == ';');
+    str++;
+    MaybePrefValue value(PrefValue(nsCString(str, length)));
+    Pref pref(nsCString(ContentPrefs::GetEarlyPref(index)),
+              /* isLocked */ false, value, MaybePrefValue());
+    prefsArray.AppendElement(pref);
+    str += length + 1;
+    MOZ_ASSERT(*(str - 1) == '|');
+  }
+
+  // We finished parsing on a '\0'. That should be the last char in the shared
+  // memory.
+  MOZ_ASSERT(str0 + prefsLen - 1 == str);
+
   Preferences::SetEarlyPreferences(&prefsArray);
   Scheduler::SetPrefs(schedulerPrefs);
   mContent.Init(IOThreadChild::message_loop(),
                 ParentPid(),
                 IOThreadChild::channel(),
                 childID,
                 isForBrowser);
   mXREEmbed.Start();
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -103,16 +103,21 @@ public:
   }
 #endif
 
 #ifdef XP_WIN
   void AddHandleToShare(HANDLE aHandle) {
     mLaunchOptions->handles_to_inherit.push_back(aHandle);
   }
 #endif
+#ifdef XP_UNIX
+  void AddFdToRemap(int aSrcFd, int aDstFd) {
+    mLaunchOptions->fds_to_remap.push_back(std::make_pair(aSrcFd, aDstFd));
+  }
+#endif
 
   /**
    * Must run on the IO thread.  Cause the OS process to exit and
    * ensure its OS resources are cleaned up.
    */
   void Join();
 
   // For bug 943174: Skip the EnsureProcessTerminated call in the destructor.