Bug 1755481: correct documentation of `nsIClipboard::getData`. r=mccr8
[gecko.git] / xpcom / threads / nsProcessCommon.cpp
blobdfed400b56f7f2266495f8b36f71746049fb8e1d
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"
17 #include "nsCOMPtr.h"
18 #include "nsIFile.h"
19 #include "nsMemory.h"
20 #include "nsProcess.h"
21 #include "prio.h"
22 #include "prenv.h"
23 #include "nsCRT.h"
24 #include "nsThreadUtils.h"
25 #include "nsIObserverService.h"
26 #include "nsXULAppAPI.h"
27 #include "mozilla/Services.h"
29 #include <stdlib.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"
37 #else
38 # ifdef XP_MACOSX
39 # include <crt_externs.h>
40 # include <spawn.h>
41 # endif
42 # ifdef XP_UNIX
43 # ifndef XP_MACOSX
44 # include "base/process_util.h"
45 # endif
46 # include <sys/wait.h>
47 # include <sys/errno.h>
48 # endif
49 # include <sys/types.h>
50 # include <signal.h>
51 #endif
53 using namespace mozilla;
55 //-------------------------------------------------------------------//
56 // nsIProcess implementation
57 //-------------------------------------------------------------------//
58 NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, nsIObserver)
60 // Constructor
61 nsProcess::nsProcess()
62 : mThread(nullptr),
63 mLock("nsProcess.mLock"),
64 mShutdown(false),
65 mBlocking(false),
66 mStartHidden(false),
67 mNoShell(false),
68 mPid(-1),
69 mExitValue(-1)
70 #if !defined(XP_UNIX)
72 mProcess(nullptr)
73 #endif
77 // Destructor
78 nsProcess::~nsProcess() = default;
80 NS_IMETHODIMP
81 nsProcess::Init(nsIFile* aExecutable) {
82 if (mExecutable) {
83 return NS_ERROR_ALREADY_INITIALIZED;
86 if (NS_WARN_IF(!aExecutable)) {
87 return NS_ERROR_INVALID_ARG;
89 bool isFile;
91 // First make sure the file exists
92 nsresult rv = aExecutable->IsFile(&isFile);
93 if (NS_FAILED(rv)) {
94 return rv;
96 if (!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
103 #ifdef XP_WIN
104 rv = mExecutable->GetTarget(mTargetPath);
105 if (NS_FAILED(rv) || mTargetPath.IsEmpty())
106 #endif
107 rv = mExecutable->GetPath(mTargetPath);
109 return rv;
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)
120 DWORD dwRetVal;
121 unsigned long exitCode = -1;
123 dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
124 if (dwRetVal != WAIT_FAILED) {
125 if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) {
126 exitCode = -1;
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) {
137 return;
140 #else
141 # ifdef XP_UNIX
142 int exitCode = -1;
143 int status = 0;
144 pid_t result;
145 do {
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
155 # else
156 int32_t exitCode = -1;
157 if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) {
158 exitCode = -1;
160 # endif
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;
167 # endif
168 process->mExitValue = exitCode;
169 if (process->mShutdown) {
170 return;
173 #endif
175 // If we ran a background thread for the monitor then notify on the main
176 // thread
177 if (NS_IsMainThread()) {
178 process->ProcessComplete();
179 } else {
180 NS_DispatchToMainThread(NewRunnableMethod(
181 "nsProcess::ProcessComplete", process, &nsProcess::ProcessComplete));
185 void nsProcess::ProcessComplete() {
186 if (mThread) {
187 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
188 if (os) {
189 os->RemoveObserver(this, "xpcom-shutdown");
191 PR_JoinThread(mThread);
192 mThread = nullptr;
195 const char* topic;
196 if (mExitValue != 0) {
197 topic = "process-failed";
198 } else {
199 topic = "process-finished";
202 mPid = -1;
203 nsCOMPtr<nsIObserver> observer = mObserver.GetValue();
204 mObserver = nullptr;
206 if (observer) {
207 observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
211 // XXXldb |aArgs| has the wrong const-ness
212 NS_IMETHODIMP
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
218 NS_IMETHODIMP
219 nsProcess::RunAsync(const char** aArgs, uint32_t aCount, nsIObserver* aObserver,
220 bool aHoldWeak) {
221 return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak);
224 nsresult nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs,
225 uint32_t aCount,
226 nsIObserver* aObserver,
227 bool aHoldWeak) {
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);
242 free(my_argv[0]);
243 free(my_argv);
244 return rv;
247 // XXXldb |aArgs| has the wrong const-ness
248 NS_IMETHODIMP
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
254 NS_IMETHODIMP
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,
262 uint32_t aCount,
263 nsIObserver* aObserver,
264 bool aHoldWeak) {
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) {
280 free(my_argv[i]);
282 free(my_argv);
283 return rv;
286 nsresult nsProcess::RunProcess(bool aBlocking, char** aMyArgv,
287 nsIObserver* aObserver, bool aHoldWeak,
288 bool aArgsUTF8) {
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;
299 if (aObserver) {
300 if (aHoldWeak) {
301 nsresult rv = NS_OK;
302 mObserver = do_GetWeakReference(aObserver, &rv);
303 NS_ENSURE_SUCCESS(rv, rv);
304 } else {
305 mObserver = aObserver;
309 mExitValue = -1;
310 mPid = -1;
312 #if defined(PROCESSMODEL_WINAPI)
313 BOOL retVal;
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
320 // CreateProcess().
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]);
334 if (mNoShell) {
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);
353 if (!retVal) {
354 return NS_ERROR_FILE_EXECUTION_FAILED;
357 CloseHandle(processInfo.hThread);
359 mProcess = processInfo.hProcess;
360 } else {
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.
372 sinfo.fMask =
373 SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS;
375 if (cmdLine) {
376 sinfo.lpParameters = cmdLine.get();
379 retVal = ShellExecuteExW(&sinfo);
380 if (!retVal) {
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.
390 pid_t newPid = 0;
391 int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, nullptr, aMyArgv,
392 *_NSGetEnviron());
393 mPid = static_cast<int32_t>(newPid);
395 if (result != 0) {
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);
404 pid_t newPid;
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);
409 } else {
410 return NS_ERROR_FAILURE;
412 #else
413 mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr);
414 if (!mProcess) {
415 return NS_ERROR_FAILURE;
417 struct MYProcess {
418 uint32_t pid;
420 MYProcess* ptrProc = (MYProcess*)mProcess;
421 mPid = ptrProc->pid;
422 #endif
424 NS_ADDREF_THIS();
425 mBlocking = aBlocking;
426 if (aBlocking) {
427 Monitor(this);
428 if (mExitValue < 0) {
429 return NS_ERROR_FILE_EXECUTION_FAILED;
431 } else {
432 mThread = CreateMonitorThread();
433 if (!mThread) {
434 NS_RELEASE_THIS();
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();
440 if (os) {
441 os->AddObserver(this, "xpcom-shutdown", false);
445 return NS_OK;
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);
457 NS_IMETHODIMP
458 nsProcess::GetIsRunning(bool* aIsRunning) {
459 if (mThread) {
460 *aIsRunning = true;
461 } else {
462 *aIsRunning = false;
465 return NS_OK;
468 NS_IMETHODIMP
469 nsProcess::GetStartHidden(bool* aStartHidden) {
470 *aStartHidden = mStartHidden;
471 return NS_OK;
474 NS_IMETHODIMP
475 nsProcess::SetStartHidden(bool aStartHidden) {
476 mStartHidden = aStartHidden;
477 return NS_OK;
480 NS_IMETHODIMP
481 nsProcess::GetNoShell(bool* aNoShell) {
482 *aNoShell = mNoShell;
483 return NS_OK;
486 NS_IMETHODIMP
487 nsProcess::SetNoShell(bool aNoShell) {
488 mNoShell = aNoShell;
489 return NS_OK;
492 NS_IMETHODIMP
493 nsProcess::GetPid(uint32_t* aPid) {
494 if (!mThread) {
495 return NS_ERROR_FAILURE;
497 if (mPid < 0) {
498 return NS_ERROR_NOT_IMPLEMENTED;
500 *aPid = mPid;
501 return NS_OK;
504 NS_IMETHODIMP
505 nsProcess::Kill() {
506 if (!mThread) {
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;
520 #else
521 if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) {
522 return NS_ERROR_FAILURE;
524 #endif
527 // We must null out mThread if we want IsRunning to return false immediately
528 // after this call.
529 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
530 if (os) {
531 os->RemoveObserver(this, "xpcom-shutdown");
533 PR_JoinThread(mThread);
534 mThread = nullptr;
536 return NS_OK;
539 NS_IMETHODIMP
540 nsProcess::GetExitValue(int32_t* aExitValue) {
541 MutexAutoLock lock(mLock);
543 *aExitValue = mExitValue;
545 return NS_OK;
548 NS_IMETHODIMP
549 nsProcess::Observe(nsISupports* aSubject, const char* aTopic,
550 const char16_t* aData) {
551 // Shutting down, drop all references
552 if (mThread) {
553 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
554 if (os) {
555 os->RemoveObserver(this, "xpcom-shutdown");
557 mThread = nullptr;
560 mObserver = nullptr;
562 MutexAutoLock lock(mLock);
563 mShutdown = true;
565 return NS_OK;