xpcom/threads/nsProcessCommon.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 /*****************************************************************************
     7  * 
     8  * nsProcess is used to execute new processes and specify if you want to
     9  * wait (blocking) or continue (non-blocking).
    10  *
    11  *****************************************************************************
    12  */
    14 #include "mozilla/ArrayUtils.h"
    16 #include "nsCOMPtr.h"
    17 #include "nsAutoPtr.h"
    18 #include "nsMemory.h"
    19 #include "nsProcess.h"
    20 #include "prio.h"
    21 #include "prenv.h"
    22 #include "nsCRT.h"
    23 #include "nsThreadUtils.h"
    24 #include "nsIObserverService.h"
    25 #include "mozilla/Services.h"
    27 #include <stdlib.h>
    29 #if defined(PROCESSMODEL_WINAPI)
    30 #include "prmem.h"
    31 #include "nsString.h"
    32 #include "nsLiteralString.h"
    33 #include "nsReadableUtils.h"
    34 #else
    35 #ifdef XP_MACOSX
    36 #include <crt_externs.h>
    37 #include <spawn.h>
    38 #include <sys/wait.h>
    39 #endif
    40 #include <sys/types.h>
    41 #include <signal.h>
    42 #endif
    44 using namespace mozilla;
    46 #ifdef XP_MACOSX
    47 cpu_type_t pref_cpu_types[2] = {
    48 #if defined(__i386__)
    49                                  CPU_TYPE_X86,
    50 #elif defined(__x86_64__)
    51                                  CPU_TYPE_X86_64,
    52 #elif defined(__ppc__)
    53                                  CPU_TYPE_POWERPC,
    54 #endif
    55                                  CPU_TYPE_ANY };
    56 #endif
    58 //-------------------------------------------------------------------//
    59 // nsIProcess implementation
    60 //-------------------------------------------------------------------//
    61 NS_IMPL_ISUPPORTS(nsProcess, nsIProcess,
    62                   nsIObserver)
    64 //Constructor
    65 nsProcess::nsProcess()
    66     : mThread(nullptr)
    67     , mLock("nsProcess.mLock")
    68     , mShutdown(false)
    69     , mBlocking(false)
    70     , mPid(-1)
    71     , mObserver(nullptr)
    72     , mWeakObserver(nullptr)
    73     , mExitValue(-1)
    74 #if !defined(XP_MACOSX)
    75     , mProcess(nullptr)
    76 #endif
    77 {
    78 }
    80 //Destructor
    81 nsProcess::~nsProcess()
    82 {
    83 }
    85 NS_IMETHODIMP
    86 nsProcess::Init(nsIFile* executable)
    87 {
    88     if (mExecutable)
    89         return NS_ERROR_ALREADY_INITIALIZED;
    91     if (NS_WARN_IF(!executable))
    92         return NS_ERROR_INVALID_ARG;
    93     bool isFile;
    95     //First make sure the file exists
    96     nsresult rv = executable->IsFile(&isFile);
    97     if (NS_FAILED(rv)) return rv;
    98     if (!isFile)
    99         return NS_ERROR_FAILURE;
   101     //Store the nsIFile in mExecutable
   102     mExecutable = executable;
   103     //Get the path because it is needed by the NSPR process creation
   104 #ifdef XP_WIN 
   105     rv = mExecutable->GetTarget(mTargetPath);
   106     if (NS_FAILED(rv) || mTargetPath.IsEmpty() )
   107 #endif
   108         rv = mExecutable->GetPath(mTargetPath);
   110     return rv;
   111 }
   114 #if defined(XP_WIN)
   115 // Out param `wideCmdLine` must be PR_Freed by the caller.
   116 static int assembleCmdLine(char *const *argv, wchar_t **wideCmdLine,
   117                            UINT codePage)
   118 {
   119     char *const *arg;
   120     char *p, *q, *cmdLine;
   121     int cmdLineSize;
   122     int numBackslashes;
   123     int i;
   124     int argNeedQuotes;
   126     /*
   127      * Find out how large the command line buffer should be.
   128      */
   129     cmdLineSize = 0;
   130     for (arg = argv; *arg; arg++) {
   131         /*
   132          * \ and " need to be escaped by a \.  In the worst case,
   133          * every character is a \ or ", so the string of length
   134          * may double.  If we quote an argument, that needs two ".
   135          * Finally, we need a space between arguments, and
   136          * a null byte at the end of command line.
   137          */
   138         cmdLineSize += 2 * strlen(*arg)  /* \ and " need to be escaped */
   139                 + 2                      /* we quote every argument */
   140                 + 1;                     /* space in between, or final null */
   141     }
   142     p = cmdLine = (char *) PR_MALLOC(cmdLineSize*sizeof(char));
   143     if (p == nullptr) {
   144         return -1;
   145     }
   147     for (arg = argv; *arg; arg++) {
   148         /* Add a space to separates the arguments */
   149         if (arg != argv) {
   150             *p++ = ' '; 
   151         }
   152         q = *arg;
   153         numBackslashes = 0;
   154         argNeedQuotes = 0;
   156         /* If the argument contains white space, it needs to be quoted. */
   157         if (strpbrk(*arg, " \f\n\r\t\v")) {
   158             argNeedQuotes = 1;
   159         }
   161         if (argNeedQuotes) {
   162             *p++ = '"';
   163         }
   164         while (*q) {
   165             if (*q == '\\') {
   166                 numBackslashes++;
   167                 q++;
   168             } else if (*q == '"') {
   169                 if (numBackslashes) {
   170                     /*
   171                      * Double the backslashes since they are followed
   172                      * by a quote
   173                      */
   174                     for (i = 0; i < 2 * numBackslashes; i++) {
   175                         *p++ = '\\';
   176                     }
   177                     numBackslashes = 0;
   178                 }
   179                 /* To escape the quote */
   180                 *p++ = '\\';
   181                 *p++ = *q++;
   182             } else {
   183                 if (numBackslashes) {
   184                     /*
   185                      * Backslashes are not followed by a quote, so
   186                      * don't need to double the backslashes.
   187                      */
   188                     for (i = 0; i < numBackslashes; i++) {
   189                         *p++ = '\\';
   190                     }
   191                     numBackslashes = 0;
   192                 }
   193                 *p++ = *q++;
   194             }
   195         }
   197         /* Now we are at the end of this argument */
   198         if (numBackslashes) {
   199             /*
   200              * Double the backslashes if we have a quote string
   201              * delimiter at the end.
   202              */
   203             if (argNeedQuotes) {
   204                 numBackslashes *= 2;
   205             }
   206             for (i = 0; i < numBackslashes; i++) {
   207                 *p++ = '\\';
   208             }
   209         }
   210         if (argNeedQuotes) {
   211             *p++ = '"';
   212         }
   213     } 
   215     *p = '\0';
   216     int32_t numChars = MultiByteToWideChar(codePage, 0, cmdLine, -1, nullptr, 0);
   217     *wideCmdLine = (wchar_t *) PR_MALLOC(numChars*sizeof(wchar_t));
   218     MultiByteToWideChar(codePage, 0, cmdLine, -1, *wideCmdLine, numChars); 
   219     PR_Free(cmdLine);
   220     return 0;
   221 }
   222 #endif
   224 void nsProcess::Monitor(void *arg)
   225 {
   226     nsRefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(arg));
   228     if (!process->mBlocking)
   229         PR_SetCurrentThreadName("RunProcess");
   231 #if defined(PROCESSMODEL_WINAPI)
   232     DWORD dwRetVal;
   233     unsigned long exitCode = -1;
   235     dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
   236     if (dwRetVal != WAIT_FAILED) {
   237         if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE)
   238             exitCode = -1;
   239     }
   241     // Lock in case Kill or GetExitCode are called during this
   242     {
   243         MutexAutoLock lock(process->mLock);
   244         CloseHandle(process->mProcess);
   245         process->mProcess = nullptr;
   246         process->mExitValue = exitCode;
   247         if (process->mShutdown)
   248             return;
   249     }
   250 #else
   251 #ifdef XP_MACOSX
   252     int exitCode = -1;
   253     int status = 0;
   254     if (waitpid(process->mPid, &status, 0) == process->mPid) {
   255         if (WIFEXITED(status)) {
   256             exitCode = WEXITSTATUS(status);
   257         }
   258         else if(WIFSIGNALED(status)) {
   259             exitCode = 256; // match NSPR's signal exit status
   260         }
   261     }
   262 #else
   263     int32_t exitCode = -1;
   264     if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS)
   265         exitCode = -1;
   266 #endif
   268     // Lock in case Kill or GetExitCode are called during this
   269     {
   270         MutexAutoLock lock(process->mLock);
   271 #if !defined(XP_MACOSX)
   272         process->mProcess = nullptr;
   273 #endif
   274         process->mExitValue = exitCode;
   275         if (process->mShutdown)
   276             return;
   277     }
   278 #endif
   280     // If we ran a background thread for the monitor then notify on the main
   281     // thread
   282     if (NS_IsMainThread()) {
   283         process->ProcessComplete();
   284     }
   285     else {
   286         nsCOMPtr<nsIRunnable> event =
   287             NS_NewRunnableMethod(process, &nsProcess::ProcessComplete);
   288         NS_DispatchToMainThread(event);
   289     }
   290 }
   292 void nsProcess::ProcessComplete()
   293 {
   294     if (mThread) {
   295         nsCOMPtr<nsIObserverService> os =
   296             mozilla::services::GetObserverService();
   297         if (os)
   298             os->RemoveObserver(this, "xpcom-shutdown");
   299         PR_JoinThread(mThread);
   300         mThread = nullptr;
   301     }
   303     const char* topic;
   304     if (mExitValue < 0)
   305         topic = "process-failed";
   306     else
   307         topic = "process-finished";
   309     mPid = -1;
   310     nsCOMPtr<nsIObserver> observer;
   311     if (mWeakObserver)
   312         observer = do_QueryReferent(mWeakObserver);
   313     else if (mObserver)
   314         observer = mObserver;
   315     mObserver = nullptr;
   316     mWeakObserver = nullptr;
   318     if (observer)
   319         observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
   320 }
   322 // XXXldb |args| has the wrong const-ness
   323 NS_IMETHODIMP  
   324 nsProcess::Run(bool blocking, const char **args, uint32_t count)
   325 {
   326     return CopyArgsAndRunProcess(blocking, args, count, nullptr, false);
   327 }
   329 // XXXldb |args| has the wrong const-ness
   330 NS_IMETHODIMP  
   331 nsProcess::RunAsync(const char **args, uint32_t count,
   332                     nsIObserver* observer, bool holdWeak)
   333 {
   334     return CopyArgsAndRunProcess(false, args, count, observer, holdWeak);
   335 }
   337 nsresult
   338 nsProcess::CopyArgsAndRunProcess(bool blocking, const char** args,
   339                                  uint32_t count, nsIObserver* observer,
   340                                  bool holdWeak)
   341 {
   342     // Add one to the count for the program name and one for null termination.
   343     char **my_argv = nullptr;
   344     my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2));
   345     if (!my_argv) {
   346         return NS_ERROR_OUT_OF_MEMORY;
   347     }
   349     my_argv[0] = ToNewUTF8String(mTargetPath);
   351     for (uint32_t i = 0; i < count; i++) {
   352         my_argv[i + 1] = const_cast<char*>(args[i]);
   353     }
   355     my_argv[count + 1] = nullptr;
   357     nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, false);
   359     NS_Free(my_argv[0]);
   360     NS_Free(my_argv);
   361     return rv;
   362 }
   364 // XXXldb |args| has the wrong const-ness
   365 NS_IMETHODIMP  
   366 nsProcess::Runw(bool blocking, const char16_t **args, uint32_t count)
   367 {
   368     return CopyArgsAndRunProcessw(blocking, args, count, nullptr, false);
   369 }
   371 // XXXldb |args| has the wrong const-ness
   372 NS_IMETHODIMP  
   373 nsProcess::RunwAsync(const char16_t **args, uint32_t count,
   374                     nsIObserver* observer, bool holdWeak)
   375 {
   376     return CopyArgsAndRunProcessw(false, args, count, observer, holdWeak);
   377 }
   379 nsresult
   380 nsProcess::CopyArgsAndRunProcessw(bool blocking, const char16_t** args,
   381                                   uint32_t count, nsIObserver* observer,
   382                                   bool holdWeak)
   383 {
   384     // Add one to the count for the program name and one for null termination.
   385     char **my_argv = nullptr;
   386     my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2));
   387     if (!my_argv) {
   388         return NS_ERROR_OUT_OF_MEMORY;
   389     }
   391     my_argv[0] = ToNewUTF8String(mTargetPath);
   393     for (uint32_t i = 0; i < count; i++) {
   394         my_argv[i + 1] = ToNewUTF8String(nsDependentString(args[i]));
   395     }
   397     my_argv[count + 1] = nullptr;
   399     nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, true);
   401     for (uint32_t i = 0; i <= count; i++) {
   402         NS_Free(my_argv[i]);
   403     }
   404     NS_Free(my_argv);
   405     return rv;
   406 }
   408 nsresult  
   409 nsProcess::RunProcess(bool blocking, char **my_argv, nsIObserver* observer,
   410                       bool holdWeak, bool argsUTF8)
   411 {
   412     if (NS_WARN_IF(!mExecutable))
   413         return NS_ERROR_NOT_INITIALIZED;
   414     if (NS_WARN_IF(mThread))
   415         return NS_ERROR_ALREADY_INITIALIZED;
   417     if (observer) {
   418         if (holdWeak) {
   419             mWeakObserver = do_GetWeakReference(observer);
   420             if (!mWeakObserver)
   421                 return NS_NOINTERFACE;
   422         }
   423         else {
   424             mObserver = observer;
   425         }
   426     }
   428     mExitValue = -1;
   429     mPid = -1;
   431 #if defined(PROCESSMODEL_WINAPI)
   432     BOOL retVal;
   433     wchar_t *cmdLine = nullptr;
   435     // The 'argv' array is null-terminated and always starts with the program path.
   436     // If the second slot is non-null then arguments are being passed.
   437     if (my_argv[1] != nullptr &&
   438         assembleCmdLine(my_argv + 1, &cmdLine, argsUTF8 ? CP_UTF8 : CP_ACP) == -1) {
   439         return NS_ERROR_FILE_EXECUTION_FAILED;    
   440     }
   442     /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
   443      * from appearing. This makes behavior the same on all platforms. The flag
   444      * will not have any effect on non-console applications.
   445      */
   447     // The program name in my_argv[0] is always UTF-8
   448     NS_ConvertUTF8toUTF16 wideFile(my_argv[0]);
   450     SHELLEXECUTEINFOW sinfo;
   451     memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
   452     sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
   453     sinfo.hwnd   = nullptr;
   454     sinfo.lpFile = wideFile.get();
   455     sinfo.nShow  = SW_SHOWNORMAL;
   456     sinfo.fMask  = SEE_MASK_FLAG_DDEWAIT |
   457                    SEE_MASK_NO_CONSOLE |
   458                    SEE_MASK_NOCLOSEPROCESS;
   460     if (cmdLine)
   461         sinfo.lpParameters = cmdLine;
   463     retVal = ShellExecuteExW(&sinfo);
   464     if (!retVal) {
   465         return NS_ERROR_FILE_EXECUTION_FAILED;
   466     }
   468     mProcess = sinfo.hProcess;
   470     if (cmdLine)
   471         PR_Free(cmdLine);
   473     mPid = GetProcessId(mProcess);
   474 #elif defined(XP_MACOSX)
   475     // Initialize spawn attributes.
   476     posix_spawnattr_t spawnattr;
   477     if (posix_spawnattr_init(&spawnattr) != 0) {
   478         return NS_ERROR_FAILURE;
   479     }
   481     // Set spawn attributes.
   482     size_t attr_count = ArrayLength(pref_cpu_types);
   483     size_t attr_ocount = 0;
   484     if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 ||
   485         attr_ocount != attr_count) {
   486         posix_spawnattr_destroy(&spawnattr);
   487         return NS_ERROR_FAILURE;
   488     }
   490     // Note that the 'argv' array is already null-terminated, which 'posix_spawnp' requires.
   491     pid_t newPid = 0;
   492     int result = posix_spawnp(&newPid, my_argv[0], nullptr, &spawnattr, my_argv, *_NSGetEnviron());
   493     mPid = static_cast<int32_t>(newPid);
   495     posix_spawnattr_destroy(&spawnattr);
   497     if (result != 0) {
   498         return NS_ERROR_FAILURE;
   499     }
   500 #else
   501     mProcess = PR_CreateProcess(my_argv[0], my_argv, nullptr, nullptr);
   502     if (!mProcess)
   503         return NS_ERROR_FAILURE;
   504     struct MYProcess {
   505         uint32_t pid;
   506     };
   507     MYProcess* ptrProc = (MYProcess *) mProcess;
   508     mPid = ptrProc->pid;
   509 #endif
   511     NS_ADDREF_THIS();
   512     mBlocking = blocking;
   513     if (blocking) {
   514         Monitor(this);
   515         if (mExitValue < 0)
   516             return NS_ERROR_FILE_EXECUTION_FAILED;
   517     }
   518     else {
   519         mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this,
   520                                   PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
   521                                   PR_JOINABLE_THREAD, 0);
   522         if (!mThread) {
   523             NS_RELEASE_THIS();
   524             return NS_ERROR_FAILURE;
   525         }
   527         // It isn't a failure if we just can't watch for shutdown
   528         nsCOMPtr<nsIObserverService> os =
   529           mozilla::services::GetObserverService();
   530         if (os)
   531             os->AddObserver(this, "xpcom-shutdown", false);
   532     }
   534     return NS_OK;
   535 }
   537 NS_IMETHODIMP nsProcess::GetIsRunning(bool *aIsRunning)
   538 {
   539     if (mThread)
   540         *aIsRunning = true;
   541     else
   542         *aIsRunning = false;
   544     return NS_OK;
   545 }
   547 NS_IMETHODIMP
   548 nsProcess::GetPid(uint32_t *aPid)
   549 {
   550     if (!mThread)
   551         return NS_ERROR_FAILURE;
   552     if (mPid < 0)
   553         return NS_ERROR_NOT_IMPLEMENTED;
   554     *aPid = mPid;
   555     return NS_OK;
   556 }
   558 NS_IMETHODIMP
   559 nsProcess::Kill()
   560 {
   561     if (!mThread)
   562         return NS_ERROR_FAILURE;
   564     {
   565         MutexAutoLock lock(mLock);
   566 #if defined(PROCESSMODEL_WINAPI)
   567         if (TerminateProcess(mProcess, 0) == 0)
   568             return NS_ERROR_FAILURE;
   569 #elif defined(XP_MACOSX)
   570         if (kill(mPid, SIGKILL) != 0)
   571             return NS_ERROR_FAILURE;
   572 #else
   573         if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS))
   574             return NS_ERROR_FAILURE;
   575 #endif
   576     }
   578     // We must null out mThread if we want IsRunning to return false immediately
   579     // after this call.
   580     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   581     if (os)
   582         os->RemoveObserver(this, "xpcom-shutdown");
   583     PR_JoinThread(mThread);
   584     mThread = nullptr;
   586     return NS_OK;
   587 }
   589 NS_IMETHODIMP
   590 nsProcess::GetExitValue(int32_t *aExitValue)
   591 {
   592     MutexAutoLock lock(mLock);
   594     *aExitValue = mExitValue;
   596     return NS_OK;
   597 }
   599 NS_IMETHODIMP
   600 nsProcess::Observe(nsISupports* subject, const char* topic, const char16_t* data)
   601 {
   602     // Shutting down, drop all references
   603     if (mThread) {
   604         nsCOMPtr<nsIObserverService> os =
   605           mozilla::services::GetObserverService();
   606         if (os)
   607             os->RemoveObserver(this, "xpcom-shutdown");
   608         mThread = nullptr;
   609     }
   611     mObserver = nullptr;
   612     mWeakObserver = nullptr;
   614     MutexAutoLock lock(mLock);
   615     mShutdown = true;
   617     return NS_OK;
   618 }

mercurial