Support the Synchronous Process Execution of Node v0.12(v0.11).

This commit is contained in:
anseki 2015-02-12 07:39:59 +09:00
parent 995f22b4bf
commit ab94a1ea2b
3 changed files with 76 additions and 60 deletions

View file

@ -191,8 +191,7 @@ grunt.initConfig({
### Platforms
The your Node and OS may not support interactively reading from stdin. The stdin interfaces are different by platforms.
If in those platforms, an error is thrown.
The stdin interfaces are different by platforms. If the platform doesn't support interactively reading from stdin, an error is thrown.
```js
try {
@ -205,18 +204,16 @@ try {
### Reading by shell
readlineSync tries reading from stdin by shell if it is needed. And, it use "piping via files" for synchronous running.
readlineSync tries reading from stdin by shell if it is needed. And if the running Node doesn't support the [Synchronous Process Execution](http://nodejs.org/api/child_process.html#child_process_synchronous_process_creation) (i.e. Node v0.10-), it use "piping via files" for synchronous running.
As everyone knows, "piping via files" is no good. It blocks event loop and a process. It may make your script be slow.
Why did I choose it? :
+ The best solution is [child_process.execSync](https://github.com/joyent/node/blob/master/doc/api/child_process.markdown#child_processexecsynccommand-options) in core modules of Node. But it is not supported by current version.
+ The good modules (native addon) for synchronous execution exist. But node-gyp can't compile those in some platforms or Node versions.
+ I think that the security is important more than the speed. Some modules have problem about security. (Those don't protect data.) I think that the speed is not needed usually, because readlineSync is used while user types keys.
Someday, I may rewrite readlineSync to use child_process.execSync, or safety module.
## Release History
* 2015-02-12 v0.5.5 Support the Synchronous Process Execution of Node v0.12(v0.11).
* 2015-01-27 v0.5.0 Add `options.noTrim`.
* 2014-07-12 v0.4.0 Add `options.noEchoBack`.
* 2014-07-12 v0.3.0 Add `setPrint()`.

View file

@ -10,6 +10,9 @@
var
BUF_SIZE = 256,
IS_WIN = process.platform === 'win32',
SHELL_PATH = IS_WIN ? 'cmd.exe' : '/bin/sh',
ALGORITHM_CIPHER = 'aes-256-cbc',
ALGORITHM_HASH = 'sha256',
@ -79,62 +82,37 @@ function _readlineSync(display, options) {
}
function _readlineShell(noEchoBack) {
// piping via files instead of execSync (node v0.12+)
// https://github.com/joyent/node/blob/master/doc/api/child_process.markdown#child_processexecsynccommand-options
// see README > Note
var shellPath, args, shellStdout,
pathStdout = getTempfile('readline-sync.stdout'),
pathStatus = getTempfile('readline-sync.status'),
pathDone = getTempfile('readline-sync.done'),
optEchoBack = noEchoBack ? ' noechoback' : '',
crypto = require('crypto'), shasum, decipher, password;
var shellStdout, command,
options = {
env: process.env,
stdio: [stdin], // ScriptPW needs piped stdin
encoding: encoding
},
optEchoBack = noEchoBack ? ' noechoback' : '';
shasum = crypto.createHash(ALGORITHM_HASH);
shasum.update('' + process.pid + (salt++) + Math.random());
password = shasum.digest('hex');
decipher = crypto.createDecipher(ALGORITHM_CIPHER, password);
if (process.platform === 'win32') {
if (IS_WIN) {
// The quote (") is escaped by node before parsed by shell. Then use ENV{Q}.
shellPath = 'cmd.exe';
args = ['/V:ON', '/S', '/C',
'%Q%' + __dirname + '\\read.bat%Q%' + optEchoBack +
' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' +
' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' +
' >%Q%' + pathStdout + '%Q%' +
' & (echo !ERRORLEVEL!)>%Q%' + pathStatus + '%Q% & (echo 1)>%Q%' + pathDone + '%Q%'];
process.env.Q = '"';
command = '%Q%' + __dirname + '\\read.bat%Q%' + optEchoBack;
} else {
shellPath = '/bin/sh';
args = ['-c',
'DATA=`(' + shellPath + ' "' + __dirname + '/read.sh"' + optEchoBack + ')`; RTN=$?;' +
' if [ $RTN -eq 0 ]; then (printf \'%s\' "$DATA" |' +
'"' + process.execPath + '" "' + __dirname + '/encrypt.js"' +
' "' + ALGORITHM_CIPHER + '" "' + password + '"' +
' >"' + pathStdout + '") fi;' +
' expr $RTN + $? >"' + pathStatus + '"; echo 1 >"' + pathDone + '"'];
command = '"' + __dirname + '/read.sh"' + optEchoBack;
}
stdin.pause(); // re-start in child process
childProc.spawn(shellPath, args, {
env: process.env,
stdio: [stdin] // ScriptPW needs piped stdin
});
while (fs.readFileSync(pathDone, {encoding: encoding}).trim() !== '1') {}
if (fs.readFileSync(pathStatus, {encoding: encoding}).trim() === '0') {
shellStdout =
decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), 'hex', encoding) +
decipher.final(encoding);
if (childProc.execFileSync) {
shellStdout = childProc.execFileSync(SHELL_PATH,
IS_WIN ? ['/S', '/C', command] : ['-c', command], options);
shellStdout = shellStdout.replace(/^'|'$/g, '');
} else {
shellStdout = _execSyncByFile(command, options);
}
fs.unlinkSync(pathStdout);
fs.unlinkSync(pathStatus);
fs.unlinkSync(pathDone);
return shellStdout;
}
// piping via files (node v0.10-)
function _execSyncByFile(command, options) {
function getTempfile(name) {
var path = require('path'), filepath, suffix = '', fd;
tempdir = tempdir || require('os').tmpdir();
@ -157,6 +135,47 @@ function getTempfile(name) {
return filepath;
}
var shellStdout,
pathStdout = getTempfile('readline-sync.stdout'),
pathStatus = getTempfile('readline-sync.status'),
pathDone = getTempfile('readline-sync.done'),
crypto = require('crypto'), shasum, decipher, password;
shasum = crypto.createHash(ALGORITHM_HASH);
shasum.update('' + process.pid + (salt++) + Math.random());
password = shasum.digest('hex');
decipher = crypto.createDecipher(ALGORITHM_CIPHER, password);
childProc.spawn(SHELL_PATH,
IS_WIN ? ['/V:ON', '/S', '/C',
command + ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' +
' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' +
' >%Q%' + pathStdout + '%Q%' +
' & (echo !ERRORLEVEL!)>%Q%' + pathStatus + '%Q% & (echo 1)>%Q%' + pathDone + '%Q%'] :
['-c',
'DATA=`(' + SHELL_PATH + ' ' + command + ')`; RTN=$?;' +
' if [ $RTN -eq 0 ]; then (printf \'%s\' "$DATA" |' +
'"' + process.execPath + '" "' + __dirname + '/encrypt.js"' +
' "' + ALGORITHM_CIPHER + '" "' + password + '"' +
' >"' + pathStdout + '") fi;' +
' expr $RTN + $? >"' + pathStatus + '"; echo 1 >"' + pathDone + '"'],
options);
while (fs.readFileSync(pathDone, {encoding: encoding}).trim() !== '1') {}
if (fs.readFileSync(pathStatus, {encoding: encoding}).trim() === '0') {
shellStdout =
decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), 'hex', encoding) +
decipher.final(encoding);
shellStdout = shellStdout.replace(/^'|'$/g, '');
}
fs.unlinkSync(pathStdout);
fs.unlinkSync(pathStatus);
fs.unlinkSync(pathDone);
return shellStdout;
}
// for dev
exports.useShellSet = function(use) { useShell = use; };

View file

@ -1,6 +1,6 @@
{
"name": "readline-sync",
"version": "0.5.4",
"version": "0.5.5",
"title": "readlineSync",
"description": "Synchronous Readline",
"keywords": [