Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SIGINT signal gets dropped during write to a pipe

Tags:

c++

pipe

signals

I have a program that dumps pcap data gathered using the libpcap to stdout using pcap_dump function, with stdout as the FILE *. There is a little bit of cleanup necessary on SIGINT, so I handle that with sigaction(). This works nicely when executed from a shell.

However, this program is intended to be called by another program, which doesn't seem to work. This "caller" program calls a pipe(), then a fork(), then the child's stdout file descriptor is closed, and replaced with the write end of the pipe. Finally, the aforementioned pcap program is executed in the child process. This way the pcap data is written to the caller program through the pipe. This also works nicely. However, when I send a SIGINT to the child process while it is writing to the pipe (well, the pcap program thinks its writing to stdout, but its file descriptor was changed), the signal seems to get dropped, and the signal handler function never gets called at all.

Why is that? If I write the pcap data to stderr or a file, the SIGINT never gets dropped. Only when writing to the pipe.

Here is how we are setting up the pipe/fork/execute:

int fd[2];

//Create pipe
pipe(fd);

pid = fork(); //We forked a child

if(pid == 0){ //We are the child now

    close(1); //close child's stdout

    dup(fd[1]); //duplicate child's stdout to the write end of the pipe

    close( fd[0]); //close unused file descriptors
    close( fd[1]);

    //Load the new program
    execlp("./collectraw", "collectraw", NULL);

    perror("Exec");
    exit(127); //Should never get called but we leave it so the child
    //doesnt accidently keep executing
}
else{ //We are the parent

    //Set up the file descriptors
    close(fd[1]);
}

then to kill the child we use:

kill( pid, SIGINT);

In the child, the callback function for our pcap_loop() can be as simple as:

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet){
    write(1,"<pretend this is like a thousand zeros>",1000); //write to stdout, which is really a pipe
}

And we will basically always drop the SIGINT. There's a lot of packets to capture by the way, so it's pretty safe to assume it's nearly always in the callback function.

But if we change from

write(1,... ); //write to stdout, which is really a pipe

to

write(2,...); //write to stderr, or writing to a file would work too

then everything gets hunky-dory again.

Why does our SIGINT get dropped during a write to a pipe?

Thanks for helping.

EDIT: The child's SIGINT handler was never being called at all, but the reason wasn't really a problem in the child, it was a problem in the parent. I used to kill the child like:

if( kill( pid, SIGINT) == -1){
    perror("Could not kill child");
}
close(pipefd);
fprintf(stdout, "Successfully killed child\n");

And this used to be our SIGCHLD handler:

void handlesigchild(int sig) {
    wait();

    printf("Cleaned up a child\n");
}

So, as described in the accepted answer, closing the pipe immediately was causing our child to exit with a SIGPIPE before the SIGINT was being handled. We just moved the close(pipefd) to the SIGCHLD handler and it works now.

like image 676
rexroni Avatar asked May 29 '15 02:05

rexroni


1 Answers

You don't show enough of your code to know what is going on. You should always try to construct an SSCCE and post that if you want people to be able to comment on your program.

Best guess: your parent exits after sending the signal, closing the read end of the pipe. This causes the client to immediately exit with a SIGPIPE, before it gets a chance to handle the SIGINT. Try doing your cleanup on SIGPIPE as well, or ignore SIGPIPE.

like image 148
Chris Dodd Avatar answered Sep 21 '22 10:09

Chris Dodd