
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/stat.h>

#include <err.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define	FIFONAME	"fifo.tmp"
#define	FT_END		3
#define	FT_FIFO		2
#define	FT_PIPE		0
#define	FT_SOCKETPAIR	1

#define	SETUP(fd, rfds, tv) do {				\
	FD_ZERO(&(rfds));					\
	FD_SET((fd), &(rfds));					\
	(tv).tv_sec = 0;					\
	(tv).tv_usec = 0;					\
} while (0)

static int filetype;

static const char *
decode_events(int events)
{
	return (events ? "set" : "clear");
}

static void
report(int num, const char *state, int expected, int got)
{
	if (!expected == !got)
		printf("ok %-2d    ", num);
	else
		printf("not ok %-2d", num);
	printf(" %s state %s: expected %s; got %s\n",
	    filetype == FT_PIPE ? "Pipe" :
	    filetype == FT_SOCKETPAIR ? "Sock" : "FIFO",
	    state, decode_events(expected), decode_events(got));
	fflush(stdout);
}

static pid_t cpid;
static pid_t ppid;
static volatile sig_atomic_t state;

static void
catch(int sig)
{
	state++;
}

static void
child(int fd, int num)
{
	fd_set rfds;
	struct timeval tv;
	int fd1, fd2;
	char buf[256];

	if (filetype == FT_FIFO) {
		fd = open(FIFONAME, O_RDONLY | O_NONBLOCK);
		if (fd < 0)
			err(1, "open for read");
	}
	if (fd >= FD_SETSIZE)
		errx(1, "fd = %d too large for select()", fd);

	if (filetype == FT_FIFO) {
		SETUP(fd, rfds, tv);
		if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
			err(1, "select");
		/*
		 * This state (a reader for which there has never been a
		 * writer) is reported quite differently for select() than
		 * for poll().  select() must see a ready-to-read descriptor
		 * since read() will see EOF and not block; it cannot
		 * distinguish this state from the one of a reader for which
		 * there has been a writer but all writers have gone away
		 * and all data has been read.  poll() and distinguish these
		 * states by returning POLLHUP only for the latter; it does
		 * this, although this makes it inconsistent with the
		 * blockability of read() in the former.
		 */
		report(num++, "0", 1, FD_ISSET(fd, &rfds));
	}
	kill(ppid, SIGUSR1);

	usleep(1);
	while (state != 1)
		;
	if (filetype != FT_FIFO) {
		/*
		 * The connection cannot be reestablished.  Use the code that
		 * delays the read until after the writer disconnects since
		 * that case is more interesting.
		 */
		state = 4;
		goto state4;
	}
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "1", 0, FD_ISSET(fd, &rfds));
	kill(ppid, SIGUSR1);

	usleep(1);
	while (state != 2)
		;
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "2", 1, FD_ISSET(fd, &rfds));
	if (read(fd, buf, sizeof buf) != 1)
		err(1, "read");
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "2a", 0, FD_ISSET(fd, &rfds));
	kill(ppid, SIGUSR1);

	usleep(1);
	while (state != 3)
		;
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "3", 1, FD_ISSET(fd, &rfds));
	kill(ppid, SIGUSR1);

	/*
	 * Now we expect a new writer, and a new connection too since
	 * we read all the data.  The only new point is that we didn't
	 * start quite from scratch since the read fd is not new.  Check
	 * startup state as above, but don't do the read as above.
	 */
	usleep(1);
	while (state != 4)
		;
state4:
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "4", 0, FD_ISSET(fd, &rfds));
	kill(ppid, SIGUSR1);

	usleep(1);
	while (state != 5)
		;
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "5", 1, FD_ISSET(fd, &rfds));
	kill(ppid, SIGUSR1);

	usleep(1);
	while (state != 6)
		;
	/*
	 * Now we have no writer, but should still have data from the old
	 * writer.  Check that we have a data-readable condition, and that
	 * the data can be read in the usual way.
	 */
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "6", 1, FD_ISSET(fd, &rfds));
	if (read(fd, buf, sizeof buf) != 1)
		err(1, "read");
	SETUP(fd, rfds, tv);
	if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
		err(1, "select");
	report(num++, "6a", 1, FD_ISSET(fd, &rfds));
	if (filetype == FT_FIFO) {
		/*
		 * Check that the readable-data condition is sticky for a
		 * new reader and for the old reader.  We really only have
		 * a hangup condition, but select() can only see this as
		 * a readable-data condition for null data.  select()
		 * cannot distinguish this state from the initial state
		 * where there is a reader but has never been a writer, so
		 * the following tests (to follow the pattern in pipepoll.c)
		 * essentially test state 0 again.
		 */
		fd2 = open(FIFONAME, O_RDONLY | O_NONBLOCK);
		if (fd2 < 0)
			err(1, "open for read");
		fd1 = fd;
		fd = fd2;
		SETUP(fd, rfds, tv);
		if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
			err(1, "select");
		report(num++, "6b", 1, FD_ISSET(fd, &rfds));
		fd = fd1;
		SETUP(fd, rfds, tv);
		if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
			err(1, "select");
		report(num++, "6c", 1, FD_ISSET(fd, &rfds));
		close(fd2);
		SETUP(fd, rfds, tv);
		if (select(fd + 1, &rfds, NULL, NULL, &tv) < 0)
			err(1, "select");
		report(num++, "6d", 1, FD_ISSET(fd, &rfds));
	}
	close(fd);
	kill(ppid, SIGUSR1);

	exit(0);
}

static void
parent(int fd)
{
	usleep(1);
	while (state != 1)
		;
	if (filetype == FT_FIFO) {
		fd = open(FIFONAME, O_WRONLY | O_NONBLOCK);
		if (fd < 0)
			err(1, "open for write");
	}
	kill(cpid, SIGUSR1);

	usleep(1);
	while (state != 2)
		;
	if (write(fd, "", 1) != 1)
		err(1, "write");
	kill(cpid, SIGUSR1);

	usleep(1);
	while (state != 3)
		;
	if (close(fd) != 0)
		err(1, "close for write");
	kill(cpid, SIGUSR1);

	usleep(1);
	while (state != 4)
		;
	if (filetype != FT_FIFO)
		return;
	fd = open(FIFONAME, O_WRONLY | O_NONBLOCK);
	if (fd < 0)
		err(1, "open for write");
	kill(cpid, SIGUSR1);

	usleep(1);
	while (state != 5)
		;
	if (write(fd, "", 1) != 1)
		err(1, "write");
	kill(cpid, SIGUSR1);

	usleep(1);
	while (state != 6)
		;
	if (close(fd) != 0)
		err(1, "close for write");
	kill(cpid, SIGUSR1);

	usleep(1);
	while (state != 7)
		;
}

int
main(void)
{
	int fd[2], num;

	num = 1;
	printf("1..20\n");
	fflush(stdout);
	signal(SIGUSR1, catch);
	ppid = getpid();
	for (filetype = 0; filetype < FT_END; filetype++) {
		switch (filetype) {
		case FT_FIFO:
			if (mkfifo(FIFONAME, 0666) != 0)
				err(1, "mkfifo");
			fd[0] = -1;
			fd[1] = -1;
			break;
		case FT_SOCKETPAIR:
			if (socketpair(AF_UNIX, SOCK_STREAM, AF_UNSPEC,
			    fd) != 0)
				err(1, "socketpair");
			break;
		case FT_PIPE:
			if (pipe(fd) != 0)
				err(1, "pipe");
			break;
		}
		state = 0;
		switch (cpid = fork()) {
		case -1:
			err(1, "fork");
		case 0:
			(void)close(fd[1]);
			child(fd[0], num);
			break;
		default:
			(void)close(fd[0]);
			parent(fd[1]);
			break;
		}
		num += filetype == FT_FIFO ? 12 : 4;
	}
	(void)unlink(FIFONAME);
	return (0);
}
