Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

exec.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. var common = require('./common');
  2. var _tempDir = require('./tempdir');
  3. var _pwd = require('./pwd');
  4. var path = require('path');
  5. var fs = require('fs');
  6. var child = require('child_process');
  7. // Hack to run child_process.exec() synchronously (sync avoids callback hell)
  8. // Uses a custom wait loop that checks for a flag file, created when the child process is done.
  9. // (Can't do a wait loop that checks for internal Node variables/messages as
  10. // Node is single-threaded; callbacks and other internal state changes are done in the
  11. // event loop).
  12. function execSync(cmd, opts) {
  13. var tempDir = _tempDir();
  14. var stdoutFile = path.resolve(tempDir+'/'+common.randomFileName()),
  15. codeFile = path.resolve(tempDir+'/'+common.randomFileName()),
  16. scriptFile = path.resolve(tempDir+'/'+common.randomFileName()),
  17. sleepFile = path.resolve(tempDir+'/'+common.randomFileName());
  18. var options = common.extend({
  19. silent: common.config.silent
  20. }, opts);
  21. var previousStdoutContent = '';
  22. // Echoes stdout changes from running process, if not silent
  23. function updateStdout() {
  24. if (options.silent || !fs.existsSync(stdoutFile))
  25. return;
  26. var stdoutContent = fs.readFileSync(stdoutFile, 'utf8');
  27. // No changes since last time?
  28. if (stdoutContent.length <= previousStdoutContent.length)
  29. return;
  30. process.stdout.write(stdoutContent.substr(previousStdoutContent.length));
  31. previousStdoutContent = stdoutContent;
  32. }
  33. function escape(str) {
  34. return (str+'').replace(/([\\"'])/g, "\\$1").replace(/\0/g, "\\0");
  35. }
  36. cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix
  37. var script =
  38. "var child = require('child_process')," +
  39. " fs = require('fs');" +
  40. "child.exec('"+escape(cmd)+"', {env: process.env, maxBuffer: 20*1024*1024}, function(err) {" +
  41. " fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');" +
  42. "});";
  43. if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile);
  44. if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile);
  45. if (fs.existsSync(codeFile)) common.unlinkSync(codeFile);
  46. fs.writeFileSync(scriptFile, script);
  47. child.exec('"'+process.execPath+'" '+scriptFile, {
  48. env: process.env,
  49. cwd: _pwd(),
  50. maxBuffer: 20*1024*1024
  51. });
  52. // The wait loop
  53. // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage
  54. // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing
  55. // CPU usage, though apparently not so much on Windows)
  56. while (!fs.existsSync(codeFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
  57. while (!fs.existsSync(stdoutFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
  58. // At this point codeFile exists, but it's not necessarily flushed yet.
  59. // Keep reading it until it is.
  60. var code = parseInt('', 10);
  61. while (isNaN(code)) {
  62. code = parseInt(fs.readFileSync(codeFile, 'utf8'), 10);
  63. }
  64. var stdout = fs.readFileSync(stdoutFile, 'utf8');
  65. // No biggie if we can't erase the files now -- they're in a temp dir anyway
  66. try { common.unlinkSync(scriptFile); } catch(e) {}
  67. try { common.unlinkSync(stdoutFile); } catch(e) {}
  68. try { common.unlinkSync(codeFile); } catch(e) {}
  69. try { common.unlinkSync(sleepFile); } catch(e) {}
  70. // some shell return codes are defined as errors, per http://tldp.org/LDP/abs/html/exitcodes.html
  71. if (code === 1 || code === 2 || code >= 126) {
  72. common.error('', true); // unix/shell doesn't really give an error message after non-zero exit codes
  73. }
  74. // True if successful, false if not
  75. var obj = {
  76. code: code,
  77. output: stdout
  78. };
  79. return obj;
  80. } // execSync()
  81. // Wrapper around exec() to enable echoing output to console in real time
  82. function execAsync(cmd, opts, callback) {
  83. var output = '';
  84. var options = common.extend({
  85. silent: common.config.silent
  86. }, opts);
  87. var c = child.exec(cmd, {env: process.env, maxBuffer: 20*1024*1024}, function(err) {
  88. if (callback)
  89. callback(err ? err.code : 0, output);
  90. });
  91. c.stdout.on('data', function(data) {
  92. output += data;
  93. if (!options.silent)
  94. process.stdout.write(data);
  95. });
  96. c.stderr.on('data', function(data) {
  97. output += data;
  98. if (!options.silent)
  99. process.stdout.write(data);
  100. });
  101. return c;
  102. }
  103. //@
  104. //@ ### exec(command [, options] [, callback])
  105. //@ Available options (all `false` by default):
  106. //@
  107. //@ + `async`: Asynchronous execution. Defaults to true if a callback is provided.
  108. //@ + `silent`: Do not echo program output to console.
  109. //@
  110. //@ Examples:
  111. //@
  112. //@ ```javascript
  113. //@ var version = exec('node --version', {silent:true}).output;
  114. //@
  115. //@ var child = exec('some_long_running_process', {async:true});
  116. //@ child.stdout.on('data', function(data) {
  117. //@ /* ... do something with data ... */
  118. //@ });
  119. //@
  120. //@ exec('some_long_running_process', function(code, output) {
  121. //@ console.log('Exit code:', code);
  122. //@ console.log('Program output:', output);
  123. //@ });
  124. //@ ```
  125. //@
  126. //@ Executes the given `command` _synchronously_, unless otherwise specified.
  127. //@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
  128. //@ `output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and
  129. //@ the `callback` gets the arguments `(code, output)`.
  130. //@
  131. //@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as
  132. //@ the current synchronous implementation uses a lot of CPU. This should be getting
  133. //@ fixed soon.
  134. function _exec(command, options, callback) {
  135. if (!command)
  136. common.error('must specify command');
  137. // Callback is defined instead of options.
  138. if (typeof options === 'function') {
  139. callback = options;
  140. options = { async: true };
  141. }
  142. // Callback is defined with options.
  143. if (typeof options === 'object' && typeof callback === 'function') {
  144. options.async = true;
  145. }
  146. options = common.extend({
  147. silent: common.config.silent,
  148. async: false
  149. }, options);
  150. if (options.async)
  151. return execAsync(command, options, callback);
  152. else
  153. return execSync(command, options);
  154. }
  155. module.exports = _exec;