diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/fs.zig | 12 | ||||
| -rw-r--r-- | src/look.zig | 22 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 25 |
3 files changed, 56 insertions, 3 deletions
@@ -677,7 +677,9 @@ pub fn resolve(p: ?*pardes.Pardes, word: []const u8, cwd: []const u8, out: *[409 if (std.mem.startsWith(u8, joined, "/virtual/")) return resolveVirtual(p, joined[8..], out); if (resolveOs(joined, out)) |found| return found; if (resolveVirtual(p, joined, out)) |found| return found; - if (resolveVirtual(p, word, out)) |found| return found; + // `.`, `..` and `./x` name the pane's directory, never the served tree: + // with that directory gone they name nothing (`.` would be /virtual/). + if (!dotRelative(word)) if (resolveVirtual(p, word, out)) |found| return found; if (resolveEmbedded(word, cwd, out)) |source| { const path = std.fmt.bufPrint(out, "/virtual/{s}", .{source.path}) catch return null; return .{ .path = path }; @@ -685,6 +687,14 @@ pub fn resolve(p: ?*pardes.Pardes, word: []const u8, cwd: []const u8, out: *[409 return null; } +/// Whether `word` is spelled relative to the directory it is looked from: +/// `.`, `..`, `./x`, `../x`. +pub fn dotRelative(word: []const u8) bool { + const w = std.mem.trimEnd(u8, word, "/"); + return std.mem.eql(u8, w, ".") or std.mem.eql(u8, w, "..") or + std.mem.startsWith(u8, word, "./") or std.mem.startsWith(u8, word, "../"); +} + /// Where a path really is on the host, and whether it is a directory. The /// path may lie in a mount this editor serves, so the turn is given up for /// the syscalls: another thread answers them. diff --git a/src/look.zig b/src/look.zig index fa86aee4..9a81a5c9 100644 --- a/src/look.zig +++ b/src/look.zig @@ -593,7 +593,11 @@ pub fn runSearch(p: *Pardes, id: usize, pat_raw: []const u8, kind: Search, start var nroots: usize = 0; for (p.panes) |slot| { const pp = slot orelse continue; - const d = Pardes.paneDir(pp); + // A pane of the served tree (/virtual/, /n/<peer>/) has no + // directory on this host to walk, and one that named `/` would + // swallow every other root: passed over. + const d = filesystem.localPath(Pardes.paneDir(pp)) orelse continue; + if (filesystem.isVirtual(if (pp.file) |f| f.path else d)) continue; var covered = false; var k: usize = 0; while (k < nroots) { @@ -1107,7 +1111,8 @@ fn resolveLookTarget(p: *Pardes, id: usize, txt: []const u8, realbuf: *[4096]u8) if (openPaneTarget(p, j, pl.at)) |target| return target; } found = resolve(p, txt, dir, realbuf); - if (found != .none or pl.path.len == 0 or pl.path[0] == '/') break; + // `.`, `..`, `./x` are this pane's directory's, not another's. + if (found != .none or pl.path.len == 0 or pl.path[0] == '/' or filesystem.dotRelative(pl.path)) break; } return found; } @@ -1407,6 +1412,13 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { } switch (found) { .none => { + // `.` or `..` from a pane whose directory is gone: said, ENOENT, + // not searched for as a word. + if (filesystem.dotRelative(trimmed)) { + const dir = Pardes.paneDir(pane); + var said: [4200]u8 = undefined; + return p.reportFailure(id, std.fmt.bufPrint(&said, "look: {s}: no such directory", .{dir[0..@min(dir.len, 4096)]}) catch "look: no such directory"); + } // A raw terminal's clicks are its program's; a look written over // 9P is no keyboard input, and searches whatever the key mode. if (pane.image != null or (pane.body.mode == .tty and !p.fs.serving)) return; @@ -1439,6 +1451,12 @@ pub fn lookAt(p: *Pardes, id: usize, operand: []const u8) void { }, .url => |u| if (u.len <= 256) p.emit(.{ .open_link = .from(u) }), .dir => |dir| { + // A directory that is not there (the pane's own, gone): said, + // ENOENT, no terminal made in it. + if (comptime pardes.hosted) if (filesystem.localPath(dir)) |local| if (!exec.isDirectory(local)) { + var said: [4200]u8 = undefined; + return p.reportFailure(id, std.fmt.bufPrint(&said, "look: {s}: no such directory", .{dir[0..@min(dir.len, 4096)]}) catch "look: no such directory"); + }; for (p.panes, 0..) |slot, i| { if (slot) |tt| if (std.mem.eql(u8, tt.cwdSlice(), dir) and exec.takesCommandLine(p, i)) { clearNavigationSelection(p, tt); diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index db37fa49..d9a479fe 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -2001,6 +2001,31 @@ test "/index shows a name as the log does: a newline in it is \\n, controls spac try testing.expectEqual(@as(u64, index.len), call(p, .{ .tag = 1, .op = .getattr, .node = @intFromEnum(tree.TopFile.index) }).reply.attr.size); } +test "a served-tree pane spoils no Grep, and Look . from a gone directory is ENOENT, not /virtual/" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + try tmp.dir.writeFile(testing.io, .{ .sub_path = "hit.txt", .data = "needle here\n" }); + var dir_buf: [4096]u8 = undefined; + const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)]; + var line: [4200]u8 = undefined; + _ = wr(p, Node.of(serialOf(p), .name), try std.fmt.bufPrint(&line, "{s}/other.txt\n", .{dir})); + // A pane of the served tree. + const virtual = try th.newPane(p); + _ = wr(p, Node.of(virtual, .name), "/virtual/index\n"); + // One named the served tree's root, whose directory is `/`. + const top = try th.newPane(p); + _ = wr(p, Node.of(top, .name), "/virtual\n"); + try testing.expectEqual(Status.ok, wr(p, Node.of(serialOf(p), .exec), "Grep needle\n").reply.status); + // `.` from a pane whose directory is gone. + const gone = try th.newPane(p); + _ = wr(p, Node.of(gone, .name), "/tmp/pardes-no-such-dir-zz/f.txt\n"); + const looked = wr(p, Node.of(gone, .look), ".\n"); + try testing.expectEqual(E.NOENT, looked.errno()); + try testing.expectStringStartsWith(looked.reply.ename, "look: /tmp/pardes-no-such-dir-zz: no such directory"); +} + test "size is monotonic: growing is never refused, and a size once taken is taken again" { const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 60 }); defer p.deinit(); |
