]> git.mdlowis.com Git - proto/labwc.git/commitdiff
common/spawn.c: add spawn_piped()
authorConsolatis <35009135+Consolatis@users.noreply.github.com>
Mon, 18 Mar 2024 05:15:15 +0000 (06:15 +0100)
committerJohan Malm <johanmalm@users.noreply.github.com>
Sun, 24 Mar 2024 21:44:16 +0000 (21:44 +0000)
include/common/spawn.h
src/common/spawn.c
src/server.c

index 6b95b321848f24f61318a9a6098dc0b1f472844e..28557482220a9cdd96b213a7a1171e061ef3e39e 100644 (file)
@@ -2,10 +2,34 @@
 #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 */
index 11b57e7dde561ec9af7439146d4aedb314f33bb5..8c0e10bafcc290ec8628318e8bf978219ac38e25 100644 (file)
@@ -1,6 +1,7 @@
 // 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)
 {
@@ -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 */
+}
index 41ba2163e00d7e73893f6a907744fd2b3dfd7ed5..ceac9cec1a59d8245d8e559dbb8ed0e47224115f 100644 (file)
@@ -2,6 +2,7 @@
 #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>
@@ -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;
 
        /*