1const Child = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5
6const std = @import("../std.zig");
7const Io = std.Io;
8const process = std.process;
9const File = std.Io.File;
10const assert = std.debug.assert;
11const Allocator = std.mem.Allocator;
12
13pub const Id = switch (native_os) {
14 .windows => std.os.windows.HANDLE,
15 .wasi => void,
16 else => std.posix.pid_t,
17};
18
19/// After `wait` or `kill` is called, this becomes `null`.
20/// On Windows this is the hProcess.
21/// On POSIX this is the pid.
22id: ?Id,
23thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void,
24/// The writing end of the child process's standard input pipe.
25/// Usage requires `process.SpawnOptions.StdIo.pipe`.
26stdin: ?File,
27/// The reading end of the child process's standard output pipe.
28/// Usage requires `process.SpawnOptions.StdIo.pipe`.
29stdout: ?File,
30/// The reading end of the child process's standard error pipe.
31/// Usage requires `process.SpawnOptions.StdIo.pipe`.
32stderr: ?File,
33/// This is available after calling wait if
34/// `request_resource_usage_statistics` was set to `true` before calling
35/// `spawn`.
36/// TODO move this data into `Term`
37resource_usage_statistics: ResourceUsageStatistics = .{},
38request_resource_usage_statistics: bool,
39
40pub const ResourceUsageStatistics = struct {
41 rusage: @TypeOf(rusage_init) = rusage_init,
42
43 /// Returns the peak resident set size of the child process, in bytes,
44 /// if available.
45 pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize {
46 switch (native_os) {
47 .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .linux, .serenity => {
48 if (rus.rusage) |ru| {
49 return @as(usize, @intCast(ru.maxrss)) * 1024;
50 } else {
51 return null;
52 }
53 },
54 .windows => {
55 if (rus.rusage) |ru| {
56 return ru.PeakWorkingSetSize;
57 } else {
58 return null;
59 }
60 },
61 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
62 if (rus.rusage) |ru| {
63 // Darwin oddly reports in bytes instead of kilobytes.
64 return @as(usize, @intCast(ru.maxrss));
65 } else {
66 return null;
67 }
68 },
69 else => return null,
70 }
71 }
72
73 const rusage_init = switch (native_os) {
74 .dragonfly,
75 .freebsd,
76 .netbsd,
77 .openbsd,
78 .illumos,
79 .linux,
80 .serenity,
81 .driverkit,
82 .ios,
83 .maccatalyst,
84 .macos,
85 .tvos,
86 .visionos,
87 .watchos,
88 => @as(?std.posix.rusage, null),
89 .windows => @as(?std.os.windows.PROCESS.VM_COUNTERS, null),
90 else => {},
91 };
92};
93
94pub const Term = union(enum) {
95 exited: u8,
96 signal: std.posix.SIG,
97 stopped: std.posix.SIG,
98 unknown: u32,
99
100 pub fn success(t: Term) bool {
101 return switch (t) {
102 .exited => |code| code == 0,
103 else => false,
104 };
105 }
106
107 pub fn format(t: Term, w: *Io.Writer) Io.Writer.Error!void {
108 switch (t) {
109 .exited => |code| return w.print("exited with code {d}", .{code}),
110 .signal => |sig| return w.print("terminated with signal {t}", .{sig}),
111 .stopped => |sig| return w.print("stopped with signal {t}", .{sig}),
112 .unknown => return w.writeAll("terminated unexpectedly"),
113 }
114 }
115};
116
117pub const Cwd = union(enum) {
118 /// CWD of the child is the same as the current CWD.
119 inherit,
120 /// On POSIX systems, `fchdir` is called after `fork` using this handle.
121 /// On Windows, the path is inferred from the provided handle and that path is used when calling `CreateProcessW`.
122 dir: Io.Dir,
123 /// On POSIX systems, `chdir` is called after `fork` using this path.
124 /// On Windows, this path is used when calling `CreateProcessW`.
125 path: []const u8,
126};
127
128/// Requests for the operating system to forcibly terminate the child process,
129/// then blocks until it terminates, then cleans up all resources.
130///
131/// Idempotent and does nothing after `wait` returns.
132///
133/// Uncancelable. Ignores unexpected errors from the operating system.
134pub fn kill(child: *Child, io: Io) void {
135 if (child.id == null) {
136 assert(child.stdin == null);
137 assert(child.stdout == null);
138 assert(child.stderr == null);
139 return;
140 }
141 io.vtable.childKill(io.userdata, child);
142 assert(child.id == null);
143}
144
145pub const WaitError = error{
146 AccessDenied,
147} || Io.Cancelable || Io.UnexpectedError;
148
149/// Blocks until child process terminates and then cleans up all resources.
150pub fn wait(child: *Child, io: Io) WaitError!Term {
151 assert(child.id != null);
152 return io.vtable.childWait(io.userdata, child);
153}
154
155test {
156 _ = Term;
157}