Improve nsIProcess, fix nsIProcess.kill().
b=442393 r=bsmedberg
--- a/browser/components/shell/src/nsGNOMEShellService.cpp
+++ b/browser/components/shell/src/nsGNOMEShellService.cpp
@@ -582,17 +582,16 @@ nsGNOMEShellService::OpenApplicationWith
return rv;
rv = process->Init(aApplication);
if (NS_FAILED(rv))
return rv;
const nsCString spec(aURI);
const char* specStr = spec.get();
- PRUint32 pid;
- return process->Run(PR_FALSE, &specStr, 1, &pid);
+ return process->Run(PR_FALSE, &specStr, 1);
}
NS_IMETHODIMP
nsGNOMEShellService::GetDefaultFeedReader(nsILocalFile** _retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
--- a/uriloader/exthandler/nsLocalHandlerApp.cpp
+++ b/uriloader/exthandler/nsLocalHandlerApp.cpp
@@ -111,18 +111,17 @@ nsLocalHandlerApp::LaunchWithIProcess(co
if (NS_FAILED(rv))
return rv;
if (NS_FAILED(rv = process->Init(mExecutable)))
return rv;
const char *string = aArg.get();
- PRUint32 pid;
- return process->Run(PR_FALSE, &string, 1, &pid);
+ return process->Run(PR_FALSE, &string, 1);
}
////////////////////////////////////////////////////////////////////////////////
//// nsILocalHandlerApp
NS_IMETHODIMP nsLocalHandlerApp::GetExecutable(nsIFile **aExecutable)
{
NS_IF_ADDREF(*aExecutable = mExecutable);
--- a/uriloader/exthandler/nsMIMEInfoImpl.cpp
+++ b/uriloader/exthandler/nsMIMEInfoImpl.cpp
@@ -429,18 +429,17 @@ nsMIMEInfoBase::LaunchWithIProcess(nsIFi
if (NS_FAILED(rv))
return rv;
if (NS_FAILED(rv = process->Init(aApp)))
return rv;
const char *string = aArg.get();
- PRUint32 pid;
- return process->Run(PR_FALSE, &string, 1, &pid);
+ return process->Run(PR_FALSE, &string, 1);
}
// nsMIMEInfoImpl implementation
NS_IMETHODIMP
nsMIMEInfoImpl::GetDefaultDescription(nsAString& aDefaultDescription)
{
if (mDefaultAppDescription.IsEmpty() && mDefaultApplication) {
// Don't want to cache this, just in case someone resets the app
--- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
@@ -1151,18 +1151,17 @@ nsOSHelperAppService::GetHandlerAndDescr
rv = file->InitWithNativePath(NS_LITERAL_CSTRING("/bin/sh"));
if (NS_FAILED(rv))
continue;
rv = process->Init(file);
if (NS_FAILED(rv))
continue;
const char *args[] = { "-c", testCommand.get() };
LOG(("Running Test: %s\n", testCommand.get()));
- PRUint32 pid;
- rv = process->Run(PR_TRUE, args, 2, &pid);
+ rv = process->Run(PR_TRUE, args, 2);
if (NS_FAILED(rv))
continue;
PRInt32 exitValue;
rv = process->GetExitValue(&exitValue);
if (NS_FAILED(rv))
continue;
LOG(("Exit code: %d\n", exitValue));
if (exitValue) {
--- a/xpcom/tests/Makefile.in
+++ b/xpcom/tests/Makefile.in
@@ -67,16 +67,19 @@ CPPSRCS = \
TestCallTemplates.cpp \
TestINIParser.cpp \
TestVersionComparator.cpp \
TestRacingServiceManager.cpp \
TestRegistrationOrder.cpp \
TestThreadPoolListener.cpp \
TestTimers.cpp \
TestOOM.cpp \
+ TestBlockingProcess.cpp \
+ TestQuickReturn.cpp \
+ TestArguments.cpp \
$(NULL)
ifndef MOZ_ENABLE_LIBXUL
CPPSRCS += \
TestAtoms.cpp \
TestPermanentAtoms.cpp \
$(NULL)
endif
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestArguments.cpp
@@ -0,0 +1,14 @@
+#include <iostream>
+#include <string>
+
+using namespace std;
+
+int main(int argc, char* argv[]) {
+
+ string test = "mozilla";
+
+ if (test.compare(argv[1]) != 0)
+ return -1;
+
+ return 0;
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestBlockingProcess.cpp
@@ -0,0 +1,9 @@
+#include <stdio.h>
+
+int main()
+{
+ char text[20];
+ fgets(text, sizeof text, stdin);
+ return 0;
+}
+
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestQuickReturn.cpp
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main () {
+
+ return 42;
+}
--- a/xpcom/tests/unit/test_bug476919.js
+++ b/xpcom/tests/unit/test_bug476919.js
@@ -17,13 +17,12 @@ function run_test() {
if (link.exists())
link.remove(false);
var ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
ln.initWithPath("/bin/ln");
var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(ln);
var args = ["-s", targetFile.path, link.path];
- var result = process.run(true, args, args.length);
- do_check_eq(result, 0);
+ process.run(true, args, args.length);
do_check_true(link.isSymlink());
}
}
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/unit/test_nsIProcess.js
@@ -0,0 +1,142 @@
+/* ***** 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 XPCOM unit tests.
+ *
+ * The Initial Developer of the Original Code is
+ * James Boston <mozilla@jamesboston.ca>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either 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 ***** */
+// nsIProcess unit test
+
+// get the path to {objdir}/dist/bin
+var bindir = Components.classes["@mozilla.org/file/directory_service;1"]
+.getService(Components.interfaces.nsIProperties)
+.get("CurProcD", Components.interfaces.nsIFile);
+
+// the the os
+var isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes);
+
+var filePrefix = "";
+var fileSuffix = "";
+
+if (isWindows) {
+ filePrefix = bindir.path + "\\";
+ fileSuffix = ".exe";
+} else {
+ filePrefix = bindir.path + "/";
+}
+
+
+// test if a process can be started, polled for its running status
+// and then killed
+function test_kill()
+{
+ var testapp = filePrefix + "TestBlockingProcess" +fileSuffix;
+ print(testapp);
+
+ var file = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsILocalFile);
+ file.initWithPath(testapp);
+
+ var process = Components.classes["@mozilla.org/process/util;1"]
+ .createInstance(Components.interfaces.nsIProcess);
+ process.init(file);
+
+ process.run(false, [], 0);
+
+ var rv = process.isRunning;
+
+ if (!rv) {
+ return false;
+ }
+
+ process.kill();
+
+ rv = process.isRunning;
+
+ if (rv){
+ return false;
+ }
+ return true;
+
+}
+
+// test if we can get an exit value from an application that is
+// guaranteed to return an exit value of 42
+function test_quick()
+{
+ var testapp = filePrefix + "TestQuickReturn" + fileSuffix;
+
+ var file = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsILocalFile);
+ file.initWithPath(testapp);
+
+ var process = Components.classes["@mozilla.org/process/util;1"]
+ .createInstance(Components.interfaces.nsIProcess);
+ process.init(file);
+
+ // to get an exit value it must be a blocking process
+ process.run(true, [], 0);
+
+ if (process.exitValue != 42) {
+ return false;
+ }
+ return true;
+}
+
+// test if an argument can be successfully passed to an application
+// that will return -1 if "mozilla" is not the first argument
+function test_arguments()
+{
+ var testapp = filePrefix + "TestArguments" + fileSuffix;
+
+ var file = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsILocalFile);
+ file.initWithPath(testapp);
+
+ var process = Components.classes["@mozilla.org/process/util;1"]
+ .createInstance(Components.interfaces.nsIProcess);
+ process.init(file);
+
+ var args= ["mozilla"];
+
+ process.run(true, args, args.length);
+
+ if (process.exitValue) {
+ return false;
+ }
+ return true;
+}
+
+function run_test() {
+ do_check_true(test_kill());
+ do_check_true(test_quick());
+ do_check_true(test_arguments());
+}
--- a/xpcom/threads/nsIProcess.idl
+++ b/xpcom/threads/nsIProcess.idl
@@ -1,32 +1,32 @@
#include "nsIFile.idl"
#include "nsISupports.idl"
-[scriptable, uuid(9da0b650-d07e-4617-a18a-250035572ac8)]
+[scriptable, uuid(d573f1f3-fcdd-4dbe-980b-4ba79e6718dc)]
interface nsIProcess : nsISupports
{
void init(in nsIFile executable);
void initWithPid(in unsigned long pid);
void kill();
/** XXX what charset? **/
/** Executes the file this object was initialized with
* @param blocking Whether to wait until the process terminates before returning or not
* @param args An array of arguments to pass to the process
- * @param count The length of the args array
- * @return the PID of the newly spawned process */
- unsigned long run(in boolean blocking, [array, size_is(count)] in string args, in unsigned long count);
+ * @param count The length of the args array */
+ void run(in boolean blocking, [array, size_is(count)] in string args, in unsigned long count);
readonly attribute nsIFile location;
readonly attribute unsigned long pid;
readonly attribute string processName;
readonly attribute unsigned long processSignature;
readonly attribute long exitValue;
+ readonly attribute unsigned long isRunning;
};
%{C++
#define NS_PROCESS_CONTRACTID "@mozilla.org/process/util;1"
#define NS_PROCESS_CLASSNAME "Process Specification"
%}
--- a/xpcom/threads/nsProcess.h
+++ b/xpcom/threads/nsProcess.h
@@ -35,37 +35,47 @@
* 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 ***** */
#ifndef _nsPROCESSWIN_H_
#define _nsPROCESSWIN_H_
+#if defined(XP_WIN) && !defined (WINCE) /* wince uses nspr */
+#define PROCESSMODEL_WINAPI
+#endif
+
#include "nsIProcess.h"
#include "nsIFile.h"
#include "nsString.h"
#include "prproces.h"
+#if defined(PROCESSMODEL_WINAPI)
+#include <windows.h>
+#endif
#define NS_PROCESS_CID \
{0x7b4eeb20, 0xd781, 0x11d4, \
{0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca}}
class nsProcess : public nsIProcess
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPROCESS
nsProcess();
private:
- ~nsProcess() {}
+ ~nsProcess();
nsCOMPtr<nsIFile> mExecutable;
PRInt32 mExitValue;
nsCString mTargetPath;
PRProcess *mProcess;
+#if defined(PROCESSMODEL_WINAPI)
+ PROCESS_INFORMATION procInfo;
+#endif
};
#endif
--- a/xpcom/threads/nsProcessCommon.cpp
+++ b/xpcom/threads/nsProcessCommon.cpp
@@ -56,38 +56,62 @@
#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"
+#else
+#include <sys/types.h>
+#include <signal.h>
#endif
//-------------------------------------------------------------------//
// nsIProcess implementation
//-------------------------------------------------------------------//
NS_IMPL_ISUPPORTS1(nsProcess, nsIProcess)
//Constructor
nsProcess::nsProcess()
: mExitValue(-1),
mProcess(nsnull)
{
+#if defined(PROCESSMODEL_WINAPI)
+ procInfo.dwProcessId = nsnull;
+#endif
+}
+
+//Destructor
+nsProcess::~nsProcess()
+{
+#if defined(PROCESSMODEL_WINAPI)
+ if (procInfo.dwProcessId) {
+ CloseHandle( procInfo.hProcess );
+ CloseHandle( procInfo.hThread );
+ }
+#else
+ if (mProcess)
+ PR_DetachProcess(mProcess);
+#endif
}
NS_IMETHODIMP
nsProcess::Init(nsIFile* executable)
{
+ //Prevent re-initializing if already attached to process
+#if defined(PROCESSMODEL_WINAPI)
+ if (procInfo.dwProcessId)
+ return NS_ERROR_ALREADY_INITIALIZED;
+#else
+ if (mProcess)
+ return NS_ERROR_ALREADY_INITIALIZED;
+#endif
+
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;
@@ -211,18 +235,17 @@ static int assembleCmdLine(char *const *
MultiByteToWideChar(CP_ACP, 0, cmdLine, -1, *wideCmdLine, numChars);
PR_Free(cmdLine);
return 0;
}
#endif
// XXXldb |args| has the wrong const-ness
NS_IMETHODIMP
-nsProcess::Run(PRBool blocking, const char **args, PRUint32 count,
- PRUint32 *pid)
+nsProcess::Run(PRBool blocking, const char **args, PRUint32 count)
{
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;
@@ -236,19 +259,18 @@ nsProcess::Run(PRBool blocking, const ch
for (i=0; i < count; i++) {
my_argv[i+1] = 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 */
+#if defined(PROCESSMODEL_WINAPI)
STARTUPINFOW startupInfo;
- PROCESS_INFORMATION procInfo;
BOOL retVal;
PRUnichar *cmdLine;
if (assembleCmdLine(my_argv, &cmdLine) == -1) {
nsMemory::Free(my_argv);
return NS_ERROR_FILE_EXECUTION_FAILED;
}
@@ -294,125 +316,107 @@ nsProcess::Run(PRBool blocking, const ch
if (GetExitCodeProcess(procInfo.hProcess, &exitCode) == FALSE) {
mExitValue = exitCode;
nsMemory::Free(my_argv);
return NS_ERROR_FAILURE;
}
mExitValue = exitCode;
CloseHandle(procInfo.hProcess);
CloseHandle(procInfo.hThread);
+ procInfo.dwProcessId = nsnull;
}
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;
- }
-
+
+ mProcess = PR_CreateProcess(mTargetPath.get(), my_argv, NULL, NULL);
+ if (mProcess) {
+ status = PR_SUCCESS;
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);
- }
+ status = PR_WaitProcess(mProcess, &mExitValue);
+ mProcess = nsnull;
+ }
}
#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::GetIsRunning(PRUint32 *aIsRunning)
+{
+#if defined(PROCESSMODEL_WINAPI)
+ DWORD ec = 0;
+ BOOL br = GetExitCodeProcess(procInfo.hProcess, &ec);
+ if (!br) {
+ aIsRunning = 0;
+ return NS_OK;
+ }
+ *aIsRunning = (ec == STILL_ACTIVE ? 1 : 0);
+ return NS_OK;
+#elif defined WINCE
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ PRUint32 pid;
+ GetPid(&pid);
+ if (pid)
+ *aIsRunning = (kill(pid, 0) != -1) ? 1 : 0;
+ return NS_OK;
+#endif
+}
+
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)
{
+#if defined(PROCESSMODEL_WINAPI)
+ if (!procInfo.dwProcessId)
+ return NS_ERROR_FAILURE;
+ *aPid = procInfo.dwProcessId;
+ return NS_OK;
+#elif defined WINCE
return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ if (!mProcess) {
+ *aPid = 0;
+ return NS_ERROR_FAILURE;
+ }
+ struct MYProcess {
+ PRUint32 pid;
+ };
+ MYProcess* ptrProc = (MYProcess *) mProcess;
+ *aPid = ptrProc->pid;
+ return NS_OK;
+#endif
}
NS_IMETHODIMP
nsProcess::GetProcessName(char** aProcessName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
@@ -421,19 +425,30 @@ nsProcess::GetProcessSignature(PRUint32
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsProcess::Kill()
{
nsresult rv = NS_OK;
+#if defined(PROCESSMODEL_WINAPI)
+ if ( TerminateProcess(procInfo.hProcess, NULL) == 0 ) {
+ rv = NS_ERROR_FAILURE;
+ }
+ else {
+ CloseHandle( procInfo.hProcess );
+ procInfo.dwProcessId = nsnull;
+ }
+#else
if (mProcess)
rv = PR_KillProcess(mProcess) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
-
+ if (rv == NS_OK)
+ mProcess = nsnull;
+#endif
return rv;
}
NS_IMETHODIMP
nsProcess::GetExitValue(PRInt32 *aExitValue)
{
*aExitValue = mExitValue;