Backed out changeset 4887f7d34df2 (bug 896896) for causing bug 933733. a=lsblakk
[gecko.git] / xpcom / threads / nsProcessCommon.cpp
blobed6cb5106e04b58be28857debcafbc5f782cbeaf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*****************************************************************************
7 *
8 * nsProcess is used to execute new processes and specify if you want to
9 * wait (blocking) or continue (non-blocking).
11 *****************************************************************************
14 #include "mozilla/Util.h"
16 #include "nsCOMPtr.h"
17 #include "nsAutoPtr.h"
18 #include "nsMemory.h"
19 #include "nsProcess.h"
20 #include "prtypes.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 };
57 #endif
59 //-------------------------------------------------------------------//
60 // nsIProcess implementation
61 //-------------------------------------------------------------------//
62 NS_IMPL_ISUPPORTS2(nsProcess, nsIProcess,
63 nsIObserver)
65 //Constructor
66 nsProcess::nsProcess()
67 : mThread(nullptr)
68 , mLock("nsProcess.mLock")
69 , mShutdown(false)
70 , mBlocking(false)
71 , mPid(-1)
72 , mObserver(nullptr)
73 , mWeakObserver(nullptr)
74 , mExitValue(-1)
75 #if !defined(XP_MACOSX)
76 , mProcess(nullptr)
77 #endif
81 //Destructor
82 nsProcess::~nsProcess()
86 NS_IMETHODIMP
87 nsProcess::Init(nsIFile* executable)
89 if (mExecutable)
90 return NS_ERROR_ALREADY_INITIALIZED;
92 NS_ENSURE_ARG_POINTER(executable);
93 bool isFile;
95 //First make sure the file exists
96 nsresult rv = executable->IsFile(&isFile);
97 if (NS_FAILED(rv)) return rv;
98 if (!isFile)
99 return NS_ERROR_FAILURE;
101 //Store the nsIFile in mExecutable
102 mExecutable = executable;
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);
110 return rv;
114 #if defined(XP_WIN)
115 // Out param `wideCmdLine` must be PR_Freed by the caller.
116 static int assembleCmdLine(char *const *argv, PRUnichar **wideCmdLine,
117 UINT codePage)
119 char *const *arg;
120 char *p, *q, *cmdLine;
121 int cmdLineSize;
122 int numBackslashes;
123 int i;
124 int argNeedQuotes;
127 * Find out how large the command line buffer should be.
129 cmdLineSize = 0;
130 for (arg = argv; *arg; arg++) {
132 * \ and " need to be escaped by a \. In the worst case,
133 * every character is a \ or ", so the string of length
134 * may double. If we quote an argument, that needs two ".
135 * Finally, we need a space between arguments, and
136 * a null byte at the end of command line.
138 cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */
139 + 2 /* we quote every argument */
140 + 1; /* space in between, or final null */
142 p = cmdLine = (char *) PR_MALLOC(cmdLineSize*sizeof(char));
143 if (p == NULL) {
144 return -1;
147 for (arg = argv; *arg; arg++) {
148 /* Add a space to separates the arguments */
149 if (arg != argv) {
150 *p++ = ' ';
152 q = *arg;
153 numBackslashes = 0;
154 argNeedQuotes = 0;
156 /* If the argument contains white space, it needs to be quoted. */
157 if (strpbrk(*arg, " \f\n\r\t\v")) {
158 argNeedQuotes = 1;
161 if (argNeedQuotes) {
162 *p++ = '"';
164 while (*q) {
165 if (*q == '\\') {
166 numBackslashes++;
167 q++;
168 } else if (*q == '"') {
169 if (numBackslashes) {
171 * Double the backslashes since they are followed
172 * by a quote
174 for (i = 0; i < 2 * numBackslashes; i++) {
175 *p++ = '\\';
177 numBackslashes = 0;
179 /* To escape the quote */
180 *p++ = '\\';
181 *p++ = *q++;
182 } else {
183 if (numBackslashes) {
185 * Backslashes are not followed by a quote, so
186 * don't need to double the backslashes.
188 for (i = 0; i < numBackslashes; i++) {
189 *p++ = '\\';
191 numBackslashes = 0;
193 *p++ = *q++;
197 /* Now we are at the end of this argument */
198 if (numBackslashes) {
200 * Double the backslashes if we have a quote string
201 * delimiter at the end.
203 if (argNeedQuotes) {
204 numBackslashes *= 2;
206 for (i = 0; i < numBackslashes; i++) {
207 *p++ = '\\';
210 if (argNeedQuotes) {
211 *p++ = '"';
215 *p = '\0';
216 int32_t numChars = MultiByteToWideChar(codePage, 0, cmdLine, -1, NULL, 0);
217 *wideCmdLine = (PRUnichar *) PR_MALLOC(numChars*sizeof(PRUnichar));
218 MultiByteToWideChar(codePage, 0, cmdLine, -1, *wideCmdLine, numChars);
219 PR_Free(cmdLine);
220 return 0;
222 #endif
224 void nsProcess::Monitor(void *arg)
226 nsRefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(arg));
228 if (!process->mBlocking)
229 PR_SetCurrentThreadName("RunProcess");
231 #if defined(PROCESSMODEL_WINAPI)
232 DWORD dwRetVal;
233 unsigned long exitCode = -1;
235 dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
236 if (dwRetVal != WAIT_FAILED) {
237 if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE)
238 exitCode = -1;
241 // Lock in case Kill or GetExitCode are called during this
243 MutexAutoLock lock(process->mLock);
244 CloseHandle(process->mProcess);
245 process->mProcess = NULL;
246 process->mExitValue = exitCode;
247 if (process->mShutdown)
248 return;
250 #else
251 #ifdef XP_MACOSX
252 int exitCode = -1;
253 int status = 0;
254 if (waitpid(process->mPid, &status, 0) == process->mPid) {
255 if (WIFEXITED(status)) {
256 exitCode = WEXITSTATUS(status);
258 else if(WIFSIGNALED(status)) {
259 exitCode = 256; // match NSPR's signal exit status
262 #else
263 int32_t exitCode = -1;
264 if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS)
265 exitCode = -1;
266 #endif
268 // Lock in case Kill or GetExitCode are called during this
270 MutexAutoLock lock(process->mLock);
271 #if !defined(XP_MACOSX)
272 process->mProcess = nullptr;
273 #endif
274 process->mExitValue = exitCode;
275 if (process->mShutdown)
276 return;
278 #endif
280 // If we ran a background thread for the monitor then notify on the main
281 // thread
282 if (NS_IsMainThread()) {
283 process->ProcessComplete();
285 else {
286 nsCOMPtr<nsIRunnable> event =
287 NS_NewRunnableMethod(process, &nsProcess::ProcessComplete);
288 NS_DispatchToMainThread(event);
292 void nsProcess::ProcessComplete()
294 if (mThread) {
295 nsCOMPtr<nsIObserverService> os =
296 mozilla::services::GetObserverService();
297 if (os)
298 os->RemoveObserver(this, "xpcom-shutdown");
299 PR_JoinThread(mThread);
300 mThread = nullptr;
303 const char* topic;
304 if (mExitValue < 0)
305 topic = "process-failed";
306 else
307 topic = "process-finished";
309 mPid = -1;
310 nsCOMPtr<nsIObserver> observer;
311 if (mWeakObserver)
312 observer = do_QueryReferent(mWeakObserver);
313 else if (mObserver)
314 observer = mObserver;
315 mObserver = nullptr;
316 mWeakObserver = nullptr;
318 if (observer)
319 observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
322 // XXXldb |args| has the wrong const-ness
323 NS_IMETHODIMP
324 nsProcess::Run(bool blocking, const char **args, uint32_t count)
326 return CopyArgsAndRunProcess(blocking, args, count, nullptr, false);
329 // XXXldb |args| has the wrong const-ness
330 NS_IMETHODIMP
331 nsProcess::RunAsync(const char **args, uint32_t count,
332 nsIObserver* observer, bool holdWeak)
334 return CopyArgsAndRunProcess(false, args, count, observer, holdWeak);
337 nsresult
338 nsProcess::CopyArgsAndRunProcess(bool blocking, const char** args,
339 uint32_t count, nsIObserver* observer,
340 bool holdWeak)
342 // Add one to the count for the program name and one for NULL termination.
343 char **my_argv = NULL;
344 my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2));
345 if (!my_argv) {
346 return NS_ERROR_OUT_OF_MEMORY;
349 my_argv[0] = ToNewUTF8String(mTargetPath);
351 for (uint32_t i = 0; i < count; i++) {
352 my_argv[i + 1] = const_cast<char*>(args[i]);
355 my_argv[count + 1] = NULL;
357 nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, false);
359 NS_Free(my_argv[0]);
360 NS_Free(my_argv);
361 return rv;
364 // XXXldb |args| has the wrong const-ness
365 NS_IMETHODIMP
366 nsProcess::Runw(bool blocking, const PRUnichar **args, uint32_t count)
368 return CopyArgsAndRunProcessw(blocking, args, count, nullptr, false);
371 // XXXldb |args| has the wrong const-ness
372 NS_IMETHODIMP
373 nsProcess::RunwAsync(const PRUnichar **args, uint32_t count,
374 nsIObserver* observer, bool holdWeak)
376 return CopyArgsAndRunProcessw(false, args, count, observer, holdWeak);
379 nsresult
380 nsProcess::CopyArgsAndRunProcessw(bool blocking, const PRUnichar** args,
381 uint32_t count, nsIObserver* observer,
382 bool holdWeak)
384 // Add one to the count for the program name and one for NULL termination.
385 char **my_argv = NULL;
386 my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2));
387 if (!my_argv) {
388 return NS_ERROR_OUT_OF_MEMORY;
391 my_argv[0] = ToNewUTF8String(mTargetPath);
393 for (uint32_t i = 0; i < count; i++) {
394 my_argv[i + 1] = ToNewUTF8String(nsDependentString(args[i]));
397 my_argv[count + 1] = NULL;
399 nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, true);
401 for (uint32_t i = 0; i <= count; i++) {
402 NS_Free(my_argv[i]);
404 NS_Free(my_argv);
405 return rv;
408 nsresult
409 nsProcess::RunProcess(bool blocking, char **my_argv, nsIObserver* observer,
410 bool holdWeak, bool argsUTF8)
412 NS_ENSURE_TRUE(mExecutable, NS_ERROR_NOT_INITIALIZED);
413 NS_ENSURE_FALSE(mThread, NS_ERROR_ALREADY_INITIALIZED);
415 if (observer) {
416 if (holdWeak) {
417 mWeakObserver = do_GetWeakReference(observer);
418 if (!mWeakObserver)
419 return NS_NOINTERFACE;
421 else {
422 mObserver = observer;
426 mExitValue = -1;
427 mPid = -1;
429 #if defined(PROCESSMODEL_WINAPI)
430 BOOL retVal;
431 PRUnichar *cmdLine = NULL;
433 // The 'argv' array is null-terminated and always starts with the program path.
434 // If the second slot is non-null then arguments are being passed.
435 if (my_argv[1] != NULL &&
436 assembleCmdLine(my_argv + 1, &cmdLine, argsUTF8 ? CP_UTF8 : CP_ACP) == -1) {
437 return NS_ERROR_FILE_EXECUTION_FAILED;
440 /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
441 * from appearing. This makes behavior the same on all platforms. The flag
442 * will not have any effect on non-console applications.
445 // The program name in my_argv[0] is always UTF-8
446 NS_ConvertUTF8toUTF16 wideFile(my_argv[0]);
448 SHELLEXECUTEINFOW sinfo;
449 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
450 sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
451 sinfo.hwnd = NULL;
452 sinfo.lpFile = wideFile.get();
453 sinfo.nShow = SW_SHOWNORMAL;
454 sinfo.fMask = SEE_MASK_FLAG_DDEWAIT |
455 SEE_MASK_NO_CONSOLE |
456 SEE_MASK_NOCLOSEPROCESS;
458 if (cmdLine)
459 sinfo.lpParameters = cmdLine;
461 retVal = ShellExecuteExW(&sinfo);
462 if (!retVal) {
463 return NS_ERROR_FILE_EXECUTION_FAILED;
466 mProcess = sinfo.hProcess;
468 if (cmdLine)
469 PR_Free(cmdLine);
471 mPid = GetProcessId(mProcess);
472 #elif defined(XP_MACOSX)
473 // Initialize spawn attributes.
474 posix_spawnattr_t spawnattr;
475 if (posix_spawnattr_init(&spawnattr) != 0) {
476 return NS_ERROR_FAILURE;
479 // Set spawn attributes.
480 size_t attr_count = ArrayLength(pref_cpu_types);
481 size_t attr_ocount = 0;
482 if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 ||
483 attr_ocount != attr_count) {
484 posix_spawnattr_destroy(&spawnattr);
485 return NS_ERROR_FAILURE;
488 // Note that the 'argv' array is already null-terminated, which 'posix_spawnp' requires.
489 pid_t newPid = 0;
490 int result = posix_spawnp(&newPid, my_argv[0], NULL, &spawnattr, my_argv, *_NSGetEnviron());
491 mPid = static_cast<int32_t>(newPid);
493 posix_spawnattr_destroy(&spawnattr);
495 if (result != 0) {
496 return NS_ERROR_FAILURE;
498 #else
499 mProcess = PR_CreateProcess(my_argv[0], my_argv, NULL, NULL);
500 if (!mProcess)
501 return NS_ERROR_FAILURE;
502 struct MYProcess {
503 uint32_t pid;
505 MYProcess* ptrProc = (MYProcess *) mProcess;
506 mPid = ptrProc->pid;
507 #endif
509 NS_ADDREF_THIS();
510 mBlocking = blocking;
511 if (blocking) {
512 Monitor(this);
513 if (mExitValue < 0)
514 return NS_ERROR_FILE_EXECUTION_FAILED;
516 else {
517 mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this,
518 PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
519 PR_JOINABLE_THREAD, 0);
520 if (!mThread) {
521 NS_RELEASE_THIS();
522 return NS_ERROR_FAILURE;
525 // It isn't a failure if we just can't watch for shutdown
526 nsCOMPtr<nsIObserverService> os =
527 mozilla::services::GetObserverService();
528 if (os)
529 os->AddObserver(this, "xpcom-shutdown", false);
532 return NS_OK;
535 NS_IMETHODIMP nsProcess::GetIsRunning(bool *aIsRunning)
537 if (mThread)
538 *aIsRunning = true;
539 else
540 *aIsRunning = false;
542 return NS_OK;
545 NS_IMETHODIMP
546 nsProcess::GetPid(uint32_t *aPid)
548 if (!mThread)
549 return NS_ERROR_FAILURE;
550 if (mPid < 0)
551 return NS_ERROR_NOT_IMPLEMENTED;
552 *aPid = mPid;
553 return NS_OK;
556 NS_IMETHODIMP
557 nsProcess::Kill()
559 if (!mThread)
560 return NS_ERROR_FAILURE;
563 MutexAutoLock lock(mLock);
564 #if defined(PROCESSMODEL_WINAPI)
565 if (TerminateProcess(mProcess, 0) == 0)
566 return NS_ERROR_FAILURE;
567 #elif defined(XP_MACOSX)
568 if (kill(mPid, SIGKILL) != 0)
569 return NS_ERROR_FAILURE;
570 #else
571 if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS))
572 return NS_ERROR_FAILURE;
573 #endif
576 // We must null out mThread if we want IsRunning to return false immediately
577 // after this call.
578 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
579 if (os)
580 os->RemoveObserver(this, "xpcom-shutdown");
581 PR_JoinThread(mThread);
582 mThread = nullptr;
584 return NS_OK;
587 NS_IMETHODIMP
588 nsProcess::GetExitValue(int32_t *aExitValue)
590 MutexAutoLock lock(mLock);
592 *aExitValue = mExitValue;
594 return NS_OK;
597 NS_IMETHODIMP
598 nsProcess::Observe(nsISupports* subject, const char* topic, const PRUnichar* data)
600 // Shutting down, drop all references
601 if (mThread) {
602 nsCOMPtr<nsIObserverService> os =
603 mozilla::services::GetObserverService();
604 if (os)
605 os->RemoveObserver(this, "xpcom-shutdown");
606 mThread = nullptr;
609 mObserver = nullptr;
610 mWeakObserver = nullptr;
612 MutexAutoLock lock(mLock);
613 mShutdown = true;
615 return NS_OK;