[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Chicken-users] Interaction of subprocess and condition handling
From: |
Lassi Kortela |
Subject: |
Re: [Chicken-users] Interaction of subprocess and condition handling |
Date: |
Sat, 20 Jul 2019 01:53:49 +0300 |
User-agent: |
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:60.0) Gecko/20100101 Thunderbird/60.8.0 |
Obviously in Chicken you would not exit the parent, but you'd exit the
child with code 126 as here. In Chicken you'd probably want some fancy
way to propagate the errno value from the child to the parent after a
failed execve(). It's unwise to do something like _exit(errno); One
solution would be to open a close-on-exec pipe from parent to child at a
known fd number and pass the errno value via the pipe. The parent would
probably have to poll() or select() on the pipe to check for error, but
I haven't thought this through.
I tried that technique and it worked surprisingly easily! Source code,
below, also downloadable at <https://misc.lassi.io/2019/exec-error-pipe.c>.
Beware that threads and signals may require extra caution! This test
program only has one thread and I didn't think about signals at all. I
also don't handle EINTR.
----------------------------------------------------------------------
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void warn(const char *msg) {
fprintf(stderr, "%s\n", msg);
}
static void warnsys(const char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
}
static void diesys(const char *msg) {
warnsys(msg);
exit(1);
}
static void die(const char *msg) {
warn(msg);
exit(1);
}
static void
set_close_on_exec(int fd)
{
int flags;
if ((flags = fcntl(fd, F_GETFD)) == -1) {
diesys("cannot get file descriptor flags");
}
flags &= ~FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
diesys("cannot set close-on-exec");
}
}
static void spawn(char **argv) {
pid_t child;
int status, exec_errno;
int fds[2];
struct pollfd pollfd;
ssize_t nbyte;
if (pipe(fds) == -1) {
diesys("cannot create pipe");
}
set_close_on_exec(fds[0]);
set_close_on_exec(fds[1]);
if ((child = fork()) == -1) {
diesys("cannot fork");
}
if (!child) {
// We are in the new child process.
close(fds[0]);
execvp(argv[0], argv);
exec_errno = errno;
nbyte = write(fds[1], &exec_errno, sizeof(exec_errno));
if (nbyte == (ssize_t)-1) {
warnsys("completely borked (child)");
} else if (nbyte != (ssize_t)sizeof(exec_errno)) {
warn("completely borked (child)");
}
_exit(126);
}
// We are in the old parent process.
close(fds[1]);
memset(&pollfd, 0, sizeof(pollfd));
pollfd.fd = fds[0];
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1) {
diesys("cannot poll");
}
exec_errno = 0;
if (pollfd.revents & POLLIN) {
nbyte = read(pollfd.fd, &exec_errno, sizeof(exec_errno));
if (nbyte == 0) {
// We don't get any data, means the pipe was closed.
} else if (nbyte == (ssize_t)-1) {
warnsys("completely borked (parent)");
} else if (nbyte != (ssize_t)sizeof(exec_errno)) {
warn("completely borked (parent)");
}
}
if (waitpid(child, &status, 0) == -1) {
diesys("cannot wait for child process");
}
if (!WIFEXITED(status)) {
die("child process did not exit normally");
}
if (exec_errno != 0) {
errno = exec_errno;
warnsys("cannot execute command");
} else if (WEXITSTATUS(status) == 0) {
// Success, all good.
} else {
fprintf(stderr, "Child exited with code %d\n",
(int)WEXITSTATUS(status));
}
}
int main(int argc, char **argv) {
if (argc < 2) {
die("usage: spawn command [args...]");
}
spawn(&argv[1]);
return 0;
}