#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(__OpenBSD__)
#include <termios.h>
#include <util.h>
#endif
#if !defined(__linux__)
#define HAVE_SOCKADDR_LEN 1
#endif
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
#define TEST_FIFO_NAME "iocond_fifo"
enum filetype {
FTYPE_NONE,
FTYPE_FIFO,
FTYPE_PIPE,
FTYPE_PTY,
FTYPE_SOCKET_TCP,
FTYPE_SOCKET_UDP,
FTYPE_SOCKET_UNIX,
};
static struct {
const char *name;
enum filetype type;
} filetypes[] = {
{ "fifo", FTYPE_FIFO },
{ "pipe", FTYPE_PIPE },
#if defined(__OpenBSD__)
{ "pty", FTYPE_PTY },
#endif
{ "socket-tcp", FTYPE_SOCKET_TCP },
{ "socket-udp", FTYPE_SOCKET_UDP },
{ "socket-unix", FTYPE_SOCKET_UNIX },
};
static enum filetype filetype = FTYPE_NONE;
static void cleanup(void);
static void proc_barrier(int);
static void proc_child(int, int);
static void proc_parent(int, int);
int
main(int argc, char *argv[])
{
const char *ftname;
int bfd[2], fds[2];
int child_fd = -1;
int parent_fd = -1;
int sock = -1;
unsigned int i;
pid_t pid;
alarm(10);
if (argc != 2) {
fprintf(stderr, "usage: %s filetype\n", argv[0]);
return 1;
}
ftname = argv[1];
for (i = 0; i < sizeof(filetypes) / sizeof(filetypes[0]); i++) {
if (strcmp(ftname, filetypes[i].name) == 0) {
filetype = filetypes[i].type;
break;
}
}
if (filetype == FTYPE_NONE)
errx(1, "unknown filetype");
if (socketpair(AF_UNIX, SOCK_STREAM, 0, bfd) == -1)
err(1, "socketpair");
atexit(cleanup);
switch (filetype) {
case FTYPE_FIFO:
(void)unlink(TEST_FIFO_NAME);
if (mkfifo(TEST_FIFO_NAME, 0644) == -1)
err(1, "mkfifo");
break;
case FTYPE_PIPE:
if (pipe(fds) == -1)
err(1, "pipe");
parent_fd = fds[0];
child_fd = fds[1];
break;
#if defined(__OpenBSD__)
case FTYPE_PTY:
if (openpty(&parent_fd, &child_fd, NULL, NULL, NULL) == -1)
err(1, "openpty");
break;
#endif
case FTYPE_SOCKET_TCP: {
struct sockaddr_in inaddr;
sock = socket(AF_INET, SOCK_STREAM, 0);
memset(&inaddr, 0, sizeof(inaddr));
#ifdef HAVE_SOCKADDR_LEN
inaddr.sin_len = sizeof(inaddr);
#endif
inaddr.sin_family = AF_INET;
inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(sock, (struct sockaddr *)&inaddr,
sizeof(inaddr)) == -1)
err(1, "bind");
if (listen(sock, 1) == -1)
err(1, "listen");
break;
}
case FTYPE_SOCKET_UDP: {
struct sockaddr_in inaddr;
sock = socket(AF_INET, SOCK_DGRAM, 0);
memset(&inaddr, 0, sizeof(inaddr));
#ifdef HAVE_SOCKADDR_LEN
inaddr.sin_len = sizeof(inaddr);
#endif
inaddr.sin_family = AF_INET;
inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(sock, (struct sockaddr *)&inaddr,
sizeof(inaddr)) == -1)
err(1, "bind");
break;
}
case FTYPE_SOCKET_UNIX:
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1)
err(1, "socketpair");
parent_fd = fds[0];
child_fd = fds[1];
break;
default:
errx(1, "unhandled filetype");
}
pid = fork();
switch (pid) {
case -1:
err(1, "fork");
case 0:
switch (filetype) {
case FTYPE_FIFO:
child_fd = open(TEST_FIFO_NAME, O_WRONLY);
if (child_fd == -1)
err(1, "child: open");
break;
case FTYPE_SOCKET_TCP: {
struct sockaddr_in inaddr;
socklen_t inaddrlen;
int on = 1;
inaddrlen = sizeof(inaddr);
if (getsockname(sock, (struct sockaddr *)&inaddr,
&inaddrlen) == -1)
err(1, "child: getsockname");
child_fd = socket(AF_INET, SOCK_STREAM, 0);
if (child_fd == -1)
err(1, "child: socket");
if (connect(child_fd, (struct sockaddr *)&inaddr,
sizeof(inaddr)) == -1)
err(1, "child: connect");
if (setsockopt(child_fd, IPPROTO_TCP, TCP_NODELAY,
&on, sizeof(on)) == -1)
err(1, "child: setsockopt(TCP_NODELAY)");
break;
}
case FTYPE_SOCKET_UDP: {
struct sockaddr_in inaddr;
socklen_t inaddrlen;
inaddrlen = sizeof(inaddr);
if (getsockname(sock, (struct sockaddr *)&inaddr,
&inaddrlen) == -1)
err(1, "child: getsockname");
child_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (child_fd == -1)
err(1, "child: socket");
if (connect(child_fd, (struct sockaddr *)&inaddr,
sizeof(inaddr)) == -1)
err(1, "child: connect");
break;
}
default:
break;
}
if (parent_fd != -1) {
close(parent_fd);
parent_fd = -1;
}
if (sock != -1) {
close(sock);
sock = -1;
}
proc_child(child_fd, bfd[1]);
_exit(0);
default:
switch (filetype) {
case FTYPE_FIFO:
parent_fd = open(TEST_FIFO_NAME, O_RDONLY);
if (parent_fd == -1)
err(1, "parent: open");
break;
case FTYPE_SOCKET_TCP: {
int on = 1;
parent_fd = accept(sock, NULL, NULL);
if (parent_fd == -1)
err(1, "parent: accept");
if (setsockopt(parent_fd, IPPROTO_TCP, TCP_NODELAY,
&on, sizeof(on)) == -1)
err(1, "parent: setsockopt(TCP_NODELAY)");
break;
}
case FTYPE_SOCKET_UDP:
parent_fd = sock;
sock = -1;
break;
default:
break;
}
if (child_fd != -1) {
close(child_fd);
child_fd = -1;
}
if (sock != -1) {
close(sock);
sock = -1;
}
proc_parent(parent_fd, bfd[0]);
break;
}
if (waitpid(pid, NULL, 0) == -1)
err(1, "waitpid");
return 0;
}
static void
cleanup(void)
{
if (filetype == FTYPE_FIFO)
(void)unlink(TEST_FIFO_NAME);
}
static void
proc_barrier(int fd)
{
int ret;
char b = 0;
ret = write(fd, &b, 1);
assert(ret == 1);
ret = read(fd, &b, 1);
assert(ret == 1);
}
static void
fdset_init(fd_set *rfd, fd_set *wfd, fd_set *efd, int fd)
{
FD_ZERO(rfd);
FD_ZERO(wfd);
FD_ZERO(efd);
FD_SET(fd, rfd);
FD_SET(fd, wfd);
FD_SET(fd, efd);
}
static void
proc_child(int fd, int bfd)
{
char buf[1024];
fd_set efd, rfd, wfd;
struct timeval tv = { 0, 1 };
struct timeval zerotv = { 0, 0 };
size_t nbytes;
int ret;
char b = 0;
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) == 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
proc_barrier(bfd);
ret = write(fd, &b, 1);
assert(ret == 1);
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) == 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
proc_barrier(bfd);
proc_barrier(bfd);
if (filetype != FTYPE_SOCKET_UDP) {
memset(buf, 0, sizeof(buf));
nbytes = 0;
for (;;) {
FD_ZERO(&wfd);
FD_SET(fd, &wfd);
ret = select(fd + 1, NULL, &wfd, NULL, &zerotv);
if (ret == 0)
break;
assert(ret == 1);
assert(FD_ISSET(fd, &wfd) != 0);
ret = write(fd, buf, sizeof(buf));
assert(ret > 0);
nbytes += ret;
}
ret = write(bfd, &nbytes, sizeof(nbytes));
assert(ret == sizeof(nbytes));
proc_barrier(bfd);
}
proc_barrier(bfd);
switch (filetype) {
#if defined(__OpenBSD__)
case FTYPE_PTY: {
proc_barrier(bfd);
ret = write(fd, &b, 1);
assert(ret == 1);
if (ioctl(fd, UIOCCMD(42), NULL) == -1)
err(1, "child: ioctl(UIOCCMD)");
ret = write(fd, &b, 1);
assert(ret == 1);
proc_barrier(bfd);
proc_barrier(bfd);
break;
}
#endif
case FTYPE_SOCKET_TCP:
ret = send(fd, &b, 1, 0);
assert(ret == 1);
ret = send(fd, &b, 1, MSG_OOB);
assert(ret == 1);
ret = send(fd, &b, 1, 0);
assert(ret == 1);
proc_barrier(bfd);
proc_barrier(bfd);
break;
default:
break;
}
switch (filetype) {
case FTYPE_SOCKET_TCP:
case FTYPE_SOCKET_UNIX:
ret = write(fd, &b, 1);
assert(ret == 1);
ret = shutdown(fd, SHUT_WR);
assert(ret == 0);
proc_barrier(bfd);
proc_barrier(bfd);
if (filetype == FTYPE_SOCKET_TCP)
usleep(10000);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 2);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
case FTYPE_FIFO:
case FTYPE_PIPE:
case FTYPE_PTY:
case FTYPE_SOCKET_UDP:
default:
break;
}
proc_barrier(bfd);
close(fd);
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, NULL);
assert(ret == -1);
assert(errno == EBADF);
}
static void
proc_parent(int fd, int bfd)
{
char buf[1024];
fd_set efd, rfd, wfd;
struct timeval tv = { 0, 1 };
struct timeval zerotv = { 0, 0 };
size_t nbytes;
int ret, retries;
char b = 0;
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
if (filetype == FTYPE_FIFO || filetype == FTYPE_PIPE)
FD_CLR(fd, &wfd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
switch (filetype) {
case FTYPE_FIFO:
case FTYPE_PIPE:
assert(ret == 0);
assert(FD_ISSET(fd, &rfd) == 0);
assert(FD_ISSET(fd, &wfd) == 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
default:
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) == 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
}
proc_barrier(bfd);
proc_barrier(bfd);
if (filetype == FTYPE_SOCKET_TCP ||
filetype == FTYPE_SOCKET_UDP)
usleep(10000);
fdset_init(&rfd, &wfd, &efd, fd);
if (filetype == FTYPE_FIFO || filetype == FTYPE_PIPE)
FD_CLR(fd, &wfd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
switch (filetype) {
case FTYPE_FIFO:
case FTYPE_PIPE:
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) == 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
case FTYPE_PTY:
case FTYPE_SOCKET_TCP:
case FTYPE_SOCKET_UDP:
case FTYPE_SOCKET_UNIX:
assert(ret == 2);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
default:
assert(0);
}
proc_barrier(bfd);
ret = read(fd, &b, 1);
assert(ret == 1);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, NULL, NULL, &tv);
assert(ret == 0);
proc_barrier(bfd);
if (filetype != FTYPE_SOCKET_UDP) {
nbytes = 0;
ret = read(bfd, &nbytes, sizeof(nbytes));
assert(ret == sizeof(nbytes));
proc_barrier(bfd);
retries = 5;
while (retries > 0) {
FD_ZERO(&rfd);
FD_SET(fd, &rfd);
ret = select(fd + 1, &rfd, NULL, NULL, &zerotv);
if (ret == 0) {
retries--;
if (nbytes > 0 && retries > 0)
usleep(10000);
continue;
}
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) != 0);
assert(nbytes > 0);
ret = read(fd, buf, MIN(sizeof(buf), nbytes));
assert(ret > 0);
nbytes -= ret;
}
assert(nbytes == 0);
}
proc_barrier(bfd);
switch (filetype) {
#if defined(__OpenBSD__)
case FTYPE_PTY: {
int off = 0;
int on = 1;
if (ioctl(fd, TIOCUCNTL, &on) == -1)
err(1, "parent: ioctl(TIOCUCNTL, 1)");
proc_barrier(bfd);
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 3);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) != 0);
ret = read(fd, buf, sizeof(buf));
assert(ret == 1);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 2);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
ret = read(fd, buf, sizeof(buf));
assert(ret == 3);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) == 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
if (ioctl(fd, TIOCUCNTL, &off) == -1)
err(1, "parent: ioctl(TIOCUCNTL, 0)");
proc_barrier(bfd);
break;
}
#endif
case FTYPE_SOCKET_TCP: {
int atmark;
int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &on,
sizeof(on)) == -1)
err(1, "parent: setsockopt(SO_OOBINLINE)");
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 3);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) != 0);
atmark = 0;
if (ioctl(fd, SIOCATMARK, &atmark) == -1)
err(1, "parent: ioctl(SIOCATMARK)");
assert(atmark == 0);
ret = recv(fd, buf, sizeof(buf), 0);
assert(ret == 1);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 3);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) != 0);
atmark = 0;
if (ioctl(fd, SIOCATMARK, &atmark) == -1)
err(1, "parent: ioctl(SIOCATMARK)");
assert(atmark != 0);
ret = recv(fd, &b, 1, 0);
assert(ret == 1);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 2);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
atmark = 0;
if (ioctl(fd, SIOCATMARK, &atmark) == -1)
err(1, "parent: ioctl(SIOCATMARK)");
assert(atmark == 0);
ret = recv(fd, buf, sizeof(buf), 0);
assert(ret == 1);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) == 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
proc_barrier(bfd);
break;
}
default:
break;
}
switch (filetype) {
case FTYPE_SOCKET_TCP:
case FTYPE_SOCKET_UNIX:
proc_barrier(bfd);
if (filetype == FTYPE_SOCKET_TCP)
usleep(10000);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, NULL, NULL, &tv);
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) != 0);
ret = read(fd, &b, 1);
assert(ret == 1);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, NULL, NULL, &tv);
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) != 0);
ret = read(fd, &b, 1);
assert(ret == 0);
ret = shutdown(fd, SHUT_WR);
assert(ret == 0);
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
assert(ret == 2);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
case FTYPE_FIFO:
case FTYPE_PIPE:
case FTYPE_PTY:
case FTYPE_SOCKET_UDP:
default:
break;
}
proc_barrier(bfd);
proc_barrier(bfd);
fdset_init(&rfd, &wfd, &efd, fd);
if (filetype == FTYPE_FIFO || filetype == FTYPE_PIPE)
FD_CLR(fd, &wfd);
ret = select(fd + 1, &rfd, &wfd, &efd, &tv);
switch (filetype) {
case FTYPE_FIFO:
case FTYPE_PIPE:
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) == 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
case FTYPE_PTY:
case FTYPE_SOCKET_TCP:
case FTYPE_SOCKET_UNIX:
assert(ret == 2);
assert(FD_ISSET(fd, &rfd) != 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
case FTYPE_SOCKET_UDP:
assert(ret == 1);
assert(FD_ISSET(fd, &rfd) == 0);
assert(FD_ISSET(fd, &wfd) != 0);
assert(FD_ISSET(fd, &efd) == 0);
break;
default:
assert(0);
}
close(fd);
}