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 /*****************************************************************************
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"
17 #include "nsAutoPtr.h"
19 #include "nsProcess.h"
24 #include "nsThreadUtils.h"
25 #include "nsIObserverService.h"
26 #include "mozilla/Services.h"
30 #if defined(PROCESSMODEL_WINAPI)
33 #include "nsLiteralString.h"
34 #include "nsReadableUtils.h"
37 #include <crt_externs.h>
41 #include <sys/types.h>
45 using namespace mozilla
;
48 cpu_type_t pref_cpu_types
[2] = {
51 #elif defined(__x86_64__)
53 #elif defined(__ppc__)
59 //-------------------------------------------------------------------//
60 // nsIProcess implementation
61 //-------------------------------------------------------------------//
62 NS_IMPL_ISUPPORTS2(nsProcess
, nsIProcess
,
66 nsProcess::nsProcess()
68 , mLock("nsProcess.mLock")
73 , mWeakObserver(nullptr)
75 #if !defined(XP_MACOSX)
82 nsProcess::~nsProcess()
87 nsProcess::Init(nsIFile
* executable
)
90 return NS_ERROR_ALREADY_INITIALIZED
;
92 NS_ENSURE_ARG_POINTER(executable
);
95 //First make sure the file exists
96 nsresult rv
= executable
->IsFile(&isFile
);
97 if (NS_FAILED(rv
)) return rv
;
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
105 rv
= mExecutable
->GetTarget(mTargetPath
);
106 if (NS_FAILED(rv
) || mTargetPath
.IsEmpty() )
108 rv
= mExecutable
->GetPath(mTargetPath
);
115 // Out param `wideCmdLine` must be PR_Freed by the caller.
116 static int assembleCmdLine(char *const *argv
, PRUnichar
**wideCmdLine
,
120 char *p
, *q
, *cmdLine
;
127 * Find out how large the command line buffer should be.
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));
147 for (arg
= argv
; *arg
; arg
++) {
148 /* Add a space to separates the arguments */
156 /* If the argument contains white space, it needs to be quoted. */
157 if (strpbrk(*arg
, " \f\n\r\t\v")) {
168 } else if (*q
== '"') {
169 if (numBackslashes
) {
171 * Double the backslashes since they are followed
174 for (i
= 0; i
< 2 * numBackslashes
; i
++) {
179 /* To escape the quote */
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
++) {
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.
206 for (i
= 0; i
< numBackslashes
; i
++) {
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
);
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)
233 unsigned long exitCode
= -1;
235 dwRetVal
= WaitForSingleObject(process
->mProcess
, INFINITE
);
236 if (dwRetVal
!= WAIT_FAILED
) {
237 if (GetExitCodeProcess(process
->mProcess
, &exitCode
) == FALSE
)
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
)
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
263 int32_t exitCode
= -1;
264 if (PR_WaitProcess(process
->mProcess
, &exitCode
) != PR_SUCCESS
)
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;
274 process
->mExitValue
= exitCode
;
275 if (process
->mShutdown
)
280 // If we ran a background thread for the monitor then notify on the main
282 if (NS_IsMainThread()) {
283 process
->ProcessComplete();
286 nsCOMPtr
<nsIRunnable
> event
=
287 NS_NewRunnableMethod(process
, &nsProcess::ProcessComplete
);
288 NS_DispatchToMainThread(event
);
292 void nsProcess::ProcessComplete()
295 nsCOMPtr
<nsIObserverService
> os
=
296 mozilla::services::GetObserverService();
298 os
->RemoveObserver(this, "xpcom-shutdown");
299 PR_JoinThread(mThread
);
305 topic
= "process-failed";
307 topic
= "process-finished";
310 nsCOMPtr
<nsIObserver
> observer
;
312 observer
= do_QueryReferent(mWeakObserver
);
314 observer
= mObserver
;
316 mWeakObserver
= nullptr;
319 observer
->Observe(NS_ISUPPORTS_CAST(nsIProcess
*, this), topic
, nullptr);
322 // XXXldb |args| has the wrong const-ness
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
331 nsProcess::RunAsync(const char **args
, uint32_t count
,
332 nsIObserver
* observer
, bool holdWeak
)
334 return CopyArgsAndRunProcess(false, args
, count
, observer
, holdWeak
);
338 nsProcess::CopyArgsAndRunProcess(bool blocking
, const char** args
,
339 uint32_t count
, nsIObserver
* observer
,
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));
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);
364 // XXXldb |args| has the wrong const-ness
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
373 nsProcess::RunwAsync(const PRUnichar
**args
, uint32_t count
,
374 nsIObserver
* observer
, bool holdWeak
)
376 return CopyArgsAndRunProcessw(false, args
, count
, observer
, holdWeak
);
380 nsProcess::CopyArgsAndRunProcessw(bool blocking
, const PRUnichar
** args
,
381 uint32_t count
, nsIObserver
* observer
,
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));
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
++) {
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
);
417 mWeakObserver
= do_GetWeakReference(observer
);
419 return NS_NOINTERFACE
;
422 mObserver
= observer
;
429 #if defined(PROCESSMODEL_WINAPI)
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
);
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
;
459 sinfo
.lpParameters
= cmdLine
;
461 retVal
= ShellExecuteExW(&sinfo
);
463 return NS_ERROR_FILE_EXECUTION_FAILED
;
466 mProcess
= sinfo
.hProcess
;
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.
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
);
496 return NS_ERROR_FAILURE
;
499 mProcess
= PR_CreateProcess(my_argv
[0], my_argv
, NULL
, NULL
);
501 return NS_ERROR_FAILURE
;
505 MYProcess
* ptrProc
= (MYProcess
*) mProcess
;
510 mBlocking
= blocking
;
514 return NS_ERROR_FILE_EXECUTION_FAILED
;
517 mThread
= PR_CreateThread(PR_SYSTEM_THREAD
, Monitor
, this,
518 PR_PRIORITY_NORMAL
, PR_LOCAL_THREAD
,
519 PR_JOINABLE_THREAD
, 0);
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();
529 os
->AddObserver(this, "xpcom-shutdown", false);
535 NS_IMETHODIMP
nsProcess::GetIsRunning(bool *aIsRunning
)
546 nsProcess::GetPid(uint32_t *aPid
)
549 return NS_ERROR_FAILURE
;
551 return NS_ERROR_NOT_IMPLEMENTED
;
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
;
571 if (!mProcess
|| (PR_KillProcess(mProcess
) != PR_SUCCESS
))
572 return NS_ERROR_FAILURE
;
576 // We must null out mThread if we want IsRunning to return false immediately
578 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
580 os
->RemoveObserver(this, "xpcom-shutdown");
581 PR_JoinThread(mThread
);
588 nsProcess::GetExitValue(int32_t *aExitValue
)
590 MutexAutoLock
lock(mLock
);
592 *aExitValue
= mExitValue
;
598 nsProcess::Observe(nsISupports
* subject
, const char* topic
, const PRUnichar
* data
)
600 // Shutting down, drop all references
602 nsCOMPtr
<nsIObserverService
> os
=
603 mozilla::services::GetObserverService();
605 os
->RemoveObserver(this, "xpcom-shutdown");
610 mWeakObserver
= nullptr;
612 MutexAutoLock
lock(mLock
);