diff options
Diffstat (limited to 'src/ninep/tree.zig')
| -rw-r--r-- | src/ninep/tree.zig | 100 |
1 files changed, 47 insertions, 53 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 53472a60..27ad02c8 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -66,10 +66,6 @@ pub const e_bad_event = "bad event syntax"; pub const e_in_use = "file in use"; pub const e_shut_down = "window shut down"; -fn reads(omode: u8) bool { - return omode & 3 != 1; // OWRITE is the one access mode that never reads -} - /// Tremove closes a pane; nothing else in the tree is created or destroyed by /// the protocol, and wstat stays a truncation. Making a pane is an open of /// /pane/new, which needs no feature of the engine's, so Tcreate is refused @@ -281,18 +277,6 @@ pub const Target = union(enum) { pane: struct { serial: u32, file: PaneFile }, }; -fn paneFileNamed(name: []const u8) ?PaneFile { - const f = std.meta.stringToEnum(PaneFile, name) orelse return null; - if (f == .dir) return null; - return if (f.inPty() and f != .pty) null else f; -} - -fn topFileNamed(name: []const u8) ?TopFile { - const f = std.meta.stringToEnum(TopFile, name) orelse return null; - // `new` is reached in /pane, where the panes it makes are. - return if (f == .root or f == .new) null else f; -} - /// Opening /pane/new makes a pane and reading the open fid answers its /// serial, which is /net/tcp/clone's mechanism exactly (kernel/network/ip/ /// devip.c, `case Qclone` in ipopen, whose Qctl read prints the number it @@ -305,13 +289,6 @@ fn topFileNamed(name: []const u8) ?TopFile { /// can never collide with one. pub const new_pane = "new"; -fn serialNamed(name: []const u8) ?u32 { - if (name.len == 0 or name.len > 10) return null; - for (name) |c| if (c < '0' or c > '9') return null; - const serial = std.fmt.parseInt(u32, name, 10) catch return null; - return if (serial == 0) null else serial; -} - pub fn nextSerialAfter(p: *Pardes, after: u32) ?u32 { var best: ?u32 = null; for (p.panes) |slot| { @@ -329,8 +306,9 @@ pub fn resolveSelf(p: *Pardes, path: []const u8) ?u64 { var parts = std.mem.tokenizeScalar(u8, normalized, '/'); const first = parts.next() orelse return root; if (!std.mem.eql(u8, first, "pane")) { - const top = topFileNamed(first) orelse return sources.node(normalized); - if (top == .pane or parts.next() != null) return null; + // `new` is reached in /pane, where the panes it makes are. + const top = std.meta.stringToEnum(TopFile, first) orelse return sources.node(normalized); + if (top == .root or top == .new or top == .pane or parts.next() != null) return null; return @intFromEnum(top); } const next = parts.next() orelse return @intFromEnum(TopFile.pane); @@ -338,9 +316,15 @@ pub fn resolveSelf(p: *Pardes, path: []const u8) ?u64 { // path: this resolves Look targets and hover previews, and a preview that // opened `new` to see what was there would make a pane per hover. if (std.mem.eql(u8, next, new_pane)) return null; - const serial = serialNamed(next) orelse return null; + // A pane directory is its serial, in decimal digits and never 0. + if (next.len > 10) return null; + for (next) |c| if (c < '0' or c > '9') return null; + const serial = std.fmt.parseInt(u32, next, 10) catch return null; + if (serial == 0) return null; const id = p.paneBySerial(serial) orelse return null; - const file = paneFileNamed(parts.next() orelse return Node.of(serial, .dir)) orelse return null; + // The pty files are reached inside pty/, not beside it. + const file = std.meta.stringToEnum(PaneFile, parts.next() orelse return Node.of(serial, .dir)) orelse return null; + if (file == .dir or (file.inPty() and file != .pty)) return null; if (file.inPty() and !p.panes[id].?.isTerminal()) return null; if (parts.next()) |name| { if (file != .pty) return null; @@ -390,7 +374,7 @@ pub fn handle(p: *Pardes, req: Req) Reply { }; return switch (req.op) { .lookup => lookup(p, req, target), - .getattr => attrReply(p, req.tag, target), + .getattr => .{ .tag = req.tag, .attr = attrOf(p, target) orelse return Reply.fail(req.tag, E.NOENT) }, .setattr => setattr(p, req, target), .open => open(p, req, target), .release => release(p, req), @@ -420,8 +404,14 @@ fn attrOf(p: *Pardes, target: Target) ?Reply.Attr { .dir = t.file.isDir(), .mode = t.file.mode(), .size = pane.fileSize(p, id, t.file), - .mtime = pane.mtimeOf(p, pn), - .version = pane.versionOf(pn, t.file), + .mtime = if (pn.file) |*f| (if (f.mtime != 0) f.mtime else p.fs.started) else p.fs.started, + // The qid version, which a client polls to watch a file for + // change. Only the pane's text can promise one: its revision + // counts every edit, and nothing else here changes in step. + .version = switch (t.file) { + .body, .data, .xdata => if (pn.file) |*f| f.revision else 0, + else => 0, + }, // A write to body or tag appends whatever its offset says, // which is why acme's dirtab marks both DMAPPEND // (editors/acme/fsys.c:78). @@ -431,11 +421,6 @@ fn attrOf(p: *Pardes, target: Target) ?Reply.Attr { } } -fn attrReply(p: *Pardes, tag: u64, target: Target) Reply { - const a = attrOf(p, target) orelse return Reply.fail(tag, E.NOENT); - return .{ .tag = tag, .attr = a }; -} - fn topSize(p: *Pardes, f: TopFile) u64 { return switch (f) { // ponytail: /screen has no length until an open renders its frame, @@ -446,7 +431,10 @@ fn topSize(p: *Pardes, f: TopFile) u64 { .status => ctl.statusLen(p), .look, .exec => ctl.resultsLen(p), .log => events.logLen(&p.fs.log), - .listeners => screen.listenersLen(p), + .listeners => listeners: { + var buf: [screen.listeners_cap]u8 = undefined; + break :listeners screen.listenersText(p, &buf).len; + }, .ctl => ctl.rootLen(p), .commands => ctl.commandsLen(), .focus => focus: { @@ -475,13 +463,18 @@ fn lookup(p: *Pardes, req: Req, target: Target) Reply { .top => |f| switch (f) { .root => root: { if (std.mem.eql(u8, name, "os")) return fs.osHandle(p, .{ .tag = req.tag, .op = .getattr, .node = fs.os_root }); - if (topFileNamed(name)) |t| break :root @intFromEnum(t); + // `new` is reached in /pane, where the panes it makes are. + if (std.meta.stringToEnum(TopFile, name)) |t| if (t != .root and t != .new) break :root @intFromEnum(t); if (sources.node(name)) |n| return sources.handle(p, .{ .tag = req.tag, .op = .getattr, .node = n }); return Reply.fail(req.tag, E.NOENT); }, .pane => pane: { if (std.mem.eql(u8, name, new_pane)) break :pane @intFromEnum(TopFile.new); - const serial = serialNamed(name) orelse return Reply.fail(req.tag, E.NOENT); + // A pane directory is its serial, in decimal digits and never 0. + if (name.len > 10) return Reply.fail(req.tag, E.NOENT); + for (name) |c| if (c < '0' or c > '9') return Reply.fail(req.tag, E.NOENT); + const serial = std.fmt.parseInt(u32, name, 10) catch return Reply.fail(req.tag, E.NOENT); + if (serial == 0) return Reply.fail(req.tag, E.NOENT); break :pane Node.of(serial, .dir); }, else => return Reply.fail(req.tag, E.NOTDIR), @@ -489,14 +482,15 @@ fn lookup(p: *Pardes, req: Req, target: Target) Reply { .pane => |t| pane: { _ = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT); const f = switch (t.file) { - .dir => paneFileNamed(name), + .dir => if (std.meta.stringToEnum(PaneFile, name)) |f| (if (f == .dir or (f.inPty() and f != .pty)) null else f) else null, .pty => pty.fileNamed(name), else => return Reply.fail(req.tag, E.NOTDIR), } orelse return Reply.fail(req.tag, E.NOENT); break :pane Node.of(t.serial, f); }, }; - return attrReply(p, req.tag, Node.target(node) orelse return Reply.fail(req.tag, E.NOENT)); + const found = Node.target(node) orelse return Reply.fail(req.tag, E.NOENT); + return .{ .tag = req.tag, .attr = attrOf(p, found) orelse return Reply.fail(req.tag, E.NOENT) }; } fn stagePaneFiles(p: *Pardes, out: *std.ArrayList(u8), serial: u32, terminal: bool, skip: *u64) void { @@ -564,20 +558,19 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply { return .{ .tag = req.tag, .payload = .{ .staged = @intCast(out.items.len) } }; } -/// Opens a pane for an open of /pane/new, whose serial becomes the handle: -/// the fid remembers which pane it made, so reading it twice answers the -/// same one and closing it leaves the pane alone. -fn makePane(p: *Pardes) ?u32 { - const slot = p.freeSlot() orelse return null; - p.newScratchBelow(p.active); - return (p.panes[slot] orelse return null).serial; -} - fn open(p: *Pardes, req: Req, target: Target) Reply { var pane_of: ?*Pane = null; const what: @FieldType(Open, "what") = switch (target) { .top => |f| switch (f) { - .new => return .{ .tag = req.tag, .handle = makePane(p) orelse return Reply.fail(req.tag, E.NFILE) }, + // The made pane's serial is the handle: the fid remembers which + // pane it made, so reading it twice answers the same one and + // closing it leaves the pane alone. + .new => { + const slot = p.freeSlot() orelse return Reply.fail(req.tag, E.NFILE); + p.newScratchBelow(p.active); + const made = p.panes[slot] orelse return Reply.fail(req.tag, E.NFILE); + return .{ .tag = req.tag, .handle = made.serial }; + }, .screen => .{ .snapshot = null }, .log => .{ .log = .{} }, else => return .{ .tag = req.tag }, @@ -586,7 +579,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply { const pn = p.panes[p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT)].?; if (t.file.inPty() and !pn.isTerminal()) return Reply.fail(req.tag, E.NOENT); pane_of = pn; - const reader = reads(req.omode); + const reader = req.omode & 3 != 1; // OWRITE is the one mode that never reads break :what switch (t.file) { .body => if (pn.isTerminal()) .{ .snapshot = null } else return .{ .tag = req.tag }, // Reads of these consume, so two readers would each see half @@ -693,7 +686,7 @@ fn setattr(p: *Pardes, req: Req, target: Target) Reply { }, .top => {}, }; - return attrReply(p, req.tag, target); + return .{ .tag = req.tag, .attr = attrOf(p, target) orelse return Reply.fail(req.tag, E.NOENT) }; } fn read(p: *Pardes, req: Req, target: Target) Reply { @@ -710,7 +703,8 @@ fn read(p: *Pardes, req: Req, target: Target) Reply { var last: u32 = 0; while (nextSerialAfter(p, last)) |serial| { last = serial; - pane.stageIndexLine(p, out, p.panes[p.paneBySerial(serial).?].?); + const pn = p.panes[p.paneBySerial(serial).?].?; + out.print(p.gpa, "{d} {s} {d} {s}\n", .{ serial, pane.kindOf(pn), @intFromBool(pane.dirtyOf(pn)), pane.nameOf(pn) }) catch {}; } break :index stagedReply(p, req); }, |
