Bumping manifests a=b2g-bump
[gecko.git] / xpcom / threads / nsProcessCommon.cpp
blob4880f478e109704d91bb6f754a3b603661a5bb3a
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/. */
7 /*****************************************************************************
9 * nsProcess is used to execute new processes and specify if you want to
10 * wait (blocking) or continue (non-blocking).
12 *****************************************************************************
15 #include "mozilla/ArrayUtils.h"
17 #include "nsCOMPtr.h"
18 #include "nsAutoPtr.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 "mozilla/Services.h"
28 #include <stdlib.h>
30 #if defined(PROCESSMODEL_WINAPI)
31 #include "prmem.h"
32 #include "nsString.h"
33 #include "nsLiteralString.h"
34 #include "nsReadableUtils.h"
35 #else
36 #ifdef XP_MACOSX
37 #include <crt_externs.h>
38 #include <spawn.h>
39 #include <sys/wait.h>
40 #endif
41 #include <sys/types.h>
42 #include <signal.h>
43 #endif
45 using namespace mozilla;
47 #ifdef XP_MACOSX
48 cpu_type_t pref_cpu_types[2] = {
49 #if defined(__i386__)
50 CPU_TYPE_X86,
51 #elif defined(__x86_64__)
52 CPU_TYPE_X86_64,
53 #elif defined(__ppc__)
54 CPU_TYPE_POWERPC,
55 #endif
56 CPU_TYPE_ANY
58 #endif
60 //-------------------------------------------------------------------//
61 // nsIProcess implementation
62 //-------------------------------------------------------------------//
63 NS_IMPL_ISUPPORTS(nsProcess, nsIProcess,
64 nsIObserver)
66 //Constructor
67 nsProcess::nsProcess()
68 : mThread(nullptr)
69 , mLock("nsProcess.mLock")
70 , mShutdown(false)
71 , mBlocking(false)
72 , mPid(-1)
73 , mObserver(nullptr)
74 , mWeakObserver(nullptr)
75 , mExitValue(-1)
76 #if !defined(XP_MACOSX)
77 , mProcess(nullptr)
78 #endif
82 //Destructor
83 nsProcess::~nsProcess()
87 NS_IMETHODIMP
88 nsProcess::Init(nsIFile* aExecutable)
90 if (mExecutable) {
91 return NS_ERROR_ALREADY_INITIALIZED;
94 if (NS_WARN_IF(!aExecutable)) {
95 return NS_ERROR_INVALID_ARG;
97 bool isFile;
99 //First make sure the file exists
100 nsresult rv = aExecutable->IsFile(&isFile);
101 if (NS_FAILED(rv)) {
102 return rv;
104 if (!isFile) {
105 return NS_ERROR_FAILURE;
108 //Store the nsIFile in mExecutable
109 mExecutable = aExecutable;
110 //Get the path because it is needed by the NSPR process creation
111 #ifdef XP_WIN
112 rv = mExecutable->GetTarget(mTargetPath);
113 if (NS_FAILED(rv) || mTargetPath.IsEmpty())
114 #endif
115 rv = mExecutable->GetPath(mTargetPath);
117 return rv;
121 #if defined(XP_WIN)
122 // Out param `aWideCmdLine` must be PR_Freed by the caller.
123 static int
124 assembleCmdLine(char* const* aArgv, wchar_t** aWideCmdLine, UINT aCodePage)
126 char* const* arg;
127 char* p;
128 char* q;
129 char* cmdLine;
130 int cmdLineSize;
131 int numBackslashes;
132 int i;
133 int argNeedQuotes;
136 * Find out how large the command line buffer should be.
138 cmdLineSize = 0;
139 for (arg = aArgv; *arg; ++arg) {
141 * \ and " need to be escaped by a \. In the worst case,
142 * every character is a \ or ", so the string of length
143 * may double. If we quote an argument, that needs two ".
144 * Finally, we need a space between arguments, and
145 * a null byte at the end of command line.
147 cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */
148 + 2 /* we quote every argument */
149 + 1; /* space in between, or final null */
151 p = cmdLine = (char*)PR_MALLOC(cmdLineSize * sizeof(char));
152 if (!p) {
153 return -1;
156 for (arg = aArgv; *arg; ++arg) {
157 /* Add a space to separates the arguments */
158 if (arg != aArgv) {
159 *p++ = ' ';
161 q = *arg;
162 numBackslashes = 0;
163 argNeedQuotes = 0;
165 /* If the argument contains white space, it needs to be quoted. */
166 if (strpbrk(*arg, " \f\n\r\t\v")) {
167 argNeedQuotes = 1;
170 if (argNeedQuotes) {
171 *p++ = '"';
173 while (*q) {
174 if (*q == '\\') {
175 numBackslashes++;
176 q++;
177 } else if (*q == '"') {
178 if (numBackslashes) {
180 * Double the backslashes since they are followed
181 * by a quote
183 for (i = 0; i < 2 * numBackslashes; i++) {
184 *p++ = '\\';
186 numBackslashes = 0;
188 /* To escape the quote */
189 *p++ = '\\';
190 *p++ = *q++;
191 } else {
192 if (numBackslashes) {
194 * Backslashes are not followed by a quote, so
195 * don't need to double the backslashes.
197 for (i = 0; i < numBackslashes; i++) {
198 *p++ = '\\';
200 numBackslashes = 0;
202 *p++ = *q++;
206 /* Now we are at the end of this argument */
207 if (numBackslashes) {
209 * Double the backslashes if we have a quote string
210 * delimiter at the end.
212 if (argNeedQuotes) {
213 numBackslashes *= 2;
215 for (i = 0; i < numBackslashes; i++) {
216 *p++ = '\\';
219 if (argNeedQuotes) {
220 *p++ = '"';
224 *p = '\0';
225 int32_t numChars = MultiByteToWideChar(aCodePage, 0, cmdLine, -1, nullptr, 0);
226 *aWideCmdLine = (wchar_t*)PR_MALLOC(numChars * sizeof(wchar_t));
227 MultiByteToWideChar(aCodePage, 0, cmdLine, -1, *aWideCmdLine, numChars);
228 PR_Free(cmdLine);
229 return 0;
231 #endif
233 void
234 nsProcess::Monitor(void* aArg)
236 nsRefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(aArg));
238 if (!process->mBlocking) {
239 PR_SetCurrentThreadName("RunProcess");
242 #if defined(PROCESSMODEL_WINAPI)
243 DWORD dwRetVal;
244 unsigned long exitCode = -1;
246 dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
247 if (dwRetVal != WAIT_FAILED) {
248 if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) {
249 exitCode = -1;
253 // Lock in case Kill or GetExitCode are called during this
255 MutexAutoLock lock(process->mLock);
256 CloseHandle(process->mProcess);
257 process->mProcess = nullptr;
258 process->mExitValue = exitCode;
259 if (process->mShutdown) {
260 return;
263 #else
264 #ifdef XP_MACOSX
265 int exitCode = -1;
266 int status = 0;
267 if (waitpid(process->mPid, &status, 0) == process->mPid) {
268 if (WIFEXITED(status)) {
269 exitCode = WEXITSTATUS(status);
270 } else if (WIFSIGNALED(status)) {
271 exitCode = 256; // match NSPR's signal exit status
274 #else
275 int32_t exitCode = -1;
276 if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) {
277 exitCode = -1;
279 #endif
281 // Lock in case Kill or GetExitCode are called during this
283 MutexAutoLock lock(process->mLock);
284 #if !defined(XP_MACOSX)
285 process->mProcess = nullptr;
286 #endif
287 process->mExitValue = exitCode;
288 if (process->mShutdown) {
289 return;
292 #endif
294 // If we ran a background thread for the monitor then notify on the main
295 // thread
296 if (NS_IsMainThread()) {
297 process->ProcessComplete();
298 } else {
299 nsCOMPtr<nsIRunnable> event =
300 NS_NewRunnableMethod(process, &nsProcess::ProcessComplete);
301 NS_DispatchToMainThread(event);
305 void
306 nsProcess::ProcessComplete()
308 if (mThread) {
309 nsCOMPtr<nsIObserverService> os =
310 mozilla::services::GetObserverService();
311 if (os) {
312 os->RemoveObserver(this, "xpcom-shutdown");
314 PR_JoinThread(mThread);
315 mThread = nullptr;
318 const char* topic;
319 if (mExitValue < 0) {
320 topic = "process-failed";
321 } else {
322 topic = "process-finished";
325 mPid = -1;
326 nsCOMPtr<nsIObserver> observer;
327 if (mWeakObserver) {
328 observer = do_QueryReferent(mWeakObserver);
329 } else if (mObserver) {
330 observer = mObserver;
332 mObserver = nullptr;
333 mWeakObserver = nullptr;
335 if (observer) {
336 observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
340 // XXXldb |aArgs| has the wrong const-ness
341 NS_IMETHODIMP
342 nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount)
344 return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false);
347 // XXXldb |aArgs| has the wrong const-ness
348 NS_IMETHODIMP
349 nsProcess::RunAsync(const char** aArgs, uint32_t aCount,
350 nsIObserver* aObserver, bool aHoldWeak)
352 return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak);
355 nsresult
356 nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs,
357 uint32_t aCount, nsIObserver* aObserver,
358 bool aHoldWeak)
360 // Add one to the aCount for the program name and one for null termination.
361 char** my_argv = nullptr;
362 my_argv = (char**)NS_Alloc(sizeof(char*) * (aCount + 2));
363 if (!my_argv) {
364 return NS_ERROR_OUT_OF_MEMORY;
367 my_argv[0] = ToNewUTF8String(mTargetPath);
369 for (uint32_t i = 0; i < aCount; ++i) {
370 my_argv[i + 1] = const_cast<char*>(aArgs[i]);
373 my_argv[aCount + 1] = nullptr;
375 nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false);
377 NS_Free(my_argv[0]);
378 NS_Free(my_argv);
379 return rv;
382 // XXXldb |aArgs| has the wrong const-ness
383 NS_IMETHODIMP
384 nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount)
386 return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false);
389 // XXXldb |aArgs| has the wrong const-ness
390 NS_IMETHODIMP
391 nsProcess::RunwAsync(const char16_t** aArgs, uint32_t aCount,
392 nsIObserver* aObserver, bool aHoldWeak)
394 return CopyArgsAndRunProcessw(false, aArgs, aCount, aObserver, aHoldWeak);
397 nsresult
398 nsProcess::CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs,
399 uint32_t aCount, nsIObserver* aObserver,
400 bool aHoldWeak)
402 // Add one to the aCount for the program name and one for null termination.
403 char** my_argv = nullptr;
404 my_argv = (char**)NS_Alloc(sizeof(char*) * (aCount + 2));
405 if (!my_argv) {
406 return NS_ERROR_OUT_OF_MEMORY;
409 my_argv[0] = ToNewUTF8String(mTargetPath);
411 for (uint32_t i = 0; i < aCount; i++) {
412 my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i]));
415 my_argv[aCount + 1] = nullptr;
417 nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true);
419 for (uint32_t i = 0; i <= aCount; ++i) {
420 NS_Free(my_argv[i]);
422 NS_Free(my_argv);
423 return rv;
426 nsresult
427 nsProcess::RunProcess(bool aBlocking, char** aMyArgv, nsIObserver* aObserver,
428 bool aHoldWeak, bool aArgsUTF8)
430 if (NS_WARN_IF(!mExecutable)) {
431 return NS_ERROR_NOT_INITIALIZED;
433 if (NS_WARN_IF(mThread)) {
434 return NS_ERROR_ALREADY_INITIALIZED;
437 if (aObserver) {
438 if (aHoldWeak) {
439 mWeakObserver = do_GetWeakReference(aObserver);
440 if (!mWeakObserver) {
441 return NS_NOINTERFACE;
443 } else {
444 mObserver = aObserver;
448 mExitValue = -1;
449 mPid = -1;
451 #if defined(PROCESSMODEL_WINAPI)
452 BOOL retVal;
453 wchar_t* cmdLine = nullptr;
455 // |aMyArgv| is null-terminated and always starts with the program path. If
456 // the second slot is non-null then arguments are being passed.
457 if (aMyArgv[1] && assembleCmdLine(aMyArgv + 1, &cmdLine,
458 aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) {
459 return NS_ERROR_FILE_EXECUTION_FAILED;
462 /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
463 * from appearing. This makes behavior the same on all platforms. The flag
464 * will not have any effect on non-console applications.
467 // The program name in aMyArgv[0] is always UTF-8
468 NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]);
470 SHELLEXECUTEINFOW sinfo;
471 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
472 sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
473 sinfo.hwnd = nullptr;
474 sinfo.lpFile = wideFile.get();
475 sinfo.nShow = SW_SHOWNORMAL;
476 sinfo.fMask = SEE_MASK_FLAG_DDEWAIT |
477 SEE_MASK_NO_CONSOLE |
478 SEE_MASK_NOCLOSEPROCESS;
480 if (cmdLine) {
481 sinfo.lpParameters = cmdLine;
484 retVal = ShellExecuteExW(&sinfo);
485 if (!retVal) {
486 return NS_ERROR_FILE_EXECUTION_FAILED;
489 mProcess = sinfo.hProcess;
491 if (cmdLine) {
492 PR_Free(cmdLine);
495 mPid = GetProcessId(mProcess);
496 #elif defined(XP_MACOSX)
497 // Initialize spawn attributes.
498 posix_spawnattr_t spawnattr;
499 if (posix_spawnattr_init(&spawnattr) != 0) {
500 return NS_ERROR_FAILURE;
503 // Set spawn attributes.
504 size_t attr_count = ArrayLength(pref_cpu_types);
505 size_t attr_ocount = 0;
506 if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types,
507 &attr_ocount) != 0 ||
508 attr_ocount != attr_count) {
509 posix_spawnattr_destroy(&spawnattr);
510 return NS_ERROR_FAILURE;
513 // Note: |aMyArgv| is already null-terminated as required by posix_spawnp.
514 pid_t newPid = 0;
515 int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, &spawnattr, aMyArgv,
516 *_NSGetEnviron());
517 mPid = static_cast<int32_t>(newPid);
519 posix_spawnattr_destroy(&spawnattr);
521 if (result != 0) {
522 return NS_ERROR_FAILURE;
524 #else
525 mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr);
526 if (!mProcess) {
527 return NS_ERROR_FAILURE;
529 struct MYProcess
531 uint32_t pid;
533 MYProcess* ptrProc = (MYProcess*)mProcess;
534 mPid = ptrProc->pid;
535 #endif
537 NS_ADDREF_THIS();
538 mBlocking = aBlocking;
539 if (aBlocking) {
540 Monitor(this);
541 if (mExitValue < 0) {
542 return NS_ERROR_FILE_EXECUTION_FAILED;
544 } else {
545 mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this,
546 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
547 PR_JOINABLE_THREAD, 0);
548 if (!mThread) {
549 NS_RELEASE_THIS();
550 return NS_ERROR_FAILURE;
553 // It isn't a failure if we just can't watch for shutdown
554 nsCOMPtr<nsIObserverService> os =
555 mozilla::services::GetObserverService();
556 if (os) {
557 os->AddObserver(this, "xpcom-shutdown", false);
561 return NS_OK;
564 NS_IMETHODIMP
565 nsProcess::GetIsRunning(bool* aIsRunning)
567 if (mThread) {
568 *aIsRunning = true;
569 } else {
570 *aIsRunning = false;
573 return NS_OK;
576 NS_IMETHODIMP
577 nsProcess::GetPid(uint32_t* aPid)
579 if (!mThread) {
580 return NS_ERROR_FAILURE;
582 if (mPid < 0) {
583 return NS_ERROR_NOT_IMPLEMENTED;
585 *aPid = mPid;
586 return NS_OK;
589 NS_IMETHODIMP
590 nsProcess::Kill()
592 if (!mThread) {
593 return NS_ERROR_FAILURE;
597 MutexAutoLock lock(mLock);
598 #if defined(PROCESSMODEL_WINAPI)
599 if (TerminateProcess(mProcess, 0) == 0) {
600 return NS_ERROR_FAILURE;
602 #elif defined(XP_MACOSX)
603 if (kill(mPid, SIGKILL) != 0) {
604 return NS_ERROR_FAILURE;
606 #else
607 if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) {
608 return NS_ERROR_FAILURE;
610 #endif
613 // We must null out mThread if we want IsRunning to return false immediately
614 // after this call.
615 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
616 if (os) {
617 os->RemoveObserver(this, "xpcom-shutdown");
619 PR_JoinThread(mThread);
620 mThread = nullptr;
622 return NS_OK;
625 NS_IMETHODIMP
626 nsProcess::GetExitValue(int32_t* aExitValue)
628 MutexAutoLock lock(mLock);
630 *aExitValue = mExitValue;
632 return NS_OK;
635 NS_IMETHODIMP
636 nsProcess::Observe(nsISupports* aSubject, const char* aTopic,
637 const char16_t* aData)
639 // Shutting down, drop all references
640 if (mThread) {
641 nsCOMPtr<nsIObserverService> os =
642 mozilla::services::GetObserverService();
643 if (os) {
644 os->RemoveObserver(this, "xpcom-shutdown");
646 mThread = nullptr;
649 mObserver = nullptr;
650 mWeakObserver = nullptr;
652 MutexAutoLock lock(mLock);
653 mShutdown = true;
655 return NS_OK;