From 2ed75d47b5d86cdc387a6cb9536fb6fbe9fdd8ed Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 30 Dec 2023 15:04:06 -0800 Subject: [PATCH] termio/exec: detect exec failure and show an error message --- src/Command.zig | 5 +++++ src/termio/Exec.zig | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Command.zig b/src/Command.zig index b02a9bc78..3c35b1cc5 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -155,6 +155,11 @@ fn startPosix(self: *Command, arena: Allocator) !void { // Finally, replace our process. _ = std.os.execveZ(pathZ, argsZ, envp) catch null; + + // If we are executing this code, the exec failed. In that scenario, + // we return a very specific error that can be detected to determine + // we're in the child. + return error.ExecFailedInChild; } fn startWindows(self: *Command, arena: Allocator) !void { diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index b08ccbada..4c204f792 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -217,7 +217,20 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData { const alloc = self.alloc; // Start our subprocess - const pty_fds = try self.subprocess.start(alloc); + const pty_fds = self.subprocess.start(alloc) catch |err| { + // If we specifically got this error then we are in the forked + // process and our child failed to execute. In that case + if (err != error.ExecFailedInChild) return err; + + // Output an error message about the exec faililng and exit. + // This generally should NOT happen because we always wrap + // our command execution either in login (macOS) or /bin/sh + // (Linux) which are usually guaranteed to exist. Still, we + // want to handle this scenario. + self.execFailedInChild() catch {}; + std.os.exit(1); + }; + errdefer self.subprocess.stop(); const pid = pid: { const command = self.subprocess.command orelse return error.ProcessNotStarted; @@ -316,6 +329,26 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData { }; } +/// This outputs an error message when exec failed and we are the +/// child process. This returns so the caller should probably exit +/// after calling this. +/// +/// Note that this usually is only called under very very rare +/// circumstances because we wrap our command execution in login +/// (macOS) or /bin/sh (Linux). So this output can be pretty crude +/// because it should never happen. Notably, this is not the error +/// users see when `command` is invalid. +fn execFailedInChild(self: *Exec) !void { + _ = self; + const stderr = std.io.getStdErr().writer(); + try stderr.writeAll("exec failed\n"); + try stderr.writeAll("press any key to exit\n"); + + var buf: [1]u8 = undefined; + var reader = std.io.getStdIn().reader(); + _ = try reader.read(&buf); +} + pub fn threadExit(self: *Exec, data: ThreadData) void { // Clear out our data since we're not active anymore. self.data = null;