#ifndef LABWC_SPAWN_H
#define LABWC_SPAWN_H
+#include <sys/types.h>
+
/**
* 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 */
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
+#include <fcntl.h>
#include <glib.h>
#include <signal.h>
#include <stdio.h>
#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)
{
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);
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 */
+}
#define _POSIX_C_SOURCE 200809L
#include "config.h"
#include <signal.h>
+#include <string.h>
#include <sys/wait.h>
#include <wlr/backend/headless.h>
#include <wlr/backend/multi.h>
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;
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)
{
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;
/*