diff --git a/cligen_io.c b/cligen_io.c index 024fc57..e57ab12 100644 --- a/cligen_io.c +++ b/cligen_io.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -106,6 +107,57 @@ cli_pipe_output_socket_set(int s) return 0; } +/*! Write to pipe socket without deadlock against the pipe child + * + * The child (eg grep) reads from and writes to the same socketpair. If this process + * blocks in write() while the child blocks writing its (large) output back, both + * wait for each other. Therefore, while the socket is not writable, drain what the + * child has produced to stdout. + * @retval 0 OK + * @retval -1 Error, or child closed its side (EPIPE) + */ +static int +pipe_socket_write(int s, + const char *buf, + size_t len) +{ + size_t off = 0; + ssize_t n; + fd_set rfds, wfds; + char rbuf[4097]; + + while (off < len){ + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(s, &rfds); + FD_SET(s, &wfds); + if (select(s+1, &rfds, &wfds, NULL, NULL) < 0){ + if (errno == EINTR) + continue; + return -1; + } + if (FD_ISSET(s, &rfds)){ + if ((n = recv(s, rbuf, sizeof(rbuf)-1, MSG_DONTWAIT)) > 0){ + rbuf[n] = '\0'; + cligen_output_basic(stdout, rbuf, n); + } + else if (n == 0){ + errno = EPIPE; + return -1; + } + else if (errno != EAGAIN && errno != EINTR) + return -1; + } + if (FD_ISSET(s, &wfds)){ + if ((n = send(s, buf+off, len-off, MSG_DONTWAIT|MSG_NOSIGNAL)) > 0) + off += n; + else if (n < 0 && errno != EAGAIN && errno != EINTR) + return -1; + } + } + return 0; +} + /*! Reset cligen_output to initial state * * For new output or when 'q' is pressed that sets d_line to -1 @@ -291,7 +343,7 @@ cligen_output(FILE *f, if (cli_pipe_output_socket_get(&s) < 0) goto done; if (s != -1){ - if (write(s, inbuf, inbuflen) < 0){ + if (pipe_socket_write(s, inbuf, inbuflen) < 0){ switch (errno){ case EPIPE: break; diff --git a/test/test_pipe.sh b/test/test_pipe.sh index 684a2cc..18bf437 100755 --- a/test/test_pipe.sh +++ b/test/test_pipe.sh @@ -9,6 +9,11 @@ fspec=$dir/spec.cli lines='"line6 def", "line7 def", "line1 abc", "line2 abc", "line3 abc", "line4 abc", "line5 def"' +# Large output: every line matches grep, total much larger than the socketpair buffer +biglines="" +for i in $(seq 1 30000); do biglines="$biglines\"line$i abc xxxxxxxxxxxxxxxxxxxx\", "; done +biglines="${biglines}\"last abc\"" + cat > $fspec < $fspec <, callback(); + big, output_fn($biglines); treename="|mypipe"; pipetree=""; @@ -92,6 +98,13 @@ expectpart "$(echo "set5 (lag\\\|hostif)-*" | $cligen_file -f $fspec 2>&1)" 0 "v newtest "Escaped vertical bar quotes" expectpart "$(echo "set5 \"(lag|hostif)-*\"" | $cligen_file -f $fspec 2>&1)" 0 "value:(lag|hostif)-*" +# Pipe child output larger than the socket buffer must not deadlock with the writer +newtest "Large output: grep matching every line" +expectpart "$(echo "big \| grep abc" | timeout 30 $cligen_file -f $fspec 2>&1 | grep -c xxxxxxxx)" 0 "30000" + +newtest "Large output: cat" +expectpart "$(echo "big \| cat" | timeout 30 $cligen_file -f $fspec 2>&1 | grep -c xxxxxxxx)" 0 "30000" + newtest "endtest" endtest