mirror of
https://github.com/ManchildProductions/UXP-Fixed.git
synced 2026-05-28 02:08:42 +00:00
4105ebb6ed
This is mostly ifdefs, but as you can see, Solaris is actually a lot like Linux. They're both more SysV than BSD at core, and most of the differences have more to do with Solaris not using glibc than anything else. I still need to audit a lot of these changes and understand why they're needed and what the alternative approaches are. After this patch, most of the core functionality needed to build Solaris is here.
968 lines
30 KiB
C++
968 lines
30 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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 "GeckoChildProcessHost.h"
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/string_util.h"
|
|
#include "base/task.h"
|
|
#include "chrome/common/chrome_switches.h"
|
|
#include "chrome/common/process_watcher.h"
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
#include "chrome/common/mach_ipc_mac.h"
|
|
#include "base/rand_util.h"
|
|
#include "nsILocalFileMac.h"
|
|
#include "SharedMemoryBasic.h"
|
|
#endif
|
|
|
|
#include "MainThreadUtils.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "prenv.h"
|
|
#include "nsXPCOMPrivate.h"
|
|
|
|
#include "nsDirectoryServiceDefs.h"
|
|
#include "nsIFile.h"
|
|
#include "nsPrintfCString.h"
|
|
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/ipc/BrowserProcessSubThread.h"
|
|
#include "mozilla/Omnijar.h"
|
|
#include "ProtocolUtils.h"
|
|
#include <sys/stat.h>
|
|
|
|
#ifdef XP_WIN
|
|
#include "nsIWinTaskbar.h"
|
|
#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
|
|
#endif
|
|
|
|
#include "nsTArray.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsHashKeys.h"
|
|
#include "nsNativeCharsetUtils.h"
|
|
#include "nscore.h" // for NS_FREE_PERMANENT_DATA
|
|
|
|
using mozilla::MonitorAutoLock;
|
|
using mozilla::ipc::GeckoChildProcessHost;
|
|
|
|
#ifdef ANDROID
|
|
// Like its predecessor in nsExceptionHandler.cpp, this is
|
|
// the magic number of a file descriptor remapping we must
|
|
// preserve for the child process.
|
|
static const int kMagicAndroidSystemPropFd = 5;
|
|
#endif
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
#include "AndroidBridge.h"
|
|
#endif
|
|
|
|
static const bool kLowRightsSubprocesses =
|
|
// We only attempted to drop privileges on gonk, because it
|
|
// had no plugins or extensions to worry about breaking.
|
|
false
|
|
;
|
|
|
|
static bool
|
|
ShouldHaveDirectoryService()
|
|
{
|
|
return GeckoProcessType_Default == XRE_GetProcessType();
|
|
}
|
|
|
|
/*static*/
|
|
base::ChildPrivileges
|
|
GeckoChildProcessHost::DefaultChildPrivileges()
|
|
{
|
|
return (kLowRightsSubprocesses ?
|
|
base::PRIVILEGES_UNPRIVILEGED : base::PRIVILEGES_INHERIT);
|
|
}
|
|
|
|
GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType,
|
|
ChildPrivileges aPrivileges)
|
|
: mProcessType(aProcessType),
|
|
mPrivileges(aPrivileges),
|
|
mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"),
|
|
mProcessState(CREATING_CHANNEL),
|
|
mChildProcessHandle(0)
|
|
#if defined(MOZ_WIDGET_COCOA)
|
|
, mChildTask(MACH_PORT_NULL)
|
|
#endif
|
|
{
|
|
MOZ_COUNT_CTOR(GeckoChildProcessHost);
|
|
}
|
|
|
|
GeckoChildProcessHost::~GeckoChildProcessHost()
|
|
|
|
{
|
|
AssertIOThread();
|
|
|
|
MOZ_COUNT_DTOR(GeckoChildProcessHost);
|
|
|
|
if (mChildProcessHandle != 0) {
|
|
#if defined(MOZ_WIDGET_COCOA)
|
|
SharedMemoryBasic::CleanupForPid(mChildProcessHandle);
|
|
#endif
|
|
ProcessWatcher::EnsureProcessTerminated(mChildProcessHandle
|
|
#ifdef NS_FREE_PERMANENT_DATA
|
|
// If we're doing leak logging, shutdown can be slow.
|
|
, false // don't "force"
|
|
#endif
|
|
);
|
|
}
|
|
|
|
#if defined(MOZ_WIDGET_COCOA)
|
|
if (mChildTask != MACH_PORT_NULL)
|
|
mach_port_deallocate(mach_task_self(), mChildTask);
|
|
#endif
|
|
}
|
|
|
|
//static
|
|
auto
|
|
GeckoChildProcessHost::GetPathToBinary(FilePath& exePath, GeckoProcessType processType) -> BinaryPathType
|
|
{
|
|
if (sRunSelfAsContentProc &&
|
|
(processType == GeckoProcessType_Content || processType == GeckoProcessType_GPU)) {
|
|
#if defined(OS_WIN)
|
|
wchar_t exePathBuf[MAXPATHLEN];
|
|
if (!::GetModuleFileNameW(nullptr, exePathBuf, MAXPATHLEN)) {
|
|
MOZ_CRASH("GetModuleFileNameW failed (FIXME)");
|
|
}
|
|
exePath = FilePath::FromWStringHack(exePathBuf);
|
|
#elif defined(OS_POSIX)
|
|
exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]);
|
|
#else
|
|
# error Sorry; target OS not supported yet.
|
|
#endif
|
|
return BinaryPathType::Self;
|
|
}
|
|
|
|
if (ShouldHaveDirectoryService()) {
|
|
MOZ_ASSERT(gGREBinPath);
|
|
#ifdef OS_WIN
|
|
exePath = FilePath(char16ptr_t(gGREBinPath));
|
|
#elif MOZ_WIDGET_COCOA
|
|
nsCOMPtr<nsIFile> childProcPath;
|
|
NS_NewLocalFile(nsDependentString(gGREBinPath), false,
|
|
getter_AddRefs(childProcPath));
|
|
|
|
// We need to use an App Bundle on OS X so that we can hide
|
|
// the dock icon. See Bug 557225.
|
|
childProcPath->AppendNative(NS_LITERAL_CSTRING("plugin-container.app"));
|
|
childProcPath->AppendNative(NS_LITERAL_CSTRING("Contents"));
|
|
childProcPath->AppendNative(NS_LITERAL_CSTRING("MacOS"));
|
|
nsCString tempCPath;
|
|
childProcPath->GetNativePath(tempCPath);
|
|
exePath = FilePath(tempCPath.get());
|
|
#else
|
|
nsCString path;
|
|
NS_CopyUnicodeToNative(nsDependentString(gGREBinPath), path);
|
|
exePath = FilePath(path.get());
|
|
#endif
|
|
}
|
|
|
|
if (exePath.empty()) {
|
|
#ifdef OS_WIN
|
|
exePath = FilePath::FromWStringHack(CommandLine::ForCurrentProcess()->program());
|
|
#else
|
|
exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]);
|
|
#endif
|
|
exePath = exePath.DirName();
|
|
}
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
exePath = exePath.AppendASCII("lib");
|
|
|
|
// We must use the PIE binary on 5.0 and higher
|
|
const char* processName = mozilla::AndroidBridge::Bridge()->GetAPIVersion() >= 21 ?
|
|
MOZ_CHILD_PROCESS_NAME_PIE : MOZ_CHILD_PROCESS_NAME;
|
|
|
|
exePath = exePath.AppendASCII(processName);
|
|
#else
|
|
exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_NAME);
|
|
#endif
|
|
|
|
return BinaryPathType::PluginContainer;
|
|
}
|
|
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
class AutoCFTypeObject {
|
|
public:
|
|
explicit AutoCFTypeObject(CFTypeRef object)
|
|
{
|
|
mObject = object;
|
|
}
|
|
~AutoCFTypeObject()
|
|
{
|
|
::CFRelease(mObject);
|
|
}
|
|
private:
|
|
CFTypeRef mObject;
|
|
};
|
|
#endif
|
|
|
|
nsresult GeckoChildProcessHost::GetArchitecturesForBinary(const char *path, uint32_t *result)
|
|
{
|
|
*result = 0;
|
|
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
|
|
(const UInt8*)path,
|
|
strlen(path),
|
|
false);
|
|
if (!url) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
AutoCFTypeObject autoPluginContainerURL(url);
|
|
|
|
CFArrayRef pluginContainerArchs = ::CFBundleCopyExecutableArchitecturesForURL(url);
|
|
if (!pluginContainerArchs) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
AutoCFTypeObject autoPluginContainerArchs(pluginContainerArchs);
|
|
|
|
CFIndex pluginArchCount = ::CFArrayGetCount(pluginContainerArchs);
|
|
for (CFIndex i = 0; i < pluginArchCount; i++) {
|
|
CFNumberRef currentArch = static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(pluginContainerArchs, i));
|
|
int currentArchInt = 0;
|
|
if (!::CFNumberGetValue(currentArch, kCFNumberIntType, ¤tArchInt)) {
|
|
continue;
|
|
}
|
|
switch (currentArchInt) {
|
|
case kCFBundleExecutableArchitectureI386:
|
|
*result |= base::PROCESS_ARCH_I386;
|
|
break;
|
|
case kCFBundleExecutableArchitectureX86_64:
|
|
*result |= base::PROCESS_ARCH_X86_64;
|
|
break;
|
|
case kCFBundleExecutableArchitecturePPC:
|
|
*result |= base::PROCESS_ARCH_PPC;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (*result ? NS_OK : NS_ERROR_FAILURE);
|
|
#else
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
#endif
|
|
}
|
|
|
|
uint32_t GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType type)
|
|
{
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
if (type == GeckoProcessType_Plugin) {
|
|
|
|
// Cache this, it shouldn't ever change.
|
|
static uint32_t pluginContainerArchs = 0;
|
|
if (pluginContainerArchs == 0) {
|
|
FilePath exePath;
|
|
GetPathToBinary(exePath, type);
|
|
nsresult rv = GetArchitecturesForBinary(exePath.value().c_str(), &pluginContainerArchs);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv) && pluginContainerArchs != 0, "Getting architecture of plugin container failed!");
|
|
if (NS_FAILED(rv) || pluginContainerArchs == 0) {
|
|
pluginContainerArchs = base::GetCurrentProcessArchitecture();
|
|
}
|
|
}
|
|
return pluginContainerArchs;
|
|
}
|
|
#endif
|
|
|
|
return base::GetCurrentProcessArchitecture();
|
|
}
|
|
|
|
// We start the unique IDs at 1 so that 0 can be used to mean that
|
|
// a component has no unique ID assigned to it.
|
|
uint32_t GeckoChildProcessHost::sNextUniqueID = 1;
|
|
|
|
/* static */
|
|
uint32_t
|
|
GeckoChildProcessHost::GetUniqueID()
|
|
{
|
|
return sNextUniqueID++;
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::PrepareLaunch()
|
|
{
|
|
#ifdef XP_WIN
|
|
if (mProcessType == GeckoProcessType_Plugin) {
|
|
InitWindowsGroupID();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
void GeckoChildProcessHost::InitWindowsGroupID()
|
|
{
|
|
// On Win7+, pass the application user model to the child, so it can
|
|
// register with it. This insures windows created by the container
|
|
// properly group with the parent app on the Win7 taskbar.
|
|
nsCOMPtr<nsIWinTaskbar> taskbarInfo =
|
|
do_GetService(NS_TASKBAR_CONTRACTID);
|
|
if (taskbarInfo) {
|
|
bool isSupported = false;
|
|
taskbarInfo->GetAvailable(&isSupported);
|
|
nsAutoString appId;
|
|
if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) {
|
|
mGroupId.Append(appId);
|
|
} else {
|
|
mGroupId.Assign('-');
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
GeckoChildProcessHost::SyncLaunch(std::vector<std::string> aExtraOpts, int aTimeoutMs, base::ProcessArchitecture arch)
|
|
{
|
|
PrepareLaunch();
|
|
|
|
MessageLoop* ioLoop = XRE_GetIOMessageLoop();
|
|
NS_ASSERTION(MessageLoop::current() != ioLoop, "sync launch from the IO thread NYI");
|
|
|
|
ioLoop->PostTask(NewNonOwningRunnableMethod
|
|
<std::vector<std::string>, base::ProcessArchitecture>
|
|
(this, &GeckoChildProcessHost::RunPerformAsyncLaunch,
|
|
aExtraOpts, arch));
|
|
|
|
return WaitUntilConnected(aTimeoutMs);
|
|
}
|
|
|
|
bool
|
|
GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts,
|
|
base::ProcessArchitecture arch)
|
|
{
|
|
PrepareLaunch();
|
|
|
|
MessageLoop* ioLoop = XRE_GetIOMessageLoop();
|
|
|
|
ioLoop->PostTask(NewNonOwningRunnableMethod
|
|
<std::vector<std::string>, base::ProcessArchitecture>
|
|
(this, &GeckoChildProcessHost::RunPerformAsyncLaunch,
|
|
aExtraOpts, arch));
|
|
|
|
// This may look like the sync launch wait, but we only delay as
|
|
// long as it takes to create the channel.
|
|
MonitorAutoLock lock(mMonitor);
|
|
while (mProcessState < CHANNEL_INITIALIZED) {
|
|
lock.Wait();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
GeckoChildProcessHost::WaitUntilConnected(int32_t aTimeoutMs)
|
|
{
|
|
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
|
|
|
|
// NB: this uses a different mechanism than the chromium parent
|
|
// class.
|
|
PRIntervalTime timeoutTicks = (aTimeoutMs > 0) ?
|
|
PR_MillisecondsToInterval(aTimeoutMs) : PR_INTERVAL_NO_TIMEOUT;
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
PRIntervalTime waitStart = PR_IntervalNow();
|
|
PRIntervalTime current;
|
|
|
|
// We'll receive several notifications, we need to exit when we
|
|
// have either successfully launched or have timed out.
|
|
while (mProcessState != PROCESS_CONNECTED) {
|
|
// If there was an error then return it, don't wait out the timeout.
|
|
if (mProcessState == PROCESS_ERROR) {
|
|
break;
|
|
}
|
|
|
|
lock.Wait(timeoutTicks);
|
|
|
|
if (timeoutTicks != PR_INTERVAL_NO_TIMEOUT) {
|
|
current = PR_IntervalNow();
|
|
PRIntervalTime elapsed = current - waitStart;
|
|
if (elapsed > timeoutTicks) {
|
|
break;
|
|
}
|
|
timeoutTicks = timeoutTicks - elapsed;
|
|
waitStart = current;
|
|
}
|
|
}
|
|
|
|
return mProcessState == PROCESS_CONNECTED;
|
|
}
|
|
|
|
bool
|
|
GeckoChildProcessHost::LaunchAndWaitForProcessHandle(StringVector aExtraOpts)
|
|
{
|
|
PrepareLaunch();
|
|
|
|
MessageLoop* ioLoop = XRE_GetIOMessageLoop();
|
|
ioLoop->PostTask(NewNonOwningRunnableMethod
|
|
<std::vector<std::string>, base::ProcessArchitecture>
|
|
(this, &GeckoChildProcessHost::RunPerformAsyncLaunch,
|
|
aExtraOpts, base::GetCurrentProcessArchitecture()));
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
while (mProcessState < PROCESS_CREATED) {
|
|
lock.Wait();
|
|
}
|
|
MOZ_ASSERT(mProcessState == PROCESS_ERROR || mChildProcessHandle);
|
|
|
|
return mProcessState < PROCESS_ERROR;
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::InitializeChannel()
|
|
{
|
|
CreateChannel();
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
mProcessState = CHANNEL_INITIALIZED;
|
|
lock.Notify();
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::Join()
|
|
{
|
|
AssertIOThread();
|
|
|
|
if (!mChildProcessHandle) {
|
|
return;
|
|
}
|
|
|
|
// If this fails, there's nothing we can do.
|
|
base::KillProcess(mChildProcessHandle, 0, /*wait*/true);
|
|
SetAlreadyDead();
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::SetAlreadyDead()
|
|
{
|
|
if (mChildProcessHandle &&
|
|
mChildProcessHandle != kInvalidProcessHandle) {
|
|
base::CloseProcessHandle(mChildProcessHandle);
|
|
}
|
|
|
|
mChildProcessHandle = 0;
|
|
}
|
|
|
|
int32_t GeckoChildProcessHost::mChildCounter = 0;
|
|
|
|
void
|
|
GeckoChildProcessHost::SetChildLogName(const char* varName, const char* origLogName,
|
|
nsACString &buffer)
|
|
{
|
|
// We currently have no portable way to launch child with environment
|
|
// different than parent. So temporarily change NSPR_LOG_FILE so child
|
|
// inherits value we want it to have. (NSPR only looks at NSPR_LOG_FILE at
|
|
// startup, so it's 'safe' to play with the parent's environment this way.)
|
|
buffer.Assign(varName);
|
|
buffer.Append(origLogName);
|
|
|
|
// Append child-specific postfix to name
|
|
buffer.AppendLiteral(".child-");
|
|
buffer.AppendInt(mChildCounter);
|
|
|
|
// Passing temporary to PR_SetEnv is ok here if we keep the temporary
|
|
// for the time we launch the sub-process. It's copied to the new
|
|
// environment.
|
|
PR_SetEnv(buffer.BeginReading());
|
|
}
|
|
|
|
bool
|
|
GeckoChildProcessHost::PerformAsyncLaunch(std::vector<std::string> aExtraOpts, base::ProcessArchitecture arch)
|
|
{
|
|
// If NSPR log files are not requested, we're done.
|
|
const char* origNSPRLogName = PR_GetEnv("NSPR_LOG_FILE");
|
|
const char* origMozLogName = PR_GetEnv("MOZ_LOG_FILE");
|
|
if (!origNSPRLogName && !origMozLogName) {
|
|
return PerformAsyncLaunchInternal(aExtraOpts, arch);
|
|
}
|
|
|
|
// - Note: this code is not called re-entrantly, nor are restoreOrig*LogName
|
|
// or mChildCounter touched by any other thread, so this is safe.
|
|
++mChildCounter;
|
|
|
|
// Must keep these on the same stack where from we call PerformAsyncLaunchInternal
|
|
// so that PR_DuplicateEnvironment() still sees a valid memory.
|
|
nsAutoCString nsprLogName;
|
|
nsAutoCString mozLogName;
|
|
|
|
if (origNSPRLogName) {
|
|
if (mRestoreOrigNSPRLogName.IsEmpty()) {
|
|
mRestoreOrigNSPRLogName.AssignLiteral("NSPR_LOG_FILE=");
|
|
mRestoreOrigNSPRLogName.Append(origNSPRLogName);
|
|
}
|
|
SetChildLogName("NSPR_LOG_FILE=", origNSPRLogName, nsprLogName);
|
|
}
|
|
if (origMozLogName) {
|
|
if (mRestoreOrigMozLogName.IsEmpty()) {
|
|
mRestoreOrigMozLogName.AssignLiteral("MOZ_LOG_FILE=");
|
|
mRestoreOrigMozLogName.Append(origMozLogName);
|
|
}
|
|
SetChildLogName("MOZ_LOG_FILE=", origMozLogName, mozLogName);
|
|
}
|
|
|
|
bool retval = PerformAsyncLaunchInternal(aExtraOpts, arch);
|
|
|
|
// Revert to original value
|
|
if (origNSPRLogName) {
|
|
PR_SetEnv(mRestoreOrigNSPRLogName.get());
|
|
}
|
|
if (origMozLogName) {
|
|
PR_SetEnv(mRestoreOrigMozLogName.get());
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool
|
|
GeckoChildProcessHost::RunPerformAsyncLaunch(std::vector<std::string> aExtraOpts,
|
|
base::ProcessArchitecture aArch)
|
|
{
|
|
InitializeChannel();
|
|
|
|
bool ok = PerformAsyncLaunch(aExtraOpts, aArch);
|
|
if (!ok) {
|
|
// WaitUntilConnected might be waiting for us to signal.
|
|
// If something failed let's set the error state and notify.
|
|
MonitorAutoLock lock(mMonitor);
|
|
mProcessState = PROCESS_ERROR;
|
|
lock.Notify();
|
|
CHROMIUM_LOG(ERROR) << "Failed to launch " <<
|
|
XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
void
|
|
#if defined(XP_WIN)
|
|
AddAppDirToCommandLine(CommandLine& aCmdLine)
|
|
#else
|
|
AddAppDirToCommandLine(std::vector<std::string>& aCmdLine)
|
|
#endif
|
|
{
|
|
// Content processes need access to application resources, so pass
|
|
// the full application directory path to the child process.
|
|
if (ShouldHaveDirectoryService()) {
|
|
nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
|
|
NS_ASSERTION(directoryService, "Expected XPCOM to be available");
|
|
if (directoryService) {
|
|
nsCOMPtr<nsIFile> appDir;
|
|
// NS_XPCOM_CURRENT_PROCESS_DIR really means the app dir, not the
|
|
// current process dir.
|
|
nsresult rv = directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR,
|
|
NS_GET_IID(nsIFile),
|
|
getter_AddRefs(appDir));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
#if defined(XP_WIN)
|
|
nsString path;
|
|
MOZ_ALWAYS_SUCCEEDS(appDir->GetPath(path));
|
|
aCmdLine.AppendLooseValue(UTF8ToWide("-appdir"));
|
|
std::wstring wpath(path.get());
|
|
aCmdLine.AppendLooseValue(wpath);
|
|
#else
|
|
nsAutoCString path;
|
|
MOZ_ALWAYS_SUCCEEDS(appDir->GetNativePath(path));
|
|
aCmdLine.push_back("-appdir");
|
|
aCmdLine.push_back(path.get());
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector<std::string>& aExtraOpts, base::ProcessArchitecture arch)
|
|
{
|
|
// We rely on the fact that InitializeChannel() has already been processed
|
|
// on the IO thread before this point is reached.
|
|
if (!GetChannel()) {
|
|
return false;
|
|
}
|
|
|
|
base::ProcessHandle process = 0;
|
|
|
|
// send the child the PID so that it can open a ProcessHandle back to us.
|
|
// probably don't want to do this in the long run
|
|
char pidstring[32];
|
|
SprintfLiteral(pidstring,"%d", base::Process::Current().pid());
|
|
|
|
const char* const childProcessType =
|
|
XRE_ChildProcessTypeToString(mProcessType);
|
|
|
|
//--------------------------------------------------
|
|
#if defined(OS_POSIX)
|
|
// For POSIX, we have to be extremely anal about *not* using
|
|
// std::wstring in code compiled with Mozilla's -fshort-wchar
|
|
// configuration, because chromium is compiled with -fno-short-wchar
|
|
// and passing wstrings from one config to the other is unsafe. So
|
|
// we split the logic here.
|
|
|
|
#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
|
|
base::environment_map newEnvVars;
|
|
ChildPrivileges privs = mPrivileges;
|
|
if (privs == base::PRIVILEGES_DEFAULT) {
|
|
privs = DefaultChildPrivileges();
|
|
}
|
|
|
|
#if defined(MOZ_WIDGET_GTK)
|
|
if (mProcessType == GeckoProcessType_Content) {
|
|
// disable IM module to avoid sandbox violation
|
|
newEnvVars["GTK_IM_MODULE"] = "gtk-im-context-simple";
|
|
}
|
|
#endif
|
|
|
|
// XPCOM may not be initialized in some subprocesses. We don't want
|
|
// to initialize XPCOM just for the directory service, especially
|
|
// since LD_LIBRARY_PATH is already set correctly in subprocesses
|
|
// (meaning that we don't need to set that up in the environment).
|
|
if (ShouldHaveDirectoryService()) {
|
|
MOZ_ASSERT(gGREBinPath);
|
|
nsCString path;
|
|
NS_CopyUnicodeToNative(nsDependentString(gGREBinPath), path);
|
|
# if defined(OS_LINUX) || defined(OS_BSD)
|
|
# if defined(MOZ_WIDGET_ANDROID)
|
|
path += "/lib";
|
|
# endif // MOZ_WIDGET_ANDROID
|
|
const char *ld_library_path = PR_GetEnv("LD_LIBRARY_PATH");
|
|
nsCString new_ld_lib_path(path.get());
|
|
|
|
# if (MOZ_WIDGET_GTK == 3)
|
|
if (mProcessType == GeckoProcessType_Plugin) {
|
|
new_ld_lib_path.Append("/gtk2:");
|
|
new_ld_lib_path.Append(path.get());
|
|
}
|
|
#endif
|
|
if (ld_library_path && *ld_library_path) {
|
|
new_ld_lib_path.Append(':');
|
|
new_ld_lib_path.Append(ld_library_path);
|
|
}
|
|
newEnvVars["LD_LIBRARY_PATH"] = new_ld_lib_path.get();
|
|
|
|
# elif OS_MACOSX
|
|
newEnvVars["DYLD_LIBRARY_PATH"] = path.get();
|
|
// XXX DYLD_INSERT_LIBRARIES should only be set when launching a plugin
|
|
// process, and has no effect on other subprocesses (the hooks in
|
|
// libplugin_child_interpose.dylib become noops). But currently it
|
|
// gets set when launching any kind of subprocess.
|
|
//
|
|
// Trigger "dyld interposing" for the dylib that contains
|
|
// plugin_child_interpose.mm. This allows us to hook OS calls in the
|
|
// plugin process (ones that don't work correctly in a background
|
|
// process). Don't break any other "dyld interposing" that has already
|
|
// been set up by whatever may have launched the browser.
|
|
const char* prevInterpose = PR_GetEnv("DYLD_INSERT_LIBRARIES");
|
|
nsCString interpose;
|
|
if (prevInterpose && strlen(prevInterpose) > 0) {
|
|
interpose.Assign(prevInterpose);
|
|
interpose.Append(':');
|
|
}
|
|
interpose.Append(path.get());
|
|
interpose.AppendLiteral("/libplugin_child_interpose.dylib");
|
|
newEnvVars["DYLD_INSERT_LIBRARIES"] = interpose.get();
|
|
# endif // OS_LINUX
|
|
}
|
|
#endif // OS_LINUX || OS_MACOSX
|
|
|
|
FilePath exePath;
|
|
BinaryPathType pathType = GetPathToBinary(exePath, mProcessType);
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
// The java wrapper unpacks this for us but can't make it executable
|
|
chmod(exePath.value().c_str(), 0700);
|
|
#endif // MOZ_WIDGET_ANDROID
|
|
|
|
#ifdef ANDROID
|
|
// Remap the Android property workspace to a well-known int,
|
|
// and update the environment to reflect the new value for the
|
|
// child process.
|
|
const char *apws = getenv("ANDROID_PROPERTY_WORKSPACE");
|
|
if (apws) {
|
|
int fd = atoi(apws);
|
|
mFileMap.push_back(std::pair<int, int>(fd, kMagicAndroidSystemPropFd));
|
|
|
|
char buf[32];
|
|
char *szptr = strchr(apws, ',');
|
|
|
|
snprintf(buf, sizeof(buf), "%d%s", kMagicAndroidSystemPropFd, szptr);
|
|
newEnvVars["ANDROID_PROPERTY_WORKSPACE"] = buf;
|
|
}
|
|
#endif // ANDROID
|
|
|
|
// remap the IPC socket fd to a well-known int, as the OS does for
|
|
// STDOUT_FILENO, for example
|
|
int srcChannelFd, dstChannelFd;
|
|
channel().GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
|
|
mFileMap.push_back(std::pair<int,int>(srcChannelFd, dstChannelFd));
|
|
|
|
// no need for kProcessChannelID, the child process inherits the
|
|
// other end of the socketpair() from us
|
|
|
|
std::vector<std::string> childArgv;
|
|
|
|
childArgv.push_back(exePath.value());
|
|
|
|
if (pathType == BinaryPathType::Self) {
|
|
childArgv.push_back("-contentproc");
|
|
}
|
|
|
|
childArgv.insert(childArgv.end(), aExtraOpts.begin(), aExtraOpts.end());
|
|
|
|
if (Omnijar::IsInitialized()) {
|
|
// Make sure that child processes can find the omnijar
|
|
// See XRE_InitCommandLine in nsAppRunner.cpp
|
|
nsAutoCString path;
|
|
nsCOMPtr<nsIFile> file = Omnijar::GetPath(Omnijar::GRE);
|
|
if (file && NS_SUCCEEDED(file->GetNativePath(path))) {
|
|
childArgv.push_back("-greomni");
|
|
childArgv.push_back(path.get());
|
|
}
|
|
file = Omnijar::GetPath(Omnijar::APP);
|
|
if (file && NS_SUCCEEDED(file->GetNativePath(path))) {
|
|
childArgv.push_back("-appomni");
|
|
childArgv.push_back(path.get());
|
|
}
|
|
}
|
|
|
|
// Add the application directory path (-appdir path)
|
|
AddAppDirToCommandLine(childArgv);
|
|
|
|
childArgv.push_back(pidstring);
|
|
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
// Add a mach port to the command line so the child can communicate its
|
|
// 'task_t' back to the parent.
|
|
//
|
|
// Put a random number into the channel name, so that a compromised renderer
|
|
// can't pretend being the child that's forked off.
|
|
std::string mach_connection_name = StringPrintf("org.mozilla.machname.%d",
|
|
base::RandInt(0, std::numeric_limits<int>::max()));
|
|
childArgv.push_back(mach_connection_name.c_str());
|
|
#endif
|
|
|
|
childArgv.push_back(childProcessType);
|
|
|
|
base::LaunchApp(childArgv, mFileMap,
|
|
#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) || defined(OS_SOLARIS)
|
|
newEnvVars, privs,
|
|
#endif
|
|
false, &process, arch);
|
|
|
|
// We're in the parent and the child was launched. Close the child FD in the
|
|
// parent as soon as possible, which will allow the parent to detect when the
|
|
// child closes its FD (either due to normal exit or due to crash).
|
|
GetChannel()->CloseClientFileDescriptor();
|
|
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
// Wait for the child process to send us its 'task_t' data.
|
|
const int kTimeoutMs = 10000;
|
|
|
|
MachReceiveMessage child_message;
|
|
ReceivePort parent_recv_port(mach_connection_name.c_str());
|
|
kern_return_t err = parent_recv_port.WaitForMessage(&child_message, kTimeoutMs);
|
|
if (err != KERN_SUCCESS) {
|
|
std::string errString = StringPrintf("0x%x %s", err, mach_error_string(err));
|
|
CHROMIUM_LOG(ERROR) << "parent WaitForMessage() failed: " << errString;
|
|
return false;
|
|
}
|
|
|
|
task_t child_task = child_message.GetTranslatedPort(0);
|
|
if (child_task == MACH_PORT_NULL) {
|
|
CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(0) failed.";
|
|
return false;
|
|
}
|
|
|
|
if (child_message.GetTranslatedPort(1) == MACH_PORT_NULL) {
|
|
CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(1) failed.";
|
|
return false;
|
|
}
|
|
MachPortSender parent_sender(child_message.GetTranslatedPort(1));
|
|
|
|
if (child_message.GetTranslatedPort(2) == MACH_PORT_NULL) {
|
|
CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(2) failed.";
|
|
}
|
|
MachPortSender* parent_recv_port_memory_ack = new MachPortSender(child_message.GetTranslatedPort(2));
|
|
|
|
if (child_message.GetTranslatedPort(3) == MACH_PORT_NULL) {
|
|
CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(3) failed.";
|
|
}
|
|
MachPortSender* parent_send_port_memory = new MachPortSender(child_message.GetTranslatedPort(3));
|
|
|
|
MachSendMessage parent_message(/* id= */0);
|
|
if (!parent_message.AddDescriptor(MachMsgPortDescriptor(bootstrap_port))) {
|
|
CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" << bootstrap_port << ") failed.";
|
|
return false;
|
|
}
|
|
|
|
ReceivePort* parent_recv_port_memory = new ReceivePort();
|
|
if (!parent_message.AddDescriptor(MachMsgPortDescriptor(parent_recv_port_memory->GetPort()))) {
|
|
CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" << parent_recv_port_memory->GetPort() << ") failed.";
|
|
return false;
|
|
}
|
|
|
|
ReceivePort* parent_send_port_memory_ack = new ReceivePort();
|
|
if (!parent_message.AddDescriptor(MachMsgPortDescriptor(parent_send_port_memory_ack->GetPort()))) {
|
|
CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" << parent_send_port_memory_ack->GetPort() << ") failed.";
|
|
return false;
|
|
}
|
|
|
|
err = parent_sender.SendMessage(parent_message, kTimeoutMs);
|
|
if (err != KERN_SUCCESS) {
|
|
std::string errString = StringPrintf("0x%x %s", err, mach_error_string(err));
|
|
CHROMIUM_LOG(ERROR) << "parent SendMessage() failed: " << errString;
|
|
return false;
|
|
}
|
|
|
|
SharedMemoryBasic::SetupMachMemory(process, parent_recv_port_memory, parent_recv_port_memory_ack,
|
|
parent_send_port_memory, parent_send_port_memory_ack, false);
|
|
|
|
#endif
|
|
|
|
//--------------------------------------------------
|
|
#elif defined(OS_WIN)
|
|
|
|
FilePath exePath;
|
|
BinaryPathType pathType = GetPathToBinary(exePath, mProcessType);
|
|
|
|
CommandLine cmdLine(exePath.ToWStringHack());
|
|
|
|
if (pathType == BinaryPathType::Self) {
|
|
cmdLine.AppendLooseValue(UTF8ToWide("-contentproc"));
|
|
}
|
|
|
|
cmdLine.AppendSwitchWithValue(switches::kProcessChannelID, channel_id());
|
|
|
|
for (std::vector<std::string>::iterator it = aExtraOpts.begin();
|
|
it != aExtraOpts.end();
|
|
++it) {
|
|
cmdLine.AppendLooseValue(UTF8ToWide(*it));
|
|
}
|
|
|
|
if (Omnijar::IsInitialized()) {
|
|
// Make sure the child process can find the omnijar
|
|
// See XRE_InitCommandLine in nsAppRunner.cpp
|
|
nsAutoString path;
|
|
nsCOMPtr<nsIFile> file = Omnijar::GetPath(Omnijar::GRE);
|
|
if (file && NS_SUCCEEDED(file->GetPath(path))) {
|
|
cmdLine.AppendLooseValue(UTF8ToWide("-greomni"));
|
|
cmdLine.AppendLooseValue(path.get());
|
|
}
|
|
file = Omnijar::GetPath(Omnijar::APP);
|
|
if (file && NS_SUCCEEDED(file->GetPath(path))) {
|
|
cmdLine.AppendLooseValue(UTF8ToWide("-appomni"));
|
|
cmdLine.AppendLooseValue(path.get());
|
|
}
|
|
}
|
|
|
|
// Add the application directory path (-appdir path)
|
|
AddAppDirToCommandLine(cmdLine);
|
|
|
|
// XXX Command line params past this point are expected to be at
|
|
// the end of the command line string, and in a specific order.
|
|
// See XRE_InitChildProcess in nsEmbedFunction.
|
|
|
|
// Win app model id
|
|
cmdLine.AppendLooseValue(mGroupId.get());
|
|
|
|
// Process id
|
|
cmdLine.AppendLooseValue(UTF8ToWide(pidstring));
|
|
|
|
// Process type
|
|
cmdLine.AppendLooseValue(UTF8ToWide(childProcessType));
|
|
|
|
{
|
|
base::LaunchApp(cmdLine, false, false, &process);
|
|
}
|
|
|
|
#else
|
|
# error Sorry
|
|
#endif
|
|
|
|
if (!process) {
|
|
return false;
|
|
}
|
|
// NB: on OS X, we block much longer than we need to in order to
|
|
// reach this call, waiting for the child process's task_t. The
|
|
// best way to fix that is to refactor this file, hard.
|
|
#if defined(MOZ_WIDGET_COCOA)
|
|
mChildTask = child_task;
|
|
#endif
|
|
|
|
if (!OpenPrivilegedHandle(base::GetProcId(process))
|
|
#ifdef XP_WIN
|
|
// If we failed in opening the process handle, try harder by duplicating
|
|
// one.
|
|
&& !::DuplicateHandle(::GetCurrentProcess(), process,
|
|
::GetCurrentProcess(), &mChildProcessHandle,
|
|
PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ |
|
|
SYNCHRONIZE,
|
|
FALSE, 0)
|
|
#endif
|
|
) {
|
|
NS_RUNTIMEABORT("cannot open handle to child process");
|
|
}
|
|
MonitorAutoLock lock(mMonitor);
|
|
mProcessState = PROCESS_CREATED;
|
|
lock.Notify();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid)
|
|
{
|
|
if (mChildProcessHandle) {
|
|
MOZ_ASSERT(aPid == base::GetProcId(mChildProcessHandle));
|
|
return true;
|
|
}
|
|
|
|
return base::OpenPrivilegedProcessHandle(aPid, &mChildProcessHandle);
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::OnChannelConnected(int32_t peer_pid)
|
|
{
|
|
if (!OpenPrivilegedHandle(peer_pid)) {
|
|
NS_RUNTIMEABORT("can't open handle to child process");
|
|
}
|
|
MonitorAutoLock lock(mMonitor);
|
|
mProcessState = PROCESS_CONNECTED;
|
|
lock.Notify();
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::OnMessageReceived(IPC::Message&& aMsg)
|
|
{
|
|
// We never process messages ourself, just save them up for the next
|
|
// listener.
|
|
mQueue.push(Move(aMsg));
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::OnChannelError()
|
|
{
|
|
// Update the process state to an error state if we have a channel
|
|
// error before we're connected. This fixes certain failures,
|
|
// but does not address the full range of possible issues described
|
|
// in the FIXME comment below.
|
|
MonitorAutoLock lock(mMonitor);
|
|
if (mProcessState < PROCESS_CONNECTED) {
|
|
mProcessState = PROCESS_ERROR;
|
|
lock.Notify();
|
|
}
|
|
// FIXME/bug 773925: save up this error for the next listener.
|
|
}
|
|
|
|
void
|
|
GeckoChildProcessHost::GetQueuedMessages(std::queue<IPC::Message>& queue)
|
|
{
|
|
// If this is called off the IO thread, bad things will happen.
|
|
DCHECK(MessageLoopForIO::current());
|
|
swap(queue, mQueue);
|
|
// We expect the next listener to take over processing of our queue.
|
|
}
|
|
|
|
bool GeckoChildProcessHost::sRunSelfAsContentProc(false);
|