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"
18 #include "nsAutoPtr.h"
20 #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__)
60 //-------------------------------------------------------------------//
61 // nsIProcess implementation
62 //-------------------------------------------------------------------//
63 NS_IMPL_ISUPPORTS(nsProcess
, nsIProcess
,
67 nsProcess::nsProcess()
69 , mLock("nsProcess.mLock")
74 , mWeakObserver(nullptr)
76 #if !defined(XP_MACOSX)
83 nsProcess::~nsProcess()
88 nsProcess::Init(nsIFile
* aExecutable
)
91 return NS_ERROR_ALREADY_INITIALIZED
;
94 if (NS_WARN_IF(!aExecutable
)) {
95 return NS_ERROR_INVALID_ARG
;
99 //First make sure the file exists
100 nsresult rv
= aExecutable
->IsFile(&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
112 rv
= mExecutable
->GetTarget(mTargetPath
);
113 if (NS_FAILED(rv
) || mTargetPath
.IsEmpty())
115 rv
= mExecutable
->GetPath(mTargetPath
);
122 // Out param `aWideCmdLine` must be PR_Freed by the caller.
124 assembleCmdLine(char* const* aArgv
, wchar_t** aWideCmdLine
, UINT aCodePage
)
136 * Find out how large the command line buffer should be.
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));
156 for (arg
= aArgv
; *arg
; ++arg
) {
157 /* Add a space to separates the arguments */
165 /* If the argument contains white space, it needs to be quoted. */
166 if (strpbrk(*arg
, " \f\n\r\t\v")) {
177 } else if (*q
== '"') {
178 if (numBackslashes
) {
180 * Double the backslashes since they are followed
183 for (i
= 0; i
< 2 * numBackslashes
; i
++) {
188 /* To escape the quote */
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
++) {
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.
215 for (i
= 0; i
< numBackslashes
; i
++) {
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
);
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)
244 unsigned long exitCode
= -1;
246 dwRetVal
= WaitForSingleObject(process
->mProcess
, INFINITE
);
247 if (dwRetVal
!= WAIT_FAILED
) {
248 if (GetExitCodeProcess(process
->mProcess
, &exitCode
) == FALSE
) {
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
) {
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
275 int32_t exitCode
= -1;
276 if (PR_WaitProcess(process
->mProcess
, &exitCode
) != PR_SUCCESS
) {
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;
287 process
->mExitValue
= exitCode
;
288 if (process
->mShutdown
) {
294 // If we ran a background thread for the monitor then notify on the main
296 if (NS_IsMainThread()) {
297 process
->ProcessComplete();
299 nsCOMPtr
<nsIRunnable
> event
=
300 NS_NewRunnableMethod(process
, &nsProcess::ProcessComplete
);
301 NS_DispatchToMainThread(event
);
306 nsProcess::ProcessComplete()
309 nsCOMPtr
<nsIObserverService
> os
=
310 mozilla::services::GetObserverService();
312 os
->RemoveObserver(this, "xpcom-shutdown");
314 PR_JoinThread(mThread
);
319 if (mExitValue
< 0) {
320 topic
= "process-failed";
322 topic
= "process-finished";
326 nsCOMPtr
<nsIObserver
> observer
;
328 observer
= do_QueryReferent(mWeakObserver
);
329 } else if (mObserver
) {
330 observer
= mObserver
;
333 mWeakObserver
= nullptr;
336 observer
->Observe(NS_ISUPPORTS_CAST(nsIProcess
*, this), topic
, nullptr);
340 // XXXldb |aArgs| has the wrong const-ness
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
349 nsProcess::RunAsync(const char** aArgs
, uint32_t aCount
,
350 nsIObserver
* aObserver
, bool aHoldWeak
)
352 return CopyArgsAndRunProcess(false, aArgs
, aCount
, aObserver
, aHoldWeak
);
356 nsProcess::CopyArgsAndRunProcess(bool aBlocking
, const char** aArgs
,
357 uint32_t aCount
, nsIObserver
* aObserver
,
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));
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);
382 // XXXldb |aArgs| has the wrong const-ness
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
391 nsProcess::RunwAsync(const char16_t
** aArgs
, uint32_t aCount
,
392 nsIObserver
* aObserver
, bool aHoldWeak
)
394 return CopyArgsAndRunProcessw(false, aArgs
, aCount
, aObserver
, aHoldWeak
);
398 nsProcess::CopyArgsAndRunProcessw(bool aBlocking
, const char16_t
** aArgs
,
399 uint32_t aCount
, nsIObserver
* aObserver
,
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));
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
) {
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
;
439 mWeakObserver
= do_GetWeakReference(aObserver
);
440 if (!mWeakObserver
) {
441 return NS_NOINTERFACE
;
444 mObserver
= aObserver
;
451 #if defined(PROCESSMODEL_WINAPI)
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
;
481 sinfo
.lpParameters
= cmdLine
;
484 retVal
= ShellExecuteExW(&sinfo
);
486 return NS_ERROR_FILE_EXECUTION_FAILED
;
489 mProcess
= sinfo
.hProcess
;
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.
515 int result
= posix_spawnp(&newPid
, aMyArgv
[0], nullptr, &spawnattr
, aMyArgv
,
517 mPid
= static_cast<int32_t>(newPid
);
519 posix_spawnattr_destroy(&spawnattr
);
522 return NS_ERROR_FAILURE
;
525 mProcess
= PR_CreateProcess(aMyArgv
[0], aMyArgv
, nullptr, nullptr);
527 return NS_ERROR_FAILURE
;
533 MYProcess
* ptrProc
= (MYProcess
*)mProcess
;
538 mBlocking
= aBlocking
;
541 if (mExitValue
< 0) {
542 return NS_ERROR_FILE_EXECUTION_FAILED
;
545 mThread
= PR_CreateThread(PR_SYSTEM_THREAD
, Monitor
, this,
546 PR_PRIORITY_NORMAL
, PR_GLOBAL_THREAD
,
547 PR_JOINABLE_THREAD
, 0);
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();
557 os
->AddObserver(this, "xpcom-shutdown", false);
565 nsProcess::GetIsRunning(bool* aIsRunning
)
577 nsProcess::GetPid(uint32_t* aPid
)
580 return NS_ERROR_FAILURE
;
583 return NS_ERROR_NOT_IMPLEMENTED
;
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
;
607 if (!mProcess
|| (PR_KillProcess(mProcess
) != PR_SUCCESS
)) {
608 return NS_ERROR_FAILURE
;
613 // We must null out mThread if we want IsRunning to return false immediately
615 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
617 os
->RemoveObserver(this, "xpcom-shutdown");
619 PR_JoinThread(mThread
);
626 nsProcess::GetExitValue(int32_t* aExitValue
)
628 MutexAutoLock
lock(mLock
);
630 *aExitValue
= mExitValue
;
636 nsProcess::Observe(nsISupports
* aSubject
, const char* aTopic
,
637 const char16_t
* aData
)
639 // Shutting down, drop all references
641 nsCOMPtr
<nsIObserverService
> os
=
642 mozilla::services::GetObserverService();
644 os
->RemoveObserver(this, "xpcom-shutdown");
650 mWeakObserver
= nullptr;
652 MutexAutoLock
lock(mLock
);