mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-28 11:28:38 +00:00
430 lines
12 KiB
C++
430 lines
12 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code, released
|
|
* March 31, 1998.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998-1999
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Don Bragg <dbragg@netscape.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* nsProcess is used to execute new processes and specify if you want to
|
|
* wait (blocking) or continue (non-blocking).
|
|
*
|
|
*****************************************************************************
|
|
*/
|
|
|
|
#include "nsCOMPtr.h"
|
|
#include "nsMemory.h"
|
|
#include "nsProcess.h"
|
|
#include "prtypes.h"
|
|
#include "prio.h"
|
|
#include "prenv.h"
|
|
#include "nsCRT.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#if defined(XP_WIN)
|
|
#include "prmem.h"
|
|
#include "nsString.h"
|
|
#include "nsLiteralString.h"
|
|
#include "nsReadableUtils.h"
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#if defined(XP_MACOSX)
|
|
#include <Processes.h>
|
|
#include "nsILocalFileMac.h"
|
|
#endif
|
|
|
|
//-------------------------------------------------------------------//
|
|
// nsIProcess implementation
|
|
//-------------------------------------------------------------------//
|
|
NS_IMPL_ISUPPORTS1(nsProcess, nsIProcess)
|
|
|
|
//Constructor
|
|
nsProcess::nsProcess()
|
|
: mExitValue(-1),
|
|
mProcess(nsnull)
|
|
{
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsProcess::Init(nsIFile* executable)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(executable);
|
|
PRBool isFile;
|
|
|
|
//First make sure the file exists
|
|
nsresult rv = executable->IsFile(&isFile);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!isFile)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
//Store the nsIFile in mExecutable
|
|
mExecutable = executable;
|
|
//Get the path because it is needed by the NSPR process creation
|
|
#ifdef XP_WIN
|
|
rv = mExecutable->GetNativeTarget(mTargetPath);
|
|
if (NS_FAILED(rv) || mTargetPath.IsEmpty() )
|
|
#endif
|
|
rv = mExecutable->GetNativePath(mTargetPath);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
#if defined(XP_WIN)
|
|
static int assembleCmdLine(char *const *argv, char **cmdLine)
|
|
{
|
|
char *const *arg;
|
|
char *p, *q;
|
|
int cmdLineSize;
|
|
int numBackslashes;
|
|
int i;
|
|
int argNeedQuotes;
|
|
|
|
/*
|
|
* Find out how large the command line buffer should be.
|
|
*/
|
|
cmdLineSize = 0;
|
|
for (arg = argv; *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);
|
|
if (p == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
for (arg = argv; *arg; arg++) {
|
|
/* Add a space to separates the arguments */
|
|
if (arg != argv) {
|
|
*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';
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
// XXXldb |args| has the wrong const-ness
|
|
NS_IMETHODIMP
|
|
nsProcess::Run(PRBool blocking, const char **args, PRUint32 count,
|
|
PRUint32 *pid)
|
|
{
|
|
NS_ENSURE_TRUE(mExecutable, NS_ERROR_NOT_INITIALIZED);
|
|
PRStatus status = PR_SUCCESS;
|
|
|
|
// make sure that when we allocate we have 1 greater than the
|
|
// count since we need to null terminate the list for the argv to
|
|
// pass into PR_CreateProcess
|
|
char **my_argv = NULL;
|
|
my_argv = (char **)nsMemory::Alloc(sizeof(char *) * (count + 2) );
|
|
if (!my_argv) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// copy the args
|
|
PRUint32 i;
|
|
for (i=0; i < count; i++) {
|
|
my_argv[i+1] = NS_CONST_CAST(char*, args[i]);
|
|
}
|
|
// we need to set argv[0] to the program name.
|
|
my_argv[0] = mTargetPath.BeginWriting();
|
|
// null terminate the array
|
|
my_argv[count+1] = NULL;
|
|
|
|
#if defined(XP_WIN) && !defined (WINCE) /* wince uses nspr */
|
|
STARTUPINFO startupInfo;
|
|
PROCESS_INFORMATION procInfo;
|
|
BOOL retVal;
|
|
char *cmdLine;
|
|
|
|
if (assembleCmdLine(my_argv, &cmdLine) == -1) {
|
|
nsMemory::Free(my_argv);
|
|
return NS_ERROR_FILE_EXECUTION_FAILED;
|
|
}
|
|
|
|
ZeroMemory(&startupInfo, sizeof(startupInfo));
|
|
startupInfo.cb = sizeof(startupInfo);
|
|
|
|
retVal = CreateProcess(NULL,
|
|
// NS_CONST_CAST(char*, mTargetPath.get()),
|
|
cmdLine,
|
|
NULL, /* security attributes for the new
|
|
* process */
|
|
NULL, /* security attributes for the primary
|
|
* thread in the new process */
|
|
FALSE, /* inherit handles */
|
|
0, /* creation flags */
|
|
NULL, /* env */
|
|
NULL, /* current drive and directory */
|
|
&startupInfo,
|
|
&procInfo
|
|
);
|
|
PR_Free( cmdLine );
|
|
if (blocking) {
|
|
|
|
// if success, wait for process termination. the early returns and such
|
|
// are a bit ugly but preserving the logic of the nspr code I copied to
|
|
// minimize our risk abit.
|
|
|
|
if ( retVal == TRUE ) {
|
|
DWORD dwRetVal;
|
|
unsigned long exitCode;
|
|
|
|
dwRetVal = WaitForSingleObject(procInfo.hProcess, INFINITE);
|
|
if (dwRetVal == WAIT_FAILED) {
|
|
nsMemory::Free(my_argv);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (GetExitCodeProcess(procInfo.hProcess, &exitCode) == FALSE) {
|
|
mExitValue = exitCode;
|
|
nsMemory::Free(my_argv);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mExitValue = exitCode;
|
|
CloseHandle(procInfo.hProcess);
|
|
}
|
|
else
|
|
status = PR_FAILURE;
|
|
}
|
|
else {
|
|
|
|
// map return value into success code
|
|
|
|
if (retVal == TRUE)
|
|
status = PR_SUCCESS;
|
|
else
|
|
status = PR_FAILURE;
|
|
}
|
|
|
|
#else // Note, this must not be an #elif ...!
|
|
|
|
#if defined(XP_MACOSX)
|
|
if (count == 0) {
|
|
FSSpec resolvedSpec;
|
|
OSErr err = noErr;
|
|
|
|
nsCOMPtr<nsILocalFileMac> macExecutable =
|
|
do_QueryInterface(mExecutable);
|
|
macExecutable->GetFSSpec(&resolvedSpec);
|
|
|
|
LaunchParamBlockRec launchPB;
|
|
launchPB.launchAppSpec = &resolvedSpec;
|
|
launchPB.launchAppParameters = NULL;
|
|
launchPB.launchBlockID = extendedBlock;
|
|
launchPB.launchEPBLength = extendedBlockLen;
|
|
launchPB.launchFileFlags = NULL;
|
|
launchPB.launchControlFlags =
|
|
launchContinue + launchNoFileFlags + launchUseMinimum;
|
|
if (!blocking)
|
|
launchPB.launchControlFlags += launchDontSwitch;
|
|
|
|
err = LaunchApplication(&launchPB);
|
|
|
|
// NOTE: blocking mode assumes you are running on a thread
|
|
// other than the UI thread that has the main event loop
|
|
if (blocking && err == noErr) {
|
|
while (1) {
|
|
ProcessInfoRec info;
|
|
info.processInfoLength = sizeof(ProcessInfoRec);
|
|
info.processName = NULL;
|
|
info.processAppSpec = NULL;
|
|
|
|
err = GetProcessInformation(&launchPB.launchProcessSN, &info);
|
|
|
|
if (err != noErr) {
|
|
// The process is no longer in the process
|
|
// manager's internal list, assume the process is
|
|
// done.
|
|
err = noErr;
|
|
|
|
break;
|
|
}
|
|
|
|
// still running so sleep some more (200 msecs)
|
|
PR_Sleep(200);
|
|
}
|
|
}
|
|
|
|
if (err != noErr) {
|
|
status = PR_FAILURE;
|
|
}
|
|
|
|
if (blocking) {
|
|
mExitValue = err;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
if (blocking) {
|
|
mProcess = PR_CreateProcess(mTargetPath.get(), my_argv, NULL,
|
|
NULL);
|
|
if (mProcess)
|
|
status = PR_WaitProcess(mProcess, &mExitValue);
|
|
} else {
|
|
status = PR_CreateProcessDetached(mTargetPath.get(), my_argv, NULL,
|
|
NULL);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// free up our argv
|
|
nsMemory::Free(my_argv);
|
|
|
|
if (status != PR_SUCCESS)
|
|
return NS_ERROR_FILE_EXECUTION_FAILED;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsProcess::InitWithPid(PRUint32 pid)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsProcess::GetLocation(nsIFile** aLocation)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsProcess::GetPid(PRUint32 *aPid)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsProcess::GetProcessName(char** aProcessName)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsProcess::GetProcessSignature(PRUint32 *aProcessSignature)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsProcess::Kill()
|
|
{
|
|
nsresult rv = NS_OK;
|
|
if (mProcess)
|
|
rv = PR_KillProcess(mProcess) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsProcess::GetExitValue(PRInt32 *aExitValue)
|
|
{
|
|
*aExitValue = mExitValue;
|
|
|
|
return NS_OK;
|
|
}
|