#include #include #include #include #include #include #include #include #include #include "buf.h" #include "list.h" #include "peer.h" #include "uuid.h" #include "wakeup.h" #include "exec.h" struct exec { struct peer peer; uint8_t id[UUID_LEN]; char *command; exec_connection_handler handler; exec_get_hello hello; void *passthrough; uint32_t *count; pid_t child; struct list_head exec_list; }; static struct list_head exec_head = LIST_HEAD_INIT(exec_head); static void exec_spawn_wrapper(struct peer *); static void exec_del(struct exec *exec) { (*exec->count)--; if (exec->child > 0) { fprintf(stderr, "E %s: Sending SIGTERM to child process %d\n", exec->id, exec->child); // Racy with the process terminating, so don't assert on it kill(exec->child, SIGTERM); assert(waitpid(exec->child, NULL, 0) == exec->child); } free(exec->command); free(exec); } static void exec_close_handler(struct peer *peer) { struct exec *exec = (struct exec *) peer; int status; assert(waitpid(exec->child, &status, WNOHANG) == exec->child); exec->child = -1; if (WIFEXITED(status)) { fprintf(stderr, "E %s: Client exited with status %d\n", exec->id, WEXITSTATUS(status)); } else { assert(WIFSIGNALED(status)); fprintf(stderr, "E %s: Client exited with signal %d\n", exec->id, WTERMSIG(status)); } uint32_t delay = wakeup_get_retry_delay_ms(1); fprintf(stderr, "E %s: Will retry in %ds\n", exec->id, delay / 1000); exec->peer.event_handler = exec_spawn_wrapper; wakeup_add((struct peer *) exec, delay); } static bool exec_hello(int fd, struct exec *exec) { if (!exec->hello) { return true; } struct buf buf = BUF_INIT, *buf_ptr = &buf; exec->hello(&buf_ptr, exec->passthrough); if (!buf_ptr->length) { return true; } return (write(fd, buf_at(buf_ptr, 0), buf_ptr->length) == (ssize_t) buf_ptr->length); } static void exec_parent(struct exec *exec, pid_t child, int fd) { exec->child = child; fprintf(stderr, "E %s: Child started as process %d\n", exec->id, exec->child); if (!exec_hello(fd, exec)) { assert(!close(fd)); exec_close_handler((struct peer *) exec); return; } exec->peer.event_handler = exec_close_handler; exec->handler(fd, exec->passthrough, (struct peer *) exec); } static void __attribute__ ((noreturn)) exec_child(const struct exec *exec, int fd) { assert(setsid() != -1); // We leave stderr open from child to parent // Other than that, fds should have CLOEXEC set if (fd != 0) { assert(dup2(fd, 0) == 0); } if (fd != 1) { assert(dup2(fd, 1) == 1); } if (fd != 0 && fd != 1) { assert(!close(fd)); } assert(!execl("/bin/sh", "sh", "-c", exec->command, NULL)); abort(); } static void exec_spawn(struct exec *exec) { fprintf(stderr, "E %s: Executing: %s\n", exec->id, exec->command); int fds[2]; assert(!socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds)); int res = fork(); assert(res >= 0); if (res) { assert(!close(fds[1])); exec_parent(exec, res, fds[0]); } else { assert(!close(fds[0])); exec_child(exec, fds[1]); } } static void exec_spawn_wrapper(struct peer *peer) { struct exec *exec = (struct exec *) peer; exec_spawn(exec); } void exec_cleanup() { struct exec *iter, *next; list_for_each_entry_safe(iter, next, &exec_head, exec_list) { exec_del(iter); } } void exec_new(char *command, exec_connection_handler handler, exec_get_hello hello, void *passthrough, uint32_t *count) { (*count)++; struct exec *exec = malloc(sizeof(*exec)); exec->peer.fd = -1; uuid_gen(exec->id); exec->command = strdup(command); exec->handler = handler; exec->hello = hello; exec->passthrough = passthrough; exec->count = count; list_add(&exec->exec_list, &exec_head); exec_spawn(exec); }