From e5488fefcb43a6dc6afc0f153bcc127da0d36eeb Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:15:15 +0100 Subject: [PATCH] common/spawn.c: add spawn_piped() --- include/common/spawn.h | 24 ++++++++++ src/common/spawn.c | 101 ++++++++++++++++++++++++++++++++++++++--- src/server.c | 61 +++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 6 deletions(-) diff --git a/include/common/spawn.h b/include/common/spawn.h index 6b95b321..28557482 100644 --- a/include/common/spawn.h +++ b/include/common/spawn.h @@ -2,10 +2,34 @@ #ifndef LABWC_SPAWN_H #define LABWC_SPAWN_H +#include + /** * spawn_async_no_shell - execute asynchronously * @command: command to be executed */ void spawn_async_no_shell(char const *command); +/** + * spawn_piped - execute asyncronously + * @command: command to be executed + * @pipe_fd: set to the read end of a pipe + * connected to stdout of the command + * + * Notes: + * The returned pid_t has to be waited for to + * not produce zombies and the pipe_fd has to + * be closed. spawn_piped_close() can be used + * to ensure both. + */ +pid_t spawn_piped(const char *command, int *pipe_fd); + +/** + * spawn_piped_close - clean up a previous + * spawn_piped() process + * @pid: will be waitpid()'d for + * @pipe_fd: will be close()'d + */ +void spawn_piped_close(pid_t pid, int pipe_fd); + #endif /* LABWC_SPAWN_H */ diff --git a/src/common/spawn.c b/src/common/spawn.c index 11b57e7d..8c0e10ba 100644 --- a/src/common/spawn.c +++ b/src/common/spawn.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include +#include #include #include #include @@ -11,6 +12,26 @@ #include "common/spawn.h" #include "common/fd_util.h" +static void +reset_signals_and_limits(void) +{ + restore_nofile_limit(); + + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + /* Restore ignored signals */ + signal(SIGPIPE, SIG_DFL); +} + +static void +set_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} + void spawn_async_no_shell(char const *command) { @@ -39,14 +60,9 @@ spawn_async_no_shell(char const *command) wlr_log(WLR_ERROR, "unable to fork()"); goto out; case 0: - restore_nofile_limit(); + reset_signals_and_limits(); setsid(); - sigset_t set; - sigemptyset(&set); - sigprocmask(SIG_SETMASK, &set, NULL); - /* Restore ignored signals */ - signal(SIGPIPE, SIG_DFL); grandchild = fork(); if (grandchild == 0) { execvp(argv[0], argv); @@ -63,3 +79,76 @@ out: g_strfreev(argv); } +pid_t +spawn_piped(const char *command, int *pipe_fd) +{ + assert(command); + + int pipe_rw[2]; + if (pipe(pipe_rw) != 0) { + wlr_log(WLR_ERROR, "unable to pipe()"); + return -1; + } + + pid_t pid = fork(); + if (pid < 0) { + close(pipe_rw[0]); + close(pipe_rw[1]); + wlr_log(WLR_ERROR, "unable to fork()"); + return pid; + } + + if (pid == 0) { + /* child */ + reset_signals_and_limits(); + + /* + * replace stdin and stderr with /dev/null + * and stdout with the write end of the pipe + */ + dup2(pipe_rw[1], STDOUT_FILENO); + close(pipe_rw[0]); + close(pipe_rw[1]); + + int dev_null = open("/dev/null", O_RDWR); + if (dev_null < 0) { + wlr_log_errno(WLR_ERROR, "opening /dev/null failed"); + /* + * Just close stdin and stderr and + * hope $command can deal with that. + */ + close(STDIN_FILENO); + close(STDERR_FILENO); + } else { + dup2(dev_null, STDIN_FILENO); + dup2(dev_null, STDERR_FILENO); + close(dev_null); + } + + execl("/bin/sh", "sh", "-c", command, NULL); + /* + * Our stderr points to /dev/null or is closed + * at this point so logging is pretty useless. + */ + _exit(1); + } + + /* labwc */ + close(pipe_rw[1]); + + /* + * Prevent leaking the read end of the pipe to further + * children forked during the lifetime of the descriptor. + */ + set_cloexec(pipe_rw[0]); + + *pipe_fd = pipe_rw[0]; + return pid; +} + +void +spawn_piped_close(pid_t pid, int pipe_fd) +{ + close(pipe_fd); + /* waitpid() is done in a generic SIGCHLD handler in src/server.c */ +} diff --git a/src/server.c b/src/server.c index 41ba2163..ceac9cec 100644 --- a/src/server.c +++ b/src/server.c @@ -2,6 +2,7 @@ #define _POSIX_C_SOURCE 200809L #include "config.h" #include +#include #include #include #include @@ -42,6 +43,7 @@ static struct wlr_compositor *compositor; static struct wl_event_source *sighup_source; static struct wl_event_source *sigint_source; static struct wl_event_source *sigterm_source; +static struct wl_event_source *sigchld_source; static struct server *g_server; @@ -83,6 +85,63 @@ handle_sigterm(int signal, void *data) return 0; } +static int +handle_sigchld(int signal, void *data) +{ + siginfo_t info; + info.si_pid = 0; + + /* First call waitid() with NOWAIT which doesn't consume the zombie */ + if (waitid(P_ALL, /*id*/ 0, &info, WEXITED | WNOHANG | WNOWAIT) == -1) { + return 0; + } + + if (info.si_pid == 0) { + /* No children in waitable state */ + return 0; + } + +#if HAVE_XWAYLAND + /* Verify that we do not break xwayland lazy initialization */ + struct server *server = data; + if (server->xwayland && server->xwayland->server + && info.si_pid == server->xwayland->server->pid) { + return 0; + } +#endif + + /* And then do the actual (consuming) lookup again */ + int ret = waitid(P_PID, info.si_pid, &info, WEXITED); + if (ret == -1) { + wlr_log(WLR_ERROR, "blocking waitid() for %ld failed: %d", + (long)info.si_pid, ret); + return 0; + } + + switch (info.si_code) { + case CLD_EXITED: + wlr_log(info.si_status == 0 ? WLR_DEBUG : WLR_ERROR, + "spawned child %ld exited with %d", + (long)info.si_pid, info.si_status); + break; + case CLD_KILLED: + case CLD_DUMPED: + ; /* works around "a label can only be part of a statement" */ + const char *signame = strsignal(info.si_status); + wlr_log(WLR_ERROR, + "spawned child %ld terminated with signal %d (%s)", + (long)info.si_pid, info.si_status, + signame ? signame : "unknown"); + break; + default: + wlr_log(WLR_ERROR, + "spawned child %ld terminated unexpectedly: %d" + " please report", (long)info.si_pid, info.si_code); + } + + return 0; +} + static void seat_inhibit_input(struct seat *seat, struct wl_client *active_client) { @@ -239,6 +298,8 @@ server_init(struct server *server) event_loop, SIGINT, handle_sigterm, server->wl_display); sigterm_source = wl_event_loop_add_signal( event_loop, SIGTERM, handle_sigterm, server->wl_display); + sigchld_source = wl_event_loop_add_signal( + event_loop, SIGCHLD, handle_sigchld, server); server->wl_event_loop = event_loop; /* -- 2.52.0