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