summaryrefslogtreecommitdiff
path: root/src/host_io.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 03:37:13 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit98e94b86a7c38e2bf4d66e57144fc9329906bb3b (patch)
tree61a7c596cd54dbc4d7a0b17243f592b2fb4bab80 /src/host_io.zig
parentd067c77813f9ce9fcc64b33e6b0284510ac02185 (diff)
downloadpardes-98e94b86a7c38e2bf4d66e57144fc9329906bb3b.tar.gz
pardes-98e94b86a7c38e2bf4d66e57144fc9329906bb3b.zip
Tty and Shell refuse a directory: not a shell
A directory passes access(X_OK), so Tty /etc made a pane whose shell exited 127 and Shell /etc was taken. The shell lookup now refuses a directory, and both say "not a shell: /etc is a directory" (Tty and Shell share one refusal, host_io.Shell.refusal). shellset's golden takes the shared wording (re-recorded by name). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/host_io.zig')
-rw-r--r--src/host_io.zig22
1 files changed, 21 insertions, 1 deletions
diff --git a/src/host_io.zig b/src/host_io.zig
index f83c7d47..10a85d8f 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -834,7 +834,7 @@ pub const Shell = struct {
@memcpy(buf[0..bin.len], bin);
buf[bin.len] = 0;
const p: [*:0]const u8 = @ptrCast(buf);
- return if (libc.access(p, X_OK) == 0) p else null;
+ return if (libc.access(p, X_OK) == 0 and !isDirectory(p)) p else null;
}
for (bin_dirs) |dir| {
if (dir.len + bin.len + 1 > buf.len) continue;
@@ -847,6 +847,26 @@ pub const Shell = struct {
return null;
}
+ /// A directory passes access(X_OK) (it can be searched): no shell.
+ pub fn isDirectory(path: [*:0]const u8) bool {
+ const d = libc.opendir(path) orelse return false;
+ _ = libc.closedir(d);
+ return true;
+ }
+
+ /// Why `bin` is no shell, for the words that name one (Tty, Shell);
+ /// null when it is one.
+ pub fn refusal(bin: []const u8, said: []u8) ?[]const u8 {
+ var buf: [std.fs.max_path_bytes]u8 = undefined;
+ if (find(bin, &buf) != null) return null;
+ if (std.mem.indexOfScalar(u8, bin, '/') != null and bin.len < buf.len) {
+ @memcpy(buf[0..bin.len], bin);
+ 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";
+ }
+
fn fallback(buf: *[std.fs.max_path_bytes]u8) [*:0]const u8 {
for (fallbacks) |f| {
@memcpy(buf[0..f.len], f);