common/spawn.c: add spawn_piped()

This commit is contained in:
Consolatis 2024-03-18 06:15:15 +01:00 committed by Johan Malm
parent 0671a3bfd3
commit e5488fefcb
3 changed files with 180 additions and 6 deletions

View 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 */

View 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>
@ -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 */
}

View 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;
/*