123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- var common = require('./common');
- var _tempDir = require('./tempdir');
- var _pwd = require('./pwd');
- var path = require('path');
- var fs = require('fs');
- var child = require('child_process');
-
- // Hack to run child_process.exec() synchronously (sync avoids callback hell)
- // Uses a custom wait loop that checks for a flag file, created when the child process is done.
- // (Can't do a wait loop that checks for internal Node variables/messages as
- // Node is single-threaded; callbacks and other internal state changes are done in the
- // event loop).
- function execSync(cmd, opts) {
- var tempDir = _tempDir();
- var stdoutFile = path.resolve(tempDir+'/'+common.randomFileName()),
- codeFile = path.resolve(tempDir+'/'+common.randomFileName()),
- scriptFile = path.resolve(tempDir+'/'+common.randomFileName()),
- sleepFile = path.resolve(tempDir+'/'+common.randomFileName());
-
- var options = common.extend({
- silent: common.config.silent
- }, opts);
-
- var previousStdoutContent = '';
- // Echoes stdout changes from running process, if not silent
- function updateStdout() {
- if (options.silent || !fs.existsSync(stdoutFile))
- return;
-
- var stdoutContent = fs.readFileSync(stdoutFile, 'utf8');
- // No changes since last time?
- if (stdoutContent.length <= previousStdoutContent.length)
- return;
-
- process.stdout.write(stdoutContent.substr(previousStdoutContent.length));
- previousStdoutContent = stdoutContent;
- }
-
- function escape(str) {
- return (str+'').replace(/([\\"'])/g, "\\$1").replace(/\0/g, "\\0");
- }
-
- cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix
-
- var script =
- "var child = require('child_process')," +
- " fs = require('fs');" +
- "child.exec('"+escape(cmd)+"', {env: process.env, maxBuffer: 20*1024*1024}, function(err) {" +
- " fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');" +
- "});";
-
- if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile);
- if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile);
- if (fs.existsSync(codeFile)) common.unlinkSync(codeFile);
-
- fs.writeFileSync(scriptFile, script);
- child.exec('"'+process.execPath+'" '+scriptFile, {
- env: process.env,
- cwd: _pwd(),
- maxBuffer: 20*1024*1024
- });
-
- // The wait loop
- // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage
- // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing
- // CPU usage, though apparently not so much on Windows)
- while (!fs.existsSync(codeFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
- while (!fs.existsSync(stdoutFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
-
- // At this point codeFile exists, but it's not necessarily flushed yet.
- // Keep reading it until it is.
- var code = parseInt('', 10);
- while (isNaN(code)) {
- code = parseInt(fs.readFileSync(codeFile, 'utf8'), 10);
- }
-
- var stdout = fs.readFileSync(stdoutFile, 'utf8');
-
- // No biggie if we can't erase the files now -- they're in a temp dir anyway
- try { common.unlinkSync(scriptFile); } catch(e) {}
- try { common.unlinkSync(stdoutFile); } catch(e) {}
- try { common.unlinkSync(codeFile); } catch(e) {}
- try { common.unlinkSync(sleepFile); } catch(e) {}
-
- // some shell return codes are defined as errors, per http://tldp.org/LDP/abs/html/exitcodes.html
- if (code === 1 || code === 2 || code >= 126) {
- common.error('', true); // unix/shell doesn't really give an error message after non-zero exit codes
- }
- // True if successful, false if not
- var obj = {
- code: code,
- output: stdout
- };
- return obj;
- } // execSync()
-
- // Wrapper around exec() to enable echoing output to console in real time
- function execAsync(cmd, opts, callback) {
- var output = '';
-
- var options = common.extend({
- silent: common.config.silent
- }, opts);
-
- var c = child.exec(cmd, {env: process.env, maxBuffer: 20*1024*1024}, function(err) {
- if (callback)
- callback(err ? err.code : 0, output);
- });
-
- c.stdout.on('data', function(data) {
- output += data;
- if (!options.silent)
- process.stdout.write(data);
- });
-
- c.stderr.on('data', function(data) {
- output += data;
- if (!options.silent)
- process.stdout.write(data);
- });
-
- return c;
- }
-
- //@
- //@ ### exec(command [, options] [, callback])
- //@ Available options (all `false` by default):
- //@
- //@ + `async`: Asynchronous execution. Defaults to true if a callback is provided.
- //@ + `silent`: Do not echo program output to console.
- //@
- //@ Examples:
- //@
- //@ ```javascript
- //@ var version = exec('node --version', {silent:true}).output;
- //@
- //@ var child = exec('some_long_running_process', {async:true});
- //@ child.stdout.on('data', function(data) {
- //@ /* ... do something with data ... */
- //@ });
- //@
- //@ exec('some_long_running_process', function(code, output) {
- //@ console.log('Exit code:', code);
- //@ console.log('Program output:', output);
- //@ });
- //@ ```
- //@
- //@ Executes the given `command` _synchronously_, unless otherwise specified.
- //@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
- //@ `output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and
- //@ the `callback` gets the arguments `(code, output)`.
- //@
- //@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as
- //@ the current synchronous implementation uses a lot of CPU. This should be getting
- //@ fixed soon.
- function _exec(command, options, callback) {
- if (!command)
- common.error('must specify command');
-
- // Callback is defined instead of options.
- if (typeof options === 'function') {
- callback = options;
- options = { async: true };
- }
-
- // Callback is defined with options.
- if (typeof options === 'object' && typeof callback === 'function') {
- options.async = true;
- }
-
- options = common.extend({
- silent: common.config.silent,
- async: false
- }, options);
-
- if (options.async)
- return execAsync(command, options, callback);
- else
- return execSync(command, options);
- }
- module.exports = _exec;
|