diff options
Diffstat (limited to '9proc/src/linux/probe.zig')
| -rw-r--r-- | 9proc/src/linux/probe.zig | 50 |
1 files changed, 47 insertions, 3 deletions
diff --git a/9proc/src/linux/probe.zig b/9proc/src/linux/probe.zig index 4db277d..c2f1026 100644 --- a/9proc/src/linux/probe.zig +++ b/9proc/src/linux/probe.zig @@ -67,6 +67,10 @@ pub const Error = error{ TooManyProviders, PathTooLong, BadAddress, + /// The unix path holds a live server; nothing is deleted or taken. + AlreadyListening, + /// The unix path holds a foreign non-socket entry; never deleted. + Occupied, /// A syscall failed; `last_errno` says which error. Syscall, }; @@ -128,6 +132,8 @@ pub fn Probe(comptime Srv: type) type { wake_fd: i32 = -1, unix_path: [108]u8 = undefined, unix_len: usize = 0, + /// The bound entry's inode; `stop` unlinks only its own socket. + unix_ino: u64 = 0, thread: ?std.Thread = null, thread_tid: std.atomic.Value(u32) = .init(0), nclients: std.atomic.Value(u32) = .init(0), @@ -233,7 +239,11 @@ pub fn Probe(comptime Srv: type) type { p.listen_fd = -1; } if (p.unix_len > 0) { - _ = linux.unlink(@ptrCast(&p.unix_path)); + // Only our own entry: a late stop must never unlink a + // name another server has since claimed. + if (unixIno(@ptrCast(&p.unix_path))) |ino| { + if (p.unix_ino != 0 and ino == p.unix_ino) _ = linux.unlink(@ptrCast(&p.unix_path)); + } p.unix_len = 0; } if (p.wake_fd >= 0) { @@ -522,14 +532,48 @@ pub fn Probe(comptime Srv: type) type { try p.check(rc); const lfd: i32 = @intCast(rc); errdefer _ = linux.close(lfd); - // No libc, so no "is it still listening" probe: unlink a stale socket and bind. - _ = linux.unlink(@ptrCast(&sa.path)); + // Nothing foreign is deleted: a non-socket entry at the path + // is refused (Occupied), a live server is refused + // (AlreadyListening), and only a socket that refuses a + // connect — a corpse — is unlinked. (A hand racing the swap + // between probe and unlink is the documented residual of this + // cheap protocol; cloud9.post claims names atomically when + // that matters.) + const st = unixStat(@ptrCast(&sa.path)) catch return error.Occupied; + if (st) |s| { + if (s.mode & linux.S.IFMT != linux.S.IFSOCK) return error.Occupied; + if (cloud9.post.probe(@ptrCast(&sa.path)) != .stale) return error.AlreadyListening; + _ = linux.unlink(@ptrCast(&sa.path)); + } try p.check(linux.bind(lfd, @ptrCast(&sa), @sizeOf(linux.sockaddr.un))); try p.check(linux.listen(lfd, 128)); p.listen_fd = lfd; p.own_listener = true; p.unix_path = sa.path; p.unix_len = path.len; + // Our entry's inode, so `stop` never unlinks a name another + // server has since claimed. + p.unix_ino = if (unixStat(@ptrCast(&p.unix_path)) catch null) |s| s.ino else 0; + } + + /// statx(2) of one path: null when it does not exist, and an + /// error when the kernel cannot say — the caller refuses rather + /// than guesses. + fn unixStat(path: [*:0]const u8) !?linux.Statx { + var stx: linux.Statx = undefined; + const rc = linux.statx(linux.AT.FDCWD, path, 0, .{ .TYPE = true, .INO = true }, &stx); + const s: isize = @bitCast(rc); + if (s == 0) return stx; + const noent: isize = @intCast(@intFromEnum(linux.E.NOENT)); + if (s == -noent) return null; + return error.StatFailed; + } + + /// The socket at `path`, by inode; null when it is gone or + /// unreadable. + fn unixIno(path: [*:0]const u8) ?u64 { + const stx = unixStat(path) catch return null; + return if (stx) |s| s.ino else null; } fn listenTcp(p: *Self, text: []const u8) Error!void { |
