Back to index

lightning-sunbird  0.9+nobinonly
nsProcessCommon.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code, released
00016  * March 31, 1998.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998-1999
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Don Bragg <dbragg@netscape.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 /*****************************************************************************
00041  * 
00042  * nsProcess is used to execute new processes and specify if you want to
00043  * wait (blocking) or continue (non-blocking).
00044  *
00045  *****************************************************************************
00046  */
00047 
00048 #include "nsCOMPtr.h"
00049 #include "nsMemory.h"
00050 #include "nsProcess.h"
00051 #include "prtypes.h"
00052 #include "prio.h"
00053 #include "prenv.h"
00054 #include "nsCRT.h"
00055 
00056 #include <stdlib.h>
00057 
00058 #if defined(XP_WIN)
00059 #include "prmem.h"
00060 #include "nsString.h"
00061 #include "nsLiteralString.h"
00062 #include "nsReadableUtils.h"
00063 #include <windows.h>
00064 #endif
00065 
00066 #if defined(XP_MACOSX)
00067 #include <Processes.h>
00068 #include "nsILocalFileMac.h"
00069 #endif
00070 
00071 //-------------------------------------------------------------------//
00072 // nsIProcess implementation
00073 //-------------------------------------------------------------------//
00074 NS_IMPL_ISUPPORTS1(nsProcess, nsIProcess)
00075 
00076 //Constructor
00077 nsProcess::nsProcess()
00078     : mExitValue(-1),
00079       mProcess(nsnull)
00080 {
00081 }
00082 
00083 NS_IMETHODIMP
00084 nsProcess::Init(nsIFile* executable)
00085 {
00086     NS_ENSURE_ARG_POINTER(executable);
00087     PRBool isFile;
00088 
00089     //First make sure the file exists
00090     nsresult rv = executable->IsFile(&isFile);
00091     if (NS_FAILED(rv)) return rv;
00092     if (!isFile)
00093         return NS_ERROR_FAILURE;
00094 
00095     //Store the nsIFile in mExecutable
00096     mExecutable = executable;
00097     //Get the path because it is needed by the NSPR process creation
00098 #ifdef XP_WIN 
00099     rv = mExecutable->GetNativeTarget(mTargetPath);
00100     if (NS_FAILED(rv) || mTargetPath.IsEmpty() )
00101 #endif
00102         rv = mExecutable->GetNativePath(mTargetPath);
00103 
00104     return rv;
00105 }
00106 
00107 
00108 #if defined(XP_WIN)
00109 static int assembleCmdLine(char *const *argv, char **cmdLine)
00110 {
00111     char *const *arg;
00112     char *p, *q;
00113     int cmdLineSize;
00114     int numBackslashes;
00115     int i;
00116     int argNeedQuotes;
00117 
00118     /*
00119      * Find out how large the command line buffer should be.
00120      */
00121     cmdLineSize = 0;
00122     for (arg = argv; *arg; arg++) {
00123         /*
00124          * \ and " need to be escaped by a \.  In the worst case,
00125          * every character is a \ or ", so the string of length
00126          * may double.  If we quote an argument, that needs two ".
00127          * Finally, we need a space between arguments, and
00128          * a null byte at the end of command line.
00129          */
00130         cmdLineSize += 2 * strlen(*arg)  /* \ and " need to be escaped */
00131                 + 2                      /* we quote every argument */
00132                 + 1;                     /* space in between, or final null */
00133     }
00134     p = *cmdLine = (char *) PR_MALLOC(cmdLineSize);
00135     if (p == NULL) {
00136         return -1;
00137     }
00138 
00139     for (arg = argv; *arg; arg++) {
00140         /* Add a space to separates the arguments */
00141         if (arg != argv) {
00142             *p++ = ' '; 
00143         }
00144         q = *arg;
00145         numBackslashes = 0;
00146         argNeedQuotes = 0;
00147 
00148         /* If the argument contains white space, it needs to be quoted. */
00149         if (strpbrk(*arg, " \f\n\r\t\v")) {
00150             argNeedQuotes = 1;
00151         }
00152 
00153         if (argNeedQuotes) {
00154             *p++ = '"';
00155         }
00156         while (*q) {
00157             if (*q == '\\') {
00158                 numBackslashes++;
00159                 q++;
00160             } else if (*q == '"') {
00161                 if (numBackslashes) {
00162                     /*
00163                      * Double the backslashes since they are followed
00164                      * by a quote
00165                      */
00166                     for (i = 0; i < 2 * numBackslashes; i++) {
00167                         *p++ = '\\';
00168                     }
00169                     numBackslashes = 0;
00170                 }
00171                 /* To escape the quote */
00172                 *p++ = '\\';
00173                 *p++ = *q++;
00174             } else {
00175                 if (numBackslashes) {
00176                     /*
00177                      * Backslashes are not followed by a quote, so
00178                      * don't need to double the backslashes.
00179                      */
00180                     for (i = 0; i < numBackslashes; i++) {
00181                         *p++ = '\\';
00182                     }
00183                     numBackslashes = 0;
00184                 }
00185                 *p++ = *q++;
00186             }
00187         }
00188 
00189         /* Now we are at the end of this argument */
00190         if (numBackslashes) {
00191             /*
00192              * Double the backslashes if we have a quote string
00193              * delimiter at the end.
00194              */
00195             if (argNeedQuotes) {
00196                 numBackslashes *= 2;
00197             }
00198             for (i = 0; i < numBackslashes; i++) {
00199                 *p++ = '\\';
00200             }
00201         }
00202         if (argNeedQuotes) {
00203             *p++ = '"';
00204         }
00205     } 
00206 
00207     *p = '\0';
00208     return 0;
00209 }
00210 #endif
00211 
00212 // XXXldb |args| has the wrong const-ness
00213 NS_IMETHODIMP  
00214 nsProcess::Run(PRBool blocking, const char **args, PRUint32 count,
00215                PRUint32 *pid)
00216 {
00217     NS_ENSURE_TRUE(mExecutable, NS_ERROR_NOT_INITIALIZED);
00218     PRStatus status = PR_SUCCESS;
00219 
00220     // make sure that when we allocate we have 1 greater than the
00221     // count since we need to null terminate the list for the argv to
00222     // pass into PR_CreateProcess
00223     char **my_argv = NULL;
00224     my_argv = (char **)nsMemory::Alloc(sizeof(char *) * (count + 2) );
00225     if (!my_argv) {
00226         return NS_ERROR_OUT_OF_MEMORY;
00227     }
00228 
00229     // copy the args
00230     PRUint32 i;
00231     for (i=0; i < count; i++) {
00232         my_argv[i+1] = NS_CONST_CAST(char*, args[i]);
00233     }
00234     // we need to set argv[0] to the program name.
00235     my_argv[0] = mTargetPath.BeginWriting();
00236     // null terminate the array
00237     my_argv[count+1] = NULL;
00238 
00239 #if defined(XP_WIN)
00240     STARTUPINFO startupInfo;
00241     PROCESS_INFORMATION procInfo;
00242     BOOL retVal;
00243     char *cmdLine;
00244 
00245     if (assembleCmdLine(my_argv, &cmdLine) == -1) {
00246         nsMemory::Free(my_argv);
00247         return NS_ERROR_FILE_EXECUTION_FAILED;    
00248     }
00249 
00250     ZeroMemory(&startupInfo, sizeof(startupInfo));
00251     startupInfo.cb = sizeof(startupInfo);
00252 
00253     retVal = CreateProcess(NULL,
00254                            // NS_CONST_CAST(char*, mTargetPath.get()),
00255                            cmdLine,
00256                            NULL,  /* security attributes for the new
00257                                    * process */
00258                            NULL,  /* security attributes for the primary
00259                                    * thread in the new process */
00260                            FALSE,  /* inherit handles */
00261                            0,     /* creation flags */
00262                            NULL,  /* env */
00263                            NULL,  /* current drive and directory */
00264                            &startupInfo,
00265                            &procInfo
00266                           );
00267     PR_Free( cmdLine );
00268     if (blocking) {
00269  
00270         // if success, wait for process termination. the early returns and such
00271         // are a bit ugly but preserving the logic of the nspr code I copied to 
00272         // minimize our risk abit.
00273 
00274         if ( retVal == TRUE ) {
00275             DWORD dwRetVal;
00276             unsigned long exitCode;
00277 
00278             dwRetVal = WaitForSingleObject(procInfo.hProcess, INFINITE);
00279             if (dwRetVal == WAIT_FAILED) {
00280                 nsMemory::Free(my_argv);
00281                 return NS_ERROR_FAILURE;
00282             }
00283             if (GetExitCodeProcess(procInfo.hProcess, &exitCode) == FALSE) {
00284                 mExitValue = exitCode;
00285                 nsMemory::Free(my_argv);
00286                return NS_ERROR_FAILURE;
00287             }
00288             mExitValue = exitCode;
00289             CloseHandle(procInfo.hProcess);
00290         }
00291         else
00292             status = PR_FAILURE;
00293     } 
00294     else {
00295 
00296         // map return value into success code
00297 
00298         if (retVal == TRUE) 
00299             status = PR_SUCCESS;
00300         else
00301             status = PR_FAILURE;
00302     }
00303 
00304 #else // Note, this must not be an #elif ...!
00305 
00306 #if defined(XP_MACOSX)
00307     if (count == 0) {
00308         FSSpec resolvedSpec;
00309         OSErr err = noErr;
00310 
00311         nsCOMPtr<nsILocalFileMac> macExecutable =
00312             do_QueryInterface(mExecutable);
00313         macExecutable->GetFSSpec(&resolvedSpec);
00314 
00315         LaunchParamBlockRec launchPB;
00316         launchPB.launchAppSpec = &resolvedSpec;
00317         launchPB.launchAppParameters = NULL;
00318         launchPB.launchBlockID = extendedBlock;
00319         launchPB.launchEPBLength = extendedBlockLen;
00320         launchPB.launchFileFlags = NULL;
00321         launchPB.launchControlFlags =
00322             launchContinue + launchNoFileFlags + launchUseMinimum;
00323         if (!blocking)
00324             launchPB.launchControlFlags += launchDontSwitch;
00325 
00326         err = LaunchApplication(&launchPB);
00327 
00328         // NOTE: blocking mode assumes you are running on a thread
00329         //       other than the UI thread that has the main event loop
00330         if (blocking && err == noErr) {
00331             while (1) {
00332                 ProcessInfoRec info;
00333                 info.processInfoLength = sizeof(ProcessInfoRec);
00334                 info.processName = NULL;
00335                 info.processAppSpec = NULL;
00336 
00337                 err = GetProcessInformation(&launchPB.launchProcessSN, &info);
00338 
00339                 if (err != noErr) {
00340                     // The process is no longer in the process
00341                     // manager's internal list, assume the process is
00342                     // done.
00343                     err = noErr;
00344 
00345                     break;
00346                 }
00347 
00348                 // still running so sleep some more (200 msecs)
00349                 PR_Sleep(200);
00350             }
00351         }
00352 
00353         if (err != noErr) {
00354             status = PR_FAILURE;
00355         }
00356 
00357         if (blocking) {
00358             mExitValue = err;
00359         }
00360     } else
00361 #endif
00362     {
00363         if (blocking) {
00364             mProcess = PR_CreateProcess(mTargetPath.get(), my_argv, NULL,
00365                                         NULL);
00366             if (mProcess)
00367                 status = PR_WaitProcess(mProcess, &mExitValue);
00368         } else {
00369             status = PR_CreateProcessDetached(mTargetPath.get(), my_argv, NULL,
00370                                           NULL);
00371         }
00372     }
00373 #endif
00374 
00375     // free up our argv
00376     nsMemory::Free(my_argv);
00377 
00378     if (status != PR_SUCCESS)
00379         return NS_ERROR_FILE_EXECUTION_FAILED;
00380 
00381     return NS_OK;
00382 }
00383 
00384 NS_IMETHODIMP nsProcess::InitWithPid(PRUint32 pid)
00385 {
00386     return NS_ERROR_NOT_IMPLEMENTED;
00387 }
00388 
00389 NS_IMETHODIMP
00390 nsProcess::GetLocation(nsIFile** aLocation)
00391 {
00392     return NS_ERROR_NOT_IMPLEMENTED;
00393 }
00394 
00395 NS_IMETHODIMP
00396 nsProcess::GetPid(PRUint32 *aPid)
00397 {
00398     return NS_ERROR_NOT_IMPLEMENTED;
00399 }
00400 
00401 NS_IMETHODIMP
00402 nsProcess::GetProcessName(char** aProcessName)
00403 {
00404     return NS_ERROR_NOT_IMPLEMENTED;
00405 }
00406 
00407 NS_IMETHODIMP
00408 nsProcess::GetProcessSignature(PRUint32 *aProcessSignature)
00409 {
00410     return NS_ERROR_NOT_IMPLEMENTED;
00411 }
00412 
00413 NS_IMETHODIMP
00414 nsProcess::Kill()
00415 {
00416     nsresult rv = NS_OK;
00417     if (mProcess)
00418         rv = PR_KillProcess(mProcess) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
00419     
00420     return rv;
00421 }
00422 
00423 NS_IMETHODIMP
00424 nsProcess::GetExitValue(PRInt32 *aExitValue)
00425 {
00426     *aExitValue = mExitValue;
00427     
00428     return NS_OK;
00429 }