diff options
Diffstat (limited to 'src/nested.zig')
| -rw-r--r-- | src/nested.zig | 61 |
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 |
