Implement master-client support.

This introduces a mode where xwl-run can run as master and
spawn client instances to serve each new wayland connection.
diff --git a/xwl.c b/xwl.c
index 83d7ce5..27a3eab 100644
--- a/xwl.c
+++ b/xwl.c
@@ -13,12 +13,16 @@
 #endif
 
 #include <assert.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/file.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
 #include <sys/wait.h>
 #include <systemd/sd-daemon.h>
 #include <unistd.h>
@@ -388,6 +392,7 @@
   int xwayland;
   pid_t xwayland_pid;
   pid_t child_pid;
+  pid_t peer_pid;
   xcb_connection_t *connection;
   const xcb_query_extension_reply_t *xfixes_extension;
   xcb_screen_t *screen;
@@ -489,6 +494,13 @@
 #define MAX(a, b) (((a) > (b)) ? (a) : (b))
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
 
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+#define LOCK_SUFFIX ".lock"
+#define LOCK_SUFFIXLEN 5
+
 static void xwl_internal_xdg_shell_ping(void *data,
                                         struct zxdg_shell_v6 *xdg_shell,
                                         uint32_t serial) {
@@ -4918,6 +4930,7 @@
 
 static void xwl_usage() {
   printf("xwl-run "
+         "[-X] "
          "[--scale=SCALE] "
          "[--app-id=ID] "
          "[--x-display=DISPLAY] "
@@ -4929,6 +4942,16 @@
          "PROGRAM [ARGS...]\n");
 }
 
+static void xwl_sigchld_handler(int signal) {
+  while (waitpid(-1, NULL, WNOHANG) > 0)
+    continue;
+}
+
+static void xwl_client_destroy_notify(struct wl_listener *listener,
+                                      void *data) {
+  exit(0);
+}
+
 int main(int argc, char **argv) {
   struct xwl xwl = {
       .runprog = NULL,
@@ -4950,6 +4973,7 @@
       .xwayland = 0,
       .xwayland_pid = -1,
       .child_pid = -1,
+      .peer_pid = -1,
       .connection = NULL,
       .xfixes_extension = NULL,
       .screen = NULL,
@@ -5008,17 +5032,21 @@
           },
       .visual_ids = {0},
       .colormaps = {0}};
-  const char *x = getenv("XWL_X");
   const char *scale = getenv("XWL_SCALE");
   const char *clipboard_manager = getenv("XWL_CLIPBOARD_MANAGER");
   const char *frame_color = getenv("XWL_FRAME_COLOR");
   const char *show_window_title = getenv("XWL_SHOW_WINDOW_TITLE");
   const char *drm_device = getenv("XWL_DRM_DEVICE");
   const char *glamor = getenv("XWL_GLAMOR");
+  const char *socket_name = "wayland-0";
   struct wl_event_loop *event_loop;
-  int sv[2], ds[2], wm[2];
+  struct wl_listener client_destroy_listener = {.notify =
+                                                    xwl_client_destroy_notify};
+  int sv[2];
   pid_t pid;
   int xdisplay = -1;
+  int master = 0;
+  int client_fd = -1;
   int rv;
   int i;
 
@@ -5033,22 +5061,36 @@
       printf("Version: %s\n", VERSION);
       return 0;
     }
-    if (strstr(arg, "-X") == arg) {
-      x = "1";
-    } else if (strstr(arg, "--scale=") == arg) {
+    if (strstr(arg, "--master") == arg) {
+      master = 1;
+    } else if (strstr(arg, "--socket") == arg) {
+      const char *s = strchr(arg, '=');
+      ++s;
+      socket_name = s;
+    } else if (strstr(arg, "--peer-pid") == arg) {
+      const char *s = strchr(arg, '=');
+      ++s;
+      xwl.peer_pid = atoi(s);
+    } else if (strstr(arg, "--client-fd") == arg) {
+      const char *s = strchr(arg, '=');
+      ++s;
+      client_fd = atoi(s);
+    } else if (strstr(arg, "--scale") == arg) {
       const char *s = strchr(arg, '=');
       ++s;
       scale = s;
-    } else if (strstr(arg, "--app-id=") == arg) {
+    } else if (strstr(arg, "--app-id") == arg) {
       const char *s = strchr(arg, '=');
       ++s;
       xwl.app_id = s;
-    } else if (strstr(arg, "--x-display=") == arg) {
+    } else if (strstr(arg, "-X") == arg) {
+      xwl.xwayland = 1;
+    } else if (strstr(arg, "--x-display") == arg) {
       const char *s = strchr(arg, '=');
       ++s;
       xdisplay = atoi(s);
       // Automatically enable X forwarding if X display is specified.
-      x = "1";
+      xwl.xwayland = 1;
     } else if (strstr(arg, "--no-exit-with-child") == arg) {
       xwl.exit_with_child = 0;
     } else if (strstr(arg, "--no-clipboard-manager") == arg) {
@@ -5078,23 +5120,133 @@
     }
   }
 
-  if (!xwl.runprog || !xwl.runprog[0]) {
-    xwl_usage();
-    return 1;
+  if (master) {
+    const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
+    char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN];
+    struct sockaddr_un addr;
+    struct sigaction sa;
+    struct stat sock_stat;
+    int lock_fd;
+    int sock_fd;
+
+    if (!runtime_dir) {
+      fprintf(stderr, "XDG_RUNTIME_DIR not set in the environment\n");
+      exit(1);
+    }
+
+    addr.sun_family = AF_LOCAL;
+    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir,
+             socket_name);
+
+    snprintf(lock_addr, sizeof(lock_addr), "%s%s", addr.sun_path, LOCK_SUFFIX);
+
+    lock_fd = open(lock_addr, O_CREAT | O_CLOEXEC,
+                   (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
+    assert(lock_fd >= 0);
+
+    rv = flock(lock_fd, LOCK_EX | LOCK_NB);
+    if (rv < 0) {
+      fprintf(stderr, "unable to lock %s, is another compositor running?\n",
+              lock_addr);
+      exit(1);
+    }
+
+    rv = stat(addr.sun_path, &sock_stat);
+    if (rv >= 0) {
+      if (sock_stat.st_mode & (S_IWUSR | S_IWGRP))
+        unlink(addr.sun_path);
+    } else {
+      assert(errno == ENOENT);
+    }
+
+    sock_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
+    assert(sock_fd >= 0);
+
+    rv = bind(sock_fd, (struct sockaddr *)&addr,
+              offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path));
+    assert(rv >= 0);
+
+    rv = listen(sock_fd, 128);
+    assert(rv >= 0);
+
+    sa.sa_handler = xwl_sigchld_handler;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    rv = sigaction(SIGCHLD, &sa, NULL);
+    assert(rv >= 0);
+
+    sd_notify(0, "READY=1");
+
+    do {
+      struct ucred ucred;
+      socklen_t length = sizeof(addr);
+
+      client_fd = accept(sock_fd, (struct sockaddr *)&addr, &length);
+      if (client_fd < 0) {
+        fprintf(stderr, "failed to accept: %m\n");
+        continue;
+      }
+
+      ucred.pid = -1;
+      length = sizeof(ucred);
+      rv = getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length);
+
+      pid = fork();
+      assert(pid != -1);
+      if (pid == 0) {
+        char client_fd_str[64], peer_pid_str[64];
+        char *args[32];
+        int i = 0, j;
+
+        close(sock_fd);
+        close(lock_fd);
+
+        args[i++] = argv[0];
+        snprintf(peer_pid_str, sizeof(peer_pid_str), "--peer-pid=%d",
+                 ucred.pid);
+        args[i++] = peer_pid_str;
+        snprintf(client_fd_str, sizeof(client_fd_str), "--client-fd=%d",
+                 client_fd);
+        args[i++] = client_fd_str;
+
+        // forward some flags.
+        for (j = 1; j < argc; ++j) {
+          char *arg = argv[j];
+          if (strstr(arg, "--scale") == arg ||
+              strstr(arg, "--drm-device") == arg) {
+            args[i++] = arg;
+          }
+        }
+
+        args[i++] = NULL;
+
+        execvp(argv[0], args);
+        _exit(EXIT_FAILURE);
+      }
+      close(client_fd);
+    } while (1);
+
+    _exit(EXIT_FAILURE);
   }
 
-  if (x)
-    xwl.xwayland = !!strcmp(x, "0");
-
-  if (scale)
-    xwl.scale = MIN(MAX_SCALE, MAX(MIN_SCALE, atof(scale)));
+  if (client_fd == -1) {
+    if (!xwl.runprog || !xwl.runprog[0]) {
+      xwl_usage();
+      return 1;
+    }
+  }
 
   if (xwl.xwayland) {
+    assert(client_fd == -1);
+
     xwl.clipboard_manager = 1;
     if (clipboard_manager)
       xwl.clipboard_manager = !!strcmp(clipboard_manager, "0");
   }
 
+  if (scale)
+    xwl.scale = MIN(MAX_SCALE, MAX(MIN_SCALE, atof(scale)));
+
   if (frame_color) {
     int r, g, b;
     if (sscanf(frame_color, "#%02x%02x%02x", &r, &g, &b) == 3) {
@@ -5109,6 +5261,14 @@
   if (drm_device && !access(drm_device, 0))
     xwl.drm_device = drm_device;
 
+  if (xwl.runprog || xwl.xwayland) {
+    // Wayland connection from client.
+    rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv);
+    assert(!rv);
+
+    client_fd = sv[0];
+  }
+
   xwl.display = wl_display_connect(NULL);
   assert(xwl.display);
 
@@ -5133,81 +5293,81 @@
   if (!xwl.viewporter)
     xwl.scale = ceil(xwl.scale);
 
-  xwl.sigchld_event_source =
-      wl_event_loop_add_signal(event_loop, SIGCHLD, xwl_handle_sigchld, &xwl);
+  xwl.client = wl_client_create(xwl.host_display, client_fd);
 
-  // Wayland connection from client.
-  rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv);
-  assert(!rv);
+  if (xwl.runprog || xwl.xwayland) {
+    xwl.sigchld_event_source =
+        wl_event_loop_add_signal(event_loop, SIGCHLD, xwl_handle_sigchld, &xwl);
 
-  xwl.client = wl_client_create(xwl.host_display, sv[0]);
-
-  if (xwl.xwayland) {
-    // Xwayland display ready socket.
-    rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ds);
-    assert(!rv);
-
-    xwl.display_event_source = wl_event_loop_add_fd(
-        event_loop, ds[0], WL_EVENT_READABLE, xwl_handle_display_event, &xwl);
-
-    // X connection to Xwayland.
-    rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm);
-    assert(!rv);
-
-    xwl.wm_fd = wm[0];
-  }
-
-  pid = fork();
-  assert(pid != -1);
-  if (pid == 0) {
     if (xwl.xwayland) {
-      char display_str[8], display_fd_str[8], wm_fd_str[8];
-      char *args[32];
-      int i = 0;
-      int fd;
+      int ds[2], wm[2];
 
-      fd = dup(ds[1]);
-      snprintf(display_fd_str, sizeof(display_fd_str), "%d", fd);
-      fd = dup(wm[1]);
-      snprintf(wm_fd_str, sizeof(wm_fd_str), "%d", fd);
+      // Xwayland display ready socket.
+      rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ds);
+      assert(!rv);
 
-      args[i++] = XWAYLAND_PATH "/Xwayland";
-      if (xdisplay > 0) {
-        snprintf(display_str, sizeof(display_str), ":%d", xdisplay);
-        args[i++] = display_str;
+      xwl.display_event_source = wl_event_loop_add_fd(
+          event_loop, ds[0], WL_EVENT_READABLE, xwl_handle_display_event, &xwl);
+
+      // X connection to Xwayland.
+      rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm);
+      assert(!rv);
+
+      xwl.wm_fd = wm[0];
+
+      pid = fork();
+      assert(pid != -1);
+      if (pid == 0) {
+        char display_str[8], display_fd_str[8], wm_fd_str[8];
+        char *args[32];
+        int i = 0;
+        int fd;
+
+        fd = dup(ds[1]);
+        snprintf(display_fd_str, sizeof(display_fd_str), "%d", fd);
+        fd = dup(wm[1]);
+        snprintf(wm_fd_str, sizeof(wm_fd_str), "%d", fd);
+
+        args[i++] = XWAYLAND_PATH "/Xwayland";
+        if (xdisplay > 0) {
+          snprintf(display_str, sizeof(display_str), ":%d", xdisplay);
+          args[i++] = display_str;
+        }
+        args[i++] = "-nolisten";
+        args[i++] = "tcp";
+        args[i++] = "-rootless";
+        if (xwl.drm_device) {
+          // Use DRM and software rendering unless glamor is enabled.
+          if (!glamor || !strcmp(glamor, "0"))
+            args[i++] = "-drm";
+        } else {
+          args[i++] = "-shm";
+        }
+        args[i++] = "-displayfd";
+        args[i++] = display_fd_str;
+        args[i++] = "-wm";
+        args[i++] = wm_fd_str;
+        args[i++] = NULL;
+
+        xwl_execvp(XWAYLAND_PATH "/Xwayland", args, sv[1]);
+        _exit(EXIT_FAILURE);
       }
-      args[i++] = "-nolisten";
-      args[i++] = "tcp";
-      args[i++] = "-rootless";
-      if (xwl.drm_device) {
-        // Use DRM and software rendering unless glamor is enabled.
-        if (!glamor || !strcmp(glamor, "0"))
-          args[i++] = "-drm";
-      } else {
-        args[i++] = "-shm";
-      }
-      args[i++] = "-displayfd";
-      args[i++] = display_fd_str;
-      args[i++] = "-wm";
-      args[i++] = wm_fd_str;
-      args[i++] = NULL;
-
-      xwl_execvp(XWAYLAND_PATH "/Xwayland", args, sv[1]);
+      close(wm[1]);
+      xwl.xwayland_pid = pid;
     } else {
-      xwl_execvp(xwl.runprog[0], xwl.runprog, sv[1]);
+      pid = fork();
+      assert(pid != -1);
+      if (pid == 0) {
+        xwl_execvp(xwl.runprog[0], xwl.runprog, sv[1]);
+        _exit(EXIT_FAILURE);
+      }
+      xwl.child_pid = pid;
     }
-    _exit(EXIT_FAILURE);
-  }
-
-  if (xwl.xwayland) {
-    close(wm[1]);
-    xwl.xwayland_pid = pid;
+    close(sv[1]);
   } else {
-    xwl.child_pid = pid;
+    wl_client_add_destroy_listener(xwl.client, &client_destroy_listener);
   }
 
-  close(sv[1]);
-
   do {
     wl_display_flush_clients(xwl.host_display);
     if (xwl.connection) {