This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch feat/e-wl-proxy-planb
in repository enlightenment.
View the commit online.
commit ccf98175288a7c02d2abb13d6bc921778053e643
Author: [email protected] <[email protected]>
AuthorDate: Sat Jun 13 11:32:11 2026 -0600
feat(e_wl_proxy): listening socket, fd-passing pipe, and fd retention queue
The transport layer beneath the proxy: a lock-guarded listening UNIX socket
and a backend connect helper (e_wl_proxy_socket); a read-and-forward pipe that
carries SCM_RIGHTS file descriptors across the proxy and closes them exactly
once on every path, with EMFILE throttling and MSG_CTRUNC detection
(e_wl_proxy_pipe); and an owned-fd FIFO that retains arrived descriptors in
order so they can be re-attached to the right message during replay
(e_wl_proxy_fdq).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
src/bin/e_wl_proxy/e_wl_proxy_fdq.c | 49 ++++++++++
src/bin/e_wl_proxy/e_wl_proxy_fdq.h | 29 ++++++
src/bin/e_wl_proxy/e_wl_proxy_pipe.c | 158 +++++++++++++++++++++++++++++++++
src/bin/e_wl_proxy/e_wl_proxy_socket.c | 99 +++++++++++++++++++++
4 files changed, 335 insertions(+)
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_fdq.c b/src/bin/e_wl_proxy/e_wl_proxy_fdq.c
new file mode 100644
index 000000000..f92fc0c42
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_fdq.c
@@ -0,0 +1,49 @@
+#include "config.h"
+#include "e_wl_proxy_fdq.h"
+#include <unistd.h>
+
+int
+e_wl_proxy_fdq_count(const E_Wl_Proxy_Fdq *q)
+{
+ return q ? (q->tail - q->head) : 0;
+}
+
+int
+e_wl_proxy_fdq_push(E_Wl_Proxy_Fdq *q, int fd)
+{
+ if (!q) { if (fd >= 0) close(fd); return -1; }
+ if (q->tail - q->head >= E_WL_PROXY_FDQ_CAP)
+ {
+ /* queue full: drop (close) rather than overflow */
+ if (fd >= 0) close(fd);
+ return -1;
+ }
+ /* compact when head has advanced and tail hit the cap */
+ if (q->tail == E_WL_PROXY_FDQ_CAP && q->head > 0)
+ {
+ int i, n = q->tail - q->head;
+ for (i = 0; i < n; i++) q->fd[i] = q->fd[q->head + i];
+ q->head = 0; q->tail = n;
+ }
+ q->fd[q->tail++] = fd;
+ return 0;
+}
+
+int
+e_wl_proxy_fdq_pop(E_Wl_Proxy_Fdq *q)
+{
+ int fd;
+ if (!q || q->head >= q->tail) return -1;
+ fd = q->fd[q->head++];
+ if (q->head == q->tail) { q->head = 0; q->tail = 0; } /* reset when drained */
+ return fd;
+}
+
+void
+e_wl_proxy_fdq_clear(E_Wl_Proxy_Fdq *q)
+{
+ if (!q) return;
+ while (q->head < q->tail)
+ { int fd = q->fd[q->head++]; if (fd >= 0) close(fd); }
+ q->head = 0; q->tail = 0;
+}
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_fdq.h b/src/bin/e_wl_proxy/e_wl_proxy_fdq.h
new file mode 100644
index 000000000..26aed8982
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_fdq.h
@@ -0,0 +1,29 @@
+#ifndef E_WL_PROXY_FDQ_H
+#define E_WL_PROXY_FDQ_H
+
+/* A small FIFO of owned file descriptors. Wayland delivers fds in order; we
+ * hold dup'd copies here until the message that consumes them is fully parsed.
+ * push() takes ownership of the fd; pop() transfers it to the caller; clear()
+ * close()s everything still queued. */
+
+#define E_WL_PROXY_FDQ_CAP 256
+
+typedef struct _E_Wl_Proxy_Fdq
+{
+ int fd[E_WL_PROXY_FDQ_CAP];
+ int head; /* index of next pop */
+ int tail; /* index of next push; count = tail - head */
+} E_Wl_Proxy_Fdq;
+
+/* Number of fds currently queued. */
+int e_wl_proxy_fdq_count(const E_Wl_Proxy_Fdq *q);
+/* Append fd (ownership transferred to the queue). If the queue is full, the fd
+ * is close()d and dropped. Returns 0 if queued, -1 if dropped. */
+int e_wl_proxy_fdq_push(E_Wl_Proxy_Fdq *q, int fd);
+/* Remove and return the oldest fd (ownership transferred to caller), or -1 if
+ * the queue is empty. */
+int e_wl_proxy_fdq_pop(E_Wl_Proxy_Fdq *q);
+/* close() every fd still queued and reset to empty. */
+void e_wl_proxy_fdq_clear(E_Wl_Proxy_Fdq *q);
+
+#endif
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_pipe.c b/src/bin/e_wl_proxy/e_wl_proxy_pipe.c
new file mode 100644
index 000000000..37fb5c622
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_pipe.c
@@ -0,0 +1,158 @@
+#include "config.h"
+#include "e_wl_proxy.h"
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+/* Write pipe->buf[off..len) plus any pending fds to `to`. fds ride on the
+ * first sendmsg that places any byte; once off > 0 they are gone. */
+int
+e_wl_proxy_pipe_flush(int to, E_Wl_Proxy_Pipe *pipe)
+{
+ while (pipe->len - pipe->off > 0)
+ {
+ struct iovec iov;
+ struct msghdr msg;
+ union {
+ struct cmsghdr align;
+ char buf[CMSG_SPACE(E_WL_PROXY_MAX_FDS * sizeof(int))];
+ } cmsgbuf;
+ ssize_t sent;
+
+ iov.iov_base = pipe->buf + pipe->off;
+ iov.iov_len = pipe->len - pipe->off;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ if (pipe->off == 0 && pipe->nfds > 0)
+ {
+ struct cmsghdr *c;
+ size_t fdbytes = pipe->nfds * sizeof(int);
+
+ memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+ msg.msg_control = cmsgbuf.buf;
+ msg.msg_controllen = CMSG_SPACE(fdbytes);
+ c = CMSG_FIRSTHDR(&msg);
+ c->cmsg_level = SOL_SOCKET;
+ c->cmsg_type = SCM_RIGHTS;
+ c->cmsg_len = CMSG_LEN(fdbytes);
+ memcpy(CMSG_DATA(c), pipe->fds, fdbytes);
+ }
+
+ do {
+ sent = sendmsg(to, &msg, MSG_NOSIGNAL);
+ } while (sent < 0 && errno == EINTR);
+
+ if (sent < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return 1;
+ return -1;
+ }
+
+ /* fds (if any) were delivered with this sendmsg; close our copies */
+ if (pipe->nfds > 0)
+ {
+ int i;
+ for (i = 0; i < pipe->nfds; i++)
+ if (pipe->fds[i] >= 0) close(pipe->fds[i]);
+ pipe->nfds = 0;
+ }
+ pipe->off += sent;
+ }
+
+ pipe->len = 0;
+ pipe->off = 0;
+ return 0;
+}
+
+ssize_t
+e_wl_proxy_forward(int from, int to, E_Wl_Proxy_Pipe *pipe,
+ int *out_fds, int *out_nfds)
+{
+ struct iovec iov;
+ struct msghdr msg;
+ union {
+ struct cmsghdr align;
+ char buf[CMSG_SPACE(E_WL_PROXY_MAX_FDS * sizeof(int))];
+ } cmsgbuf;
+ struct cmsghdr *c;
+ ssize_t n;
+
+ /* caller guarantees the pipe is drained before reading more */
+ if (pipe->len != 0) return 0;
+
+ iov.iov_base = pipe->buf;
+ iov.iov_len = sizeof(pipe->buf);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+ do {
+ n = recvmsg(from, &msg, MSG_CMSG_CLOEXEC);
+ } while (n < 0 && errno == EINTR);
+
+ if (n == 0) return -1; /* EOF */
+ if (n < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return 0;
+ return -1;
+ }
+
+ /* collect any fds that arrived with this chunk */
+ pipe->nfds = 0;
+ for (c = CMSG_FIRSTHDR(&msg); c; c = CMSG_NXTHDR(&msg, c))
+ {
+ if (c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS)
+ {
+ int cnt, i;
+ if (c->cmsg_len < CMSG_LEN(0)) continue; /* malformed cmsg: skip */
+ cnt = (c->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ for (i = 0; i < cnt && pipe->nfds < E_WL_PROXY_MAX_FDS; i++)
+ memcpy(&pipe->fds[pipe->nfds++], CMSG_DATA(c) + i * sizeof(int), sizeof(int));
+ }
+ }
+
+ /* If ancillary fds did not fit they were dropped by the kernel; forwarding
+ * the bytes without them would corrupt the protocol stream, so tear down. */
+ if (msg.msg_flags & MSG_CTRUNC)
+ {
+ int i;
+ for (i = 0; i < pipe->nfds; i++) close(pipe->fds[i]);
+ pipe->nfds = 0;
+ return -1;
+ }
+
+ /* hand the caller a dup of each arrived fd for retention (the originals are
+ * forwarded and closed by the transport once delivered) */
+ if (out_nfds) *out_nfds = 0;
+ if (out_fds && out_nfds)
+ {
+ int i;
+ for (i = 0; i < pipe->nfds; i++)
+ {
+ int d = -1;
+ if (pipe->fds[i] >= 0)
+ { d = dup(pipe->fds[i]); if (d >= 0) fcntl(d, F_SETFD, FD_CLOEXEC); }
+ out_fds[(*out_nfds)++] = d;
+ }
+ }
+
+ pipe->len = (int)n;
+ pipe->off = 0;
+
+ /* push it out now; any unsent remainder stays buffered for the writable cb.
+ * A flush error means the *write* side (`to`) died — return -2 so the caller
+ * can tell it apart from a read-side EOF (-1) and react per direction (e.g.
+ * freeze the client when the backend dies, rather than tear the client down). */
+ if (e_wl_proxy_pipe_flush(to, pipe) < 0) return -2;
+
+ return n;
+}
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_socket.c b/src/bin/e_wl_proxy/e_wl_proxy_socket.c
new file mode 100644
index 000000000..a52b7f66e
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_socket.c
@@ -0,0 +1,99 @@
+#include "config.h"
+#include "e_wl_proxy.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/file.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+int
+e_wl_proxy_socket_path(const char *name, char *buf, size_t buflen)
+{
+ const char *runtime;
+ int n;
+
+ if (!name || name[0] == '\0' || strchr(name, '/')) return -1;
+
+ runtime = getenv("XDG_RUNTIME_DIR");
+ if (!runtime || runtime[0] == '\0') return -1;
+
+ n = snprintf(buf, buflen, "%s/%s", runtime, name);
+ if (n < 0 || (size_t)n >= buflen) return -1;
+
+ return 0;
+}
+
+static int
+_fill_addr(const char *name, struct sockaddr_un *addr)
+{
+ char path[256];
+
+ if (e_wl_proxy_socket_path(name, path, sizeof(path)) < 0) return -1;
+ if (strlen(path) >= sizeof(addr->sun_path)) return -1;
+
+ memset(addr, 0, sizeof(*addr));
+ addr->sun_family = AF_UNIX;
+ strcpy(addr->sun_path, path);
+ return 0;
+}
+
+int
+e_wl_proxy_listen(const char *name)
+{
+ struct sockaddr_un addr;
+ char lockpath[256 + 8];
+ char path[256];
+ int fd, lock;
+
+ if (e_wl_proxy_socket_path(name, path, sizeof(path)) < 0) return -1;
+ if (_fill_addr(name, &addr) < 0) return -1;
+
+ /* take "<path>.lock" so two servers can't share the name */
+ snprintf(lockpath, sizeof(lockpath), "%s.lock", path);
+ lock = open(lockpath, O_CREAT | O_CLOEXEC, 0660);
+ if (lock < 0) return -1;
+ if (flock(lock, LOCK_EX | LOCK_NB) < 0) { close(lock); return -1; }
+
+ /* a leftover socket from a crash is safe to remove now we hold the lock */
+ unlink(path);
+
+ fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0) { close(lock); return -1; }
+
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) goto err;
+ if (listen(fd, 128) < 0) goto err;
+
+ /* intentionally leak `lock`: it must stay open for the socket's lifetime.
+ * The process exit releases it. */
+ return fd;
+
+err:
+ close(fd);
+ close(lock);
+ return -1;
+}
+
+int
+e_wl_proxy_connect(const char *name)
+{
+ struct sockaddr_un addr;
+ int fd;
+
+ if (_fill_addr(name, &addr) < 0) return -1;
+
+ fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0) return -1;
+
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ {
+ if (errno != EINPROGRESS) { close(fd); return -1; }
+ /* nonblocking connect in progress: fine for a unix socket, it
+ * completes effectively immediately; caller treats it as connected. */
+ }
+
+ return fd;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.