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