I am working on a personal project, and one of the things it does is to call a blocking operation on a file descriptor while a forked+exec'ed child process is running. It needs to know when the forked process exits while reading events from that file descriptor.
After some discussion on libera.chat's #posix channel, what I ended up doing was to open a self-pipe and create a new thread. This new thread waits for the child process and writes a byte into the pipe when the child terminates. The main thread poll(2)
s both the event file descriptor and the reading end of the pipe in a loop; it will break the loop after getting a single byte from the pipe and do whatever it needs to do when getting something from the event file descriptor. You can check the actual code here.
I am using poll2(2)
in order to close both pipe ends after exec'ing on the child.
I abstracted this bit out the actual program into the following snippet, which is what I want to be reviewed:
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define LEN(a) (sizeof(a)/sizeof((a)[0]))
struct Child {
pid_t pid;
int fd;
};
static void *
waitthread(void *arg)
{
struct Child *child = (struct Child *)arg;
char byte = 0xFF;
while (waitpid(child->pid, NULL, 0) == -1)
if (errno != EINTR)
err(EXIT_FAILURE, "wait");
while (write(child->fd, &byte, 1) == -1)
if (errno != EAGAIN)
err(EXIT_FAILURE, "write");
pthread_exit(0);
}
int
main(int argc, char *argv[])
{
enum { FILE_STDIN, FILE_CHILD };
enum { FILE_READ, FILE_WRITE };
struct Child child;
struct pollfd pollfds[2];
ssize_t nread;
pthread_t thrd;
int pipefds[2];
char byte;
char buf[BUFSIZ];
if (argc < 2) {
(void)fprintf(stderr, "usage: %s command [argument ...]\n", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe2(pipefds, O_NONBLOCK | O_CLOEXEC) == -1)
err(EXIT_FAILURE, "pipe2");
child.fd = pipefds[FILE_WRITE];
if ((child.pid = fork()) == -1)
err(EXIT_FAILURE, "fork");
if (child.pid == 0) {
execvp(argv[1], argv + 1);
err(EXIT_FAILURE, "execvp");
}
pollfds[FILE_CHILD].fd = pipefds[FILE_READ];
pollfds[FILE_STDIN].fd = STDIN_FILENO;
pollfds[FILE_STDIN].events = pollfds[FILE_CHILD].events = POLLIN;
if (pthread_create(&thrd, NULL, waitthread, &child) != 0)
errx(EXIT_FAILURE, "could not create thread");
for (;;) {
if (poll(pollfds, LEN(pollfds), -1) == -1) {
if (errno == EINTR)
continue;
err(EXIT_FAILURE, "poll");
}
if (pollfds[FILE_STDIN].revents & (POLLERR | POLLNVAL))
errx(EXIT_FAILURE, "%d: bad fd", pollfds[FILE_STDIN].fd);
if (pollfds[FILE_CHILD].revents & (POLLERR | POLLNVAL))
errx(EXIT_FAILURE, "%d: bad fd", pollfds[FILE_CHILD].fd);
if (pollfds[FILE_STDIN].revents & POLLHUP)
pollfds[FILE_STDIN].fd = -1;
if (pollfds[FILE_CHILD].revents & POLLHUP)
pollfds[FILE_CHILD].fd = -1;
if (pollfds[FILE_CHILD].revents & POLLIN) {
if (read(pollfds[FILE_CHILD].fd, &byte, 1) != -1) {
warnx("we are done now");
break;
}
if (errno != EAGAIN)
err(EXIT_FAILURE, "read");
}
if (pollfds[FILE_STDIN].revents & POLLIN) {
nread = read(pollfds[FILE_STDIN].fd, buf, sizeof(buf));
if (nread == -1) {
if (errno == EINTR)
continue;
err(EXIT_FAILURE, "read");
}
(void)fprintf(
stderr,
"read %zd byte%s from stdin!\n",
nread,
&"s"[nread == 1]
);
}
}
(void)pthread_join(thrd, NULL);
(void)close(pipefds[FILE_READ]);
(void)close(pipefds[FILE_WRITE]);
return EXIT_SUCCESS;
}
Compile it with cc -lpthread c.c
and run it passing a command to be spawned, especially one that takes some time to return, like ./a.out sleep 5
. It will read what you write into standard input and write into standard output how many bytes it has read; and while doing that, it will also wait for the spawned command to terminate.
Is it OK?
Can it fail somehow?
How can I improve it (without adding any Linuxism)?
poll(2)
on a file descriptor for the socket for the connection to the X server. The actual program is a GUI file manager which can spawn a script on user interaction (this script can, for example open a xmessage dialog asking whether to delete file when the user press DEL, or popup a xmenu when the user right clicks a file). It needs to wait for this script to terminate while kkeep responding to window events (like resizing or closing the window) from the X connection. \$\endgroup\$