Search code examples
node.jspipestdoutchild-processquilt

Error "No such device or address" from child_process.exec, pipe helps. Why?


I'm using child_process.exec to execute various quilt commands and capture their output. This works fine so far for all commands except for quilt graph. which results in an error "No such device or address". That error does not occur if I run the command from the command line.

The quilt graph command, a Bash script, runs (slightly paraphrased)

cat $QUILT_PC/applied_patches | eval $QUILT_DIR/scripts/dependency-graph - $pipe

The file applied_patches contains a list of patches, dependency-graph is a Perl script, and pipe is either an empty string or | dot -Tps.

I noticed that child_process.exec does not fail if pipe is actually a pipe. Since I don't want PostScript output (which is what | dot -Tps does), I appended | cat to the arguments of child_process.exec and now execute something like quilt graph | cat. But that obviously is a kludge.

Using child_process.exec to run strace /usr/bin/quilt graph gives

...
stat("/usr/share/quilt/compat/bash", {st_mode=S_IFREG|0755, st_size=1234376, ...}) = 0
geteuid()                               = 1000
getegid()                               = 1000
getuid()                                = 1000
getgid()                                = 1000
access("/usr/share/quilt/compat/bash", R_OK) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, [INT TERM CHLD], [], 8) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa1838aa10) = 32080
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGINT, {sa_handler=0x5574b6621d30, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7faa183c8d60}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7faa183c8d60}, 8) = 0
wait4(-1, No such device or address
[{WIFEXITED(s) && WEXITSTATUS(s) == 6}], 0, NULL) = 32080
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7faa183c8d60}, {sa_handler=0x5574b6621d30, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7faa183c8d60}, 8) = 0
ioctl(2, TIOCGWINSZ, 0x7ffd8531dd60)    = -1 ENOTTY (Inappropriate ioctl for device)
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=32080, si_uid=1000, si_status=6, si_utime=1, si_stime=0} ---
wait4(-1, 0x7ffd8531d6d0, WNOHANG, NULL) = -1 ECHILD (No child processes)
rt_sigreturn({mask=[]})                 = 0
read(255, "", 2938)                     = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
exit_group(6)                           = ?
+++ exited with 6 +++

Using child_process.exec to run strace -f /usr/bin/quilt graph gives

...
[pid 23333] write(1, "./linebuffer.h\n./remotetcp.c\n", 29) = 29
[pid 23320] <... read resumed>"./linebuffer.h\n./remotetcp.c\n", 8192) = 29
[pid 23333] close(1 <unfinished ...>
[pid 23320] read(3,  <unfinished ...>
[pid 23333] <... close resumed>)        = 0
[pid 23333] close(2)                    = 0
[pid 23333] exit_group(0)               = ?
[pid 23333] +++ exited with 0 +++
[pid 23332] <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 23333
[pid 23332] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23333, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
[pid 23332] rt_sigreturn({mask=[]})     = 23333
[pid 23332] wait4(-1, 0x7ffd722fcb6c, WNOHANG, NULL) = -1 ECHILD (No child processes)
[pid 23332] exit_group(0)               = ?
[pid 23332] +++ exited with 0 +++
[pid 23320] <... read resumed>"", 8192) = 0
[pid 23320] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23332, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
[pid 23320] fstat(3, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
[pid 23320] close(3)                    = 0
[pid 23320] wait4(23332, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 23332
[pid 23320] openat(AT_FDCWD, "/dev/stdout", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = -1 ENXIO (No such device or address)
[pid 23320] write(2, "No such device or address\n", 26No such device or address
) = 26
[pid 23320] rt_sigaction(SIGHUP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGINT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGQUIT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGILL, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGTRAP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGABRT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGFPE, NULL, {sa_handler=SIG_IGN, sa_mask=[FPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f16699c2d60}, 8) = 0
[pid 23320] rt_sigaction(SIGKILL, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGUSR1, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGUSR2, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGPIPE, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGALRM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGTERM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGSTKFLT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGCONT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGSTOP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGTSTP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGTTIN, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGTTOU, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGURG, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGXCPU, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGXFSZ, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGVTALRM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGPROF, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGWINCH, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGIO, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGPWR, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGSYS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_2, NULL, {sa_handler=SIG_IGN, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_3, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_4, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_5, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_6, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_7, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_8, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_9, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_10, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_11, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_12, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_13, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_14, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_15, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_16, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_17, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_18, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_19, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_20, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_21, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_22, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_23, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_24, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_25, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_26, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_27, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_28, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_29, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_30, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_31, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGRT_32, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGABRT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] rt_sigaction(SIGIO, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 23320] exit_group(6)               = ?
[pid 23320] +++ exited with 6 +++
[pid 23319] <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 6}], 0, NULL) = 23320
[pid 23319] rt_sigaction(SIGINT, {sa_handler=0x55ad0f08acd0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fc509dd6d60}, {sa_handler=0x55ad0f068d30, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fc509dd6d60}, 8) = 0
[pid 23319] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 23319] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23320, si_uid=1000, si_status=6, si_utime=6, si_stime=9} ---
[pid 23319] wait4(-1, 0x7ffd19cbf710, WNOHANG, NULL) = -1 ECHILD (No child processes)
[pid 23319] rt_sigreturn({mask=[]})     = 0
[pid 23319] exit_group(6)               = ?
[pid 23319] +++ exited with 6 +++
[pid 23299] <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 6}], 0, NULL) = 23319
[pid 23299] rt_sigaction(SIGINT, {sa_handler=0x55ad0f08acd0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fc509dd6d60}, {sa_handler=0x55ad0f068d30, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fc509dd6d60}, 8) = 0
[pid 23299] ioctl(2, TIOCGWINSZ, 0x7ffd19cc00c0) = -1 ENOTTY (Inappropriate ioctl for device)
[pid 23299] rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
[pid 23299] close(3)                    = -1 EBADF (Bad file descriptor)
[pid 23299] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 23299] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23318, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
[pid 23299] wait4(-1, 0x7ffd19cbfc90, WNOHANG, NULL) = -1 ECHILD (No child processes)
[pid 23299] rt_sigreturn({mask=[]})     = 0
[pid 23299] rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
[pid 23299] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
[pid 23299] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 23299] exit_group(6)               = ?
[pid 23299] +++ exited with 6 +++
<... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 6}], 0, NULL) = 23299
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f76a5bdbd60}, {sa_handler=0x55c3675d9d30, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f76a5bdbd60}, 8) = 0
ioctl(2, TIOCGWINSZ, 0x7ffccdbe2b00)    = -1 ENOTTY (Inappropriate ioctl for device)
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23299, si_uid=1000, si_status=6, si_utime=0, si_stime=3} ---
wait4(-1, 0x7ffccdbe2490, WNOHANG, NULL) = -1 ECHILD (No child processes)
rt_sigreturn({mask=[]})                 = 0
read(255, "", 2938)                     = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
exit_group(6)                           = ?
+++ exited with 6 +++

Could somebody please explain what causes the error and how to avoid it?


Solution

  • This line of your strace output is the smoking gun:

    [pid 23320] openat(AT_FDCWD, "/dev/stdout", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = -1 ENXIO (No such device or address)
    

    Now notice these errors listed by man 2 openat:

           ENODEV pathname refers to a device special file and no
                  corresponding device exists.  (This is a Linux kernel bug;
                  in this situation ENXIO must be returned.)
    
           ENXIO  O_NONBLOCK | O_WRONLY is set, the named file is a FIFO,
                  and no process has the FIFO open for reading.
    
           ENXIO  The file is a device special file and no corresponding
                  device exists.
    
           ENXIO  The file is a UNIX domain socket.
    

    The first reason for ENXIO doesn't apply, since O_NONBLOCK wasn't set. The second reason doesn't apply, since Linux would (non-compliantly) return ENODEV instead if it did. So that points to the third reason. Let's try a simple test to confirm that theory:

    const { exec } = require('node:child_process');
    
    exec('tail -f /dev/null');
    

    Run that, then look for the child's PID with ps, and do ls -l /proc/PID/fd/. You'll see the FDs have entries like 1 -> 'socket:[1404159]', which confirms that standard out is indeed a UNIX domain socket.


    So the problem is created by a combination of three things:

    1. $QUILT_DIR/scripts/dependency-graph opens /dev/stdout for writing instead of just using FD 1 directly
    2. Processes created by child_process.exec have a UNIX domain socket as their standard output
    3. Linux doesn't let you open or openat a UNIX domain socket

    This is somewhat documented under options.stdio:

    Currently, these are not actual Unix pipes and therefore the child process can not use them by their descriptor files, e.g. /dev/fd/2 or /dev/stdout.

    I'm not sure why nodejs doesn't just use actual pipes instead of UNIX domain sockets here, but your workaround of | cat might be your least-bad option. (Doing that works since then dependency-graph does have a real pipe as standard output, and neither cat nor dot -Tps try to open /dev/stdout like it does.)