summaryrefslogtreecommitdiff
path: root/src/host_io.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 09:09:47 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit4d4818f7baf356866d323e4b337a64a3dddd8adb (patch)
tree3128484fd60a000c5e0fe207b4e34e658d3bdad6 /src/host_io.zig
parent44aa1ff5bf19b4020e9b4600e5286dea35a3ba5c (diff)
downloadpardes-4d4818f7baf356866d323e4b337a64a3dddd8adb.tar.gz
pardes-4d4818f7baf356866d323e4b337a64a3dddd8adb.zip
pty/ctl exec fails the write when the shell cannot start; a directory removed under a shell leaves its name
`exec` on pty/ctl restarted a shell in a directory that was gone, the write succeeding and the pane dead; and the pane was then renamed `<dir> (deleted)` from /proc, so a restart failed even once the directory was back. `exec` is now refused up front, ENOENT, where the directory is gone; a shell the host cannot start fails the waiting write (late failure, as a Save's); and a `(deleted)` cwd is no name to take. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/host_io.zig')
-rw-r--r--src/host_io.zig34
1 files changed, 33 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;