compositor: Add support for building Xwayland
authorMarius Vlad <marius.vlad@collabora.com>
Mon, 8 Apr 2024 12:37:42 +0000 (15:37 +0300)
committerMarius Vlad <marius.vlad@collabora.com>
Tue, 9 Apr 2024 08:41:36 +0000 (11:41 +0300)
This updates with the Weston's front-end.

Bug-AGL: SPEC-5096, SPEC-5061
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
Change-Id: Id421bdd04fc6943e991cbf51e9478450411721ef

meson.build
shared/process-util.c
shared/process-util.h
src/desktop.c
src/xwayland.c

index 3b92cca..9b942f0 100644 (file)
@@ -220,6 +220,8 @@ if get_option('xwayland')
         config_h.set('BUILD_XWAYLAND', '1')
 
         srcs_agl_compositor += 'src/xwayland.c'
+        srcs_agl_compositor += 'shared/xalloc.c'
+        srcs_agl_compositor += 'shared/process-util.c'
         config_h.set_quoted('XSERVER_PATH', get_option('xwayland-path'))
 endif
 
index e36c647..fe895d2 100644 (file)
@@ -68,7 +68,8 @@ fdstr_close_all(struct fdstr *s)
        unsigned i;
 
        for (i = 0; i < ARRAY_LENGTH(s->fds); i++) {
-               close(s->fds[i]);
+               if (s->fds[i] >= 0)
+                       close(s->fds[i]);
                s->fds[i] = -1;
        }
 }
index 05543f6..aa35c77 100644 (file)
@@ -59,6 +59,7 @@ fdstr_clear_cloexec_fd1(struct fdstr *s);
 void
 fdstr_close_all(struct fdstr *s);
 
+#define FDSTR_INIT ((struct fdstr){ { 0 }, { -1, -1 }})
 
 /**
  * A container for environment variables and/or process arguments, designed to
index d5b0be9..d3c1d4e 100644 (file)
@@ -602,8 +602,8 @@ transform_handler(struct wl_listener *listener, void *data)
        if (!weston_view_is_mapped(ivisurf->view))
                return;
 
-       x = ivisurf->view->geometry.x;
-       y = ivisurf->view->geometry.y;
+       x = ivisurf->view->geometry.pos_offset.x;
+       y = ivisurf->view->geometry.pos_offset.y;
 
        api->send_position(surface, x, y);
 #endif
index 8ac5edc..229ce75 100644 (file)
 #include <string.h>
 #include <errno.h>
 #include <sys/socket.h>
+#include <fcntl.h>
 
 #include <libweston/libweston.h>
 #include <weston.h>
 #include <libweston/xwayland-api.h>
 #include "shared/helpers.h"
+#include "shared/process-util.h"
+#include "shared/xalloc.h"
+
+#ifdef HAVE_XWAYLAND_LISTENFD
+#  define LISTEN_STR "-listenfd"
+#else
+#  define LISTEN_STR "-listen"
+#endif
 
 struct wet_xwayland {
-       struct ivi_compositor *ivi_compositor;
+       struct weston_compositor *compositor;
        const struct weston_xwayland_api *api;
        struct weston_xwayland *xwayland;
-       struct wl_event_source *sigusr1_source;
-       struct wl_client *client;
+       struct wl_event_source *display_fd_source;
        int wm_fd;
-       struct weston_process process;
+       struct wet_process *process;
 };
 
+static void
+process_destroy(struct wet_process *process, int status, bool call_cleanup)
+{
+        wl_list_remove(&process->link);
+        if (call_cleanup && process->cleanup)
+                process->cleanup(process, status, process->cleanup_data);
+        free(process->path);
+        free(process);
+}
+
 static int
-handle_sigusr1(int signal_number, void *data)
+handle_display_fd(int fd, uint32_t mask, void *data)
+{
+       struct wet_xwayland *wxw = data;
+       char buf[64];
+       ssize_t n;
+
+       /* xwayland exited before being ready, don't finish initialization,
+        * the process watcher will cleanup */
+       if (!(mask & WL_EVENT_READABLE))
+               goto out;
+
+       /* Xwayland writes to the pipe twice, so if we close it too early
+        * it's possible the second write will fail and Xwayland shuts down.
+        * Make sure we read until end of line marker to avoid this. */
+       n = read(fd, buf, sizeof buf);
+       if (n < 0 && errno != EAGAIN) {
+               weston_log("read from Xwayland display_fd failed: %s\n",
+                               strerror(errno));
+               goto out;
+       }
+       /* Returning 1 here means recheck and call us again if required. */
+       if (n <= 0 || (n > 0 && buf[n - 1] != '\n'))
+               return 1;
+
+       wxw->api->xserver_loaded(wxw->xwayland, wxw->wm_fd);
+
+out:
+       wl_event_source_remove(wxw->display_fd_source);
+       close(fd);
+
+       return 0;
+}
+
+
+static void
+xserver_cleanup(struct wet_process *process, int status, void *data)
 {
        struct wet_xwayland *wxw = data;
 
-       /* We'd be safer if we actually had the struct
-        * signalfd_siginfo from the signalfd data and could verify
-        * this came from Xwayland.*/
-       wxw->api->xserver_loaded(wxw->xwayland, wxw->client, wxw->wm_fd);
-       wl_event_source_remove(wxw->sigusr1_source);
+       /* We only have one Xwayland process active, so make sure it's the
+        * right one */
+       assert(process == wxw->process);
 
-       return 1;
+       wxw->api->xserver_exited(wxw->xwayland);
+       wxw->process = NULL;
 }
+static void
+cleanup_for_child_process() {
+       sigset_t allsigs;
 
-static pid_t
+       /* Put the client in a new session so it won't catch signals
+       * intended for the parent. Sharing a session can be
+       * confusing when launching weston under gdb, as the ctrl-c
+       * intended for gdb will pass to the child, and weston
+       * will cleanly shut down when the child exits.
+       */
+       setsid();
+
+       /* do not give our signal mask to the new process */
+       sigfillset(&allsigs);
+       sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
+}
+
+static struct wet_process *
+client_launch(struct weston_compositor *compositor,
+                 struct custom_env *child_env,
+                 int *no_cloexec_fds,
+                 size_t num_no_cloexec_fds,
+                 wet_process_cleanup_func_t cleanup,
+                 void *cleanup_data)
+{
+       struct ivi_compositor *ivi = to_ivi_compositor(compositor);
+       struct wet_process *proc = NULL;
+       const char *fail_cloexec = "Couldn't unset CLOEXEC on child FDs";
+       const char *fail_seteuid = "Couldn't call seteuid";
+       char *fail_exec;
+       char * const *argp;
+       char * const *envp;
+       pid_t pid;
+       int err;
+       size_t i;
+       size_t written __attribute__((unused));
+
+       argp = custom_env_get_argp(child_env);
+       envp = custom_env_get_envp(child_env);
+
+       weston_log("launching '%s'\n", argp[0]);
+       str_printf(&fail_exec, "Error: Couldn't launch client '%s'\n", argp[0]);
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               cleanup_for_child_process();
+
+               /* Launch clients as the user. Do not launch clients with wrong euid. */
+               if (seteuid(getuid()) == -1) {
+                       written = write(STDERR_FILENO, fail_seteuid,
+                                       strlen(fail_seteuid));
+                       _exit(EXIT_FAILURE);
+               }
+
+               for (i = 0; i < num_no_cloexec_fds; i++) {
+                       err = os_fd_clear_cloexec(no_cloexec_fds[i]);
+                       if (err < 0) {
+                               written = write(STDERR_FILENO, fail_cloexec,
+                                               strlen(fail_cloexec));
+                               _exit(EXIT_FAILURE);
+                       }
+               }
+
+               execve(argp[0], argp, envp);
+
+               if (fail_exec)
+                       written = write(STDERR_FILENO, fail_exec,
+                                       strlen(fail_exec));
+               _exit(EXIT_FAILURE);
+
+       default:
+               proc = xzalloc(sizeof(*proc));
+               proc->pid = pid;
+               proc->cleanup = cleanup;
+               proc->cleanup_data = cleanup_data;
+               proc->path = strdup(argp[0]);
+               wl_list_insert(&ivi->child_process_list, &proc->link);
+               break;
+
+       case -1:
+               weston_log("weston_client_launch: "
+                          "fork failed while launching '%s': %s\n", argp[0],
+                          strerror(errno));
+               break;
+       }
+
+       custom_env_fini(child_env);
+       free(fail_exec);
+       return proc;
+}
+
+static struct weston_config *
+ivi_get_config(struct weston_compositor *ec)
+{
+        struct ivi_compositor *ivi = to_ivi_compositor(ec);
+
+        return ivi->config;
+}
+
+
+static struct wl_client *
 spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd)
 {
        struct wet_xwayland *wxw = user_data;
-       pid_t pid;
-       char s[12], abstract_fd_str[12], unix_fd_str[12], wm_fd_str[12];
-       int sv[2], wm[2], fd;
+       struct fdstr wayland_socket = FDSTR_INIT;
+       struct fdstr x11_abstract_socket = FDSTR_INIT;
+       struct fdstr x11_unix_socket = FDSTR_INIT;
+       struct fdstr x11_wm_socket = FDSTR_INIT;
+       struct fdstr display_pipe = FDSTR_INIT;
+
        char *xserver = NULL;
-       struct weston_config *config = wxw->ivi_compositor->config;
+       struct weston_config *config = ivi_get_config(wxw->compositor);
        struct weston_config_section *section;
+       struct wl_client *client;
+       struct wl_event_loop *loop;
+       struct custom_env child_env;
+       int no_cloexec_fds[5];
+       size_t num_no_cloexec_fds = 0;
+       size_t written __attribute__ ((unused));
 
-       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) {
+       if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, wayland_socket.fds) < 0) {
                weston_log("wl connection socketpair failed\n");
-               return 1;
+               goto err;
        }
+       fdstr_update_str1(&wayland_socket);
+       no_cloexec_fds[num_no_cloexec_fds++] = wayland_socket.fds[1];
 
-       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm) < 0) {
+       if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, x11_wm_socket.fds) < 0) {
                weston_log("X wm connection socketpair failed\n");
-               return 1;
+               goto err;
        }
+       fdstr_update_str1(&x11_wm_socket);
+       no_cloexec_fds[num_no_cloexec_fds++] = x11_wm_socket.fds[1];
 
-       pid = fork();
-       switch (pid) {
-       case 0:
-               /* SOCK_CLOEXEC closes both ends, so we need to unset
-                * the flag on the client fd. */
-               fd = dup(sv[1]);
-               if (fd < 0)
-                       goto fail;
-               snprintf(s, sizeof s, "%d", fd);
-               setenv("WAYLAND_SOCKET", s, 1);
-
-               fd = dup(abstract_fd);
-               if (fd < 0)
-                       goto fail;
-               snprintf(abstract_fd_str, sizeof abstract_fd_str, "%d", fd);
-               fd = dup(unix_fd);
-               if (fd < 0)
-                       goto fail;
-               snprintf(unix_fd_str, sizeof unix_fd_str, "%d", fd);
-               fd = dup(wm[1]);
-               if (fd < 0)
-                       goto fail;
-               snprintf(wm_fd_str, sizeof wm_fd_str, "%d", fd);
-
-               section = weston_config_get_section(config,
-                                                   "xwayland", NULL, NULL);
-               weston_config_section_get_string(section, "path",
-                                                &xserver, XSERVER_PATH);
-
-               /* Ignore SIGUSR1 in the child, which will make the X
-                * server send SIGUSR1 to the parent (weston) when
-                * it's done with initialization.  During
-                * initialization the X server will round trip and
-                * block on the wayland compositor, so avoid making
-                * blocking requests (like xcb_connect_to_fd) until
-                * it's done with that. */
-               signal(SIGUSR1, SIG_IGN);
-
-               if (execl(xserver,
-                         xserver,
-                         display,
-                         "-rootless",
-#ifdef HAVE_XWAYLAND_LISTENFD
-                         "-listenfd", abstract_fd_str,
-                         "-listenfd", unix_fd_str,
-#else
-                         "-listen", abstract_fd_str,
-                         "-listen", unix_fd_str,
-#endif
-                         "-wm", wm_fd_str,
-                         "-terminate",
-                         NULL) < 0)
-                       weston_log("exec of '%s %s -rootless "
-#ifdef HAVE_XWAYLAND_LISTENFD
-                                  "-listenfd %s -listenfd %s "
-#else
-                                  "-listen %s -listen %s "
-#endif
-                                  "-wm %s -terminate' failed: %s\n",
-                                  xserver, display,
-                                  abstract_fd_str, unix_fd_str, wm_fd_str,
-                                  strerror(errno));
-       fail:
-               _exit(EXIT_FAILURE);
+       if (pipe2(display_pipe.fds, O_CLOEXEC) < 0) {
+               weston_log("pipe creation for displayfd failed\n");
+               goto err;
+       }
+       fdstr_update_str1(&display_pipe);
+       no_cloexec_fds[num_no_cloexec_fds++] = display_pipe.fds[1];
 
-       default:
-               close(sv[1]);
-               wxw->client = wl_client_create(wxw->ivi_compositor->compositor->wl_display, sv[0]);
+       fdstr_set_fd1(&x11_abstract_socket, abstract_fd);
+       no_cloexec_fds[num_no_cloexec_fds++] = abstract_fd;
 
-               close(wm[1]);
-               wxw->wm_fd = wm[0];
+       fdstr_set_fd1(&x11_unix_socket, unix_fd);
+       no_cloexec_fds[num_no_cloexec_fds++] = unix_fd;
 
-               wxw->process.pid = pid;
-               wl_list_insert(&wxw->ivi_compositor->child_process_list,
-                              &wxw->process.link);
-               break;
+       assert(num_no_cloexec_fds <= ARRAY_LENGTH(no_cloexec_fds));
 
-       case -1:
-               weston_log("Failed to fork to spawn xserver process\n");
-               break;
+       section = weston_config_get_section(config, "xwayland", NULL, NULL);
+       weston_config_section_get_string(section, "path",
+                                        &xserver, XSERVER_PATH);
+       custom_env_init_from_environ(&child_env);
+       custom_env_set_env_var(&child_env, "WAYLAND_SOCKET", wayland_socket.str1);
+
+       custom_env_add_arg(&child_env, xserver);
+       custom_env_add_arg(&child_env, display);
+       custom_env_add_arg(&child_env, "-rootless");
+       custom_env_add_arg(&child_env, LISTEN_STR);
+       custom_env_add_arg(&child_env, x11_abstract_socket.str1);
+       custom_env_add_arg(&child_env, LISTEN_STR);
+       custom_env_add_arg(&child_env, x11_unix_socket.str1);
+       custom_env_add_arg(&child_env, "-displayfd");
+       custom_env_add_arg(&child_env, display_pipe.str1);
+       custom_env_add_arg(&child_env, "-wm");
+       custom_env_add_arg(&child_env, x11_wm_socket.str1);
+       custom_env_add_arg(&child_env, "-terminate");
+
+       wxw->process = client_launch(wxw->compositor, &child_env,
+                                    no_cloexec_fds, num_no_cloexec_fds,
+                                    xserver_cleanup, wxw);
+       if (!wxw->process) {
+               weston_log("Couldn't start Xwayland\n");
+               goto err;
+       }
+
+       client = wl_client_create(wxw->compositor->wl_display,
+                                 wayland_socket.fds[0]);
+       if (!client) {
+               weston_log("Couldn't create client for Xwayland\n");
+               goto err_proc;
        }
 
-       return pid;
+       wxw->wm_fd = x11_wm_socket.fds[0];
+
+       /* Now we can no longer fail, close the child end of our sockets */
+       close(wayland_socket.fds[1]);
+       close(x11_wm_socket.fds[1]);
+       close(display_pipe.fds[1]);
+
+       /* During initialization the X server will round trip
+        * and block on the wayland compositor, so avoid making
+        * blocking requests (like xcb_connect_to_fd) until
+        * it's done with that. */
+       loop = wl_display_get_event_loop(wxw->compositor->wl_display);
+       wxw->display_fd_source =
+               wl_event_loop_add_fd(loop, display_pipe.fds[0],
+                                    WL_EVENT_READABLE,
+                                    handle_display_fd, wxw);
+
+       free(xserver);
+
+       return client;
+
+
+err_proc:
+       wl_list_remove(&wxw->process->link);
+err:
+       free(xserver);
+       fdstr_close_all(&display_pipe);
+       fdstr_close_all(&x11_wm_socket);
+       fdstr_close_all(&wayland_socket);
+       free(wxw->process);
+       return NULL;
 }
 
-static void
-xserver_cleanup(struct weston_process *process, int status)
+void
+wet_xwayland_destroy(struct weston_compositor *compositor, void *data)
 {
-       struct wet_xwayland *wxw =
-               container_of(process, struct wet_xwayland, process);
-       struct wl_event_loop *loop =
-               wl_display_get_event_loop(wxw->ivi_compositor->compositor->wl_display);
-
-       wxw->api->xserver_exited(wxw->xwayland, status);
-       wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1,
-                                                      handle_sigusr1, wxw);
-       wxw->client = NULL;
+       struct wet_xwayland *wxw = data;
+
+       /* Calling this will call the process cleanup, in turn cleaning up the
+        * client and the core Xwayland state */
+       if (wxw->process)
+               process_destroy(wxw->process, 0, true);
+
+       free(wxw);
 }
 
-int
-wet_load_xwayland(struct weston_compositor *comp)
+void *
+wet_load_xwayland(struct weston_compositor *compositor)
 {
        const struct weston_xwayland_api *api;
        struct weston_xwayland *xwayland;
        struct wet_xwayland *wxw;
-       struct wl_event_loop *loop;
-       struct ivi_compositor *ivi = to_ivi_compositor(comp);
 
-       if (weston_compositor_load_xwayland(comp) < 0)
-               return -1;
+       if (weston_compositor_load_xwayland(compositor) < 0)
+               return NULL;
 
-       api = weston_xwayland_get_api(comp);
+       api = weston_xwayland_get_api(compositor);
        if (!api) {
                weston_log("Failed to get the xwayland module API.\n");
-               return -1;
+               return NULL;
        }
 
-       xwayland = api->get(comp);
+       xwayland = api->get(compositor);
        if (!xwayland) {
                weston_log("Failed to get the xwayland object.\n");
-               return -1;
+               return NULL;
        }
 
        wxw = zalloc(sizeof *wxw);
        if (!wxw)
-               return -1;
+               return NULL;
 
-       wxw->ivi_compositor = ivi;
+       wxw->compositor = compositor;
        wxw->api = api;
        wxw->xwayland = xwayland;
-       wxw->process.cleanup = xserver_cleanup;
        if (api->listen(xwayland, wxw, spawn_xserver) < 0)
-               return -1;
+               return NULL;
 
-       loop = wl_display_get_event_loop(comp->wl_display);
-       wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1,
-                                                      handle_sigusr1, wxw);
-
-       return 0;
+       return wxw;
 }