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"
20 #include "nsProcess.h"
24 #include "nsThreadUtils.h"
25 #include "nsIObserverService.h"
26 #include "nsXULAppAPI.h"
27 #include "mozilla/Services.h"
31 #if defined(PROCESSMODEL_WINAPI)
32 # include "nsString.h"
33 # include "nsLiteralString.h"
34 # include "nsReadableUtils.h"
35 # include "mozilla/AssembleCmdLine.h"
36 # include "mozilla/UniquePtrExtensions.h"
39 # include <crt_externs.h>
44 # include "base/process_util.h"
46 # include <sys/wait.h>
47 # include <sys/errno.h>
49 # include <sys/types.h>
53 using namespace mozilla
;
55 //-------------------------------------------------------------------//
56 // nsIProcess implementation
57 //-------------------------------------------------------------------//
58 NS_IMPL_ISUPPORTS(nsProcess
, nsIProcess
, nsIObserver
)
61 nsProcess::nsProcess()
63 mLock("nsProcess.mLock"),
78 nsProcess::~nsProcess() = default;
81 nsProcess::Init(nsIFile
* aExecutable
) {
83 return NS_ERROR_ALREADY_INITIALIZED
;
86 if (NS_WARN_IF(!aExecutable
)) {
87 return NS_ERROR_INVALID_ARG
;
91 // First make sure the file exists
92 nsresult rv
= aExecutable
->IsFile(&isFile
);
97 return NS_ERROR_FAILURE
;
100 // Store the nsIFile in mExecutable
101 mExecutable
= aExecutable
;
102 // Get the path because it is needed by the NSPR process creation
104 rv
= mExecutable
->GetTarget(mTargetPath
);
105 if (NS_FAILED(rv
) || mTargetPath
.IsEmpty())
107 rv
= mExecutable
->GetPath(mTargetPath
);
112 void nsProcess::Monitor(void* aArg
) {
113 RefPtr
<nsProcess
> process
= dont_AddRef(static_cast<nsProcess
*>(aArg
));
115 if (!process
->mBlocking
) {
116 NS_SetCurrentThreadName("RunProcess");
119 #if defined(PROCESSMODEL_WINAPI)
121 unsigned long exitCode
= -1;
123 dwRetVal
= WaitForSingleObject(process
->mProcess
, INFINITE
);
124 if (dwRetVal
!= WAIT_FAILED
) {
125 if (GetExitCodeProcess(process
->mProcess
, &exitCode
) == FALSE
) {
130 // Lock in case Kill or GetExitCode are called during this
132 MutexAutoLock
lock(process
->mLock
);
133 CloseHandle(process
->mProcess
);
134 process
->mProcess
= nullptr;
135 process
->mExitValue
= exitCode
;
136 if (process
->mShutdown
) {
146 result
= waitpid(process
->mPid
, &status
, 0);
147 } while (result
== -1 && errno
== EINTR
);
148 if (result
== process
->mPid
) {
149 if (WIFEXITED(status
)) {
150 exitCode
= WEXITSTATUS(status
);
151 } else if (WIFSIGNALED(status
)) {
152 exitCode
= 256; // match NSPR's signal exit status
156 int32_t exitCode
= -1;
157 if (PR_WaitProcess(process
->mProcess
, &exitCode
) != PR_SUCCESS
) {
162 // Lock in case Kill or GetExitCode are called during this
164 MutexAutoLock
lock(process
->mLock
);
165 # if !defined(XP_UNIX)
166 process
->mProcess
= nullptr;
168 process
->mExitValue
= exitCode
;
169 if (process
->mShutdown
) {
175 // If we ran a background thread for the monitor then notify on the main
177 if (NS_IsMainThread()) {
178 process
->ProcessComplete();
180 NS_DispatchToMainThread(NewRunnableMethod(
181 "nsProcess::ProcessComplete", process
, &nsProcess::ProcessComplete
));
185 void nsProcess::ProcessComplete() {
187 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
189 os
->RemoveObserver(this, "xpcom-shutdown");
191 PR_JoinThread(mThread
);
196 if (mExitValue
!= 0) {
197 topic
= "process-failed";
199 topic
= "process-finished";
203 nsCOMPtr
<nsIObserver
> observer
= mObserver
.GetValue();
207 observer
->Observe(NS_ISUPPORTS_CAST(nsIProcess
*, this), topic
, nullptr);
211 // XXXldb |aArgs| has the wrong const-ness
213 nsProcess::Run(bool aBlocking
, const char** aArgs
, uint32_t aCount
) {
214 return CopyArgsAndRunProcess(aBlocking
, aArgs
, aCount
, nullptr, false);
217 // XXXldb |aArgs| has the wrong const-ness
219 nsProcess::RunAsync(const char** aArgs
, uint32_t aCount
, nsIObserver
* aObserver
,
221 return CopyArgsAndRunProcess(false, aArgs
, aCount
, aObserver
, aHoldWeak
);
224 nsresult
nsProcess::CopyArgsAndRunProcess(bool aBlocking
, const char** aArgs
,
226 nsIObserver
* aObserver
,
228 // Add one to the aCount for the program name and one for null termination.
229 char** my_argv
= nullptr;
230 my_argv
= (char**)moz_xmalloc(sizeof(char*) * (aCount
+ 2));
232 my_argv
[0] = ToNewUTF8String(mTargetPath
);
234 for (uint32_t i
= 0; i
< aCount
; ++i
) {
235 my_argv
[i
+ 1] = const_cast<char*>(aArgs
[i
]);
238 my_argv
[aCount
+ 1] = nullptr;
240 nsresult rv
= RunProcess(aBlocking
, my_argv
, aObserver
, aHoldWeak
, false);
247 // XXXldb |aArgs| has the wrong const-ness
249 nsProcess::Runw(bool aBlocking
, const char16_t
** aArgs
, uint32_t aCount
) {
250 return CopyArgsAndRunProcessw(aBlocking
, aArgs
, aCount
, nullptr, false);
253 // XXXldb |aArgs| has the wrong const-ness
255 nsProcess::RunwAsync(const char16_t
** aArgs
, uint32_t aCount
,
256 nsIObserver
* aObserver
, bool aHoldWeak
) {
257 return CopyArgsAndRunProcessw(false, aArgs
, aCount
, aObserver
, aHoldWeak
);
260 nsresult
nsProcess::CopyArgsAndRunProcessw(bool aBlocking
,
261 const char16_t
** aArgs
,
263 nsIObserver
* aObserver
,
265 // Add one to the aCount for the program name and one for null termination.
266 char** my_argv
= nullptr;
267 my_argv
= (char**)moz_xmalloc(sizeof(char*) * (aCount
+ 2));
269 my_argv
[0] = ToNewUTF8String(mTargetPath
);
271 for (uint32_t i
= 0; i
< aCount
; i
++) {
272 my_argv
[i
+ 1] = ToNewUTF8String(nsDependentString(aArgs
[i
]));
275 my_argv
[aCount
+ 1] = nullptr;
277 nsresult rv
= RunProcess(aBlocking
, my_argv
, aObserver
, aHoldWeak
, true);
279 for (uint32_t i
= 0; i
<= aCount
; ++i
) {
286 nsresult
nsProcess::RunProcess(bool aBlocking
, char** aMyArgv
,
287 nsIObserver
* aObserver
, bool aHoldWeak
,
289 NS_WARNING_ASSERTION(!XRE_IsContentProcess(),
290 "No launching of new processes in the content process");
292 if (NS_WARN_IF(!mExecutable
)) {
293 return NS_ERROR_NOT_INITIALIZED
;
295 if (NS_WARN_IF(mThread
)) {
296 return NS_ERROR_ALREADY_INITIALIZED
;
302 mObserver
= do_GetWeakReference(aObserver
, &rv
);
303 NS_ENSURE_SUCCESS(rv
, rv
);
305 mObserver
= aObserver
;
312 #if defined(PROCESSMODEL_WINAPI)
314 UniqueFreePtr
<wchar_t> cmdLine
;
316 // |aMyArgv| is null-terminated and always starts with the program path. If
317 // the second slot is non-null then arguments are being passed.
318 if (aMyArgv
[1] || mNoShell
) {
319 // Pass the executable path as argv[0] to the launched program when calling
321 char** argv
= mNoShell
? aMyArgv
: aMyArgv
+ 1;
323 wchar_t* assembledCmdLine
= nullptr;
324 if (assembleCmdLine(argv
, &assembledCmdLine
,
325 aArgsUTF8
? CP_UTF8
: CP_ACP
) == -1) {
326 return NS_ERROR_FILE_EXECUTION_FAILED
;
328 cmdLine
.reset(assembledCmdLine
);
331 // The program name in aMyArgv[0] is always UTF-8
332 NS_ConvertUTF8toUTF16
wideFile(aMyArgv
[0]);
335 STARTUPINFO startupInfo
;
336 ZeroMemory(&startupInfo
, sizeof(startupInfo
));
337 startupInfo
.cb
= sizeof(startupInfo
);
338 startupInfo
.dwFlags
= STARTF_USESHOWWINDOW
;
339 startupInfo
.wShowWindow
= mStartHidden
? SW_HIDE
: SW_SHOWNORMAL
;
341 PROCESS_INFORMATION processInfo
;
342 retVal
= CreateProcess(/* lpApplicationName = */ wideFile
.get(),
343 /* lpCommandLine */ cmdLine
.get(),
344 /* lpProcessAttributes = */ NULL
,
345 /* lpThreadAttributes = */ NULL
,
346 /* bInheritHandles = */ FALSE
,
347 /* dwCreationFlags = */ 0,
348 /* lpEnvironment = */ NULL
,
349 /* lpCurrentDirectory = */ NULL
,
350 /* lpStartupInfo = */ &startupInfo
,
351 /* lpProcessInformation */ &processInfo
);
354 return NS_ERROR_FILE_EXECUTION_FAILED
;
357 CloseHandle(processInfo
.hThread
);
359 mProcess
= processInfo
.hProcess
;
361 SHELLEXECUTEINFOW sinfo
;
362 memset(&sinfo
, 0, sizeof(SHELLEXECUTEINFOW
));
363 sinfo
.cbSize
= sizeof(SHELLEXECUTEINFOW
);
364 sinfo
.hwnd
= nullptr;
365 sinfo
.lpFile
= wideFile
.get();
366 sinfo
.nShow
= mStartHidden
? SW_HIDE
: SW_SHOWNORMAL
;
368 /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
369 * from appearing. This makes behavior the same on all platforms. The flag
370 * will not have any effect on non-console applications.
373 SEE_MASK_FLAG_DDEWAIT
| SEE_MASK_NO_CONSOLE
| SEE_MASK_NOCLOSEPROCESS
;
376 sinfo
.lpParameters
= cmdLine
.get();
379 retVal
= ShellExecuteExW(&sinfo
);
381 return NS_ERROR_FILE_EXECUTION_FAILED
;
384 mProcess
= sinfo
.hProcess
;
387 mPid
= GetProcessId(mProcess
);
388 #elif defined(XP_MACOSX)
389 // Note: |aMyArgv| is already null-terminated as required by posix_spawnp.
391 int result
= posix_spawnp(&newPid
, aMyArgv
[0], nullptr, nullptr, aMyArgv
,
393 mPid
= static_cast<int32_t>(newPid
);
396 return NS_ERROR_FAILURE
;
398 #elif defined(XP_UNIX)
399 base::LaunchOptions options
;
400 std::vector
<std::string
> argvVec
;
401 for (char** arg
= aMyArgv
; *arg
!= nullptr; ++arg
) {
402 argvVec
.push_back(*arg
);
405 if (base::LaunchApp(argvVec
, options
, &newPid
)) {
406 static_assert(sizeof(pid_t
) <= sizeof(int32_t),
407 "mPid is large enough to hold a pid");
408 mPid
= static_cast<int32_t>(newPid
);
410 return NS_ERROR_FAILURE
;
413 mProcess
= PR_CreateProcess(aMyArgv
[0], aMyArgv
, nullptr, nullptr);
415 return NS_ERROR_FAILURE
;
420 MYProcess
* ptrProc
= (MYProcess
*)mProcess
;
425 mBlocking
= aBlocking
;
428 if (mExitValue
< 0) {
429 return NS_ERROR_FILE_EXECUTION_FAILED
;
432 mThread
= CreateMonitorThread();
435 return NS_ERROR_FAILURE
;
438 // It isn't a failure if we just can't watch for shutdown
439 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
441 os
->AddObserver(this, "xpcom-shutdown", false);
448 // We don't guarantee that monitor threads are joined before Gecko exits, which
449 // can cause TSAN to complain about thread leaks. We handle this with a TSAN
450 // suppression, and route thread creation through this helper so that the
451 // suppression is as narrowly-scoped as possible.
452 PRThread
* nsProcess::CreateMonitorThread() {
453 return PR_CreateThread(PR_SYSTEM_THREAD
, Monitor
, this, PR_PRIORITY_NORMAL
,
454 PR_GLOBAL_THREAD
, PR_JOINABLE_THREAD
, 0);
458 nsProcess::GetIsRunning(bool* aIsRunning
) {
469 nsProcess::GetStartHidden(bool* aStartHidden
) {
470 *aStartHidden
= mStartHidden
;
475 nsProcess::SetStartHidden(bool aStartHidden
) {
476 mStartHidden
= aStartHidden
;
481 nsProcess::GetNoShell(bool* aNoShell
) {
482 *aNoShell
= mNoShell
;
487 nsProcess::SetNoShell(bool aNoShell
) {
493 nsProcess::GetPid(uint32_t* aPid
) {
495 return NS_ERROR_FAILURE
;
498 return NS_ERROR_NOT_IMPLEMENTED
;
507 return NS_ERROR_FAILURE
;
511 MutexAutoLock
lock(mLock
);
512 #if defined(PROCESSMODEL_WINAPI)
513 if (TerminateProcess(mProcess
, 0) == 0) {
514 return NS_ERROR_FAILURE
;
516 #elif defined(XP_UNIX)
517 if (kill(mPid
, SIGKILL
) != 0) {
518 return NS_ERROR_FAILURE
;
521 if (!mProcess
|| (PR_KillProcess(mProcess
) != PR_SUCCESS
)) {
522 return NS_ERROR_FAILURE
;
527 // We must null out mThread if we want IsRunning to return false immediately
529 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
531 os
->RemoveObserver(this, "xpcom-shutdown");
533 PR_JoinThread(mThread
);
540 nsProcess::GetExitValue(int32_t* aExitValue
) {
541 MutexAutoLock
lock(mLock
);
543 *aExitValue
= mExitValue
;
549 nsProcess::Observe(nsISupports
* aSubject
, const char* aTopic
,
550 const char16_t
* aData
) {
551 // Shutting down, drop all references
553 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
555 os
->RemoveObserver(this, "xpcom-shutdown");
562 MutexAutoLock
lock(mLock
);