diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/host_io.zig | 34 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 19 | ||||
| -rw-r--r-- | src/ninep/pty.zig | 6 | ||||
| -rw-r--r-- | src/pardes.zig | 6 |
4 files changed, 64 insertions, 1 deletions
diff --git a/src/host_io.zig b/src/host_io.zig index 1db0dac9..10bcf0d7 100644 --- a/src/host_io.zig +++ b/src/host_io.zig @@ -1286,7 +1286,14 @@ pub fn shellCwd(pid: libc.pid_t, buf: []u8) ?[]const u8 { const path = std.fmt.bufPrintSentinel(&pbuf, "/proc/{d}/cwd", .{pid}, 0) catch return null; const n = libc.readlink(path, buf.ptr, buf.len); if (n <= 0 or n >= buf.len) return null; - return buf[0..@intCast(n)]; + const link = buf[0..@intCast(n)]; + // A directory removed under the shell reads `<path> (deleted)`: + // no directory to be named by, so the name it had stays, and a + // restart works there once it is back. + if (std.mem.endsWith(u8, link, " (deleted)")) { + _ = std.Io.Dir.cwd().statFile(std.Io.Threaded.global_single_threaded.io(), link, .{}) catch return null; + } + return link; }, .macos, .ios, .tvos, .watchos, .visionos => { var info: proc_vnodepathinfo = undefined; @@ -1330,6 +1337,31 @@ test "shell cwd rejects truncation and preserves an owned child path longer than try std.testing.expectEqualStrings(directory, shellCwd(child.pid, &result) orelse return error.MissingCwd); } +test "a shell whose directory was removed is not named `... (deleted)`" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + if (!haveFile("/bin/sh")) return error.SkipZigTest; + const io = std.testing.io; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + try tmp.dir.createDirPath(io, "gone"); + var path_buf: [4096]u8 = undefined; + const directory = path_buf[0..try tmp.dir.realPathFile(io, "gone", &path_buf)]; + const rcs: Shell.PromptFiles = .{}; + const child = try forkShell(null, 0, &rcs, "/bin/sh", directory, 24, 80, null); + defer { + _ = libc.kill(child.pid, libc.SIG.KILL); + _ = libc.waitpid(child.pid, null, 0); + _ = libc.close(child.file.handle); + } + var sh: TestShell = .{ .master = child.file.handle, .pid = child.pid }; + try std.testing.expect(writeFd(child.file.handle, "printf '\\160ardes-cwd-ready\\n'\n")); + try std.testing.expect(sh.waitText("pardes-cwd-ready", 5_000)); + var result: [4096]u8 = undefined; + try std.testing.expectEqualStrings(directory, shellCwd(child.pid, &result) orelse return error.MissingCwd); + try tmp.dir.deleteDir(io, "gone"); + try std.testing.expect(shellCwd(child.pid, &result) == null); +} + extern "c" fn tcgetpgrp(fd: c_int) libc.pid_t; const occ_max_depth: u8 = 8; diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index eea033ed..2f94dd3c 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -1949,6 +1949,25 @@ test "a command line from a missing directory fails ENOENT and makes no pane; a try testing.expect(th.logHas(p, "shell: directory missing")); } +test "pty/ctl exec in a directory that is gone fails ENOENT; a shell that cannot start fails the write" { + const p = try th.withTerm(testing.allocator); + defer p.deinit(); + const Starting = struct { + fn spawn(_: ?*anyopaque, _: u8, _: []const u8) void {} + }; + p.host = .{ .vtable = &.{ .spawn = Starting.spawn } }; + defer p.host = .{}; + const serial = serialOf(p); + p.setCwd(p.paneBySerial(serial).?, "/tmp/pardes-no-such-dir-zz"); + const refused = wr(p, Node.of(serial, .pty_ctl), "exec\n"); + try testing.expectEqual(E.NOENT, refused.errno()); + try testing.expectStringStartsWith(refused.reply.ename, "exec: /tmp/pardes-no-such-dir-zz: no such directory"); + // The host's own failure is the waiting write's (late_failure). + p.fs.late_failure_len = 0; + p.shellFailed(@intCast(p.paneBySerial(serial).?), error.AccessDenied); + try testing.expectEqualStrings("shell: access denied", p.fs.late_failure[0..p.fs.late_failure_len]); +} + 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(); diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig index 68f98554..aa0f499a 100644 --- a/src/ninep/pty.zig +++ b/src/ninep/pty.zig @@ -61,6 +61,12 @@ pub fn writeCtl(p: *Pardes, req: Req, id: usize) Reply { while (it.next()) |raw| { const line = std.mem.trim(u8, raw, " \t\r"); if (line.len == 0) continue; + // A shell restarted where the directory is gone would not start: + // refused before anything runs, as Tty there is. + if (!apply and std.mem.eql(u8, line, "exec")) if (p.panes[id]) |pane| if (pardes.exec.missingDir(p, pane.cwdSlice())) { + const dir = pane.cwdSlice(); + return tree.failText(req.tag, E.NOENT, std.fmt.bufPrint(&p.fs.ename, "exec: {s}: no such directory", .{dir[0..@min(dir.len, 256)]}) catch "exec: no such directory"); + }; if (!verb(p, id, line, apply)) return tree.failText(req.tag, E.INVAL, if (outOfRange(line)) e_winsize_range else e_bad_pty_ctl); } } diff --git a/src/pardes.zig b/src/pardes.zig index 60a5dfe3..1afb1e24 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5251,6 +5251,12 @@ pub const Pardes = struct { p.reportFailure(id, "shell: directory missing (no such directory)") else p.reportError(id, "shell", err); + // A 9P write that asked for the shell (pty/ctl's exec), waiting on + // it, fails with what was said, as a failed Save's does. + const said = pane.msg[0..pane.msg_len]; + const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len); + @memcpy(p.fs.late_failure[0..kept.len], kept); + p.fs.late_failure_len = @intCast(kept.len); // A command that never started ended: `exit 127`, as a shell says of // a command it could not run, so a follower waiting on its exit // hears one, its tag stops saying it runs, and Kill finds nothing. |
