Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion cligen_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <net/if.h>

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions test/test_pipe.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<EOF
prompt="cli> "; # Assignment of prompt
comment="#"; # Same comment as in syntax
Expand All @@ -24,6 +29,7 @@ cat > $fspec <<EOF
}
set4 @treeref;
set5 <string>, callback();
big, output_fn($biglines);

treename="|mypipe";
pipetree="";
Expand Down Expand Up @@ -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

Expand Down