summaryrefslogtreecommitdiff
path: root/src/nested.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/nested.zig')
-rw-r--r--src/nested.zig61
1 files changed, 41 insertions, 20 deletions
diff --git a/src/nested.zig b/src/nested.zig
index eb01b2e0..887d0703 100644
--- a/src/nested.zig
+++ b/src/nested.zig
@@ -36,21 +36,28 @@ const libc = std.c;
extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
extern "c" fn unsetenv(name: [*:0]const u8) c_int;
-const darwin = switch (builtin.os.tag) {
+/// THE SOCKET CONVENTIONS BELOW ARE SHARED, and the ones marked `pub` are
+/// shared with src/detached/server.zig — a second unix socket in the same
+/// per-user directory, under a different name (`pardes-detached-<name>.sock`
+/// rather than `pardes-<pid>.sock`). They were copied into that file when it
+/// landed; one directory vetted by two different predicates is exactly the
+/// divergence the reasoning here is meant to prevent, so there is one of each.
+pub const darwin = switch (builtin.os.tag) {
.macos, .ios, .tvos, .watchos, .visionos => true,
else => false,
};
/// This module is only as portable as its two ingredients: a way to name the
-/// executable and parent of an arbitrary pid, and unix sockets.
-const supported = builtin.os.tag == .linux or darwin;
+/// executable and parent of an arbitrary pid, and unix sockets. The detached
+/// transport needs the second alone, and the same answer.
+pub const supported = builtin.os.tag == .linux or darwin;
/// `sun_path` is 108 bytes on linux and 104 on darwin, and it is the hard
/// limit on this whole feature: a path that does not fit is not a socket
/// address, it is a truncated one pointing somewhere else. Taken from the
/// struct so that the buffers, the fit checks and the memcpy below cannot
/// disagree with the kernel or with each other.
-const sun_path_len = @typeInfo(@FieldType(libc.sockaddr.un, "path")).array.len;
+pub const sun_path_len = @typeInfo(@FieldType(libc.sockaddr.un, "path")).array.len;
/// libproc, darwin's answer to /proc. `proc_pidpath` is readlink of
/// `/proc/<pid>/exe`; `PROC_PIDTBSDINFO` carries the parent pid that linux
@@ -72,10 +79,15 @@ extern "c" fn proc_pidpath(pid: c_int, buffer: *anyopaque, buffersize: u32) c_in
extern "c" fn proc_pidinfo(pid: c_int, flavor: c_int, arg: u64, buffer: *anyopaque, buffersize: c_int) c_int;
/// Linux opens sockets CLOEXEC in one call; darwin has to set it afterwards.
-/// The gap is a race only against a fork on another thread, and both callers
-/// are past that: `listen` runs before the first pane exists, and `acceptLine`
-/// runs on a thread of its own long after spawning has settled.
-fn setCloexec(fd: c_int) void {
+/// The gap is a race only against a fork on another thread, and every caller
+/// is past that: `listen` runs before the first pane exists, `acceptLine` runs
+/// on a thread of its own long after spawning has settled, and the detached
+/// session forks nothing at all (its ptys live in its frontends).
+///
+/// CLOEXEC still matters for both: a `--detach` session is long-lived, and an
+/// inherited listener would keep its socket bound long after it ended — the
+/// same shape as the inherited lock fd that once held a flock forever.
+pub fn setCloexec(fd: c_int) void {
const FD_CLOEXEC: c_int = 1;
_ = libc.fcntl(fd, libc.F.SETFD, FD_CLOEXEC);
}
@@ -87,10 +99,12 @@ pub const max_line = 4200;
/// Where the sockets live. `$XDG_RUNTIME_DIR` first — a per-user 0700 tmpfs
/// the login session already cleans up — else `~/.local/state/pardes`, which
-/// is per-user for the same reason a home directory is. Asked by the client
-/// (to derive the path), by the listener (to create and vet it) and by the
-/// sweeper (to scan it), so it is written once.
-fn socketDir(buf: *[sun_path_len:0]u8) ?[:0]const u8 {
+/// is per-user for the same reason a home directory is. NEVER /tmp: these
+/// sockets take a command line, or keystrokes into a live editor. Asked by the
+/// client (to derive the path), by the listener (to create and vet it), by the
+/// sweeper (to scan it) and by the detached transport (all three, for its own
+/// name), so it is written once.
+pub fn socketDir(buf: *[sun_path_len:0]u8) ?[:0]const u8 {
if (libc.getenv("XDG_RUNTIME_DIR")) |x|
return std.fmt.bufPrintSentinel(buf, "{s}", .{std.mem.span(x)}, 0) catch null;
const home = libc.getenv("HOME") orelse return null;
@@ -311,14 +325,19 @@ pub fn sendLook(pid: libc.pid_t, path: []const u8, line: usize) bool {
return true;
}
-/// The three things ensureSocketDir has to know about a path, from whichever
+/// The two things `ensureSocketDir` has to know about a path, from whichever
/// call the platform actually offers. Darwin has fstatat and no statx; on
/// linux std.c.fstatat is `void` — glibc hides it behind a versioned symbol
-/// std cannot name — so linux asks statx for the same three fields. Both
-/// spellings refuse to follow a symlink, which is the point of asking.
-const DirFacts = struct { mode: u32, uid: libc.uid_t };
+/// std cannot name — so linux asks statx for the same fields. Both spellings
+/// refuse to follow a symlink, which is the point of asking.
+///
+/// `pub` for the detached transport, which vets the same directory and also
+/// vets the SOCKET FILE with it (src/detached/server.zig `vetted`): `mode`
+/// carries the type bits, so one call answers "is this a socket, ours, and
+/// private" as well as it answers it for a directory.
+pub const DirFacts = struct { mode: u32, uid: libc.uid_t };
-fn statNoFollow(path: [:0]const u8) ?DirFacts {
+pub fn statNoFollow(path: [:0]const u8) ?DirFacts {
if (comptime darwin) {
var st: libc.Stat = undefined;
if (libc.fstatat(libc.AT.FDCWD, path, &st, libc.AT.SYMLINK_NOFOLLOW) != 0) return null;
@@ -334,9 +353,11 @@ fn statNoFollow(path: [:0]const u8) ?DirFacts {
/// Create the socket directory if it is missing and refuse it unless it is a
/// directory WE own with nothing granted to group or other. A planted path is
-/// the whole attack on a socket that runs commands, and $XDG_RUNTIME_DIR
-/// passes this untouched (the login session already makes it 0700).
-fn ensureSocketDir(dir: [:0]const u8) bool {
+/// the whole attack on a socket that runs commands — or, for the detached
+/// transport that shares this, on one that carries keystrokes into a live
+/// editor — and $XDG_RUNTIME_DIR passes this untouched (the login session
+/// already makes it 0700).
+pub fn ensureSocketDir(dir: [:0]const u8) bool {
// mkdir -p, because the HOME branch is three levels deep and a machine
// without ~/.local/state would otherwise switch the feature off in
// silence. Under $XDG_RUNTIME_DIR every prefix already exists and simply