michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /***************************************************************************** michael@0: * michael@0: * nsProcess is used to execute new processes and specify if you want to michael@0: * wait (blocking) or continue (non-blocking). michael@0: * michael@0: ***************************************************************************** michael@0: */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsMemory.h" michael@0: #include "nsProcess.h" michael@0: #include "prio.h" michael@0: #include "prenv.h" michael@0: #include "nsCRT.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: #include michael@0: michael@0: #if defined(PROCESSMODEL_WINAPI) michael@0: #include "prmem.h" michael@0: #include "nsString.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsReadableUtils.h" michael@0: #else michael@0: #ifdef XP_MACOSX michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef XP_MACOSX michael@0: cpu_type_t pref_cpu_types[2] = { michael@0: #if defined(__i386__) michael@0: CPU_TYPE_X86, michael@0: #elif defined(__x86_64__) michael@0: CPU_TYPE_X86_64, michael@0: #elif defined(__ppc__) michael@0: CPU_TYPE_POWERPC, michael@0: #endif michael@0: CPU_TYPE_ANY }; michael@0: #endif michael@0: michael@0: //-------------------------------------------------------------------// michael@0: // nsIProcess implementation michael@0: //-------------------------------------------------------------------// michael@0: NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, michael@0: nsIObserver) michael@0: michael@0: //Constructor michael@0: nsProcess::nsProcess() michael@0: : mThread(nullptr) michael@0: , mLock("nsProcess.mLock") michael@0: , mShutdown(false) michael@0: , mBlocking(false) michael@0: , mPid(-1) michael@0: , mObserver(nullptr) michael@0: , mWeakObserver(nullptr) michael@0: , mExitValue(-1) michael@0: #if !defined(XP_MACOSX) michael@0: , mProcess(nullptr) michael@0: #endif michael@0: { michael@0: } michael@0: michael@0: //Destructor michael@0: nsProcess::~nsProcess() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProcess::Init(nsIFile* executable) michael@0: { michael@0: if (mExecutable) michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: michael@0: if (NS_WARN_IF(!executable)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: bool isFile; michael@0: michael@0: //First make sure the file exists michael@0: nsresult rv = executable->IsFile(&isFile); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (!isFile) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: //Store the nsIFile in mExecutable michael@0: mExecutable = executable; michael@0: //Get the path because it is needed by the NSPR process creation michael@0: #ifdef XP_WIN michael@0: rv = mExecutable->GetTarget(mTargetPath); michael@0: if (NS_FAILED(rv) || mTargetPath.IsEmpty() ) michael@0: #endif michael@0: rv = mExecutable->GetPath(mTargetPath); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: #if defined(XP_WIN) michael@0: // Out param `wideCmdLine` must be PR_Freed by the caller. michael@0: static int assembleCmdLine(char *const *argv, wchar_t **wideCmdLine, michael@0: UINT codePage) michael@0: { michael@0: char *const *arg; michael@0: char *p, *q, *cmdLine; michael@0: int cmdLineSize; michael@0: int numBackslashes; michael@0: int i; michael@0: int argNeedQuotes; michael@0: michael@0: /* michael@0: * Find out how large the command line buffer should be. michael@0: */ michael@0: cmdLineSize = 0; michael@0: for (arg = argv; *arg; arg++) { michael@0: /* michael@0: * \ and " need to be escaped by a \. In the worst case, michael@0: * every character is a \ or ", so the string of length michael@0: * may double. If we quote an argument, that needs two ". michael@0: * Finally, we need a space between arguments, and michael@0: * a null byte at the end of command line. michael@0: */ michael@0: cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */ michael@0: + 2 /* we quote every argument */ michael@0: + 1; /* space in between, or final null */ michael@0: } michael@0: p = cmdLine = (char *) PR_MALLOC(cmdLineSize*sizeof(char)); michael@0: if (p == nullptr) { michael@0: return -1; michael@0: } michael@0: michael@0: for (arg = argv; *arg; arg++) { michael@0: /* Add a space to separates the arguments */ michael@0: if (arg != argv) { michael@0: *p++ = ' '; michael@0: } michael@0: q = *arg; michael@0: numBackslashes = 0; michael@0: argNeedQuotes = 0; michael@0: michael@0: /* If the argument contains white space, it needs to be quoted. */ michael@0: if (strpbrk(*arg, " \f\n\r\t\v")) { michael@0: argNeedQuotes = 1; michael@0: } michael@0: michael@0: if (argNeedQuotes) { michael@0: *p++ = '"'; michael@0: } michael@0: while (*q) { michael@0: if (*q == '\\') { michael@0: numBackslashes++; michael@0: q++; michael@0: } else if (*q == '"') { michael@0: if (numBackslashes) { michael@0: /* michael@0: * Double the backslashes since they are followed michael@0: * by a quote michael@0: */ michael@0: for (i = 0; i < 2 * numBackslashes; i++) { michael@0: *p++ = '\\'; michael@0: } michael@0: numBackslashes = 0; michael@0: } michael@0: /* To escape the quote */ michael@0: *p++ = '\\'; michael@0: *p++ = *q++; michael@0: } else { michael@0: if (numBackslashes) { michael@0: /* michael@0: * Backslashes are not followed by a quote, so michael@0: * don't need to double the backslashes. michael@0: */ michael@0: for (i = 0; i < numBackslashes; i++) { michael@0: *p++ = '\\'; michael@0: } michael@0: numBackslashes = 0; michael@0: } michael@0: *p++ = *q++; michael@0: } michael@0: } michael@0: michael@0: /* Now we are at the end of this argument */ michael@0: if (numBackslashes) { michael@0: /* michael@0: * Double the backslashes if we have a quote string michael@0: * delimiter at the end. michael@0: */ michael@0: if (argNeedQuotes) { michael@0: numBackslashes *= 2; michael@0: } michael@0: for (i = 0; i < numBackslashes; i++) { michael@0: *p++ = '\\'; michael@0: } michael@0: } michael@0: if (argNeedQuotes) { michael@0: *p++ = '"'; michael@0: } michael@0: } michael@0: michael@0: *p = '\0'; michael@0: int32_t numChars = MultiByteToWideChar(codePage, 0, cmdLine, -1, nullptr, 0); michael@0: *wideCmdLine = (wchar_t *) PR_MALLOC(numChars*sizeof(wchar_t)); michael@0: MultiByteToWideChar(codePage, 0, cmdLine, -1, *wideCmdLine, numChars); michael@0: PR_Free(cmdLine); michael@0: return 0; michael@0: } michael@0: #endif michael@0: michael@0: void nsProcess::Monitor(void *arg) michael@0: { michael@0: nsRefPtr process = dont_AddRef(static_cast(arg)); michael@0: michael@0: if (!process->mBlocking) michael@0: PR_SetCurrentThreadName("RunProcess"); michael@0: michael@0: #if defined(PROCESSMODEL_WINAPI) michael@0: DWORD dwRetVal; michael@0: unsigned long exitCode = -1; michael@0: michael@0: dwRetVal = WaitForSingleObject(process->mProcess, INFINITE); michael@0: if (dwRetVal != WAIT_FAILED) { michael@0: if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) michael@0: exitCode = -1; michael@0: } michael@0: michael@0: // Lock in case Kill or GetExitCode are called during this michael@0: { michael@0: MutexAutoLock lock(process->mLock); michael@0: CloseHandle(process->mProcess); michael@0: process->mProcess = nullptr; michael@0: process->mExitValue = exitCode; michael@0: if (process->mShutdown) michael@0: return; michael@0: } michael@0: #else michael@0: #ifdef XP_MACOSX michael@0: int exitCode = -1; michael@0: int status = 0; michael@0: if (waitpid(process->mPid, &status, 0) == process->mPid) { michael@0: if (WIFEXITED(status)) { michael@0: exitCode = WEXITSTATUS(status); michael@0: } michael@0: else if(WIFSIGNALED(status)) { michael@0: exitCode = 256; // match NSPR's signal exit status michael@0: } michael@0: } michael@0: #else michael@0: int32_t exitCode = -1; michael@0: if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) michael@0: exitCode = -1; michael@0: #endif michael@0: michael@0: // Lock in case Kill or GetExitCode are called during this michael@0: { michael@0: MutexAutoLock lock(process->mLock); michael@0: #if !defined(XP_MACOSX) michael@0: process->mProcess = nullptr; michael@0: #endif michael@0: process->mExitValue = exitCode; michael@0: if (process->mShutdown) michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: // If we ran a background thread for the monitor then notify on the main michael@0: // thread michael@0: if (NS_IsMainThread()) { michael@0: process->ProcessComplete(); michael@0: } michael@0: else { michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(process, &nsProcess::ProcessComplete); michael@0: NS_DispatchToMainThread(event); michael@0: } michael@0: } michael@0: michael@0: void nsProcess::ProcessComplete() michael@0: { michael@0: if (mThread) { michael@0: nsCOMPtr os = michael@0: mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->RemoveObserver(this, "xpcom-shutdown"); michael@0: PR_JoinThread(mThread); michael@0: mThread = nullptr; michael@0: } michael@0: michael@0: const char* topic; michael@0: if (mExitValue < 0) michael@0: topic = "process-failed"; michael@0: else michael@0: topic = "process-finished"; michael@0: michael@0: mPid = -1; michael@0: nsCOMPtr observer; michael@0: if (mWeakObserver) michael@0: observer = do_QueryReferent(mWeakObserver); michael@0: else if (mObserver) michael@0: observer = mObserver; michael@0: mObserver = nullptr; michael@0: mWeakObserver = nullptr; michael@0: michael@0: if (observer) michael@0: observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr); michael@0: } michael@0: michael@0: // XXXldb |args| has the wrong const-ness michael@0: NS_IMETHODIMP michael@0: nsProcess::Run(bool blocking, const char **args, uint32_t count) michael@0: { michael@0: return CopyArgsAndRunProcess(blocking, args, count, nullptr, false); michael@0: } michael@0: michael@0: // XXXldb |args| has the wrong const-ness michael@0: NS_IMETHODIMP michael@0: nsProcess::RunAsync(const char **args, uint32_t count, michael@0: nsIObserver* observer, bool holdWeak) michael@0: { michael@0: return CopyArgsAndRunProcess(false, args, count, observer, holdWeak); michael@0: } michael@0: michael@0: nsresult michael@0: nsProcess::CopyArgsAndRunProcess(bool blocking, const char** args, michael@0: uint32_t count, nsIObserver* observer, michael@0: bool holdWeak) michael@0: { michael@0: // Add one to the count for the program name and one for null termination. michael@0: char **my_argv = nullptr; michael@0: my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2)); michael@0: if (!my_argv) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: my_argv[0] = ToNewUTF8String(mTargetPath); michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: my_argv[i + 1] = const_cast(args[i]); michael@0: } michael@0: michael@0: my_argv[count + 1] = nullptr; michael@0: michael@0: nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, false); michael@0: michael@0: NS_Free(my_argv[0]); michael@0: NS_Free(my_argv); michael@0: return rv; michael@0: } michael@0: michael@0: // XXXldb |args| has the wrong const-ness michael@0: NS_IMETHODIMP michael@0: nsProcess::Runw(bool blocking, const char16_t **args, uint32_t count) michael@0: { michael@0: return CopyArgsAndRunProcessw(blocking, args, count, nullptr, false); michael@0: } michael@0: michael@0: // XXXldb |args| has the wrong const-ness michael@0: NS_IMETHODIMP michael@0: nsProcess::RunwAsync(const char16_t **args, uint32_t count, michael@0: nsIObserver* observer, bool holdWeak) michael@0: { michael@0: return CopyArgsAndRunProcessw(false, args, count, observer, holdWeak); michael@0: } michael@0: michael@0: nsresult michael@0: nsProcess::CopyArgsAndRunProcessw(bool blocking, const char16_t** args, michael@0: uint32_t count, nsIObserver* observer, michael@0: bool holdWeak) michael@0: { michael@0: // Add one to the count for the program name and one for null termination. michael@0: char **my_argv = nullptr; michael@0: my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2)); michael@0: if (!my_argv) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: my_argv[0] = ToNewUTF8String(mTargetPath); michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: my_argv[i + 1] = ToNewUTF8String(nsDependentString(args[i])); michael@0: } michael@0: michael@0: my_argv[count + 1] = nullptr; michael@0: michael@0: nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, true); michael@0: michael@0: for (uint32_t i = 0; i <= count; i++) { michael@0: NS_Free(my_argv[i]); michael@0: } michael@0: NS_Free(my_argv); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsProcess::RunProcess(bool blocking, char **my_argv, nsIObserver* observer, michael@0: bool holdWeak, bool argsUTF8) michael@0: { michael@0: if (NS_WARN_IF(!mExecutable)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: if (NS_WARN_IF(mThread)) michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: michael@0: if (observer) { michael@0: if (holdWeak) { michael@0: mWeakObserver = do_GetWeakReference(observer); michael@0: if (!mWeakObserver) michael@0: return NS_NOINTERFACE; michael@0: } michael@0: else { michael@0: mObserver = observer; michael@0: } michael@0: } michael@0: michael@0: mExitValue = -1; michael@0: mPid = -1; michael@0: michael@0: #if defined(PROCESSMODEL_WINAPI) michael@0: BOOL retVal; michael@0: wchar_t *cmdLine = nullptr; michael@0: michael@0: // The 'argv' array is null-terminated and always starts with the program path. michael@0: // If the second slot is non-null then arguments are being passed. michael@0: if (my_argv[1] != nullptr && michael@0: assembleCmdLine(my_argv + 1, &cmdLine, argsUTF8 ? CP_UTF8 : CP_ACP) == -1) { michael@0: return NS_ERROR_FILE_EXECUTION_FAILED; michael@0: } michael@0: michael@0: /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows michael@0: * from appearing. This makes behavior the same on all platforms. The flag michael@0: * will not have any effect on non-console applications. michael@0: */ michael@0: michael@0: // The program name in my_argv[0] is always UTF-8 michael@0: NS_ConvertUTF8toUTF16 wideFile(my_argv[0]); michael@0: michael@0: SHELLEXECUTEINFOW sinfo; michael@0: memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); michael@0: sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); michael@0: sinfo.hwnd = nullptr; michael@0: sinfo.lpFile = wideFile.get(); michael@0: sinfo.nShow = SW_SHOWNORMAL; michael@0: sinfo.fMask = SEE_MASK_FLAG_DDEWAIT | michael@0: SEE_MASK_NO_CONSOLE | michael@0: SEE_MASK_NOCLOSEPROCESS; michael@0: michael@0: if (cmdLine) michael@0: sinfo.lpParameters = cmdLine; michael@0: michael@0: retVal = ShellExecuteExW(&sinfo); michael@0: if (!retVal) { michael@0: return NS_ERROR_FILE_EXECUTION_FAILED; michael@0: } michael@0: michael@0: mProcess = sinfo.hProcess; michael@0: michael@0: if (cmdLine) michael@0: PR_Free(cmdLine); michael@0: michael@0: mPid = GetProcessId(mProcess); michael@0: #elif defined(XP_MACOSX) michael@0: // Initialize spawn attributes. michael@0: posix_spawnattr_t spawnattr; michael@0: if (posix_spawnattr_init(&spawnattr) != 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Set spawn attributes. michael@0: size_t attr_count = ArrayLength(pref_cpu_types); michael@0: size_t attr_ocount = 0; michael@0: if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 || michael@0: attr_ocount != attr_count) { michael@0: posix_spawnattr_destroy(&spawnattr); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Note that the 'argv' array is already null-terminated, which 'posix_spawnp' requires. michael@0: pid_t newPid = 0; michael@0: int result = posix_spawnp(&newPid, my_argv[0], nullptr, &spawnattr, my_argv, *_NSGetEnviron()); michael@0: mPid = static_cast(newPid); michael@0: michael@0: posix_spawnattr_destroy(&spawnattr); michael@0: michael@0: if (result != 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #else michael@0: mProcess = PR_CreateProcess(my_argv[0], my_argv, nullptr, nullptr); michael@0: if (!mProcess) michael@0: return NS_ERROR_FAILURE; michael@0: struct MYProcess { michael@0: uint32_t pid; michael@0: }; michael@0: MYProcess* ptrProc = (MYProcess *) mProcess; michael@0: mPid = ptrProc->pid; michael@0: #endif michael@0: michael@0: NS_ADDREF_THIS(); michael@0: mBlocking = blocking; michael@0: if (blocking) { michael@0: Monitor(this); michael@0: if (mExitValue < 0) michael@0: return NS_ERROR_FILE_EXECUTION_FAILED; michael@0: } michael@0: else { michael@0: mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, michael@0: PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, 0); michael@0: if (!mThread) { michael@0: NS_RELEASE_THIS(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // It isn't a failure if we just can't watch for shutdown michael@0: nsCOMPtr os = michael@0: mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->AddObserver(this, "xpcom-shutdown", false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsProcess::GetIsRunning(bool *aIsRunning) michael@0: { michael@0: if (mThread) michael@0: *aIsRunning = true; michael@0: else michael@0: *aIsRunning = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProcess::GetPid(uint32_t *aPid) michael@0: { michael@0: if (!mThread) michael@0: return NS_ERROR_FAILURE; michael@0: if (mPid < 0) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: *aPid = mPid; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProcess::Kill() michael@0: { michael@0: if (!mThread) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: #if defined(PROCESSMODEL_WINAPI) michael@0: if (TerminateProcess(mProcess, 0) == 0) michael@0: return NS_ERROR_FAILURE; michael@0: #elif defined(XP_MACOSX) michael@0: if (kill(mPid, SIGKILL) != 0) michael@0: return NS_ERROR_FAILURE; michael@0: #else michael@0: if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) michael@0: return NS_ERROR_FAILURE; michael@0: #endif michael@0: } michael@0: michael@0: // We must null out mThread if we want IsRunning to return false immediately michael@0: // after this call. michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->RemoveObserver(this, "xpcom-shutdown"); michael@0: PR_JoinThread(mThread); michael@0: mThread = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProcess::GetExitValue(int32_t *aExitValue) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: *aExitValue = mExitValue; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProcess::Observe(nsISupports* subject, const char* topic, const char16_t* data) michael@0: { michael@0: // Shutting down, drop all references michael@0: if (mThread) { michael@0: nsCOMPtr os = michael@0: mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->RemoveObserver(this, "xpcom-shutdown"); michael@0: mThread = nullptr; michael@0: } michael@0: michael@0: mObserver = nullptr; michael@0: mWeakObserver = nullptr; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: mShutdown = true; michael@0: michael@0: return NS_OK; michael@0: }