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?
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:
$QUILT_DIR/scripts/dependency-graph
opens /dev/stdout
for writing instead of just using FD 1 directlychild_process.exec
have a UNIX domain socket as their standard outputopen
or openat
a UNIX domain socketThis 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.)