summaryrefslogtreecommitdiff
path: root/9proc
diff options
context:
space:
mode:
Diffstat (limited to '9proc')
-rw-r--r--9proc/src/linux/probe.zig50
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 {