How can I trap a signal (`SIGPIPE`) for a socket that closes?

15,064

You could simply ignore SIGPIPE. It's a useless, annoying signal.

signal(SIGPIPE, SIG_IGN);

If you ignore it then your program will instead receive an EPIPE error code from the failed write() call. This lets you handle the I/O error at a sensible place in your code rather than in some global signal handler.

EPIPE

fd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIGPIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.)

Share:
15,064

Related videos on Youtube

Sergey Kotyushkin
Author by

Sergey Kotyushkin

Updated on June 04, 2022

Comments

  • Sergey Kotyushkin
    Sergey Kotyushkin almost 2 years

    I've written a server that accepts a socket connection on a secondary port for the purposes of streaming debugging information that normally goes to stderr. This second port --an error serving port-- is only intended to have one connection at a time, which, is convenient, because it allows to me redirect stderr using a dup2(2) call. (See Can I redirect a parent process's stderr to a socket file descriptor on a forked process?).

    The following code is nearly satisfactory in every regard. When a client logs into the port, the stderr stream is directed to the socket. When another client logs in, the stream is redirected again, and the first client stops receiving: entirely satisfactory.

    Where it falls short in the design is when the client closes the connection, the server crashes because it is trying to write() to a socket that is closed.

    I've got a rudimentary signal handler for the normal child processes, but I'm not sure how to handle the specific signal from the parent process when the error socket closes.

    How can I trap the signal (in the parent) that the connection on the ERR_PORT_NUM has closed and have the signal handler reopen (or dup) stderr back to /dev/null for the next awaiting error client?

    Also, what should I do with an original error client connection when a second connects? Currently the first client is left dangling. Even a non-graceful shut-down of the first connection is acceptable.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/socket.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <pwd.h>
    #include <signal.h>
    #include <netinet/in.h>
    #include <sys/mman.h>
    
    #define PORT_NUM 12345
    #define ERR_PORT_NUM 54321
    
    static void child_handler(int signum)
    {
        switch (signum) {
            case SIGALRM:
                exit(EXIT_FAILURE);
                break;
            case SIGUSR1:
                exit(EXIT_SUCCESS);
                break;
            case SIGCHLD:
                exit(EXIT_FAILURE);
                break;
        }
    }
    
    static void daemonize(void)
    {
        /* Trap signals that we expect to recieve */
        signal(SIGUSR1, child_handler);
        signal(SIGALRM, child_handler);
    
        signal(SIGCHLD, SIG_IGN);   /* A child process dies */
        signal(SIGTSTP, SIG_IGN);   /* Various TTY signals */
        signal(SIGTTOU, SIG_IGN);
        signal(SIGTTIN, SIG_IGN);
        signal(SIGHUP, SIG_IGN);    /* Ignore hangup signal */
        signal(SIGTERM, SIG_DFL);   /* Die on SIGTERM */
    
        freopen("/dev/null", "r", stdin);
        freopen("/dev/null", "w", stdout);
        freopen("/dev/null", "w", stderr);
    }
    
    static void server_work(void)
    {
        int sockfd, err_sockfd;
        socklen_t clilen;
        struct sockaddr_in serv_addr, cli_addr, err_serv_addr, err_cli_addr;
        struct timeval tv = { 0 };
        int new_stderr;
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        err_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0 || err_sockfd < 0)
            return;
    
        memset((char *) &serv_addr, '\0', sizeof(serv_addr));
        memset((char *) &err_serv_addr, '\0', sizeof(serv_addr));
    
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = INADDR_ANY;
        serv_addr.sin_port = htons(PORT_NUM);
    
        err_serv_addr.sin_family = AF_INET;
        err_serv_addr.sin_addr.s_addr = INADDR_ANY;
        err_serv_addr.sin_port = htons(ERR_PORT_NUM);
    
        if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
            < 0)
            return;
        if (bind
            (err_sockfd, (struct sockaddr *) &err_serv_addr,
             sizeof(err_serv_addr)) < 0)
            return;
    
        listen(sockfd, 5);
        listen(err_sockfd, 5);
    
        clilen = sizeof(cli_addr);
    
        while (1) {
            int maxfd;
            fd_set read_sockets_set;
    
            FD_ZERO(&read_sockets_set);
            FD_SET(sockfd, &read_sockets_set);
            FD_SET(err_sockfd, &read_sockets_set);
    
            maxfd = (err_sockfd > sockfd) ? err_sockfd : sockfd;
    
            if (select(maxfd + 1, &read_sockets_set, NULL, NULL, NULL) < 0) {
                break;
            }
            if (FD_ISSET(sockfd, &read_sockets_set)) {
                /* Typical process fork(2) and such ... not gremaine to the question. */
            }
            if (FD_ISSET(err_sockfd, &read_sockets_set)) {
                new_stderr =
                    accept(err_sockfd, (struct sockaddr *) &err_cli_addr,
                           &clilen);
                dup2(new_stderr, STDERR_FILENO);
            }
        }
        close(sockfd);
        close(err_sockfd);
        return;
    }
    
    int main(int argc, char *argv[])
    {
        daemonize();                /* greatly abbreviated for question */
    
        server_work();
        return 0;
    }
    
  • Sergey Kotyushkin
    Sergey Kotyushkin over 12 years
    hmm ... the failing write() calls are inside the library. Which appear to ignore the return values. I suppose I could do an if (ftell(stderr) < 0) ... just before my dup2() call. Messy ... but it is only a debugging tool.
  • sudo
    sudo almost 10 years
    Where should we put signal(SIGPIPE, SIG_IGN);? Just anywhere before any sockets are created, or is it once per thread?