1
/* ***** BEGIN LICENSE BLOCK *****
2
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
4
* The contents of this file are subject to the Mozilla Public
5
* License Version 1.1 (the "MPL"); you may not use this file
6
* except in compliance with the MPL. You may obtain a copy of
7
* the MPL at http://www.mozilla.org/MPL/
9
* Software distributed under the MPL is distributed on an "AS
10
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11
* implied. See the MPL for the specific language governing
12
* rights and limitations under the MPL.
14
* The Original Code is IPC-Pipe.
16
* The Initial Developer of this code is Patrick Brunschwig.
17
* Portions created by Patrick Brunschwig <patrick@mozilla-enigmail.org>
18
* are Copyright (C) 2010 Patrick Brunschwig.
19
* All Rights Reserved.
23
* Alternatively, the contents of this file may be used under the terms of
24
* either the GNU General Public License Version 2 or later (the "GPL"), or
25
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26
* in which case the provisions of the GPL or the LGPL are applicable instead
27
* of those above. If you wish to allow use of your version of this file only
28
* under the terms of either the GPL or the LGPL, and not to allow others to
29
* use your version of this file under the terms of the MPL, indicate your
30
* decision by deleting the provisions above and replace them with the notice
31
* and other provisions required by the GPL or the LGPL. If you do not delete
32
* the provisions above, a recipient may use your version of this file under
33
* the terms of any one of the MPL, the GPL or the LGPL.
34
* ***** END LICENSE BLOCK ***** */
38
* Import into a JS component using
39
* 'Components.utils.import("resource://gre/modules/subprocess.jsm");'
41
* This object allows to start a process, and read/write data to/from it
42
* using stdin/stdout/stderr streams.
45
* var p = subprocess.call({
46
* command: '/bin/foo',
47
* arguments: ['-v', 'foo'],
48
* environment: [ "XYZ=abc", "MYVAR=def" ],
49
* workdir: '/home/foo',
50
* stdin: subprocess.WritablePipe(function() {
51
* this.write("Writing example data\n");
54
* stdout: subprocess.ReadablePipe(function(data) {
55
* dump("got data on stdout:" +data+"\n");
57
* stderr: subprocess.ReadablePipe(function(data) {
58
* dump("got data on stderr:" +data+"\n");
60
* onFinished: subprocess.Terminate(function() {
61
* dump("process terminated with " +this.exitCode + "\n");
65
* p.wait(); // wait for the subprocess to terminate
68
* Description of parameters:
69
* --------------------------
70
* Apart from <command>, all arguments are optional.
72
* command: either a |nsIFile| object pointing to an executable file or a
73
* String containing the platform-dependent path to an executable
76
* arguments: optional string array containing the arguments to the command.
78
* environment: optional string array containing environment variables to pass
79
* to the command. The array elements must have the form
80
* "VAR=data". Please note that if environment is defined, it
81
* replaces any existing environment variables for the subprocess.
83
* workdir: optional; either a |nsIFile| object pointing to a directory or a
84
* String containing the platform-dependent path to a directory to
85
* become the current working directory of the subprocess.
87
* stdin: optional input data for the process to be passed on standard
88
* input. stdin can either be a string or a function. If stdin is a
89
* string, then the string content is passed to the process. If
90
* stdin is a function defined using subprocess.WritablePipe, input
91
* data can be written synchronously to the process using
93
* The stream to the subprocess can be closed with this.close().
95
* stdout: an optional function that can receive output data from the
96
* process. The stdout-function is called asynchronously; it can be
97
* called mutliple times during the execution of a process. Please
98
* note that null-characters might need to be escaped with
99
* something like 'data.replace(/\0/g, "\\0");'.
100
* stdout needs to be defined using subprocess.ReadablePipe.
102
* stderr: an optional function that can receive output sent to stderr. The
103
* function is only called synchronously when the process has
104
* terminated. Again, null-characters might need to be escaped.
105
* stderr needs to be defined using subprocess.ReadablePipe.
107
* onFinished: optional function that is called when the process has terminated.
108
* The exit code from the process available via this.exitCode. If
109
* stdout is not defined, then the output from stdout is available
110
* via this.stdoutData. onFinished needs to be defined using
111
* subprocess.Terminate.
113
* mergeStderr: optional boolean value. If true, stderr is merged with stdout;
114
* no data will be provided to stderr.
117
* Description of object returned by subprocess.call(...)
118
* ------------------------------------------------------
119
* The object returned by subprocess.call offers a few methods that can be
122
* wait(): waits for the subprocess to terminate. It is not required to use
123
* wait; onFinshed and stderr will be called in any case when the
124
* subprocess terminated.
126
* kill(): kill the subprocess. Any open pipes will be closed and
127
* onFinished will be called.
133
* Be careful if you create more than one subprocess in parallel. Because
134
* p.wait() is blocking the termination of other processes, you cannot wait on
135
* the same thread for more than one subprocess to terminate, unless you know
136
* the sequence in which the subprocesses finish. Therefore it is safer to
137
* create new threads if you need to execute several subprocesses at the same
143
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
145
var EXPORTED_SYMBOLS = [ "subprocess" ];
147
const NS_PIPETRANSPORT_CONTRACTID = "@mozilla.org/ipc/pipe-transport;1";
148
const NS_IPCBUFFER_CONTRACTID = "@mozilla.org/ipc/ipc-buffer;1";
149
const Cc = Components.classes;
150
const Ci = Components.interfaces;
155
* Constructor method to create a subprocess.
157
* @param commandObj object defining the input parameters. See documentation
160
call: function (commandObj) {
161
var pipeObj = new PipeObj();
162
if (! pipeObj) return null;
163
pipeObj.init(commandObj);
167
* Create a pipe that writes data to the subprocess
169
* @param func function definition that implements writing to the pipe
170
* using "this.write(txt)".
172
WritablePipe: function(func) {
173
var pipeWriterObj = {
174
_pipeTransport: null,
175
write: function(data) {
176
this._pipeTransport.writeSync(data, data.length);
179
this._pipeTransport.closeStdin();
183
return pipeWriterObj;
186
* Create a pipe that read data from the subprocess (stdout or stderr)
188
* @param func function definition that implements reading from the pipe.
189
* The 1st parameter holds the data read from the subprocess.
191
ReadablePipe: function(func) {
192
var pipeReaderObj = {
193
callbackFunction: func,
194
onDataAvailable: function(data) {
195
this.callbackFunction(data);
198
return pipeReaderObj;
201
* Create a function that listens to the termination of the subprocess.
203
* @param func function definition that implements the listener.
205
Terminate: function(func) {
206
var onFinishedObj = {
208
callbackFunction: func,
209
callback: function (exitCode) {
210
this.exitCode = exitCode;
211
this.callbackFunction();
214
return onFinishedObj;
220
* Stream Listener object for handling callbacks from the subprocess' stdout
222
function StdoutStreamListener(cmdObj)
224
this._cmdObj = cmdObj;
225
this._reqObserver = null;
228
StdoutStreamListener.prototype = {
229
QueryInterface: XPCOMUtils.generateQI(
230
[ Ci.nsIRequestObserver, Ci.nsIStreamListener ]),
233
observe: function (aReqObserver, aContext) {
234
this._reqObserver = aReqObserver;
237
// nsIRequestObserver
238
onStartRequest: function(aRequest, aContext) {
239
if (this._reqObserver)
240
this._reqObserver.onStartRequest(aRequest, aContext);
243
// nsIRequestObserver
244
onStopRequest: function(aRequest, aContext, aStatusCode) {
245
// call to stderr and onFinished from here to avoid mandatory use of
247
if (this._reqObserver)
248
this._reqObserver.onStopRequest(aRequest, aContext, aStatusCode);
250
// unset assigned variables to avoid memory leak
251
this._reqObserver=null;
256
onDataAvailable: function(aRequest, aContext, aInputStream, offset, count) {
258
let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
259
Ci.nsIScriptableInputStream);
260
sis.init(aInputStream);
261
if ("readBytes" in sis) {
262
// Gecko > 2.0b4, supports NULL characters
263
this._cmdObj.stdout.onDataAvailable(sis.readBytes(count));
267
this._cmdObj.stdout.onDataAvailable(sis.read(count));
269
// unset variable to avoid memory leak
276
* Listener for handling subprocess termination
278
function OnFinishedListener(pipeObj)
280
this._pipeObj = pipeObj;
283
OnFinishedListener.prototype = {
284
QueryInterface: XPCOMUtils.generateQI([ Ci.nsIRequestObserver ]),
286
// nsIRequestObserver
287
onStartRequest: function(aRequest, aContext) {
291
// nsIRequestObserver
292
onStopRequest: function(aRequest, aContext, aStatusCode) {
294
// call to stderr and onFinished from here to avoid mandatory use of
297
let cmdObj = this._pipeObj._cmdObj;
299
if (typeof(cmdObj.stderr) == "object" && (! cmdObj.mergeStderr)) {
300
cmdObj.stderr.onDataAvailable( this._pipeObj.stderrData.getData());
301
this._pipeObj.stderrData.shutdown();
304
if (typeof(cmdObj.onFinished) == "object") {
305
if (cmdObj.stdout == null) {
306
cmdObj.onFinished.stdoutData =
307
this._pipeObj.stdoutListener.getData();
308
this._pipeObj.stdoutListener.shutdown();
311
cmdObj.onFinished.callback(this._pipeObj._pipeTransport.exitValue);
314
// unset assigned variables to avoid memory leak
315
this._pipeObj = null;
320
* Class to represent a running process. This class that is returned
321
* from subprocess.call(...)
326
PipeObj.prototype = {
330
* Initializer method.
332
* @param cmdObj Object as defined for subprocess.call()
334
init: function(cmdObj) {
335
this._cmdObj = cmdObj;
337
// create & open pipeListener for stderr, no matter if needed or not
338
this.stderrData = Cc[NS_IPCBUFFER_CONTRACTID].createInstance(
340
this.stderrData.open(-1, true);
343
if (typeof (cmdObj.command) == "string") {
344
let localfile = Cc["@mozilla.org/file/local;1"].createInstance(
346
localfile.initWithPath(cmdObj.command);
347
cmdObj._commandFile = localfile.QueryInterface(Ci.nsIFile);
350
cmdObj._commandFile = cmdObj.command;
352
if (typeof (cmdObj.arguments) != "object") cmdObj.arguments = [];
353
if (typeof (cmdObj.environment) != "object") cmdObj.environment = [];
354
if (typeof (cmdObj.workdir) == "string") {
355
let localfile= Cc["@mozilla.org/file/local;1"].createInstance(
357
localfile.initWithPath(cmdObj.workdir);
358
cmdObj._cwd = localfile.QueryInterface(Ci.nsIFile);
360
else if (typeof (cmdObj.workdir) == "object") {
361
cmdObj._cwd = cmdObj.workdir;
367
this._pipeTransport = Cc[NS_PIPETRANSPORT_CONTRACTID].createInstance(
368
Ci.nsIPipeTransport);
369
this._pipeTransport.initWithWorkDir(cmdObj._commandFile, cmdObj._cwd,
370
Ci.nsIPipeTransport.INHERIT_PROC_ATTRIBS);
372
this.stdoutListener = null;
373
if (typeof(cmdObj.stdout) == "object") {
374
// add listener for asynchronous processing of data
375
this.stdoutListener = new StdoutStreamListener(cmdObj);
378
this.stdoutListener = Cc[NS_IPCBUFFER_CONTRACTID].createInstance(
380
this.stdoutListener.open(-1, true);
383
if (typeof(cmdObj.stderr) == "object" ||
384
typeof(cmdObj.onFinished) == "object")
385
this.stdoutListener.observe(new OnFinishedListener(this), null);
387
this._pipeTransport.openPipe(cmdObj.arguments, cmdObj.arguments.length,
388
cmdObj.environment, cmdObj.environment.length,
389
0, "", true, cmdObj.mergeStderr ? true : false,
392
this._pipeTransport.asyncRead(this.stdoutListener, null, 0, -1, 0);
394
if (typeof(cmdObj.stdin) == "string") {
395
this._pipeTransport.writeSync(cmdObj.stdin, cmdObj.stdin.length);
396
this._pipeTransport.closeStdin();
398
else if (typeof(cmdObj.stdin) == "object") {
399
cmdObj.stdin._pipeTransport = this._pipeTransport;
400
cmdObj.stdin.startWriting();
405
* Wait for the subprocess to complete. This method is blocking.
408
this._pipeTransport.join();
412
* Kill the subprocess
416
this._pipeTransport.kill();