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 
7 /*****************************************************************************
8  *
9  * nsProcess is used to execute new processes and specify if you want to
10  * wait (blocking) or continue (non-blocking).
11  *
12  *****************************************************************************
13  */
14 
15 #include "mozilla/ArrayUtils.h"
16 
17 #include "nsCOMPtr.h"
18 #include "nsIFile.h"
19 #include "nsMemory.h"
20 #include "nsProcess.h"
21 #include "prio.h"
22 #include "prenv.h"
23 #include "nsCRT.h"
24 #include "nsThreadUtils.h"
25 #include "nsIObserverService.h"
26 #include "nsXULAppAPI.h"
27 #include "mozilla/Services.h"
28 #include "GeckoProfiler.h"
29 
30 #include <stdlib.h>
31 
32 #if defined(PROCESSMODEL_WINAPI)
33 #  include "nsString.h"
34 #  include "nsLiteralString.h"
35 #  include "nsReadableUtils.h"
36 #  include "mozilla/AssembleCmdLine.h"
37 #  include "mozilla/UniquePtrExtensions.h"
38 #else
39 #  ifdef XP_MACOSX
40 #    include <crt_externs.h>
41 #    include <spawn.h>
42 #  endif
43 #  ifdef XP_UNIX
44 #    ifndef XP_MACOSX
45 #      include "base/process_util.h"
46 #    endif
47 #    include <sys/wait.h>
48 #    include <sys/errno.h>
49 #  endif
50 #  include <sys/types.h>
51 #  include <signal.h>
52 #endif
53 
54 using namespace mozilla;
55 
56 //-------------------------------------------------------------------//
57 // nsIProcess implementation
58 //-------------------------------------------------------------------//
NS_IMPL_ISUPPORTS(nsProcess,nsIProcess,nsIObserver)59 NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, nsIObserver)
60 
61 // Constructor
62 nsProcess::nsProcess()
63     : mThread(nullptr),
64       mLock("nsProcess.mLock"),
65       mShutdown(false),
66       mBlocking(false),
67       mStartHidden(false),
68       mNoShell(false),
69       mPid(-1),
70       mExitValue(-1)
71 #if !defined(XP_UNIX)
72       ,
73       mProcess(nullptr)
74 #endif
75 {
76 }
77 
78 // Destructor
79 nsProcess::~nsProcess() = default;
80 
81 NS_IMETHODIMP
Init(nsIFile * aExecutable)82 nsProcess::Init(nsIFile* aExecutable) {
83   if (mExecutable) {
84     return NS_ERROR_ALREADY_INITIALIZED;
85   }
86 
87   if (NS_WARN_IF(!aExecutable)) {
88     return NS_ERROR_INVALID_ARG;
89   }
90   bool isFile;
91 
92   // First make sure the file exists
93   nsresult rv = aExecutable->IsFile(&isFile);
94   if (NS_FAILED(rv)) {
95     return rv;
96   }
97   if (!isFile) {
98     return NS_ERROR_FAILURE;
99   }
100 
101   // Store the nsIFile in mExecutable
102   mExecutable = aExecutable;
103   // Get the path because it is needed by the NSPR process creation
104 #ifdef XP_WIN
105   rv = mExecutable->GetTarget(mTargetPath);
106   if (NS_FAILED(rv) || mTargetPath.IsEmpty())
107 #endif
108     rv = mExecutable->GetPath(mTargetPath);
109 
110   return rv;
111 }
112 
Monitor(void * aArg)113 void nsProcess::Monitor(void* aArg) {
114   RefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(aArg));
115 
116 #ifdef MOZ_GECKO_PROFILER
117   Maybe<AutoProfilerRegisterThread> registerThread;
118   if (!process->mBlocking) {
119     registerThread.emplace("RunProcess");
120   }
121 #endif
122   if (!process->mBlocking) {
123     NS_SetCurrentThreadName("RunProcess");
124   }
125 
126 #if defined(PROCESSMODEL_WINAPI)
127   DWORD dwRetVal;
128   unsigned long exitCode = -1;
129 
130   dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
131   if (dwRetVal != WAIT_FAILED) {
132     if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) {
133       exitCode = -1;
134     }
135   }
136 
137   // Lock in case Kill or GetExitCode are called during this
138   {
139     MutexAutoLock lock(process->mLock);
140     CloseHandle(process->mProcess);
141     process->mProcess = nullptr;
142     process->mExitValue = exitCode;
143     if (process->mShutdown) {
144       return;
145     }
146   }
147 #else
148 #  ifdef XP_UNIX
149   int exitCode = -1;
150   int status = 0;
151   pid_t result;
152   do {
153     result = waitpid(process->mPid, &status, 0);
154   } while (result == -1 && errno == EINTR);
155   if (result == process->mPid) {
156     if (WIFEXITED(status)) {
157       exitCode = WEXITSTATUS(status);
158     } else if (WIFSIGNALED(status)) {
159       exitCode = 256;  // match NSPR's signal exit status
160     }
161   }
162 #  else
163   int32_t exitCode = -1;
164   if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) {
165     exitCode = -1;
166   }
167 #  endif
168 
169   // Lock in case Kill or GetExitCode are called during this
170   {
171     MutexAutoLock lock(process->mLock);
172 #  if !defined(XP_UNIX)
173     process->mProcess = nullptr;
174 #  endif
175     process->mExitValue = exitCode;
176     if (process->mShutdown) {
177       return;
178     }
179   }
180 #endif
181 
182   // If we ran a background thread for the monitor then notify on the main
183   // thread
184   if (NS_IsMainThread()) {
185     process->ProcessComplete();
186   } else {
187     NS_DispatchToMainThread(NewRunnableMethod(
188         "nsProcess::ProcessComplete", process, &nsProcess::ProcessComplete));
189   }
190 }
191 
ProcessComplete()192 void nsProcess::ProcessComplete() {
193   if (mThread) {
194     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
195     if (os) {
196       os->RemoveObserver(this, "xpcom-shutdown");
197     }
198     PR_JoinThread(mThread);
199     mThread = nullptr;
200   }
201 
202   const char* topic;
203   if (mExitValue != 0) {
204     topic = "process-failed";
205   } else {
206     topic = "process-finished";
207   }
208 
209   mPid = -1;
210   nsCOMPtr<nsIObserver> observer = mObserver.GetValue();
211   mObserver = nullptr;
212 
213   if (observer) {
214     observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
215   }
216 }
217 
218 // XXXldb |aArgs| has the wrong const-ness
219 NS_IMETHODIMP
Run(bool aBlocking,const char ** aArgs,uint32_t aCount)220 nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount) {
221   return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false);
222 }
223 
224 // XXXldb |aArgs| has the wrong const-ness
225 NS_IMETHODIMP
RunAsync(const char ** aArgs,uint32_t aCount,nsIObserver * aObserver,bool aHoldWeak)226 nsProcess::RunAsync(const char** aArgs, uint32_t aCount, nsIObserver* aObserver,
227                     bool aHoldWeak) {
228   return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak);
229 }
230 
CopyArgsAndRunProcess(bool aBlocking,const char ** aArgs,uint32_t aCount,nsIObserver * aObserver,bool aHoldWeak)231 nsresult nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs,
232                                           uint32_t aCount,
233                                           nsIObserver* aObserver,
234                                           bool aHoldWeak) {
235   // Add one to the aCount for the program name and one for null termination.
236   char** my_argv = nullptr;
237   my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2));
238 
239   my_argv[0] = ToNewUTF8String(mTargetPath);
240 
241   for (uint32_t i = 0; i < aCount; ++i) {
242     my_argv[i + 1] = const_cast<char*>(aArgs[i]);
243   }
244 
245   my_argv[aCount + 1] = nullptr;
246 
247   nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false);
248 
249   free(my_argv[0]);
250   free(my_argv);
251   return rv;
252 }
253 
254 // XXXldb |aArgs| has the wrong const-ness
255 NS_IMETHODIMP
Runw(bool aBlocking,const char16_t ** aArgs,uint32_t aCount)256 nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount) {
257   return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false);
258 }
259 
260 // XXXldb |aArgs| has the wrong const-ness
261 NS_IMETHODIMP
RunwAsync(const char16_t ** aArgs,uint32_t aCount,nsIObserver * aObserver,bool aHoldWeak)262 nsProcess::RunwAsync(const char16_t** aArgs, uint32_t aCount,
263                      nsIObserver* aObserver, bool aHoldWeak) {
264   return CopyArgsAndRunProcessw(false, aArgs, aCount, aObserver, aHoldWeak);
265 }
266 
CopyArgsAndRunProcessw(bool aBlocking,const char16_t ** aArgs,uint32_t aCount,nsIObserver * aObserver,bool aHoldWeak)267 nsresult nsProcess::CopyArgsAndRunProcessw(bool aBlocking,
268                                            const char16_t** aArgs,
269                                            uint32_t aCount,
270                                            nsIObserver* aObserver,
271                                            bool aHoldWeak) {
272   // Add one to the aCount for the program name and one for null termination.
273   char** my_argv = nullptr;
274   my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2));
275 
276   my_argv[0] = ToNewUTF8String(mTargetPath);
277 
278   for (uint32_t i = 0; i < aCount; i++) {
279     my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i]));
280   }
281 
282   my_argv[aCount + 1] = nullptr;
283 
284   nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true);
285 
286   for (uint32_t i = 0; i <= aCount; ++i) {
287     free(my_argv[i]);
288   }
289   free(my_argv);
290   return rv;
291 }
292 
RunProcess(bool aBlocking,char ** aMyArgv,nsIObserver * aObserver,bool aHoldWeak,bool aArgsUTF8)293 nsresult nsProcess::RunProcess(bool aBlocking, char** aMyArgv,
294                                nsIObserver* aObserver, bool aHoldWeak,
295                                bool aArgsUTF8) {
296   NS_WARNING_ASSERTION(!XRE_IsContentProcess(),
297                        "No launching of new processes in the content process");
298 
299   if (NS_WARN_IF(!mExecutable)) {
300     return NS_ERROR_NOT_INITIALIZED;
301   }
302   if (NS_WARN_IF(mThread)) {
303     return NS_ERROR_ALREADY_INITIALIZED;
304   }
305 
306   if (aObserver) {
307     if (aHoldWeak) {
308       nsresult rv = NS_OK;
309       mObserver = do_GetWeakReference(aObserver, &rv);
310       NS_ENSURE_SUCCESS(rv, rv);
311     } else {
312       mObserver = aObserver;
313     }
314   }
315 
316   mExitValue = -1;
317   mPid = -1;
318 
319 #if defined(PROCESSMODEL_WINAPI)
320   BOOL retVal;
321   UniqueFreePtr<wchar_t> cmdLine;
322 
323   // |aMyArgv| is null-terminated and always starts with the program path. If
324   // the second slot is non-null then arguments are being passed.
325   if (aMyArgv[1] || mNoShell) {
326     // Pass the executable path as argv[0] to the launched program when calling
327     // CreateProcess().
328     char** argv = mNoShell ? aMyArgv : aMyArgv + 1;
329 
330     wchar_t* assembledCmdLine = nullptr;
331     if (assembleCmdLine(argv, &assembledCmdLine,
332                         aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) {
333       return NS_ERROR_FILE_EXECUTION_FAILED;
334     }
335     cmdLine.reset(assembledCmdLine);
336   }
337 
338   // The program name in aMyArgv[0] is always UTF-8
339   NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]);
340 
341   if (mNoShell) {
342     STARTUPINFO startupInfo;
343     ZeroMemory(&startupInfo, sizeof(startupInfo));
344     startupInfo.cb = sizeof(startupInfo);
345     startupInfo.dwFlags = STARTF_USESHOWWINDOW;
346     startupInfo.wShowWindow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL;
347 
348     PROCESS_INFORMATION processInfo;
349     retVal = CreateProcess(/* lpApplicationName = */ wideFile.get(),
350                            /* lpCommandLine */ cmdLine.get(),
351                            /* lpProcessAttributes = */ NULL,
352                            /* lpThreadAttributes = */ NULL,
353                            /* bInheritHandles = */ FALSE,
354                            /* dwCreationFlags = */ 0,
355                            /* lpEnvironment = */ NULL,
356                            /* lpCurrentDirectory = */ NULL,
357                            /* lpStartupInfo = */ &startupInfo,
358                            /* lpProcessInformation */ &processInfo);
359 
360     if (!retVal) {
361       return NS_ERROR_FILE_EXECUTION_FAILED;
362     }
363 
364     CloseHandle(processInfo.hThread);
365 
366     mProcess = processInfo.hProcess;
367   } else {
368     SHELLEXECUTEINFOW sinfo;
369     memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
370     sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
371     sinfo.hwnd = nullptr;
372     sinfo.lpFile = wideFile.get();
373     sinfo.nShow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL;
374 
375     /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
376      * from appearing. This makes behavior the same on all platforms. The flag
377      * will not have any effect on non-console applications.
378      */
379     sinfo.fMask =
380         SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS;
381 
382     if (cmdLine) {
383       sinfo.lpParameters = cmdLine.get();
384     }
385 
386     retVal = ShellExecuteExW(&sinfo);
387     if (!retVal) {
388       return NS_ERROR_FILE_EXECUTION_FAILED;
389     }
390 
391     mProcess = sinfo.hProcess;
392   }
393 
394   mPid = GetProcessId(mProcess);
395 #elif defined(XP_MACOSX)
396   // Note: |aMyArgv| is already null-terminated as required by posix_spawnp.
397   pid_t newPid = 0;
398   int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, nullptr, aMyArgv,
399                             *_NSGetEnviron());
400   mPid = static_cast<int32_t>(newPid);
401 
402   if (result != 0) {
403     return NS_ERROR_FAILURE;
404   }
405 #elif defined(XP_UNIX)
406   base::LaunchOptions options;
407   std::vector<std::string> argvVec;
408   for (char** arg = aMyArgv; *arg != nullptr; ++arg) {
409     argvVec.push_back(*arg);
410   }
411   pid_t newPid;
412   if (base::LaunchApp(argvVec, options, &newPid)) {
413     static_assert(sizeof(pid_t) <= sizeof(int32_t),
414                   "mPid is large enough to hold a pid");
415     mPid = static_cast<int32_t>(newPid);
416   } else {
417     return NS_ERROR_FAILURE;
418   }
419 #else
420   mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr);
421   if (!mProcess) {
422     return NS_ERROR_FAILURE;
423   }
424   struct MYProcess {
425     uint32_t pid;
426   };
427   MYProcess* ptrProc = (MYProcess*)mProcess;
428   mPid = ptrProc->pid;
429 #endif
430 
431   NS_ADDREF_THIS();
432   mBlocking = aBlocking;
433   if (aBlocking) {
434     Monitor(this);
435     if (mExitValue < 0) {
436       return NS_ERROR_FILE_EXECUTION_FAILED;
437     }
438   } else {
439     mThread =
440         PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, PR_PRIORITY_NORMAL,
441                         PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
442     if (!mThread) {
443       NS_RELEASE_THIS();
444       return NS_ERROR_FAILURE;
445     }
446 
447     // It isn't a failure if we just can't watch for shutdown
448     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
449     if (os) {
450       os->AddObserver(this, "xpcom-shutdown", false);
451     }
452   }
453 
454   return NS_OK;
455 }
456 
457 NS_IMETHODIMP
GetIsRunning(bool * aIsRunning)458 nsProcess::GetIsRunning(bool* aIsRunning) {
459   if (mThread) {
460     *aIsRunning = true;
461   } else {
462     *aIsRunning = false;
463   }
464 
465   return NS_OK;
466 }
467 
468 NS_IMETHODIMP
GetStartHidden(bool * aStartHidden)469 nsProcess::GetStartHidden(bool* aStartHidden) {
470   *aStartHidden = mStartHidden;
471   return NS_OK;
472 }
473 
474 NS_IMETHODIMP
SetStartHidden(bool aStartHidden)475 nsProcess::SetStartHidden(bool aStartHidden) {
476   mStartHidden = aStartHidden;
477   return NS_OK;
478 }
479 
480 NS_IMETHODIMP
GetNoShell(bool * aNoShell)481 nsProcess::GetNoShell(bool* aNoShell) {
482   *aNoShell = mNoShell;
483   return NS_OK;
484 }
485 
486 NS_IMETHODIMP
SetNoShell(bool aNoShell)487 nsProcess::SetNoShell(bool aNoShell) {
488   mNoShell = aNoShell;
489   return NS_OK;
490 }
491 
492 NS_IMETHODIMP
GetPid(uint32_t * aPid)493 nsProcess::GetPid(uint32_t* aPid) {
494   if (!mThread) {
495     return NS_ERROR_FAILURE;
496   }
497   if (mPid < 0) {
498     return NS_ERROR_NOT_IMPLEMENTED;
499   }
500   *aPid = mPid;
501   return NS_OK;
502 }
503 
504 NS_IMETHODIMP
Kill()505 nsProcess::Kill() {
506   if (!mThread) {
507     return NS_ERROR_FAILURE;
508   }
509 
510   {
511     MutexAutoLock lock(mLock);
512 #if defined(PROCESSMODEL_WINAPI)
513     if (TerminateProcess(mProcess, 0) == 0) {
514       return NS_ERROR_FAILURE;
515     }
516 #elif defined(XP_UNIX)
517     if (kill(mPid, SIGKILL) != 0) {
518       return NS_ERROR_FAILURE;
519     }
520 #else
521     if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) {
522       return NS_ERROR_FAILURE;
523     }
524 #endif
525   }
526 
527   // We must null out mThread if we want IsRunning to return false immediately
528   // after this call.
529   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
530   if (os) {
531     os->RemoveObserver(this, "xpcom-shutdown");
532   }
533   PR_JoinThread(mThread);
534   mThread = nullptr;
535 
536   return NS_OK;
537 }
538 
539 NS_IMETHODIMP
GetExitValue(int32_t * aExitValue)540 nsProcess::GetExitValue(int32_t* aExitValue) {
541   MutexAutoLock lock(mLock);
542 
543   *aExitValue = mExitValue;
544 
545   return NS_OK;
546 }
547 
548 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)549 nsProcess::Observe(nsISupports* aSubject, const char* aTopic,
550                    const char16_t* aData) {
551   // Shutting down, drop all references
552   if (mThread) {
553     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
554     if (os) {
555       os->RemoveObserver(this, "xpcom-shutdown");
556     }
557     mThread = nullptr;
558   }
559 
560   mObserver = nullptr;
561 
562   MutexAutoLock lock(mLock);
563   mShutdown = true;
564 
565   return NS_OK;
566 }
567