From c3c3ef27ede2f69ef4f8c6acb10808f02ee46bb2 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 11:48:32 -0300 Subject: A builtin or setting this build lacks is refused GUI-only, from an exec too On a terminal, Fonts written to an exec was no builtin there, so it ran as a shell command and ended exit 127; ctl said unknown control message. Both now say Fonts is GUI-only, invalid here (a builtin another platform's build has, not in this build). Co-Authored-By: Claude Opus 5.5 --- docs/fs.md | 4 +++- src/builtins.zig | 19 +++++++++++++++++++ src/exec.zig | 8 ++++++++ src/ninep/ctl.zig | 12 ++++++++++-- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/docs/fs.md b/docs/fs.md index 5de73b86..70a76c92 100644 --- a/docs/fs.md +++ b/docs/fs.md @@ -253,7 +253,9 @@ takes (`Verbose on`, `WindowOpacity 70`, `PanelSlide off`, `DumpDir `, `LocationsConfig ...`), so writing what it reads back changes nothing; a setting the frontend cannot show (`Lift`, `GripWidth` on a terminal) is refused as `Lift is GUI-only, invalid here` -(EINVAL: a request this build cannot take); a `Shell` or `Tty` naming no +(EINVAL: a request this build cannot take), and so is a builtin only the +GUI has (`Fonts`), written to an `exec` too, where it would otherwise run as +a shell command; a `Shell` or `Tty` naming no shell says `no shell "x", not found` (ENOENT); a `DumpDir` whose last directory is missing has it made at the Dump, and one further up missing says `Dump : no such directory` (ENOENT), one that is no directory diff --git a/src/builtins.zig b/src/builtins.zig index 9025da6d..b0499dc0 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -56,6 +56,22 @@ fn isEnabled(comptime T: type) bool { return !@hasDecl(T, "enabled") or T.enabled; } +/// Why `word`, a builtin some build has, is none in this one: `GUI-only` +/// for the GUI's own (`gui_only`), else `not in this build`; null for a +/// word that is one here, or in no build. +pub fn absentWhy(name: []const u8) ?[]const u8 { + @setEvalBranchQuota(20000); + inline for (@typeInfo(@This()).@"struct".decls) |d| { + if (comptime @TypeOf(@field(@This(), d.name)) == type) { + const T = @field(@This(), d.name); + if (comptime @typeInfo(T) == .@"struct" and @hasDecl(T, "run") and !isEnabled(T)) { + if (std.mem.eql(u8, d.name, name)) return if (@hasDecl(T, "gui_only")) "GUI-only" else "not in this build"; + } + } + } + return null; +} + fn manualBuiltinCount() comptime_int { comptime { var count = 0; @@ -814,6 +830,7 @@ pub const Fonts = struct { pub const scope: Scope = .session; pub const output: OutputTraits = .{ .name = config.fonts_buffer, .steps = true, .commands = true }; pub const enabled = capabilities.font_picker; + pub const gui_only = true; pub fn run(c: Ctx) void { if (comptime enabled) apply(c) else unreachable; } @@ -1319,6 +1336,7 @@ pub const EffectCode = struct { pub const takes_arg = true; pub const requires_arg = true; pub const enabled = capabilities.panel_transitions or capabilities.scene_shaders; + pub const gui_only = true; pub const output: OutputTraits = .{ .name = config.effect_code_buffer }; pub fn run(c: Ctx) void { if (comptime enabled) @@ -1335,6 +1353,7 @@ pub const Pet = struct { pub const takes_arg = true; pub const requires_arg = true; pub const enabled = pardes.platform == .gui; + pub const gui_only = true; pub fn run(c: Ctx) void { const name = std.mem.trim(u8, c.arg orelse return, " \t\r\n"); c.p.settings.pet = std.meta.stringToEnum(@TypeOf(c.p.settings.pet), name) orelse return; diff --git a/src/exec.zig b/src/exec.zig index 9ec6a426..cf4e01ab 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -657,6 +657,14 @@ pub fn executeFrom(p: *Pardes, id: usize, txt: []const u8, from_body: bool) ?usi .sent => |to| return to, .asked => return null, }; + // A builtin or setting this build lacks (Fonts on a terminal) is said, + // never run as a shell command. + const word = cmd[0 .. std.mem.indexOfAny(u8, cmd, " \t") orelse cmd.len]; + if (builtins.absentWhy(word) orelse if (config.Runtime.find(word) != null) @as([]const u8, "GUI-only") else null) |why| { + var said: [96]u8 = undefined; + p.reportFailure(id, std.fmt.bufPrint(&said, "{s} is {s}, invalid here", .{ word, why }) catch "GUI-only, invalid here"); + return null; + } if (!takesCommandLine(p, id)) return runCommand(p, id, cmd); panes.Terminal.padOutputBelowEdits(p, id); panes.Terminal.noteCommand(pane, cmd); diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 68b62186..c2a78fb8 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -388,11 +388,11 @@ fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ? const b = std.meta.stringToEnum(Builtin, word) orelse { // A setting this build's frontend cannot show (Lift, GripWidth on a // terminal) is known, and says so rather than sound misspelt. - if (config.Runtime.find(word) != null) { + if (builtins.absentWhy(word) orelse if (config.Runtime.find(word) != null) @as([]const u8, "GUI-only") else null) |absent| { var why: [64]u8 = undefined; // EINVAL, a request this build cannot take, in words 9ns reads // as that: `invalid`. - return refuse(p, req, std.fmt.bufPrint(&why, "{s} is GUI-only, invalid here", .{word}) catch "GUI-only, invalid here", line); + return refuse(p, req, std.fmt.bufPrint(&why, "{s} is {s}, invalid here", .{ word, absent }) catch "GUI-only, invalid here", line); } return refuse(p, req, "unknown control message", line); }; @@ -1704,6 +1704,14 @@ test "a setting this frontend cannot show says GUI-only, and DumpDir reads back if (!builtins.capabilities.lift) { try testing.expectEqualStrings("Lift is GUI-only, invalid here \"Lift on\"", wr(p, root_ctl, "Lift on\n").reply.ename); try testing.expectEqualStrings("GripWidth is GUI-only, invalid here \"GripWidth 120\"", wr(p, root_ctl, "GripWidth 120\n").reply.ename); + // A builtin the GUI alone has, written to a ctl or an exec, never + // runs as a shell command. + if (comptime !pardes.font_picker) { + try testing.expectEqualStrings("Fonts is GUI-only, invalid here \"Fonts\"", wr(p, root_ctl, "Fonts\n").reply.ename); + const ran = wr(p, root_exec, "Fonts\n"); + try testing.expectEqual(E.IO, ran.errno()); + try testing.expectEqualStrings("Fonts is GUI-only, invalid here", ran.reply.ename); + } } try testing.expectEqualStrings("unknown control message \"Liftt on\"", wr(p, root_ctl, "Liftt on\n").reply.ename); var buf: [900]u8 = undefined; -- cgit v1.3