From e21965c564bf0670c0e95ca493ea69bbc9626577 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 12:38:13 -0300 Subject: Tty or Shell naming a file that is there but not executable says so It said no shell "x", not found, as for a name that is nowhere. A file that exists without its execute bit now says not a shell: x is not executable. Co-Authored-By: Claude Opus 5.5 --- src/host_io.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/host_io.zig b/src/host_io.zig index 85ba94f4..072f3100 100644 --- a/src/host_io.zig +++ b/src/host_io.zig @@ -872,6 +872,8 @@ pub const Shell = struct { @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"; + // There, but no program: said so, not "not found". + if (libc.access(@ptrCast(&buf), 0) == 0) return std.fmt.bufPrint(said, "not a shell: {s} is not executable", .{bin}) catch "not a shell: not executable"; } // "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"; @@ -912,6 +914,9 @@ pub const Shell = struct { try std.testing.expectEqualStrings("interpreter /no/such/interp not found", refusal(try std.fmt.bufPrint(&path, "{s}/bad", .{dir}), &said).?); try std.testing.expect(refusal(try std.fmt.bufPrint(&path, "{s}/good", .{dir}), &said) == null); try std.testing.expect(std.mem.startsWith(u8, refusal(try std.fmt.bufPrint(&path, "{s}/none", .{dir}), &said).?, "no shell ")); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "plain", .data = "echo\n" }); + const plain = try std.fmt.bufPrint(&path, "{s}/plain", .{dir}); + try std.testing.expect(std.mem.endsWith(u8, refusal(plain, &said).?, "plain is not executable")); } fn fallback(buf: *[std.fs.max_path_bytes]u8) [*:0]const u8 { -- cgit v1.3