summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 00:50:45 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit7aa057e29b729ad6d9ac53e3a06f99858fc449e4 (patch)
treea0010efb342018f32f98d9e805680306feb88836 /src/ninep/tree.zig
parente563be42a5b3e9243e2c01d34cf8e94d2047b753 (diff)
downloadpardes-7aa057e29b729ad6d9ac53e3a06f99858fc449e4.tar.gz
pardes-7aa057e29b729ad6d9ac53e3a06f99858fc449e4.zip
A column has a ctl, an exec and an rmdir over 9P, and Restore maps its serial
A script could read and write a column's tag but not act on the column. col/<serial>/ctl takes Delcol, Joincol, New and Tty, each acting on that column as from its tag; col/<serial>/exec runs a word as a middle click in the tag; rmdir col/<serial> closes an empty column and refuses one with panes (ENOTEMPTY, an err record saying why). The root ctl's refusal of Delcol and Joincol points at col/<serial>/ctl. A dump keeps each column's serial and Restore logs restoredcol <old> <new>; the dump golden gains them. Documented: Joincol's survivor, the column on the right, keeps its serial and tag. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/tree.zig')
-rw-r--r--src/ninep/tree.zig38
1 files changed, 30 insertions, 8 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index c4ade1f7..baf8c772 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -272,7 +272,16 @@ pub const PaneFile = enum(u5) {
/// /col/<serial> and its tag: a column is named by its serial, which stays
/// while it lives as a pane's does; this bit, past a pane serial's 32, keeps
/// the two apart.
-pub const ColFile = enum(u5) { dir = 0, tag = 1 };
+pub const ColFile = enum(u5) {
+ dir = 0,
+ tag = 1,
+ ctl = 2,
+ exec = 3,
+
+ pub fn fileName(f: ColFile) []const u8 {
+ return @tagName(f);
+ }
+};
const col_bit: u59 = 1 << 58;
pub const Node = packed struct(u64) {
@@ -454,10 +463,14 @@ fn attrOf(p: *Pardes, target: Target) ?Reply.Attr {
.col => |c| {
if (layout.columnBySerial(p, c.serial) == null) return null;
return .{
- .name = if (c.file == .dir) (std.fmt.bufPrint(&p.fs.node_name, "{d}", .{c.serial}) catch unreachable) else "tag",
+ .name = if (c.file == .dir) (std.fmt.bufPrint(&p.fs.node_name, "{d}", .{c.serial}) catch unreachable) else c.file.fileName(),
.node = Node.ofCol(c.serial, c.file),
.dir = c.file == .dir,
- .mode = if (c.file == .dir) 0o755 else 0o666,
+ .mode = switch (c.file) {
+ .dir => 0o755,
+ .ctl, .exec => 0o222,
+ .tag => 0o666,
+ },
.size = if (c.file == .tag) cols.headerText(p, c.serial).?.len + 1 else 0,
.mtime = p.fs.started,
.append = c.file == .tag,
@@ -562,8 +575,9 @@ fn lookup(p: *Pardes, req: Req, target: Target) Reply {
},
.col => |c| col: {
if (c.file != .dir) return Reply.fail(req.tag, E.NOTDIR);
- if (!std.mem.eql(u8, name, "tag")) return Reply.fail(req.tag, E.NOENT);
- break :col Node.ofCol(c.serial, .tag);
+ const f = std.meta.stringToEnum(ColFile, name) orelse return Reply.fail(req.tag, E.NOENT);
+ if (f == .dir) return Reply.fail(req.tag, E.NOENT);
+ break :col Node.ofCol(c.serial, f);
},
.pane => |t| pane: {
_ = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT);
@@ -638,7 +652,9 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply {
.col => |c| {
if (layout.columnBySerial(p, c.serial) == null) return Reply.fail(req.tag, E.NOENT);
if (c.file != .dir) return Reply.fail(req.tag, E.NOTDIR);
- if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, Node.ofCol(c.serial, .tag), false, "tag");
+ inline for (.{ ColFile.tag, ColFile.ctl, ColFile.exec }) |f| {
+ if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, Node.ofCol(c.serial, f), false, f.fileName());
+ }
},
.pane => |t| {
const id = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT);
@@ -729,7 +745,8 @@ fn remove(p: *Pardes, req: Req) Reply {
const target = Node.target(req.node) orelse return Reply.fail(req.tag, E.NOENT);
const t = switch (target) {
.pane => |t| t,
- .top, .col => return Reply.fail(req.tag, E.PERM),
+ .top => return Reply.fail(req.tag, E.PERM),
+ .col => |c| return cols.remove(p, req, c.serial, c.file),
};
if (t.file != .dir) return Reply.fail(req.tag, E.PERM);
const id = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT);
@@ -902,7 +919,12 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
if (t.file.inPty() and !pn.isTerminal()) return Reply.fail(req.tag, E.NOENT);
return pane.write(p, req, id, pn, t.file);
},
- .col => |c| return if (c.file == .tag) cols.write(p, req, c.serial) else Reply.fail(req.tag, E.PERM),
+ .col => |c| return switch (c.file) {
+ .tag => cols.write(p, req, c.serial),
+ .ctl => cols.writeCtl(p, req, c.serial),
+ .exec => cols.writeExec(p, req, c.serial),
+ .dir => Reply.fail(req.tag, E.PERM),
+ },
}
}