summaryrefslogtreecommitdiff
path: root/src/fs.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/fs.zig')
-rw-r--r--src/fs.zig49
1 files changed, 49 insertions, 0 deletions
diff --git a/src/fs.zig b/src/fs.zig
index 7cb08980..5c75eb72 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -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);