/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=2 autoindent cindent expandtab: */ /* 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 "ProcessUtils.h" #include "nsString.h" #include #ifdef MOZ_B2G_LOADER #include #include #include "nsAutoPtr.h" #include "mozilla/Assertions.h" #include "mozilla/ipc/PProcLoaderParent.h" #include "mozilla/ipc/PProcLoaderChild.h" #include "mozilla/ipc/Transport.h" #include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/ipc/IOThreadChild.h" #include "mozilla/dom/ContentProcess.h" #include "base/file_descriptor_shuffle.h" #include "mozilla/BackgroundHangMonitor.h" #include "mozilla/DebugOnly.h" #include "mozilla/UniquePtr.h" #include "mozilla/unused.h" #include "base/process_util.h" #include "base/eintr_wrapper.h" #include "mozilla/Preferences.h" #include "prenv.h" #include "nsXULAppAPI.h" // export XRE_* functions #include "nsAppRunner.h" int content_process_main(int argc, char *argv[]); typedef mozilla::Vector FdArray; #endif /* MOZ_B2G_LOADER */ namespace mozilla { namespace ipc { void SetThisProcessName(const char *aName) { prctl(PR_SET_NAME, (unsigned long)aName, 0uL, 0uL, 0uL); } #ifdef MOZ_B2G_LOADER /** * How does B2G Loader Work? * * <> <> * ProcLoaderParent -----> ProcLoaderChild * ^ | * | load() | content_process_main() * | V * ProcLoaderClient Nuwa/plugin-container * ^ * | ProcLoaderLoad() * ... * ContentParent * * * B2G loader includes an IPC protocol PProcLoader for communication * between client (parent) and server (child). The b2g process is the * client. It requests the server to load/start the Nuwa process with * the given arguments, env variables, and file descriptors. * * ProcLoaderClientInit() is called by B2G loader to initialize the * client side, the b2g process. Then the b2g_main() is called to * start b2g process. * * ProcLoaderClientGeckoInit() is called by XRE_main() to create the * parent actor, |ProcLoaderParent|, of PProcLoader for servicing the * request to run Nuwa process later once Gecko has been initialized. * * ProcLoaderServiceRun() is called by the server process. It starts * an IOThread and event loop to serve the |ProcLoaderChild| * implmentation of PProcLoader protocol as the child actor. Once it * recieves a load() request, it stops the IOThread and event loop, * then starts running the main function of the content process with * the given arguments. * * NOTE: The server process serves at most one load() request. */ using namespace mozilla::dom; using base::ChildPrivileges; using base::InjectionArc; using base::InjectiveMultimap; using base::ProcessHandle; using base::ProcessId; using base::SetCurrentProcessPrivileges; using base::ShuffleFileDescriptors; using base::file_handle_mapping_vector; static bool sProcLoaderClientOnDeinit = false; static DebugOnly sProcLoaderClientInitialized = false; static DebugOnly sProcLoaderClientGeckoInitialized = false; static pid_t sProcLoaderPid = 0; static int sProcLoaderChannelFd = -1; static PProcLoaderParent *sProcLoaderParent = nullptr; static MessageLoop *sProcLoaderLoop = nullptr; static mozilla::UniquePtr sReservedFds; static void ProcLoaderClientDeinit(); /** * Some file descriptors, like the child IPC channel FD, must be opened at * specific numbers. To ensure this, we pre-reserve kReservedFileDescriptors FDs * starting from kBeginReserveFileDescriptor so that operations like * __android_log_print() won't take these magic FDs. */ static const size_t kReservedFileDescriptors = 5; static const int kBeginReserveFileDescriptor = STDERR_FILENO + 1; class ProcLoaderParent : public PProcLoaderParent { public: ProcLoaderParent() {} virtual ~ProcLoaderParent() {} virtual void ActorDestroy(ActorDestroyReason aWhy) override; virtual bool RecvLoadComplete(const int32_t &aPid, const int32_t &aCookie) override; }; void ProcLoaderParent::ActorDestroy(ActorDestroyReason aWhy) { if (aWhy == AbnormalShutdown) { NS_WARNING("ProcLoaderParent is destroyed abnormally."); } if (sProcLoaderClientOnDeinit) { // Get error for closing while the channel is already error. return; } // Destroy self asynchronously. ProcLoaderClientDeinit(); } static void _ProcLoaderParentDestroy(PProcLoaderParent *aLoader) { delete aLoader; sProcLoaderClientOnDeinit = false; } bool ProcLoaderParent::RecvLoadComplete(const int32_t &aPid, const int32_t &aCookie) { return true; } static void CloseFileDescriptors(FdArray& aFds) { for (size_t i = 0; i < aFds.length(); i++) { Unused << HANDLE_EINTR(close(aFds[i])); } } /** * Initialize the client of B2G loader for loader itself. * * The initialization of B2G loader are divided into two stages. First * stage is to collect child info passed from the main program of the * loader. Second stage is to initialize Gecko according to info from the * first stage and make the client of loader service ready. * * \param aPeerPid is the pid of the child. * \param aChannelFd is the file descriptor of the socket used for IPC. */ static void ProcLoaderClientInit(pid_t aPeerPid, int aChannelFd) { MOZ_ASSERT(!sProcLoaderClientInitialized, "call ProcLoaderClientInit() more than once"); MOZ_ASSERT(aPeerPid != 0 && aChannelFd != -1, "invalid argument"); sProcLoaderPid = aPeerPid; sProcLoaderChannelFd = aChannelFd; sProcLoaderClientInitialized = true; } /** * Initialize the client of B2G loader for Gecko. */ void ProcLoaderClientGeckoInit() { MOZ_ASSERT(sProcLoaderClientInitialized, "call ProcLoaderClientInit() at first"); MOZ_ASSERT(!sProcLoaderClientGeckoInitialized, "call ProcLoaderClientGeckoInit() more than once"); if (!Preferences::GetBool("dom.ipc.processPrelaunch.enabled", false)) { kill(sProcLoaderPid, SIGKILL); sProcLoaderPid = 0; close(sProcLoaderChannelFd); sProcLoaderChannelFd = -1; return; } sProcLoaderClientGeckoInitialized = true; TransportDescriptor fd; fd.mFd = base::FileDescriptor(sProcLoaderChannelFd, /*auto_close=*/ false); sProcLoaderChannelFd = -1; Transport *transport = OpenDescriptor(fd, Transport::MODE_CLIENT); sProcLoaderParent = new ProcLoaderParent(); sProcLoaderParent->Open(transport, sProcLoaderPid, XRE_GetIOMessageLoop(), ParentSide); sProcLoaderLoop = MessageLoop::current(); } bool ProcLoaderIsInitialized() { return sProcLoaderPid != 0; } /** * Shutdown and destroy the client of B2G loader service. */ static void ProcLoaderClientDeinit() { MOZ_ASSERT(sProcLoaderClientGeckoInitialized && sProcLoaderClientInitialized); sProcLoaderClientGeckoInitialized = false; sProcLoaderClientInitialized = false; sProcLoaderClientOnDeinit = true; MOZ_ASSERT(sProcLoaderParent != nullptr); PProcLoaderParent *procLoaderParent = sProcLoaderParent; sProcLoaderParent = nullptr; sProcLoaderLoop = nullptr; MessageLoop::current()-> PostTask(FROM_HERE, NewRunnableFunction(&_ProcLoaderParentDestroy, procLoaderParent)); } struct AsyncSendLoadData { nsTArray mArgv; nsTArray mEnv; nsTArray mFdsremap; ChildPrivileges mPrivs; int mCookie; }; static void AsyncSendLoad(AsyncSendLoadData *aLoad) { PProcLoaderParent *loader = sProcLoaderParent; DebugOnly ok = loader->SendLoad(aLoad->mArgv, aLoad->mEnv, aLoad->mFdsremap, aLoad->mPrivs, aLoad->mCookie); MOZ_ASSERT(ok); delete aLoad; } /** * Request the loader service, the server, to load Nuwa. */ bool ProcLoaderLoad(const char *aArgv[], const char *aEnvp[], const file_handle_mapping_vector &aFdsRemap, const ChildPrivileges aPrivs, ProcessHandle *aProcessHandle) { static int cookie=0; int i; if (sProcLoaderParent == nullptr || sProcLoaderPid == 0) { return false; } AsyncSendLoadData *load = new AsyncSendLoadData(); nsTArray &argv = load->mArgv; for (i = 0; aArgv[i] != nullptr; i++) { argv.AppendElement(nsCString(aArgv[i])); } nsTArray &env = load->mEnv; for (i = 0; aEnvp[i] != nullptr; i++) { env.AppendElement(nsCString(aEnvp[i])); } nsTArray &fdsremap = load->mFdsremap; for (file_handle_mapping_vector::const_iterator fdmap = aFdsRemap.begin(); fdmap != aFdsRemap.end(); fdmap++) { fdsremap.AppendElement(FDRemap(FileDescriptor(fdmap->first), fdmap->second)); } load->mPrivs = aPrivs; load->mCookie = cookie++; *aProcessHandle = sProcLoaderPid; sProcLoaderPid = 0; sProcLoaderLoop->PostTask(FROM_HERE, NewRunnableFunction(AsyncSendLoad, load)); return true; } class ProcLoaderRunnerBase; static bool sProcLoaderServing = false; static ProcLoaderRunnerBase *sProcLoaderDispatchedTask = nullptr; class ProcLoaderRunnerBase { public: virtual int DoWork() = 0; virtual ~ProcLoaderRunnerBase() {} }; class ProcLoaderNoopRunner : public ProcLoaderRunnerBase { public: virtual int DoWork(); }; int ProcLoaderNoopRunner::DoWork() { return 0; } /** * The runner to load Nuwa at the current process. */ class ProcLoaderLoadRunner : public ProcLoaderRunnerBase { private: const nsTArray mArgv; const nsTArray mEnv; const nsTArray mFdsRemap; const ChildPrivileges mPrivs; void ShuffleFds(); public: ProcLoaderLoadRunner(const InfallibleTArray& aArgv, const InfallibleTArray& aEnv, const InfallibleTArray& aFdsRemap, const ChildPrivileges aPrivs) : mArgv(aArgv) , mEnv(aEnv) , mFdsRemap(aFdsRemap) , mPrivs(aPrivs) {} int DoWork(); }; void ProcLoaderLoadRunner::ShuffleFds() { unsigned int i; MOZ_ASSERT(mFdsRemap.Length() <= kReservedFileDescriptors); InjectiveMultimap fd_shuffle; fd_shuffle.reserve(mFdsRemap.Length()); for (i = 0; i < mFdsRemap.Length(); i++) { const FDRemap *map = &mFdsRemap[i]; int fd = map->fd().PlatformHandle(); int tofd = map->mapto(); // The FD that is dup2()'d from needs to be closed after shuffling. fd_shuffle.push_back(InjectionArc(fd, tofd, /*in_close=*/true)); // Erase from sReservedFds we will use. for (int* toErase = sReservedFds->begin(); toErase < sReservedFds->end(); toErase++) { if (tofd == *toErase) { sReservedFds->erase(toErase); break; } } } DebugOnly ok = ShuffleFileDescriptors(&fd_shuffle); // Close the FDs that are reserved but not used after // ShuffleFileDescriptors(). MOZ_ASSERT(sReservedFds); CloseFileDescriptors(*sReservedFds); sReservedFds = nullptr; // Note that we don'e call ::base::CloseSuperfluousFds() here, assuming that // The file descriptor inherited from the parent are also necessary for us. } int ProcLoaderLoadRunner::DoWork() { unsigned int i; ShuffleFds(); unsigned int argc = mArgv.Length(); char **argv = new char *[argc + 1]; for (i = 0; i < argc; i++) { argv[i] = ::strdup(mArgv[i].get()); } argv[argc] = nullptr; unsigned int envc = mEnv.Length(); for (i = 0; i < envc; i++) { PR_SetEnv(mEnv[i].get()); } SetCurrentProcessPrivileges(mPrivs); // Start Nuwa (main function) int ret = content_process_main(argc, argv); for (i = 0; i < argc; i++) { free(argv[i]); } delete[] argv; return ret; } class ProcLoaderChild : public PProcLoaderChild { pid_t mPeerPid; public: ProcLoaderChild(pid_t aPeerPid) : mPeerPid(aPeerPid) {} virtual void ActorDestroy(ActorDestroyReason aWhy) override; virtual bool RecvLoad(InfallibleTArray&& aArgv, InfallibleTArray&& aEnv, InfallibleTArray&& aFdsremap, const uint32_t& aPrivs, const int32_t& aCookie); virtual void OnChannelError(); }; void ProcLoaderChild::ActorDestroy(ActorDestroyReason aWhy) { } static void _ProcLoaderChildDestroy(ProcLoaderChild *aChild) { aChild->Close(); delete aChild; MessageLoop::current()->Quit(); } bool ProcLoaderChild::RecvLoad(InfallibleTArray&& aArgv, InfallibleTArray&& aEnv, InfallibleTArray&& aFdsRemap, const uint32_t& aPrivs, const int32_t& aCookie) { if (!sProcLoaderServing) { return true; } sProcLoaderServing = false; MOZ_ASSERT(sProcLoaderDispatchedTask == nullptr); ChildPrivileges privs = static_cast(aPrivs); sProcLoaderDispatchedTask = new ProcLoaderLoadRunner(aArgv, aEnv, aFdsRemap, privs); SendLoadComplete(mPeerPid, aCookie); MessageLoop::current()->PostTask(FROM_HERE, NewRunnableFunction(_ProcLoaderChildDestroy, this)); return true; } void ProcLoaderChild::OnChannelError() { if (!sProcLoaderServing) { return; } sProcLoaderServing = false; PProcLoaderChild::OnChannelError(); MOZ_ASSERT(sProcLoaderDispatchedTask == nullptr); sProcLoaderDispatchedTask = new ProcLoaderNoopRunner(); MessageLoop::current()->PostTask(FROM_HERE, NewRunnableFunction(_ProcLoaderChildDestroy, this)); } /** * A helper class which calls NS_LogInit/NS_LogTerm in its scope. */ class ScopedLogging { public: ScopedLogging() { NS_LogInit(); } ~ScopedLogging() { NS_LogTerm(); } }; /** * Run service of ProcLoader. * * \param aPeerPid is the pid of the parent. * \param aFd is the file descriptor of the socket for IPC. * * See the comment near the head of this file. */ static int ProcLoaderServiceRun(pid_t aPeerPid, int aFd, int aArgc, const char *aArgv[], FdArray& aReservedFds) { // Make a copy of aReservedFds. It will be used when we dup() the magic file // descriptors when ProcLoaderChild::RecvLoad() runs. sReservedFds = MakeUnique(mozilla::Move(aReservedFds)); ScopedLogging logging; char **_argv; _argv = new char *[aArgc + 1]; for (int i = 0; i < aArgc; i++) { _argv[i] = ::strdup(aArgv[i]); MOZ_ASSERT(_argv[i] != nullptr); } _argv[aArgc] = nullptr; gArgv = _argv; gArgc = aArgc; { nsresult rv = XRE_InitCommandLine(aArgc, _argv); if (NS_FAILED(rv)) { MOZ_CRASH(); } TransportDescriptor fd; fd.mFd = base::FileDescriptor(aFd, /*auto_close =*/false); MOZ_ASSERT(!sProcLoaderServing); MessageLoop loop; nsAutoPtr process; process = new ContentProcess(aPeerPid); ChildThread *iothread = process->child_thread(); Transport *transport = OpenDescriptor(fd, Transport::MODE_CLIENT); ProcLoaderChild *loaderChild = new ProcLoaderChild(aPeerPid); // Pass a message loop to initialize (connect) the channel // (connection). loaderChild->Open(transport, aPeerPid, iothread->message_loop()); BackgroundHangMonitor::Prohibit(); sProcLoaderServing = true; loop.Run(); BackgroundHangMonitor::Allow(); XRE_DeinitCommandLine(); } MOZ_ASSERT(sProcLoaderDispatchedTask != nullptr); ProcLoaderRunnerBase *task = sProcLoaderDispatchedTask; sProcLoaderDispatchedTask = nullptr; int ret = task->DoWork(); delete task; for (int i = 0; i < aArgc; i++) { free(_argv[i]); } delete[] _argv; return ret; } #endif /* MOZ_B2G_LOADER */ } // namespace ipc } // namespace mozilla #ifdef MOZ_B2G_LOADER void XRE_ProcLoaderClientInit(pid_t aPeerPid, int aChannelFd, FdArray& aReservedFds) { // We already performed fork(). It's safe to free the "danger zone" of file // descriptors . mozilla::ipc::CloseFileDescriptors(aReservedFds); mozilla::ipc::ProcLoaderClientInit(aPeerPid, aChannelFd); } int XRE_ProcLoaderServiceRun(pid_t aPeerPid, int aFd, int aArgc, const char *aArgv[], FdArray& aReservedFds) { return mozilla::ipc::ProcLoaderServiceRun(aPeerPid, aFd, aArgc, aArgv, aReservedFds); } #endif /* MOZ_B2G_LOADER */