~ubuntu-branches/ubuntu/trusty/enigmail/trusty-updates

« back to all changes in this revision

Viewing changes to extensions/enigmail/ipc/modules/subprocess.jsm

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2011-06-07 14:35:53 UTC
  • mfrom: (0.12.1 upstream)
  • Revision ID: package-import@ubuntu.com-20110607143553-fbgqhhvh8g8h6j1y
Tags: 2:1.2~a2~cvs20110606t2200-0ubuntu1
* Update to latest trunk snapshot for Thunderbird beta compat

* Remove build/pgo/profileserver.py from debian/clean. The new build
  system has a target depending on this
  - update debian/clean
* Drop debian/patches/autoconf.diff, just generate this at build time
* Refresh debian/patches/build_system_dont_link_libxul.diff
* libipc seems to be renamed to libipc-pipe. Fix genxpi and chrome.manifest
  to fix this 
  - add debian/patches/ipc-pipe_rename.diff
  - update debian/patches/series
* The makefiles in extensions/enigmail/ipc have an incorrect DEPTH
  attribute. Fix this so that they can find the rest of the build system
  - add debian/patches/makefile_depth.diff
  - update debian/patches/series
* Drop debian/patches/makefile-in-empty-xpcom-fix.diff - fixed in the
  current version
* Don't register a class ID multiple times, as this breaks enigmail entirely
  - add debian/patches/dont_register_cids_multiple_times.diff
  - update debian/patches/series
* Look for the Thunderbird 5 SDK
  - update debian/rules
  - update debian/control
* Run autoconf2.13 at build time
  - update debian/rules
  - update debian/control
* Add useless mesa-common-dev build-dep, just to satisfy the build system.
  We should just patch this out entirely really, but that's for another upload
  - update debian/control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* ***** BEGIN LICENSE BLOCK *****
 
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
3
 *
 
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/
 
8
 *
 
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.
 
13
 *
 
14
 * The Original Code is IPC-Pipe.
 
15
 *
 
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.
 
20
 *
 
21
 * Contributor(s):
 
22
 *
 
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 ***** */
 
35
 
 
36
 
 
37
/*
 
38
 * Import into a JS component using
 
39
 * 'Components.utils.import("resource://gre/modules/subprocess.jsm");'
 
40
 *
 
41
 * This object allows to start a process, and read/write data to/from it
 
42
 * using stdin/stdout/stderr streams.
 
43
 * Usage example:
 
44
 *
 
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");
 
52
 *      this.close();
 
53
 *    }),
 
54
 *    stdout: subprocess.ReadablePipe(function(data) {
 
55
 *      dump("got data on stdout:" +data+"\n");
 
56
 *    }),
 
57
 *    stderr: subprocess.ReadablePipe(function(data) {
 
58
 *      dump("got data on stderr:" +data+"\n");
 
59
 *    }),
 
60
 *    onFinished: subprocess.Terminate(function() {
 
61
 *      dump("process terminated with " +this.exitCode + "\n");
 
62
 *    }),
 
63
 *    mergeStderr: false
 
64
 *  });
 
65
 *  p.wait(); // wait for the subprocess to terminate
 
66
 *
 
67
 *
 
68
 * Description of parameters:
 
69
 * --------------------------
 
70
 * Apart from <command>, all arguments are optional.
 
71
 *
 
72
 * command:     either a |nsIFile| object pointing to an executable file or a
 
73
 *              String containing the platform-dependent path to an executable
 
74
 *              file.
 
75
 *
 
76
 * arguments:   optional string array containing the arguments to the command.
 
77
 *
 
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.
 
82
 *
 
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.
 
86
 *
 
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
 
92
 *              this.write(string).
 
93
 *              The stream to the subprocess can be closed with this.close().
 
94
 *
 
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.
 
101
 *
 
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.
 
106
 *
 
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.
 
112
 *
 
113
 * mergeStderr: optional boolean value. If true, stderr is merged with stdout;
 
114
 *              no data will be provided to stderr.
 
115
 *
 
116
 *
 
117
 * Description of object returned by subprocess.call(...)
 
118
 * ------------------------------------------------------
 
119
 * The object returned by subprocess.call offers a few methods that can be
 
120
 * executed:
 
121
 *
 
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.
 
125
 *
 
126
 * kill():      kill the subprocess. Any open pipes will be closed and
 
127
 *              onFinished will be called.
 
128
 *
 
129
 *
 
130
 * Important notes
 
131
 * ---------------
 
132
 *
 
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
 
138
 * time.
 
139
 *
 
140
 */
 
141
 
 
142
 
 
143
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
144
 
 
145
var EXPORTED_SYMBOLS = [ "subprocess" ];
 
146
 
 
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;
 
151
 
 
152
var subprocess = {
 
153
 
 
154
  /**
 
155
   * Constructor method to create a subprocess.
 
156
   *
 
157
   * @param commandObj  object defining the input parameters. See documentation
 
158
   *                    above for details.
 
159
   */
 
160
  call: function (commandObj) {
 
161
    var pipeObj = new PipeObj();
 
162
    if (! pipeObj) return null;
 
163
    pipeObj.init(commandObj);
 
164
    return pipeObj;
 
165
  },
 
166
  /**
 
167
   * Create a pipe that writes data to the subprocess
 
168
   *
 
169
   * @param func  function definition that implements writing to the pipe
 
170
   *              using "this.write(txt)".
 
171
   */
 
172
  WritablePipe: function(func) {
 
173
    var pipeWriterObj = {
 
174
      _pipeTransport: null,
 
175
      write: function(data) {
 
176
        this._pipeTransport.writeSync(data, data.length);
 
177
      },
 
178
      close: function() {
 
179
        this._pipeTransport.closeStdin();
 
180
      },
 
181
      startWriting: func
 
182
    };
 
183
    return pipeWriterObj;
 
184
  },
 
185
  /**
 
186
   * Create a pipe that read data from the subprocess (stdout or stderr)
 
187
   *
 
188
   * @param func  function definition that implements reading from the pipe.
 
189
   *              The 1st parameter holds the data read from the subprocess.
 
190
   */
 
191
  ReadablePipe: function(func) {
 
192
    var pipeReaderObj = {
 
193
      callbackFunction: func,
 
194
      onDataAvailable: function(data) {
 
195
        this.callbackFunction(data);
 
196
      }
 
197
    }
 
198
    return pipeReaderObj;
 
199
  },
 
200
  /**
 
201
   * Create a function that listens to the termination of the subprocess.
 
202
   *
 
203
   * @param func   function definition that implements the listener.
 
204
   */
 
205
  Terminate: function(func) {
 
206
    var onFinishedObj = {
 
207
      stdoutData: null,
 
208
      callbackFunction: func,
 
209
      callback: function (exitCode) {
 
210
        this.exitCode = exitCode;
 
211
        this.callbackFunction();
 
212
      }
 
213
    };
 
214
    return onFinishedObj;
 
215
  },
 
216
};
 
217
 
 
218
 
 
219
/**
 
220
 * Stream Listener object for handling callbacks from the subprocess' stdout
 
221
 */
 
222
function StdoutStreamListener(cmdObj)
 
223
{
 
224
  this._cmdObj = cmdObj;
 
225
  this._reqObserver = null;
 
226
}
 
227
 
 
228
StdoutStreamListener.prototype = {
 
229
  QueryInterface: XPCOMUtils.generateQI(
 
230
    [ Ci.nsIRequestObserver, Ci.nsIStreamListener ]),
 
231
 
 
232
  // nsIObserver
 
233
  observe: function (aReqObserver, aContext) {
 
234
    this._reqObserver = aReqObserver;
 
235
  },
 
236
 
 
237
  // nsIRequestObserver
 
238
  onStartRequest: function(aRequest, aContext) {
 
239
    if (this._reqObserver)
 
240
      this._reqObserver.onStartRequest(aRequest, aContext);
 
241
  },
 
242
 
 
243
  // nsIRequestObserver
 
244
  onStopRequest: function(aRequest, aContext, aStatusCode) {
 
245
    // call to stderr and onFinished from here to avoid mandatory use of
 
246
    // p.wait()
 
247
    if (this._reqObserver)
 
248
      this._reqObserver.onStopRequest(aRequest, aContext, aStatusCode);
 
249
 
 
250
    // unset assigned variables to avoid memory leak
 
251
    this._reqObserver=null;
 
252
    this._cmdObj=null;
 
253
  },
 
254
 
 
255
  // nsIStreamListener
 
256
  onDataAvailable: function(aRequest, aContext, aInputStream, offset, count) {
 
257
 
 
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));
 
264
    }
 
265
    else
 
266
      // Gecko <= 2.0b4
 
267
      this._cmdObj.stdout.onDataAvailable(sis.read(count));
 
268
 
 
269
    // unset variable to avoid memory leak
 
270
    sis = null;
 
271
  }
 
272
 
 
273
};
 
274
 
 
275
/**
 
276
 * Listener for handling subprocess termination
 
277
 */
 
278
function OnFinishedListener(pipeObj)
 
279
{
 
280
  this._pipeObj = pipeObj;
 
281
}
 
282
 
 
283
OnFinishedListener.prototype = {
 
284
  QueryInterface: XPCOMUtils.generateQI([ Ci.nsIRequestObserver ]),
 
285
 
 
286
  // nsIRequestObserver
 
287
  onStartRequest: function(aRequest, aContext) {
 
288
    // do nothing
 
289
  },
 
290
 
 
291
  // nsIRequestObserver
 
292
  onStopRequest: function(aRequest, aContext, aStatusCode) {
 
293
 
 
294
    // call to stderr and onFinished from here to avoid mandatory use of
 
295
    // p.wait()
 
296
 
 
297
    let cmdObj = this._pipeObj._cmdObj;
 
298
 
 
299
    if (typeof(cmdObj.stderr) == "object" && (! cmdObj.mergeStderr)) {
 
300
      cmdObj.stderr.onDataAvailable( this._pipeObj.stderrData.getData());
 
301
      this._pipeObj.stderrData.shutdown();
 
302
    }
 
303
 
 
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();
 
309
      }
 
310
 
 
311
      cmdObj.onFinished.callback(this._pipeObj._pipeTransport.exitValue);
 
312
    }
 
313
 
 
314
    // unset assigned variables to avoid memory leak
 
315
    this._pipeObj = null;
 
316
  }
 
317
}
 
318
 
 
319
/**
 
320
 * Class to represent a running process. This class that is returned
 
321
 * from subprocess.call(...)
 
322
 */
 
323
function PipeObj()
 
324
{}
 
325
 
 
326
PipeObj.prototype = {
 
327
  stderrData: null,
 
328
 
 
329
  /**
 
330
   * Initializer method.
 
331
   *
 
332
   * @param cmdObj  Object as defined for subprocess.call()
 
333
   */
 
334
  init: function(cmdObj) {
 
335
    this._cmdObj = cmdObj;
 
336
 
 
337
    // create & open pipeListener for stderr, no matter if needed or not
 
338
    this.stderrData = Cc[NS_IPCBUFFER_CONTRACTID].createInstance(
 
339
      Ci.nsIIPCBuffer);
 
340
    this.stderrData.open(-1, true);
 
341
 
 
342
 
 
343
    if (typeof (cmdObj.command) == "string") {
 
344
      let localfile = Cc["@mozilla.org/file/local;1"].createInstance(
 
345
        Ci.nsILocalFile);
 
346
      localfile.initWithPath(cmdObj.command);
 
347
      cmdObj._commandFile = localfile.QueryInterface(Ci.nsIFile);
 
348
    }
 
349
    else {
 
350
      cmdObj._commandFile = cmdObj.command;
 
351
    }
 
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(
 
356
        Ci.nsILocalFile);
 
357
      localfile.initWithPath(cmdObj.workdir);
 
358
      cmdObj._cwd = localfile.QueryInterface(Ci.nsIFile);
 
359
    }
 
360
    else if (typeof (cmdObj.workdir) == "object") {
 
361
      cmdObj._cwd = cmdObj.workdir;
 
362
    }
 
363
    else {
 
364
      cmdObj._cwd = null;
 
365
    }
 
366
 
 
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);
 
371
 
 
372
    this.stdoutListener = null;
 
373
    if (typeof(cmdObj.stdout) == "object") {
 
374
      // add listener for asynchronous processing of data
 
375
      this.stdoutListener = new StdoutStreamListener(cmdObj);
 
376
    }
 
377
    else {
 
378
      this.stdoutListener = Cc[NS_IPCBUFFER_CONTRACTID].createInstance(
 
379
        Ci.nsIIPCBuffer);
 
380
      this.stdoutListener.open(-1, true);
 
381
    }
 
382
 
 
383
    if (typeof(cmdObj.stderr) == "object" ||
 
384
        typeof(cmdObj.onFinished) == "object")
 
385
      this.stdoutListener.observe(new OnFinishedListener(this), null);
 
386
 
 
387
    this._pipeTransport.openPipe(cmdObj.arguments, cmdObj.arguments.length,
 
388
                                 cmdObj.environment, cmdObj.environment.length,
 
389
                                 0, "", true, cmdObj.mergeStderr ? true : false,
 
390
                                 this.stderrData);
 
391
 
 
392
    this._pipeTransport.asyncRead(this.stdoutListener, null, 0, -1, 0);
 
393
 
 
394
    if (typeof(cmdObj.stdin) == "string") {
 
395
      this._pipeTransport.writeSync(cmdObj.stdin, cmdObj.stdin.length);
 
396
      this._pipeTransport.closeStdin();
 
397
    }
 
398
    else if (typeof(cmdObj.stdin) == "object") {
 
399
      cmdObj.stdin._pipeTransport = this._pipeTransport;
 
400
      cmdObj.stdin.startWriting();
 
401
    }
 
402
  }, // init
 
403
 
 
404
  /**
 
405
   * Wait for the subprocess to complete. This method is blocking.
 
406
   */
 
407
  wait: function () {
 
408
    this._pipeTransport.join();
 
409
  },
 
410
 
 
411
  /**
 
412
   * Kill the subprocess
 
413
   */
 
414
  kill: function() {
 
415
    try {
 
416
      this._pipeTransport.kill();
 
417
    }
 
418
    catch(ex) {
 
419
      // do nothing
 
420
    }
 
421
  }
 
422
}; // PipeObj