Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162const Command = @This();
const std = @import("std");const builtin = @import("builtin");const Pty = @import("Pty.zig");const Terminal = @import("Terminal.zig");
const linux = std.os.linux;const posix = std.posix;
argv: []const []const u8,
working_directory: ?[]const u8,
// Set after spawn()pid: ?std.posix.pid_t = null,
env_map: *const std.process.Environ.Map,
pty: Pty,
pub fn spawn(self: *Command, io: std.Io, allocator: std.mem.Allocator) !void { var arena_allocator = std.heap.ArenaAllocator.init(allocator); defer arena_allocator.deinit(); const arena = arena_allocator.allocator();
// Keep fork->exec child path allocation-free, following std/Io/Threaded.zig:posixExecv const argv_block = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null); for (self.argv, 0..) |arg, i| argv_block[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr; const env_block = try self.env_map.createPosixBlock(arena, .{}); const path = self.env_map.get("PATH") orelse std.Io.Threaded.default_PATH;
const pid = pid: { const rc = linux.fork(); break :pid switch (linux.errno(rc)) { .SUCCESS => rc, else => return error.ForkError, }; }; if (pid == 0) { // we are the child _ = std.os.linux.setsid();
// set the controlling terminal var u: c_uint = std.posix.STDIN_FILENO; if (posix.system.ioctl(self.pty.tty.handle, posix.T.IOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError;
// set up io { const rc = linux.dup2(self.pty.tty.handle, std.posix.STDIN_FILENO); switch (linux.errno(rc)) { .SUCCESS => {}, else => return error.Dup2Failed, } } { const rc = linux.dup2(self.pty.tty.handle, std.posix.STDOUT_FILENO); switch (linux.errno(rc)) { .SUCCESS => {}, else => return error.Dup2Failed, } } { const rc = linux.dup2(self.pty.tty.handle, std.posix.STDERR_FILENO); switch (linux.errno(rc)) { .SUCCESS => {}, else => return error.Dup2Failed, } } self.pty.tty.close(io); if (self.pty.pty.handle > 2) self.pty.pty.close(io);
if (self.working_directory) |wd| { const wd_z = try posix.toPosixPath(wd); if (linux.errno(linux.chdir(&wd_z)) != .SUCCESS) return error.ChdirFailed; }
// exec execvpeLinux(argv_block.ptr, env_block, self.argv[0], path) catch {}; linux.exit(127); }
// we are the parent self.pid = @intCast(pid);
if (!Terminal.global_sigchild_installed) { Terminal.global_sigchild_installed = true; var act = posix.Sigaction{ .handler = .{ .handler = handleSigChild }, .mask = switch (builtin.os.tag) { .macos => 0, .linux => posix.sigemptyset(), else => @compileError("os not supported"), }, .flags = 0, }; posix.sigaction(posix.SIG.CHLD, &act, null); }
return;}
fn handleSigChild(_: posix.SIG) callconv(.c) void { var status: i32 = undefined; const rc = linux.waitpid(-1, &status, 0); const pid: i32 = switch (linux.errno(rc)) { .SUCCESS => @intCast(rc), else => return, };
Terminal.global_vt_mutex.lock(Terminal.global_io) catch return; defer Terminal.global_vt_mutex.unlock(Terminal.global_io); var vt = Terminal.global_vts.get(pid) orelse return; vt.event_queue.push(.exited) catch {};}
pub fn kill(self: *Command) void { if (self.pid) |pid| { posix.kill(pid, posix.SIG.TERM) catch {}; self.pid = null; }}
// Keep fork->exec child path allocation-free, following std/Io/Threaded.zig:posixExecvfn execvpeLinux( argv: [*:null]const ?[*:0]const u8, env_block: std.process.Environ.PosixBlock, arg0: []const u8, path: []const u8,) !noreturn { // This implementation is largely copied from std/Io/Threaded.zig // (`spawnPosix` + `posixExecv`/`posixExecveat`) and adapted for this PTY fork path. if (std.mem.indexOfScalar(u8, arg0, '/') != null) { const path_z = try posix.toPosixPath(arg0); return std.Io.Threaded.posixExecveat(posix.AT.FDCWD, &path_z, argv, env_block); }
var it = std.mem.tokenizeScalar(u8, path, std.fs.path.delimiter); var path_buf: [posix.PATH_MAX]u8 = undefined; var err: std.process.ReplaceError = error.FileNotFound; var seen_eacces = false;
while (it.next()) |dir| { const path_len = dir.len + arg0.len + 1; if (path_buf.len < path_len + 1) return error.NameTooLong; @memcpy(path_buf[0..dir.len], dir); path_buf[dir.len] = '/'; @memcpy(path_buf[dir.len + 1 ..][0..arg0.len], arg0); path_buf[path_len] = 0; const full_path = path_buf[0..path_len :0].ptr; err = std.Io.Threaded.posixExecveat(posix.AT.FDCWD, full_path, argv, env_block); switch (err) { error.AccessDenied => seen_eacces = true, error.FileNotFound, error.NotDir => {}, else => |e| return e, } }
if (seen_eacces) return error.AccessDenied; return err;}