Last active
December 6, 2019 19:56
-
-
Save davepacheco/4724185 to your computer and use it in GitHub Desktop.
Surprising Node.js/bash interaction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* testspawn.js: exercises surprising interaction between Node.js "spawn" and | |
* bash. To use this: | |
* | |
* (1) Create a file in the same directory called "foo.rc", with just one line: | |
* | |
* echo "loaded foo.rc" | |
* | |
* (2) Run this program as: | |
* | |
* $ node testspawn.js | |
* | |
* This program uses Node's child_process.spawn in a loop to invoke | |
* "bash --rcfile foo.rc -c /bin/true" (that is, invoke "/bin/true"). The | |
* surprising behavior is that sometimes you will see "loaded foo.rc", but | |
* sometimes you won't. The reason for this is complicated. | |
* | |
* In this case, bash determines whether to load the bashrc file based on | |
* whether it believes its stdin refers to a network socket. On systems with | |
* getpeername(2), bash attempts to detect this by calling getpeername(2) on | |
* stdin to see if it returns ENOTSOCK or EINVAL. When Node's spawn is invoked | |
* with default arguments, Node creates a pipe from the parent to the child | |
* process's stdin, and that pipe is implemented with a Unix domain socket. | |
* You'd therefore expect that bash would always load the bashrc file when | |
* invoked this way from Node. However, getpeername(2) returns EINVAL on | |
* sockets that have been shutdown (see [1]). (It's unclear if this means the | |
* send- or receive-side has been shutdown, but based on the illumos kernel | |
* implementation, this appears to mean that the *send* side has been shut | |
* down.) Whether this is the case appears to depend on when Node actually | |
* shuts down its end of the socket, which appears to happen when we call end(). | |
* | |
* The program below uses a random interval between 0 and "maxtime" milliseconds | |
* between spawning the child and shutting down the socket. On my system, this | |
* results in seeing each behavior roughly 50% of the time. | |
* | |
* The implications of this can be quite significant: if you use Node.js to | |
* invoke "bash", it can be invoked with a totally different environment (as | |
* determined by your ~/.bashrc file) depending on the timing. | |
* | |
* [1] http://pubs.opengroup.org/onlinepubs/7908799/xns/getpeername.html | |
*/ | |
var maxtime = 5; | |
function go() | |
{ | |
console.log('spawning'); | |
var child = mod_child.spawn('bash', | |
[ '--rcfile', 'foo.rc', '-c', '/bin/true' ], { | |
'env': {}, | |
'stdio': [ null, process.stdout, process.stderr ] | |
}); | |
child.on('exit', go); | |
setTimeout(function () { | |
child.stdin.end(); | |
}, Math.round(Math.random() * maxtime)); | |
} | |
go(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment