319 lines
9.5 KiB
C++
319 lines
9.5 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 "ForkServiceChild.h"
|
|
#include "ForkServer.h"
|
|
#include "chrome/common/process_watcher.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/ipc/GeckoChildProcessHost.h"
|
|
#include "mozilla/ipc/IPDLParamTraits.h"
|
|
#include "mozilla/ipc/ProtocolMessageUtils.h"
|
|
#include "mozilla/StaticPrefs_dom.h"
|
|
#include "mozilla/Services.h"
|
|
#include "ipc/IPCMessageUtilsSpecializations.h"
|
|
#include "nsIObserverService.h"
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
namespace mozilla {
|
|
namespace ipc {
|
|
|
|
extern LazyLogModule gForkServiceLog;
|
|
|
|
StaticMutex ForkServiceChild::sMutex;
|
|
StaticRefPtr<ForkServiceChild> ForkServiceChild::sSingleton;
|
|
Atomic<bool> ForkServiceChild::sForkServiceUsed;
|
|
|
|
#ifndef SOCK_CLOEXEC
|
|
static bool ConfigurePipeFd(int aFd) {
|
|
int flags = fcntl(aFd, F_GETFD, 0);
|
|
return flags != -1 && fcntl(aFd, F_SETFD, flags | FD_CLOEXEC) != -1;
|
|
}
|
|
#endif
|
|
|
|
// Create a socketpair with both ends marked as close-on-exec
|
|
static Result<Ok, LaunchError> CreateSocketPair(UniqueFileHandle& aFD0,
|
|
UniqueFileHandle& aFD1) {
|
|
int fds[2];
|
|
#ifdef SOCK_CLOEXEC
|
|
constexpr int type = SOCK_STREAM | SOCK_CLOEXEC;
|
|
#else
|
|
constexpr int type = SOCK_STREAM;
|
|
#endif
|
|
|
|
if (socketpair(AF_UNIX, type, 0, fds) < 0) {
|
|
return Err(LaunchError("FSC::CSP::sp", errno));
|
|
}
|
|
|
|
#ifndef SOCK_CLOEXEC
|
|
if (!ConfigurePipeFd(server.get()) || !ConfigurePipeFd(client.get())) {
|
|
return Err(LaunchError("FSC::CSP::cfg", errno));
|
|
}
|
|
#endif
|
|
|
|
aFD0.reset(fds[0]);
|
|
aFD1.reset(fds[1]);
|
|
|
|
return Ok();
|
|
}
|
|
|
|
void ForkServiceChild::StartForkServer() {
|
|
UniqueFileHandle server;
|
|
UniqueFileHandle client;
|
|
if (CreateSocketPair(server, client).isErr()) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Error,
|
|
("failed to create fork server socket"));
|
|
return;
|
|
}
|
|
|
|
GeckoChildProcessHost* subprocess =
|
|
new GeckoChildProcessHost(GeckoProcessType_ForkServer, false);
|
|
|
|
geckoargs::ChildProcessArgs extraOpts;
|
|
geckoargs::sIPCHandle.Put(std::move(client), extraOpts);
|
|
geckoargs::sSignalPipe.Put(ProcessWatcher::GetSignalPipe(), extraOpts);
|
|
|
|
if (!subprocess->LaunchAndWaitForProcessHandle(std::move(extraOpts))) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Error, ("failed to launch fork server"));
|
|
return;
|
|
}
|
|
|
|
sForkServiceUsed = true;
|
|
StaticMutexAutoLock smal(sMutex);
|
|
// Can't use MakeRefPtr; ctor is private.
|
|
MOZ_ASSERT(sSingleton == nullptr);
|
|
sSingleton = new ForkServiceChild(server.release(), subprocess);
|
|
}
|
|
|
|
void ForkServiceChild::StopForkServer() {
|
|
RefPtr<ForkServiceChild> oldChild;
|
|
{
|
|
StaticMutexAutoLock smal(sMutex);
|
|
oldChild = sSingleton.forget();
|
|
}
|
|
// Drop the old reference outside of the lock to avoid lock order
|
|
// cycles via the GeckoChildProcessHost dtor.
|
|
}
|
|
|
|
RefPtr<ForkServiceChild> ForkServiceChild::Get() {
|
|
RefPtr<ForkServiceChild> child;
|
|
{
|
|
StaticMutexAutoLock smal(sMutex);
|
|
child = sSingleton;
|
|
}
|
|
return child;
|
|
}
|
|
|
|
ForkServiceChild::ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess)
|
|
: mMutex("mozilla.ipc.ForkServiceChild.mMutex"),
|
|
mFailed(false),
|
|
mProcess(aProcess) {
|
|
mTcver = MakeUnique<MiniTransceiver>(aFd);
|
|
}
|
|
|
|
ForkServiceChild::~ForkServiceChild() {
|
|
close(mTcver->GetFD());
|
|
// This can be synchronous during browser shutdown, so do it *after*
|
|
// causing the fork server to exit by closning the socket:
|
|
mProcess->Destroy();
|
|
}
|
|
|
|
Result<Ok, LaunchError> ForkServiceChild::SendForkNewSubprocess(
|
|
geckoargs::ChildProcessArgs&& aArgs, base::LaunchOptions&& aOptions,
|
|
pid_t* aPid) {
|
|
// Double-check there are no unsupported options.
|
|
MOZ_ASSERT(aOptions.workdir.empty());
|
|
MOZ_ASSERT(!aOptions.full_env);
|
|
MOZ_ASSERT(!aOptions.wait);
|
|
MOZ_ASSERT(aOptions.fds_to_remap.size() == aArgs.mFiles.size());
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
if (mFailed) {
|
|
return Err(LaunchError("FSC::SFNS::Failed"));
|
|
}
|
|
|
|
UniqueFileHandle execParent;
|
|
{
|
|
UniqueFileHandle execChild;
|
|
IPC::Message msg(MSG_ROUTING_CONTROL, Msg_ForkNewSubprocess__ID);
|
|
|
|
MOZ_TRY(CreateSocketPair(execParent, execChild));
|
|
|
|
IPC::MessageWriter writer(msg);
|
|
#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
|
|
WriteParam(&writer, aOptions.fork_flags);
|
|
WriteParam(&writer, std::move(aOptions.sandbox_chroot_server));
|
|
#endif
|
|
WriteIPDLParam(&writer, nullptr, std::move(execChild));
|
|
if (!mTcver->Send(msg)) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("the pipe to the fork server is closed or having errors"));
|
|
OnError();
|
|
return Err(LaunchError("FSC::SFNS::Send"));
|
|
}
|
|
}
|
|
|
|
{
|
|
MiniTransceiver execTcver(execParent.get());
|
|
IPC::Message execMsg(MSG_ROUTING_CONTROL, Msg_SubprocessExecInfo__ID);
|
|
IPC::MessageWriter execWriter(execMsg);
|
|
WriteParam(&execWriter, aOptions.env_map);
|
|
WriteParam(&execWriter, aArgs.mArgs);
|
|
WriteParam(&execWriter, std::move(aArgs.mFiles));
|
|
if (!execTcver.Send(execMsg)) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("failed to send exec info to the fork server"));
|
|
OnError();
|
|
return Err(LaunchError("FSC::SFNS::Send2"));
|
|
}
|
|
}
|
|
execParent = nullptr;
|
|
|
|
UniquePtr<IPC::Message> reply;
|
|
if (!mTcver->Recv(reply)) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("the pipe to the fork server is closed or having errors"));
|
|
OnError();
|
|
return Err(LaunchError("FSC::SFNS::Recv"));
|
|
}
|
|
|
|
if (reply->type() != Reply_ForkNewSubprocess__ID) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("unknown reply type %d", reply->type()));
|
|
return Err(LaunchError("FSC::SFNS::Type"));
|
|
}
|
|
IPC::MessageReader reader(*reply);
|
|
|
|
if (!ReadIPDLParam(&reader, nullptr, aPid)) {
|
|
MOZ_CRASH("Error deserializing 'pid_t'");
|
|
}
|
|
reader.EndRead();
|
|
|
|
return Ok();
|
|
}
|
|
|
|
auto ForkServiceChild::SendWaitPid(pid_t aPid, bool aBlock)
|
|
-> Result<ProcStatus, int> {
|
|
MutexAutoLock lock(mMutex);
|
|
if (mFailed) {
|
|
return Err(ECONNRESET);
|
|
}
|
|
|
|
IPC::Message msg(MSG_ROUTING_CONTROL, Msg_WaitPid__ID);
|
|
IPC::MessageWriter writer(msg);
|
|
WriteParam(&writer, aPid);
|
|
WriteParam(&writer, aBlock);
|
|
|
|
if (!mTcver->Send(msg)) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("the pipe to the fork server is closed or having errors"));
|
|
OnError();
|
|
return Err(ECONNRESET);
|
|
}
|
|
|
|
UniquePtr<IPC::Message> reply;
|
|
if (!mTcver->Recv(reply)) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("the pipe to the fork server is closed or having errors"));
|
|
OnError();
|
|
return Err(ECONNRESET);
|
|
}
|
|
|
|
if (reply->type() != Reply_WaitPid__ID) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("unknown reply type %d", reply->type()));
|
|
OnError();
|
|
return Err(EPROTO);
|
|
}
|
|
IPC::MessageReader reader(*reply);
|
|
|
|
// Both sides of the Result are isomorphic to int.
|
|
bool isErr = false;
|
|
int value = 0;
|
|
if (!ReadParam(&reader, &isErr) || !ReadParam(&reader, &value)) {
|
|
MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
|
|
("deserialization error in waitpid reply"));
|
|
OnError();
|
|
return Err(EPROTO);
|
|
}
|
|
|
|
// This can't use ?: because the types are different.
|
|
if (isErr) {
|
|
return Err(value);
|
|
}
|
|
return ProcStatus{value};
|
|
}
|
|
|
|
void ForkServiceChild::OnError() {
|
|
mFailed = true;
|
|
ForkServerLauncher::RestartForkServer();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ForkServerLauncher, nsIObserver)
|
|
|
|
bool ForkServerLauncher::sHaveStartedClient = false;
|
|
StaticRefPtr<ForkServerLauncher> ForkServerLauncher::sSingleton;
|
|
|
|
ForkServerLauncher::ForkServerLauncher() {}
|
|
|
|
ForkServerLauncher::~ForkServerLauncher() {}
|
|
|
|
already_AddRefed<ForkServerLauncher> ForkServerLauncher::Create() {
|
|
if (sSingleton == nullptr) {
|
|
sSingleton = new ForkServerLauncher();
|
|
}
|
|
RefPtr<ForkServerLauncher> launcher = sSingleton;
|
|
return launcher.forget();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ForkServerLauncher::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (strcmp(aTopic, NS_XPCOM_STARTUP_CATEGORY) == 0) {
|
|
nsCOMPtr<nsIObserverService> obsSvc =
|
|
mozilla::services::GetObserverService();
|
|
MOZ_ASSERT(obsSvc != nullptr);
|
|
// preferences are not available until final-ui-startup
|
|
obsSvc->AddObserver(this, "final-ui-startup", false);
|
|
} else if (!sHaveStartedClient && strcmp(aTopic, "final-ui-startup") == 0) {
|
|
if (StaticPrefs::dom_ipc_forkserver_enable_AtStartup()) {
|
|
sHaveStartedClient = true;
|
|
ForkServiceChild::StartForkServer();
|
|
|
|
nsCOMPtr<nsIObserverService> obsSvc =
|
|
mozilla::services::GetObserverService();
|
|
MOZ_ASSERT(obsSvc != nullptr);
|
|
obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
|
|
} else {
|
|
sSingleton = nullptr;
|
|
}
|
|
}
|
|
|
|
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
|
|
// To make leak checker happy!
|
|
sSingleton = nullptr;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void ForkServerLauncher::RestartForkServer() {
|
|
// Restart fork server
|
|
NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
|
|
NS_NewRunnableFunction("OnForkServerError",
|
|
[] {
|
|
if (sSingleton) {
|
|
ForkServiceChild::StopForkServer();
|
|
ForkServiceChild::StartForkServer();
|
|
}
|
|
}),
|
|
EventQueuePriority::Idle));
|
|
}
|
|
|
|
} // namespace ipc
|
|
} // namespace mozilla
|