this repo has no description
1const Command = @This();
2
3const std = @import("std");
4const builtin = @import("builtin");
5const Pty = @import("Pty.zig");
6const Terminal = @import("Terminal.zig");
7
8const linux = std.os.linux;
9const posix = std.posix;
10
11argv: []const []const u8,
12
13working_directory: ?[]const u8,
14
15// Set after spawn()
16pid: ?std.posix.pid_t = null,
17
18env_map: *const std.process.Environ.Map,
19
20pty: Pty,
21
22pub fn spawn(self: *Command, io: std.Io, allocator: std.mem.Allocator) !void {
23 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
24 defer arena_allocator.deinit();
25 const arena = arena_allocator.allocator();
26
27 // Keep fork->exec child path allocation-free, following std/Io/Threaded.zig:posixExecv
28 const argv_block = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);
29 for (self.argv, 0..) |arg, i| argv_block[i] = (try arena.dupeZ(u8, arg)).ptr;
30 const env_block = try self.env_map.createPosixBlock(arena, .{});
31 const path = self.env_map.get("PATH") orelse std.Io.Threaded.default_PATH;
32
33 const pid = pid: {
34 const rc = linux.fork();
35 break :pid switch (linux.errno(rc)) {
36 .SUCCESS => rc,
37 else => return error.ForkError,
38 };
39 };
40 if (pid == 0) {
41 // we are the child
42 _ = std.os.linux.setsid();
43
44 // set the controlling terminal
45 var u: c_uint = std.posix.STDIN_FILENO;
46 if (posix.system.ioctl(self.pty.tty.handle, posix.T.IOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError;
47
48 // set up io
49 {
50 const rc = linux.dup2(self.pty.tty.handle, std.posix.STDIN_FILENO);
51 switch (linux.errno(rc)) {
52 .SUCCESS => {},
53 else => return error.Dup2Failed,
54 }
55 }
56 {
57 const rc = linux.dup2(self.pty.tty.handle, std.posix.STDOUT_FILENO);
58 switch (linux.errno(rc)) {
59 .SUCCESS => {},
60 else => return error.Dup2Failed,
61 }
62 }
63 {
64 const rc = linux.dup2(self.pty.tty.handle, std.posix.STDERR_FILENO);
65 switch (linux.errno(rc)) {
66 .SUCCESS => {},
67 else => return error.Dup2Failed,
68 }
69 }
70 self.pty.tty.close(io);
71 if (self.pty.pty.handle > 2) self.pty.pty.close(io);
72
73 if (self.working_directory) |wd| {
74 const wd_z = try posix.toPosixPath(wd);
75 if (linux.errno(linux.chdir(&wd_z)) != .SUCCESS) return error.ChdirFailed;
76 }
77
78 // exec
79 execvpeLinux(argv_block.ptr, env_block, self.argv[0], path) catch {};
80 linux.exit(127);
81 }
82
83 // we are the parent
84 self.pid = @intCast(pid);
85
86 if (!Terminal.global_sigchild_installed) {
87 Terminal.global_sigchild_installed = true;
88 var act = posix.Sigaction{
89 .handler = .{ .handler = handleSigChild },
90 .mask = switch (builtin.os.tag) {
91 .macos => 0,
92 .linux => posix.sigemptyset(),
93 else => @compileError("os not supported"),
94 },
95 .flags = 0,
96 };
97 posix.sigaction(posix.SIG.CHLD, &act, null);
98 }
99
100 return;
101}
102
103fn handleSigChild(_: posix.SIG) callconv(.c) void {
104 var status: u32 = undefined;
105 const rc = linux.waitpid(-1, &status, 0);
106 const pid: i32 = switch (linux.errno(rc)) {
107 .SUCCESS => @intCast(rc),
108 else => return,
109 };
110
111 Terminal.global_vt_mutex.lock(Terminal.global_io) catch return;
112 defer Terminal.global_vt_mutex.unlock(Terminal.global_io);
113 var vt = Terminal.global_vts.get(pid) orelse return;
114 vt.event_queue.push(.exited) catch {};
115}
116
117pub fn kill(self: *Command) void {
118 if (self.pid) |pid| {
119 posix.kill(pid, posix.SIG.TERM) catch {};
120 self.pid = null;
121 }
122}
123
124// Keep fork->exec child path allocation-free, following std/Io/Threaded.zig:posixExecv
125fn execvpeLinux(
126 argv: [*:null]const ?[*:0]const u8,
127 env_block: std.process.Environ.PosixBlock,
128 arg0: []const u8,
129 path: []const u8,
130) !noreturn {
131 // This implementation is largely copied from std/Io/Threaded.zig
132 // (`spawnPosix` + `posixExecv`/`posixExecvPath`) and adapted for this PTY fork path.
133 if (std.mem.indexOfScalar(u8, arg0, '/') != null) {
134 const path_z = try posix.toPosixPath(arg0);
135 return std.Io.Threaded.posixExecvPath(&path_z, argv, env_block);
136 }
137
138 var it = std.mem.tokenizeScalar(u8, path, std.fs.path.delimiter);
139 var path_buf: [posix.PATH_MAX]u8 = undefined;
140 var err: std.process.ReplaceError = error.FileNotFound;
141 var seen_eacces = false;
142
143 while (it.next()) |dir| {
144 const path_len = dir.len + arg0.len + 1;
145 if (path_buf.len < path_len + 1) return error.NameTooLong;
146 @memcpy(path_buf[0..dir.len], dir);
147 path_buf[dir.len] = '/';
148 @memcpy(path_buf[dir.len + 1 ..][0..arg0.len], arg0);
149 path_buf[path_len] = 0;
150 const full_path = path_buf[0..path_len :0].ptr;
151 err = std.Io.Threaded.posixExecvPath(full_path, argv, env_block);
152 switch (err) {
153 error.AccessDenied => seen_eacces = true,
154 error.FileNotFound, error.NotDir => {},
155 else => |e| return e,
156 }
157 }
158
159 if (seen_eacces) return error.AccessDenied;
160 return err;
161}