Using pselect() to avoid a signal race

/* An example of a free of signal race program using sigprocmask() and
 * pselect(). */
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
 
/* Flag that tells the daemon to exit. */
static volatile int exit_request = 0;
 
/* Signal handler. */
static void hdl (int sig)
{
	exit_request = 1;
}
 
/* Accept client on listening socket lfd and close the connection
 * immediatelly. */
static void handle_client (int lfd)
{
	int sock = accept (lfd, NULL, 0);
	if (sock < 0) {
		perror ("accept");
		exit (1);
	}
 
	puts ("accepted client");
 
	close (sock);
}
 
int main (int argc, char *argv[])
{
	int lfd;
	struct sockaddr_in myaddr;
	int yes = 1;
	sigset_t mask;
	sigset_t orig_mask;
	struct sigaction act;
 
	memset (&act, 0, sizeof(act));
	act.sa_handler = hdl;
 
	/* This server should shut down on SIGTERM. */
	if (sigaction(SIGTERM, &act, 0)) {
		perror ("sigaction");
		return 1;
	}
 
	sigemptyset (&mask);
	sigaddset (&mask, SIGTERM);
 
	if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
		perror ("sigprocmask");
		return 1;
	}
 
	lfd = socket (AF_INET, SOCK_STREAM, 0);
	if (lfd < 0) {
		perror ("socket");
		return 1;
	}
 
	if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
				&yes, sizeof(int)) == -1) {
		perror ("setsockopt");
		return 1;
	}
 
	memset (&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.s_addr = INADDR_ANY;
	myaddr.sin_port = htons (10000);
 
	if (bind(lfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
		perror ("bind");
		return 1;
	}
 
	if (listen(lfd, 5) < 0) {
		perror ("listen");
		return 1;
	}
 
	while (!exit_request) {
		fd_set fds;
		int res;
 
		/* BANG! we can get SIGTERM at this point, but it will be
		 * delivered while we are in pselect(), because now
		 * we block SIGTERM.
		 */
 
		FD_ZERO (&fds);
		FD_SET (lfd, &fds);
 
		res = pselect (lfd + 1, &fds, NULL, NULL, NULL, &orig_mask);
		if (res < 0 && errno != EINTR) {
			perror ("select");
			return 1;
		}
		else if (exit_request) {
			puts ("exit");
			break;
		}
		else if (res == 0)
			continue;
 
		if (FD_ISSET(lfd, &fds)) {
			handle_client (lfd);
		}
	}
 
	return 0;
}

Comments

Why does pselect()'s file descriptor add 1?

It is just not obvious to me why '1' is added to the file descriptor in the call to pselect (lfd + 1, ... ). Can someone explain why?

RTFM

From http://tinyurl.com/78x3fqv: nfds is the highest-numbered file descriptor in any of the three sets, plus 1. Also from UNIX Network Programming by Stevens, at pp. 163 Stevens describes how select() works (which is similar to pselect() but different) and explains that the first parameter, maxfds1, specifies the number of descriptors to be tested. Its value is to be the maximum file descriptor value plus one.

man select says: "nfds is

man select says: "nfds is the highest-numbered file descriptor in any of the three sets, plus 1"

'exit_request' checked twice

would you mind explaining why 'exit_request' is checked twice in the while(1) {...} loop above i.e. while (1) { .... else if (exit_request) { puts ("exit"); break; } else if (exit_request) break; ..... }

It was a mistake, I've fixed

It was a mistake, I've fixed the example. There is only need for a single check there.