diff options
Diffstat (limited to 'src/fs.zig')
| -rw-r--r-- | src/fs.zig | 49 |
1 files changed, 49 insertions, 0 deletions
@@ -681,8 +681,56 @@ pub fn resolve(p: ?*pardes.Pardes, word: []const u8, cwd: []const u8, out: *[409 return null; } +/// The name this session is posted under, taken from its socket path. +var own_name_buf: [64]u8 = undefined; +var own_name_len: usize = 0; + +pub fn noteOwnSocket(socket_path: []const u8) void { + own_name_len = 0; + const base = std.fs.path.basename(socket_path); + const head = "pardes-9p-"; + const tail = ".sock"; + if (!std.mem.startsWith(u8, base, head) or !std.mem.endsWith(u8, base, tail)) return; + const name = base[head.len .. base.len - tail.len]; + if (name.len == 0 or name.len > own_name_buf.len) return; + @memcpy(own_name_buf[0..name.len], name); + own_name_len = name.len; +} + +/// Is this path inside this editor's OWN 9P tree, as a mount presents it? +/// +/// Touching one from inside the editor deadlocks the session outright: the +/// realpath, the stat and the read all leave through the mount and come back +/// as 9P requests that only the editor's loop can answer, while that loop is +/// blocked making them. The filesystem then stops answering anybody. So the +/// answer has to come from the NAME, before any syscall -- the syscall is the +/// thing that never returns. Look at `/n/self/...` instead, which the editor +/// serves from memory without leaving the process. +pub fn isOwnMount(path: []const u8) bool { + if (own_name_len == 0) return false; + var buf: [own_name_buf.len + 16]u8 = undefined; + const needle = std.fmt.bufPrint(&buf, "/pardes/{s}/", .{own_name_buf[0..own_name_len]}) catch return false; + return std.mem.indexOf(u8, path, needle) != null; +} + +test "a path inside this session's own mount is refused before any syscall" { + noteOwnSocket("/run/user/1000/pardes-9p-demo.sock"); + defer own_name_len = 0; + try std.testing.expect(isOwnMount("/mnt/9p/pardes/demo/index")); + try std.testing.expect(isOwnMount("/mnt/9p/pardes/demo/pane/new")); + try std.testing.expect(!isOwnMount("/mnt/9p/pardes/other/index")); + try std.testing.expect(!isOwnMount("/home/goblin/src/pardes/demo.zig")); + var out: [4096]u8 = undefined; + try std.testing.expect(resolveOs("/mnt/9p/pardes/demo/index", &out) == null); + + // A session with no listener has no name, so nothing is refused. + noteOwnSocket("/tmp/not-a-pardes-socket"); + try std.testing.expect(!isOwnMount("/mnt/9p/pardes/demo/index")); +} + pub fn resolveOs(path: []const u8, out: *[4096]u8) ?Resolved { if (comptime !platform_has_fs) return null; + if (isOwnMount(path)) return null; var z: [4096]u8 = undefined; const path_z = std.fmt.bufPrintSentinel(&z, "{s}", .{path}, 0) catch return null; const resolved = realpath(path_z, out) orelse return null; @@ -706,6 +754,7 @@ pub fn read(p: *pardes.Pardes, path: []const u8) ![]u8 { } pub fn readLimit(p: *pardes.Pardes, path: []const u8, max_bytes: usize) ![]u8 { + if (isOwnMount(path)) return error.OwnMount; const limit = @min(max_bytes, limits.max_file_bytes); if (std.mem.eql(u8, std.mem.trimEnd(u8, path, "/"), "/n")) { var out: std.Io.Writer.Allocating = .init(p.gpa); |
