diff options
Diffstat (limited to 'src/host_io.zig')
| -rw-r--r-- | src/host_io.zig | 82 |
1 files changed, 78 insertions, 4 deletions
diff --git a/src/host_io.zig b/src/host_io.zig index 1db0dac9..913930ac 100644 --- a/src/host_io.zig +++ b/src/host_io.zig @@ -867,7 +867,8 @@ pub const Shell = struct { buf[bin.len] = 0; if (isDirectory(@ptrCast(&buf))) return std.fmt.bufPrint(said, "not a shell: {s} is a directory", .{bin}) catch "not a shell: a directory"; } - return std.fmt.bufPrint(said, "no shell \"{s}\" (a name on the usual paths, or a path to one)", .{bin}) catch "no such shell"; + // "not found": the words a mount reads as ENOENT. + return std.fmt.bufPrint(said, "no shell \"{s}\", not found (a name on the usual paths, or a path to one)", .{bin}) catch "no such shell"; } fn fallback(buf: *[std.fs.max_path_bytes]u8) [*:0]const u8 { @@ -1156,17 +1157,58 @@ pub fn forkShell( var env_slots: ChildEnv.Slots = undefined; const envp = ChildEnv.build(&env_slots); const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 }; + // The child says here why it could not become the shell (its chdir or + // its exec failed, as for a script whose interpreter is not there): the + // pipe closes on exec, so the parent reads nothing when the shell runs, + // and a reason when it did not, before anyone is told it started. + var told: [2]c_int = .{ -1, -1 }; + // pipe and then CLOEXEC, not pipe2, which macOS has not. + if (libc.pipe(&told) != 0) return error.PipeFailed; + ninep_io.setCloexec(told[0]); + ninep_io.setCloexec(told[1]); const pid = forkpty(&master, null, null, &ws); - if (pid < 0) return error.ForkFailed; + if (pid < 0) { + _ = libc.close(told[0]); + _ = libc.close(told[1]); + return error.ForkFailed; + } if (pid == 0) { var set = posix.sigemptyset(); posix.sigaddset(&set, posix.SIG.WINCH); posix.sigprocmask(posix.SIG.UNBLOCK, &set, null); - if (cwd_z) |path| if (chdir(path.ptr) != 0) _exit(126); + if (cwd_z) |path| if (chdir(path.ptr) != 0) { + const why = [_]u8{ 'c', @truncate(@intFromEnum(libc.errno(@as(c_int, -1)))) }; + _ = libc.write(told[1], &why, why.len); + _exit(126); + }; if (envp) |env| _ = execve(spawn.path, &spawn.argv, env); _ = execv(spawn.path, &spawn.argv); + const why = [_]u8{ 'x', @truncate(@intFromEnum(libc.errno(@as(c_int, -1)))) }; + _ = libc.write(told[1], &why, why.len); _exit(127); } + _ = libc.close(told[1]); + const failed = failed: { + var why: [2]u8 = undefined; + pardes.turn.yield(); + defer pardes.turn.back(); + const n = libc.read(told[0], &why, why.len); + _ = libc.close(told[0]); + if (n != 2) break :failed null; + break :failed why; + }; + if (failed) |why| { + _ = libc.close(master); + _ = libc.waitpid(pid, null, 0); + const e: libc.E = @enumFromInt(why[1]); + if (why[0] == 'c') return if (e == .NOENT or e == .NOTDIR) error.FileNotFound else error.AccessDenied; + return switch (e) { + .NOENT => error.ShellNotFound, // itself, or its script's interpreter + .ACCES, .PERM => error.ShellNotExecutable, + .NOEXEC => error.ShellNotExecutable, + else => error.ShellDidNotStart, + }; + } ninep_io.setCloexec(master); if (core) |c| c.acknowledgeShell(pane, std.mem.span(spawn.path), one_line == null and spawn.argv[1] != null); return .{ .file = .{ .handle = master, .flags = .{ .nonblocking = false } }, .pid = pid }; @@ -1286,7 +1328,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 +1379,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; |
