1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ForkServiceChild.h"
7 #include "ForkServer.h"
8 #include "mozilla/ipc/IPDLParamTraits.h"
9 #include "mozilla/Logging.h"
10 #include "mozilla/ipc/GeckoChildProcessHost.h"
11 #include "mozilla/ipc/ProtocolMessageUtils.h"
12 #include "mozilla/StaticPrefs_dom.h"
13 #include "mozilla/Services.h"
14 #include "ipc/IPCMessageUtilsSpecializations.h"
15 #include "nsIObserverService.h"
16 
17 #include <unistd.h>
18 #include <fcntl.h>
19 
20 namespace mozilla {
21 namespace ipc {
22 
23 extern LazyLogModule gForkServiceLog;
24 
25 mozilla::UniquePtr<ForkServiceChild> ForkServiceChild::sForkServiceChild;
26 
StartForkServer()27 void ForkServiceChild::StartForkServer() {
28   std::vector<std::string> extraArgs;
29 
30   GeckoChildProcessHost* subprocess =
31       new GeckoChildProcessHost(GeckoProcessType_ForkServer, false);
32   subprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs));
33 
34   int fd = subprocess->GetChannel()->GetFileDescriptor();
35   fd = dup(fd);  // Dup it because the channel will close it.
36   int fs_flags = fcntl(fd, F_GETFL, 0);
37   fcntl(fd, F_SETFL, fs_flags & ~O_NONBLOCK);
38   int fd_flags = fcntl(fd, F_GETFD, 0);
39   fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC);
40 
41   sForkServiceChild = mozilla::MakeUnique<ForkServiceChild>(fd, subprocess);
42 
43   // Without doing this, IO thread may intercept messages since the
44   // IPC::Channel created by it is still open.
45   subprocess->GetChannel()->Close();
46 }
47 
StopForkServer()48 void ForkServiceChild::StopForkServer() { sForkServiceChild = nullptr; }
49 
ForkServiceChild(int aFd,GeckoChildProcessHost * aProcess)50 ForkServiceChild::ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess)
51     : mWaitForHello(true), mFailed(false), mProcess(aProcess) {
52   mTcver = MakeUnique<MiniTransceiver>(aFd);
53 }
54 
~ForkServiceChild()55 ForkServiceChild::~ForkServiceChild() {
56   mProcess->Destroy();
57   close(mTcver->GetFD());
58 }
59 
SendForkNewSubprocess(const nsTArray<nsCString> & aArgv,const nsTArray<EnvVar> & aEnvMap,const nsTArray<FdMapping> & aFdsRemap,pid_t * aPid)60 bool ForkServiceChild::SendForkNewSubprocess(
61     const nsTArray<nsCString>& aArgv, const nsTArray<EnvVar>& aEnvMap,
62     const nsTArray<FdMapping>& aFdsRemap, pid_t* aPid) {
63   if (mWaitForHello) {
64     // IPC::Channel created by the GeckoChildProcessHost has
65     // already send a HELLO.  It is expected to receive a hello
66     // message from the fork server too.
67     IPC::Message hello;
68     mTcver->RecvInfallible(hello, "Fail to receive HELLO message");
69     MOZ_ASSERT(hello.type() == ForkServer::kHELLO_MESSAGE_TYPE);
70     mWaitForHello = false;
71   }
72 
73   mRecvPid = -1;
74   IPC::Message msg(MSG_ROUTING_CONTROL, Msg_ForkNewSubprocess__ID);
75 
76   IPC::MessageWriter writer(msg);
77   WriteIPDLParam(&writer, nullptr, aArgv);
78   WriteIPDLParam(&writer, nullptr, aEnvMap);
79   WriteIPDLParam(&writer, nullptr, aFdsRemap);
80   if (!mTcver->Send(msg)) {
81     MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
82             ("the pipe to the fork server is closed or having errors"));
83     OnError();
84     return false;
85   }
86 
87   IPC::Message reply;
88   if (!mTcver->Recv(reply)) {
89     MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
90             ("the pipe to the fork server is closed or having errors"));
91     OnError();
92     return false;
93   }
94   OnMessageReceived(std::move(reply));
95 
96   MOZ_ASSERT(mRecvPid != -1);
97   *aPid = mRecvPid;
98   return true;
99 }
100 
OnMessageReceived(IPC::Message && message)101 void ForkServiceChild::OnMessageReceived(IPC::Message&& message) {
102   if (message.type() != Reply_ForkNewSubprocess__ID) {
103     MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
104             ("unknown reply type %d", message.type()));
105     return;
106   }
107   IPC::MessageReader reader(message);
108 
109   if (!ReadIPDLParam(&reader, nullptr, &mRecvPid)) {
110     MOZ_CRASH("Error deserializing 'pid_t'");
111   }
112   reader.EndRead();
113 }
114 
OnError()115 void ForkServiceChild::OnError() {
116   mFailed = true;
117   ForkServerLauncher::RestartForkServer();
118 }
119 
120 NS_IMPL_ISUPPORTS(ForkServerLauncher, nsIObserver)
121 
122 bool ForkServerLauncher::mHaveStartedClient = false;
123 StaticRefPtr<ForkServerLauncher> ForkServerLauncher::mSingleton;
124 
ForkServerLauncher()125 ForkServerLauncher::ForkServerLauncher() {}
126 
~ForkServerLauncher()127 ForkServerLauncher::~ForkServerLauncher() {}
128 
Create()129 already_AddRefed<ForkServerLauncher> ForkServerLauncher::Create() {
130   if (mSingleton == nullptr) {
131     mSingleton = new ForkServerLauncher();
132   }
133   RefPtr<ForkServerLauncher> launcher = mSingleton;
134   return launcher.forget();
135 }
136 
137 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)138 ForkServerLauncher::Observe(nsISupports* aSubject, const char* aTopic,
139                             const char16_t* aData) {
140   if (strcmp(aTopic, NS_XPCOM_STARTUP_CATEGORY) == 0) {
141     nsCOMPtr<nsIObserverService> obsSvc =
142         mozilla::services::GetObserverService();
143     MOZ_ASSERT(obsSvc != nullptr);
144     // preferences are not available until final-ui-startup
145     obsSvc->AddObserver(this, "final-ui-startup", false);
146   } else if (!mHaveStartedClient && strcmp(aTopic, "final-ui-startup") == 0) {
147     if (StaticPrefs::dom_ipc_forkserver_enable_AtStartup()) {
148       mHaveStartedClient = true;
149       ForkServiceChild::StartForkServer();
150 
151       nsCOMPtr<nsIObserverService> obsSvc =
152           mozilla::services::GetObserverService();
153       MOZ_ASSERT(obsSvc != nullptr);
154       obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
155     } else {
156       mSingleton = nullptr;
157     }
158   }
159 
160   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
161     if (mHaveStartedClient) {
162       mHaveStartedClient = false;
163       ForkServiceChild::StopForkServer();
164     }
165 
166     // To make leak checker happy!
167     mSingleton = nullptr;
168   }
169   return NS_OK;
170 }
171 
RestartForkServer()172 void ForkServerLauncher::RestartForkServer() {
173   // Restart fork server
174   NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
175       NS_NewRunnableFunction("OnForkServerError",
176                              [] {
177                                if (mSingleton) {
178                                  ForkServiceChild::StopForkServer();
179                                  ForkServiceChild::StartForkServer();
180                                }
181                              }),
182       EventQueuePriority::Idle));
183 }
184 
185 }  // namespace ipc
186 }  // namespace mozilla
187