Skip to content

Instantly share code, notes, and snippets.

@davepacheco
Last active December 6, 2019 19:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davepacheco/4724185 to your computer and use it in GitHub Desktop.
Save davepacheco/4724185 to your computer and use it in GitHub Desktop.
Surprising Node.js/bash interaction
/*
* 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