Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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;
}
like image 898
Jamie Avatar asked Jan 12 '12 02:01

Jamie


People also ask

How do I ignore SIGPIPE signal?

To ignore the SIGPIPE signal, use the following code: signal(SIGPIPE, SIG_IGN); If you're using the send() call, another option is to use the MSG_NOSIGNAL option, which will turn the SIGPIPE behavior off on a per call basis. Note that not all operating systems support the MSG_NOSIGNAL flag.

What can cause SIGPIPE?

Another cause of SIGPIPE is when you try to output to a socket that isn't connected. See Sending Data. Resource lost. This signal is generated when you have an advisory lock on an NFS file, and the NFS server reboots and forgets about your lock.

What is SIGPIPE error?

A sigpipe error occurs when a pipe the Maple client is using to communicate to another process closes unexpectedly.


1 Answers

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.)

like image 141
John Kugelman Avatar answered Sep 28 '22 18:09

John Kugelman