Merge branch 'spawn-func' into 'master'

Draft: xwayland: Introduce xwayland_spawn_func_t

See merge request wlroots/wlroots!5011
This commit is contained in:
Kenny Levinsen 2025-04-21 08:00:33 +00:00
commit 08ff543344
4 changed files with 68 additions and 76 deletions

View file

@ -14,16 +14,24 @@
#include <time.h> #include <time.h>
#include <wayland-server-core.h> #include <wayland-server-core.h>
/**
* Xwayland spawn function. pathname and args are compatible with the execv
* family of functions. envp is a NULL terminated list of enironment variables
* that must be added before exec, while uncloexec is a -1 terminated list of
* file descriptors that must have CLOEXEC unset after fork but before exec.
*/
typedef bool (*xwayland_spawn_func_t)(char *pathname, char *args[], char *envp[], int uncloexec[]);
struct wlr_xwayland_server_options { struct wlr_xwayland_server_options {
bool lazy; bool lazy;
bool enable_wm; bool enable_wm;
bool no_touch_pointer_emulation; bool no_touch_pointer_emulation;
bool force_xrandr_emulation; bool force_xrandr_emulation;
int terminate_delay; // in seconds, 0 to terminate immediately int terminate_delay; // in seconds, 0 to terminate immediately
xwayland_spawn_func_t spawn_handler;
}; };
struct wlr_xwayland_server { struct wlr_xwayland_server {
pid_t pid;
struct wl_client *client; struct wl_client *client;
struct wl_event_source *pipe_source; struct wl_event_source *pipe_source;
int wm_fd[2], wl_fd[2]; int wm_fd[2], wl_fd[2];

View file

@ -15,6 +15,7 @@
#include <xcb/xcb_ewmh.h> #include <xcb/xcb_ewmh.h>
#include <xcb/xcb_icccm.h> #include <xcb/xcb_icccm.h>
#include <wlr/util/addon.h> #include <wlr/util/addon.h>
#include <wlr/xwayland/server.h>
struct wlr_box; struct wlr_box;
struct wlr_xwm; struct wlr_xwm;
@ -266,7 +267,7 @@ struct wlr_xwayland_minimize_event {
* client tries to connect. * client tries to connect.
*/ */
struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display,
struct wlr_compositor *compositor, bool lazy); struct wlr_compositor *compositor, bool lazy, xwayland_spawn_func_t spawn_func);
/** /**
* Create an XWM from an existing Xwayland server. * Create an XWM from an existing Xwayland server.

View file

@ -1,3 +1,4 @@
#define _XOPEN_SOURCE 700 // for putenv
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
@ -23,25 +24,48 @@ static void safe_close(int fd) {
} }
} }
noreturn static void exec_xwayland(struct wlr_xwayland_server *server, static bool default_xwayland_spawn_func(char *program, char *args[], char *envp[], int uncloexec[]) {
int notify_fd) { for (int idx = 0; uncloexec[idx] != -1; idx++) {
if (!set_cloexec(server->x_fd[0], false) || if (!set_cloexec(uncloexec[idx], false)) {
!set_cloexec(server->x_fd[1], false) || wlr_log(WLR_ERROR, "Unable to clear CLOEXEC");
!set_cloexec(server->wl_fd[1], false)) { _exit(EXIT_FAILURE);
wlr_log(WLR_ERROR, "Failed to unset CLOEXEC on FD"); }
_exit(EXIT_FAILURE);
}
if (server->options.enable_wm && !set_cloexec(server->wm_fd[1], false)) {
wlr_log(WLR_ERROR, "Failed to unset CLOEXEC on FD");
_exit(EXIT_FAILURE);
} }
// The compositor may have messed with signal handling, try to clean it up for (int idx = 0; envp[idx] != NULL; idx++) {
sigset_t set; if (putenv(envp[idx]) != 0) {
sigemptyset(&set); wlr_log_errno(WLR_ERROR, "Unable to putenv: %s", envp[idx]);
sigprocmask(SIG_SETMASK, &set, NULL); _exit(EXIT_FAILURE);
signal(SIGPIPE, SIG_DFL); }
signal(SIGCHLD, SIG_DFL); }
enum wlr_log_importance verbosity = wlr_log_get_verbosity();
int devnull = open("/dev/null", O_WRONLY | O_CREAT | O_CLOEXEC, 0666);
if (devnull < 0) {
wlr_log_errno(WLR_ERROR, "xwayland: failed to open /dev/null");
_exit(EXIT_FAILURE);
}
if (verbosity < WLR_INFO) {
dup2(devnull, STDOUT_FILENO);
}
if (verbosity < WLR_INFO) {
dup2(devnull, STDERR_FILENO);
}
execv(program, args);
wlr_log_errno(WLR_ERROR, "execve failed");
_exit(EXIT_SUCCESS); // Close child process
}
static bool spawn_xwayland(struct wlr_xwayland_server *server,
int notify_fd) {
int uncloexec[] = {
server->x_fd[0],
server->x_fd[1],
server->wl_fd[1],
server->options.enable_wm ? server->wm_fd[1] : -1,
-1,
};
char *argv[64] = {0}; char *argv[64] = {0};
size_t i = 0; size_t i = 0;
@ -107,27 +131,15 @@ noreturn static void exec_xwayland(struct wlr_xwayland_server *server,
assert(i <= sizeof(argv) / sizeof(argv[0])); assert(i <= sizeof(argv) / sizeof(argv[0]));
char wayland_socket_str[16]; char wayland_socket_str[32];
snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", server->wl_fd[1]); snprintf(wayland_socket_str, sizeof(wayland_socket_str), "WAYLAND_SOCKET=%d", server->wl_fd[1]);
setenv("WAYLAND_SOCKET", wayland_socket_str, true);
wlr_log(WLR_INFO, "Starting Xwayland on :%d", server->display); char *envp[] = {
wayland_socket_str,
NULL,
};
// Closes stdout/stderr depending on log verbosity char *xwayland_path = getenv("WLR_XWAYLAND");
enum wlr_log_importance verbosity = wlr_log_get_verbosity();
int devnull = open("/dev/null", O_WRONLY | O_CREAT | O_CLOEXEC, 0666);
if (devnull < 0) {
wlr_log_errno(WLR_ERROR, "XWayland: failed to open /dev/null");
_exit(EXIT_FAILURE);
}
if (verbosity < WLR_INFO) {
dup2(devnull, STDOUT_FILENO);
}
if (verbosity < WLR_ERROR) {
dup2(devnull, STDERR_FILENO);
}
const char *xwayland_path = getenv("WLR_XWAYLAND");
if (xwayland_path) { if (xwayland_path) {
wlr_log(WLR_INFO, "Using Xwayland binary '%s' due to WLR_XWAYLAND", wlr_log(WLR_INFO, "Using Xwayland binary '%s' due to WLR_XWAYLAND",
xwayland_path); xwayland_path);
@ -135,12 +147,7 @@ noreturn static void exec_xwayland(struct wlr_xwayland_server *server,
xwayland_path = XWAYLAND_PATH; xwayland_path = XWAYLAND_PATH;
} }
// This returns if and only if the call fails return server->options.spawn_handler(xwayland_path, argv, envp, uncloexec);
execvp(xwayland_path, argv);
wlr_log_errno(WLR_ERROR, "failed to exec %s", xwayland_path);
close(devnull);
_exit(EXIT_FAILURE);
} }
static void server_finish_process(struct wlr_xwayland_server *server) { static void server_finish_process(struct wlr_xwayland_server *server) {
@ -260,24 +267,6 @@ static int xserver_handle_ready(int fd, uint32_t mask, void *data) {
} }
} }
while (waitpid(server->pid, NULL, 0) < 0) {
if (errno == EINTR) {
continue;
}
/* If some application has installed a SIGCHLD handler, they
* may race and waitpid() on our child, which will cause this
* waitpid() to fail. We have a signal from the
* notify pipe that things are ready, so this waitpid() is only
* to prevent zombies, which will have already been reaped by
* the application's SIGCHLD handler.
*/
if (errno == ECHILD) {
break;
}
wlr_log_errno(WLR_ERROR, "waitpid for Xwayland fork failed");
goto error;
}
/* Xwayland will only write on the fd once it has finished its /* Xwayland will only write on the fd once it has finished its
* initial setup. Getting an event here without READABLE means * initial setup. Getting an event here without READABLE means
* the server end failed. * the server end failed.
@ -387,23 +376,12 @@ static bool server_start(struct wlr_xwayland_server *server) {
wl_signal_emit_mutable(&server->events.start, NULL); wl_signal_emit_mutable(&server->events.start, NULL);
server->pid = fork(); if (!spawn_xwayland(server, notify_fd[1])) {
if (server->pid < 0) { wlr_log(WLR_ERROR, "Spawning Xwayland failed");
wlr_log_errno(WLR_ERROR, "fork failed");
close(notify_fd[0]); close(notify_fd[0]);
close(notify_fd[1]); close(notify_fd[1]);
server_finish_process(server); server_finish_process(server);
return false; return false;
} else if (server->pid == 0) {
pid_t pid = fork();
if (pid < 0) {
wlr_log_errno(WLR_ERROR, "second fork failed");
_exit(EXIT_FAILURE);
} else if (pid == 0) {
exec_xwayland(server, notify_fd[1]);
}
_exit(EXIT_SUCCESS);
} }
/* close child fds */ /* close child fds */
@ -488,6 +466,10 @@ struct wlr_xwayland_server *wlr_xwayland_server_create(
server->wl_display = wl_display; server->wl_display = wl_display;
server->options = *options; server->options = *options;
if (server->options.spawn_handler == NULL) {
server->options.spawn_handler = default_xwayland_spawn_func;
}
#if !HAVE_XWAYLAND_TERMINATE_DELAY #if !HAVE_XWAYLAND_TERMINATE_DELAY
server->options.terminate_delay = 0; server->options.terminate_delay = 0;
#endif #endif

View file

@ -136,7 +136,7 @@ struct wlr_xwayland *wlr_xwayland_create_with_server(struct wl_display *wl_displ
} }
struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display,
struct wlr_compositor *compositor, bool lazy) { struct wlr_compositor *compositor, bool lazy, xwayland_spawn_func_t spawn_func) {
struct wlr_xwayland_shell_v1 *shell_v1 = wlr_xwayland_shell_v1_create(wl_display, 1); struct wlr_xwayland_shell_v1 *shell_v1 = wlr_xwayland_shell_v1_create(wl_display, 1);
if (shell_v1 == NULL) { if (shell_v1 == NULL) {
return NULL; return NULL;
@ -145,6 +145,7 @@ struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display,
struct wlr_xwayland_server_options options = { struct wlr_xwayland_server_options options = {
.lazy = lazy, .lazy = lazy,
.enable_wm = true, .enable_wm = true,
.spawn_handler = spawn_func,
#if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE #if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE
.terminate_delay = lazy ? 10 : 0, .terminate_delay = lazy ? 10 : 0,
#endif #endif