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"
19 #include "nsProcess.h"
23 #include "nsThreadUtils.h"
24 #include "nsIObserverService.h"
25 #include "nsXULAppAPI.h"
26 #include "mozilla/Services.h"
30 #if defined(PROCESSMODEL_WINAPI)
31 # include "nsString.h"
32 # include "nsLiteralString.h"
33 # include "nsReadableUtils.h"
34 # include "mozilla/AssembleCmdLine.h"
35 # include "mozilla/UniquePtrExtensions.h"
38 # include <crt_externs.h>
43 # include "base/process_util.h"
45 # include <sys/wait.h>
46 # include <sys/errno.h>
48 # include <sys/types.h>
52 using namespace mozilla
;
54 //-------------------------------------------------------------------//
55 // nsIProcess implementation
56 //-------------------------------------------------------------------//
57 NS_IMPL_ISUPPORTS(nsProcess
, nsIProcess
, nsIObserver
)
60 nsProcess::nsProcess()
62 mLock("nsProcess.mLock"),
77 nsProcess::~nsProcess() = default;
80 nsProcess::Init(nsIFile
* aExecutable
) {
82 return NS_ERROR_ALREADY_INITIALIZED
;
85 if (NS_WARN_IF(!aExecutable
)) {
86 return NS_ERROR_INVALID_ARG
;
90 // First make sure the file exists
91 nsresult rv
= aExecutable
->IsFile(&isFile
);
96 return NS_ERROR_FAILURE
;
99 // Store the nsIFile in mExecutable
100 mExecutable
= aExecutable
;
101 // Get the path because it is needed by the NSPR process creation
103 rv
= mExecutable
->GetTarget(mTargetPath
);
104 if (NS_FAILED(rv
) || mTargetPath
.IsEmpty())
106 rv
= mExecutable
->GetPath(mTargetPath
);
111 void nsProcess::Monitor(void* aArg
) {
112 RefPtr
<nsProcess
> process
= dont_AddRef(static_cast<nsProcess
*>(aArg
));
114 if (!process
->mBlocking
) {
115 NS_SetCurrentThreadName("RunProcess");
118 #if defined(PROCESSMODEL_WINAPI)
119 HANDLE processHandle
;
121 // The mutex region cannot include WaitForSingleObject otherwise we'll
122 // block calls such as Kill. So lock on access and store a local.
123 MutexAutoLock
lock(process
->mLock
);
124 processHandle
= process
->mProcess
;
128 unsigned long exitCode
= -1;
130 dwRetVal
= WaitForSingleObject(processHandle
, INFINITE
);
131 if (dwRetVal
!= WAIT_FAILED
) {
132 if (GetExitCodeProcess(processHandle
, &exitCode
) == FALSE
) {
137 // Lock in case Kill or GetExitCode are called during this.
139 MutexAutoLock
lock(process
->mLock
);
140 CloseHandle(process
->mProcess
);
141 process
->mProcess
= nullptr;
142 process
->mExitValue
= exitCode
;
143 if (process
->mShutdown
) {
153 result
= waitpid(process
->mPid
, &status
, 0);
154 } while (result
== -1 && errno
== EINTR
);
155 if (result
== process
->mPid
) {
156 if (WIFEXITED(status
)) {
157 exitCode
= WEXITSTATUS(status
);
158 } else if (WIFSIGNALED(status
)) {
159 exitCode
= 256; // match NSPR's signal exit status
163 int32_t exitCode
= -1;
164 PRProcess
* prProcess
;
166 // The mutex region cannot include PR_WaitProcess otherwise we'll
167 // block calls such as Kill. So lock on access and store a local.
168 MutexAutoLock
lock(process
->mLock
);
169 prProcess
= process
->mProcess
;
171 if (PR_WaitProcess(prProcess
, &exitCode
) != PR_SUCCESS
) {
176 // Lock in case Kill or GetExitCode are called during this
178 MutexAutoLock
lock(process
->mLock
);
179 # if !defined(XP_UNIX)
180 process
->mProcess
= nullptr;
182 process
->mExitValue
= exitCode
;
183 if (process
->mShutdown
) {
189 // If we ran a background thread for the monitor then notify on the main
191 if (NS_IsMainThread()) {
192 process
->ProcessComplete();
194 NS_DispatchToMainThread(NewRunnableMethod(
195 "nsProcess::ProcessComplete", process
, &nsProcess::ProcessComplete
));
199 void nsProcess::ProcessComplete() {
201 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
203 os
->RemoveObserver(this, "xpcom-shutdown");
205 PR_JoinThread(mThread
);
211 MutexAutoLock
lock(mLock
);
212 if (mExitValue
!= 0) {
213 topic
= "process-failed";
215 topic
= "process-finished";
220 nsCOMPtr
<nsIObserver
> observer
= mObserver
.GetValue();
224 observer
->Observe(NS_ISUPPORTS_CAST(nsIProcess
*, this), topic
, nullptr);
228 // XXXldb |aArgs| has the wrong const-ness
230 nsProcess::Run(bool aBlocking
, const char** aArgs
, uint32_t aCount
) {
231 return CopyArgsAndRunProcess(aBlocking
, aArgs
, aCount
, nullptr, false);
234 // XXXldb |aArgs| has the wrong const-ness
236 nsProcess::RunAsync(const char** aArgs
, uint32_t aCount
, nsIObserver
* aObserver
,
238 return CopyArgsAndRunProcess(false, aArgs
, aCount
, aObserver
, aHoldWeak
);
241 nsresult
nsProcess::CopyArgsAndRunProcess(bool aBlocking
, const char** aArgs
,
243 nsIObserver
* aObserver
,
245 // Add one to the aCount for the program name and one for null termination.
246 char** my_argv
= nullptr;
247 my_argv
= (char**)moz_xmalloc(sizeof(char*) * (aCount
+ 2));
249 my_argv
[0] = ToNewUTF8String(mTargetPath
);
251 for (uint32_t i
= 0; i
< aCount
; ++i
) {
252 my_argv
[i
+ 1] = const_cast<char*>(aArgs
[i
]);
255 my_argv
[aCount
+ 1] = nullptr;
257 nsresult rv
= RunProcess(aBlocking
, my_argv
, aObserver
, aHoldWeak
, false);
264 // XXXldb |aArgs| has the wrong const-ness
266 nsProcess::Runw(bool aBlocking
, const char16_t
** aArgs
, uint32_t aCount
) {
267 return CopyArgsAndRunProcessw(aBlocking
, aArgs
, aCount
, nullptr, false);
270 // XXXldb |aArgs| has the wrong const-ness
272 nsProcess::RunwAsync(const char16_t
** aArgs
, uint32_t aCount
,
273 nsIObserver
* aObserver
, bool aHoldWeak
) {
274 return CopyArgsAndRunProcessw(false, aArgs
, aCount
, aObserver
, aHoldWeak
);
277 nsresult
nsProcess::CopyArgsAndRunProcessw(bool aBlocking
,
278 const char16_t
** aArgs
,
280 nsIObserver
* aObserver
,
282 // Add one to the aCount for the program name and one for null termination.
283 char** my_argv
= nullptr;
284 my_argv
= (char**)moz_xmalloc(sizeof(char*) * (aCount
+ 2));
286 my_argv
[0] = ToNewUTF8String(mTargetPath
);
288 for (uint32_t i
= 0; i
< aCount
; i
++) {
289 my_argv
[i
+ 1] = ToNewUTF8String(nsDependentString(aArgs
[i
]));
292 my_argv
[aCount
+ 1] = nullptr;
294 nsresult rv
= RunProcess(aBlocking
, my_argv
, aObserver
, aHoldWeak
, true);
296 for (uint32_t i
= 0; i
<= aCount
; ++i
) {
303 nsresult
nsProcess::RunProcess(bool aBlocking
, char** aMyArgv
,
304 nsIObserver
* aObserver
, bool aHoldWeak
,
306 NS_WARNING_ASSERTION(!XRE_IsContentProcess(),
307 "No launching of new processes in the content process");
309 if (NS_WARN_IF(!mExecutable
)) {
310 return NS_ERROR_NOT_INITIALIZED
;
312 if (NS_WARN_IF(mThread
)) {
313 return NS_ERROR_ALREADY_INITIALIZED
;
319 mObserver
= do_GetWeakReference(aObserver
, &rv
);
320 NS_ENSURE_SUCCESS(rv
, rv
);
322 mObserver
= aObserver
;
327 MutexAutoLock
lock(mLock
);
332 #if defined(PROCESSMODEL_WINAPI)
334 UniqueFreePtr
<wchar_t> cmdLine
;
336 // |aMyArgv| is null-terminated and always starts with the program path. If
337 // the second slot is non-null then arguments are being passed.
338 if (aMyArgv
[1] || mNoShell
) {
339 // Pass the executable path as argv[0] to the launched program when calling
341 char** argv
= mNoShell
? aMyArgv
: aMyArgv
+ 1;
343 wchar_t* assembledCmdLine
= nullptr;
344 if (assembleCmdLine(argv
, &assembledCmdLine
,
345 aArgsUTF8
? CP_UTF8
: CP_ACP
) == -1) {
346 return NS_ERROR_FILE_EXECUTION_FAILED
;
348 cmdLine
.reset(assembledCmdLine
);
351 // The program name in aMyArgv[0] is always UTF-8
352 NS_ConvertUTF8toUTF16
wideFile(aMyArgv
[0]);
355 STARTUPINFO startupInfo
;
356 ZeroMemory(&startupInfo
, sizeof(startupInfo
));
357 startupInfo
.cb
= sizeof(startupInfo
);
358 startupInfo
.dwFlags
= STARTF_USESHOWWINDOW
;
359 startupInfo
.wShowWindow
= mStartHidden
? SW_HIDE
: SW_SHOWNORMAL
;
361 PROCESS_INFORMATION processInfo
;
362 retVal
= CreateProcess(/* lpApplicationName = */ wideFile
.get(),
363 /* lpCommandLine */ cmdLine
.get(),
364 /* lpProcessAttributes = */ NULL
,
365 /* lpThreadAttributes = */ NULL
,
366 /* bInheritHandles = */ FALSE
,
367 /* dwCreationFlags = */ 0,
368 /* lpEnvironment = */ NULL
,
369 /* lpCurrentDirectory = */ NULL
,
370 /* lpStartupInfo = */ &startupInfo
,
371 /* lpProcessInformation */ &processInfo
);
374 return NS_ERROR_FILE_EXECUTION_FAILED
;
377 CloseHandle(processInfo
.hThread
);
379 // TODO(bug 1763051): assess if we need further work around this locking.
380 MutexAutoLock
lock(mLock
);
381 mProcess
= processInfo
.hProcess
;
383 SHELLEXECUTEINFOW sinfo
;
384 memset(&sinfo
, 0, sizeof(SHELLEXECUTEINFOW
));
385 sinfo
.cbSize
= sizeof(SHELLEXECUTEINFOW
);
386 sinfo
.hwnd
= nullptr;
387 sinfo
.lpFile
= wideFile
.get();
388 sinfo
.nShow
= mStartHidden
? SW_HIDE
: SW_SHOWNORMAL
;
390 /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
391 * from appearing. This makes behavior the same on all platforms. The flag
392 * will not have any effect on non-console applications.
395 SEE_MASK_FLAG_DDEWAIT
| SEE_MASK_NO_CONSOLE
| SEE_MASK_NOCLOSEPROCESS
;
398 sinfo
.lpParameters
= cmdLine
.get();
401 retVal
= ShellExecuteExW(&sinfo
);
403 return NS_ERROR_FILE_EXECUTION_FAILED
;
406 MutexAutoLock
lock(mLock
);
407 mProcess
= sinfo
.hProcess
;
411 MutexAutoLock
lock(mLock
);
412 mPid
= GetProcessId(mProcess
);
414 #elif defined(XP_MACOSX)
415 // Note: |aMyArgv| is already null-terminated as required by posix_spawnp.
417 int result
= posix_spawnp(&newPid
, aMyArgv
[0], nullptr, nullptr, aMyArgv
,
419 mPid
= static_cast<int32_t>(newPid
);
422 return NS_ERROR_FAILURE
;
424 #elif defined(XP_UNIX)
425 base::LaunchOptions options
;
426 std::vector
<std::string
> argvVec
;
427 for (char** arg
= aMyArgv
; *arg
!= nullptr; ++arg
) {
428 argvVec
.push_back(*arg
);
431 if (base::LaunchApp(argvVec
, std::move(options
), &newPid
).isOk()) {
432 static_assert(sizeof(pid_t
) <= sizeof(int32_t),
433 "mPid is large enough to hold a pid");
434 mPid
= static_cast<int32_t>(newPid
);
436 return NS_ERROR_FAILURE
;
440 PRProcess
* prProcess
=
441 PR_CreateProcess(aMyArgv
[0], aMyArgv
, nullptr, nullptr);
443 return NS_ERROR_FAILURE
;
446 MutexAutoLock
lock(mLock
);
447 mProcess
= prProcess
;
452 MYProcess
* ptrProc
= (MYProcess
*)mProcess
;
458 mBlocking
= aBlocking
;
461 MutexAutoLock
lock(mLock
);
462 if (mExitValue
< 0) {
463 return NS_ERROR_FILE_EXECUTION_FAILED
;
466 mThread
= CreateMonitorThread();
469 return NS_ERROR_FAILURE
;
472 // It isn't a failure if we just can't watch for shutdown
473 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
475 os
->AddObserver(this, "xpcom-shutdown", false);
482 // We don't guarantee that monitor threads are joined before Gecko exits, which
483 // can cause TSAN to complain about thread leaks. We handle this with a TSAN
484 // suppression, and route thread creation through this helper so that the
485 // suppression is as narrowly-scoped as possible.
486 PRThread
* nsProcess::CreateMonitorThread() {
487 return PR_CreateThread(PR_SYSTEM_THREAD
, Monitor
, this, PR_PRIORITY_NORMAL
,
488 PR_GLOBAL_THREAD
, PR_JOINABLE_THREAD
, 0);
492 nsProcess::GetIsRunning(bool* aIsRunning
) {
503 nsProcess::GetStartHidden(bool* aStartHidden
) {
504 *aStartHidden
= mStartHidden
;
509 nsProcess::SetStartHidden(bool aStartHidden
) {
510 mStartHidden
= aStartHidden
;
515 nsProcess::GetNoShell(bool* aNoShell
) {
516 *aNoShell
= mNoShell
;
521 nsProcess::SetNoShell(bool aNoShell
) {
527 nsProcess::GetPid(uint32_t* aPid
) {
529 return NS_ERROR_FAILURE
;
532 return NS_ERROR_NOT_IMPLEMENTED
;
541 return NS_ERROR_FAILURE
;
545 MutexAutoLock
lock(mLock
);
546 #if defined(PROCESSMODEL_WINAPI)
547 if (TerminateProcess(mProcess
, 0) == 0) {
548 return NS_ERROR_FAILURE
;
550 #elif defined(XP_UNIX)
551 if (kill(mPid
, SIGKILL
) != 0) {
552 return NS_ERROR_FAILURE
;
555 if (!mProcess
|| (PR_KillProcess(mProcess
) != PR_SUCCESS
)) {
556 return NS_ERROR_FAILURE
;
561 // We must null out mThread if we want IsRunning to return false immediately
563 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
565 os
->RemoveObserver(this, "xpcom-shutdown");
567 PR_JoinThread(mThread
);
574 nsProcess::GetExitValue(int32_t* aExitValue
) {
575 MutexAutoLock
lock(mLock
);
577 *aExitValue
= mExitValue
;
583 nsProcess::Observe(nsISupports
* aSubject
, const char* aTopic
,
584 const char16_t
* aData
) {
585 // Shutting down, drop all references
587 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
589 os
->RemoveObserver(this, "xpcom-shutdown");
596 MutexAutoLock
lock(mLock
);