1const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std.zig");
5const Io = std.Io;
6const Dir = std.Io.Dir;
7const File = std.Io.File;
8const fs = std.fs;
9const mem = std.mem;
10const math = std.math;
11const Allocator = std.mem.Allocator;
12const assert = std.debug.assert;
13const testing = std.testing;
14const posix = std.posix;
15const windows = std.os.windows;
16const unicode = std.unicode;
17const max_path_bytes = std.fs.max_path_bytes;
18
19pub const Child = @import("process/Child.zig");
20pub const Args = @import("process/Args.zig");
21pub const Environ = @import("process/Environ.zig");
22pub const Preopens = @import("process/Preopens.zig");
23
24/// A standard set of pre-initialized useful APIs for programs to take
25/// advantage of. This is the type of the first parameter of the main function.
26/// Applications wanting more flexibility can accept `Init.Minimal` instead.
27///
28/// Completion of https://github.com/ziglang/zig/issues/24510 will also allow
29/// the second parameter of the main function to be a custom struct that
30/// contain auto-parsed CLI arguments.
31pub const Init = struct {
32 /// `Init` is a superset of `Minimal`; the latter is included here.
33 minimal: Minimal,
34 /// Permanent storage for the entire process, cleaned automatically on
35 /// exit. Threadsafe.
36 arena: *std.heap.ArenaAllocator,
37 /// A default-selected general purpose allocator for temporary heap
38 /// allocations. Debug mode will set up leak checking if possible.
39 /// Threadsafe.
40 gpa: Allocator,
41 /// An appropriate default Io implementation based on the target
42 /// configuration. Debug mode will set up leak checking if possible.
43 io: Io,
44 /// Environment variables, initialized with `gpa`. Not threadsafe.
45 environ_map: *Environ.Map,
46 /// Named files that have been provided by the parent process. This is
47 /// mainly useful on WASI, but can be used on other systems to mimic the
48 /// behavior with respect to stdio.
49 preopens: Preopens,
50
51 /// Alternative to `Init` as the first parameter of the main function.
52 pub const Minimal = struct {
53 /// Environment variables.
54 environ: Environ,
55 /// Command line arguments.
56 args: Args,
57 };
58};
59
60pub const CurrentPathError = error{
61 NameTooLong,
62 /// Not possible on Windows. Always returned on WASI.
63 CurrentDirUnlinked,
64} || Io.Cancelable || Io.UnexpectedError;
65
66/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
67/// On other platforms, the result is an opaque sequence of bytes with no
68/// particular encoding.
69pub fn currentPath(io: Io, buffer: []u8) CurrentPathError!usize {
70 return io.vtable.processCurrentPath(io.userdata, buffer);
71}
72
73pub const CurrentPathAllocError = Allocator.Error || error{
74 /// Not possible on Windows. Always returned on WASI.
75 CurrentDirUnlinked,
76} || Io.Cancelable || Io.UnexpectedError;
77
78/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
79/// On other platforms, the result is an opaque sequence of bytes with no
80/// particular encoding.
81///
82/// Caller owns returned memory.
83pub fn currentPathAlloc(io: Io, allocator: Allocator) CurrentPathAllocError![:0]u8 {
84 var buffer: [max_path_bytes]u8 = undefined;
85 const n = currentPath(io, &buffer) catch |err| switch (err) {
86 error.NameTooLong => unreachable,
87 else => |e| return e,
88 };
89 return allocator.dupeSentinel(u8, buffer[0..n], 0);
90}
91
92test currentPathAlloc {
93 const cwd = try currentPathAlloc(testing.io, testing.allocator);
94 testing.allocator.free(cwd);
95}
96
97pub const UserInfo = struct {
98 uid: posix.uid_t,
99 gid: posix.gid_t,
100};
101
102/// POSIX function which gets a uid from username.
103pub fn getUserInfo(io: Io, name: []const u8) !UserInfo {
104 return switch (native_os) {
105 .linux,
106 .driverkit,
107 .ios,
108 .maccatalyst,
109 .macos,
110 .tvos,
111 .visionos,
112 .watchos,
113 .freebsd,
114 .netbsd,
115 .openbsd,
116 .haiku,
117 .illumos,
118 .serenity,
119 => posixGetUserInfo(io, name),
120 else => @compileError("Unsupported OS"),
121 };
122}
123
124/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
125/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
126pub fn posixGetUserInfo(io: Io, name: []const u8) !UserInfo {
127 const file = try Io.Dir.openFileAbsolute(io, "/etc/passwd", .{});
128 defer file.close(io);
129 var buffer: [4096]u8 = undefined;
130 var file_reader = file.reader(io, &buffer);
131 return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) {
132 error.ReadFailed => return file_reader.err.?,
133 error.EndOfStream => return error.UserNotFound,
134 error.CorruptPasswordFile => |e| return e,
135 };
136}
137
138fn posixGetUserInfoPasswdStream(name: []const u8, reader: *std.Io.Reader) !UserInfo {
139 const State = enum {
140 start,
141 wait_for_next_line,
142 skip_password,
143 read_user_id,
144 read_group_id,
145 };
146
147 var name_index: usize = 0;
148 var uid: posix.uid_t = 0;
149 var gid: posix.gid_t = 0;
150
151 sw: switch (State.start) {
152 .start => switch (try reader.takeByte()) {
153 ':' => {
154 if (name_index == name.len) {
155 continue :sw .skip_password;
156 } else {
157 continue :sw .wait_for_next_line;
158 }
159 },
160 '\n' => return error.CorruptPasswordFile,
161 else => |byte| {
162 if (name_index == name.len or name[name_index] != byte) {
163 continue :sw .wait_for_next_line;
164 }
165 name_index += 1;
166 continue :sw .start;
167 },
168 },
169 .wait_for_next_line => switch (try reader.takeByte()) {
170 '\n' => {
171 name_index = 0;
172 continue :sw .start;
173 },
174 else => continue :sw .wait_for_next_line,
175 },
176 .skip_password => switch (try reader.takeByte()) {
177 '\n' => return error.CorruptPasswordFile,
178 ':' => {
179 continue :sw .read_user_id;
180 },
181 else => continue :sw .skip_password,
182 },
183 .read_user_id => switch (try reader.takeByte()) {
184 ':' => {
185 continue :sw .read_group_id;
186 },
187 '\n' => return error.CorruptPasswordFile,
188 else => |byte| {
189 const digit = switch (byte) {
190 '0'...'9' => byte - '0',
191 else => return error.CorruptPasswordFile,
192 };
193 {
194 const ov = @mulWithOverflow(uid, 10);
195 if (ov[1] != 0) return error.CorruptPasswordFile;
196 uid = ov[0];
197 }
198 {
199 const ov = @addWithOverflow(uid, digit);
200 if (ov[1] != 0) return error.CorruptPasswordFile;
201 uid = ov[0];
202 }
203 continue :sw .read_user_id;
204 },
205 },
206 .read_group_id => switch (try reader.takeByte()) {
207 '\n', ':' => return .{
208 .uid = uid,
209 .gid = gid,
210 },
211 else => |byte| {
212 const digit = switch (byte) {
213 '0'...'9' => byte - '0',
214 else => return error.CorruptPasswordFile,
215 };
216 {
217 const ov = @mulWithOverflow(gid, 10);
218 if (ov[1] != 0) return error.CorruptPasswordFile;
219 gid = ov[0];
220 }
221 {
222 const ov = @addWithOverflow(gid, digit);
223 if (ov[1] != 0) return error.CorruptPasswordFile;
224 gid = ov[0];
225 }
226 continue :sw .read_group_id;
227 },
228 },
229 }
230 comptime unreachable;
231}
232
233pub fn getBaseAddress() usize {
234 switch (native_os) {
235 .linux => {
236 const phdrs = std.posix.getSelfPhdrs();
237 var base: usize = 0;
238 for (phdrs) |phdr| switch (phdr.type) {
239 .LOAD => return base + phdr.vaddr,
240 .PHDR => base = @intFromPtr(phdrs.ptr) - phdr.vaddr,
241 else => {},
242 } else unreachable;
243 },
244 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
245 return @intFromPtr(&std.c._mh_execute_header);
246 },
247 .windows => return @intFromPtr(windows.peb().ImageBaseAddress),
248 else => @compileError("Unsupported OS"),
249 }
250}
251
252/// Tells whether the target operating system supports replacing the current
253/// process image. If this is `false` then calling `replace` or `replaceFile`
254/// functions will return `error.OperationUnsupported`.
255pub const can_replace = switch (native_os) {
256 .windows, .haiku, .wasi => false,
257 else => true,
258};
259
260/// Tells whether spawning child processes is supported.
261pub const can_spawn = switch (native_os) {
262 .wasi, .ios, .tvos, .visionos, .watchos => false,
263 else => true,
264};
265
266pub const ReplaceError = error{
267 /// The target operating system cannot replace the process image with a new
268 /// one.
269 OperationUnsupported,
270 SystemResources,
271 AccessDenied,
272 PermissionDenied,
273 InvalidExe,
274 FileSystem,
275 IsDir,
276 FileNotFound,
277 NotDir,
278 FileBusy,
279 ProcessFdQuotaExceeded,
280 SystemFdQuotaExceeded,
281} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
282
283pub const ReplaceOptions = struct {
284 argv: []const []const u8,
285 expand_arg0: ArgExpansion = .no_expand,
286 /// Replaces the environment when provided. The PATH value from here is
287 /// never used to resolve `argv[0]`.
288 environ_map: ?*const Environ.Map = null,
289};
290
291/// Replaces the current process image with the executed process. If this
292/// function succeeds, it does not return.
293///
294/// `argv[0]` is the name of the process to replace the current one with. If it
295/// is not already a file path (i.e. it contains '/'), it is resolved into a
296/// file path based on PATH from the parent environment.
297///
298/// It is illegal to call this function in a fork() child.
299pub fn replace(io: Io, options: ReplaceOptions) ReplaceError {
300 return io.vtable.processReplace(io.userdata, options);
301}
302
303/// Replaces the current process image with the executed process. If this
304/// function succeeds, it does not return.
305///
306/// `argv[0]` is the file path of the process to replace the current one with,
307/// relative to `dir`. It is *always* treated as a file path, even if it does
308/// not contain '/'.
309///
310/// It is illegal to call this function in a fork() child.
311pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError {
312 return io.vtable.processReplacePath(io.userdata, dir, options);
313}
314
315pub const ArgExpansion = enum { expand, no_expand };
316
317/// File name extensions supported natively by `CreateProcess()` on Windows.
318pub const WindowsExtension = enum {
319 bat,
320 cmd,
321 com,
322 exe,
323
324 /// Length of the longest supported extension (in ASCII characters)
325 pub const max_len = 3;
326};
327
328pub const SpawnError = error{
329 /// The operating system does not support creating child processes.
330 OperationUnsupported,
331 OutOfMemory,
332 /// POSIX-only. `StdIo.ignore` was selected and opening `/dev/null` returned ENODEV.
333 NoDevice,
334 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
335 /// https://wtf-8.codeberg.page/
336 InvalidWtf8,
337 /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
338 /// within arguments when executing a `.bat`/`.cmd` script.
339 /// - NUL/LF signifiies end of arguments, so anything afterwards
340 /// would be lost after execution.
341 /// - CR is stripped by `cmd.exe`, so any CR codepoints
342 /// would be lost after execution.
343 InvalidBatchScriptArg,
344 SystemResources,
345 AccessDenied,
346 PermissionDenied,
347 InvalidExe,
348 FileSystem,
349 IsDir,
350 FileNotFound,
351 NotDir,
352 FileBusy,
353 ProcessFdQuotaExceeded,
354 SystemFdQuotaExceeded,
355 ResourceLimitReached,
356 InvalidUserId,
357 InvalidProcessGroupId,
358 SymLinkLoop,
359 InvalidName,
360 /// An attempt was made to change the process group ID of one of the
361 /// children of the calling process and the child had already performed an
362 /// image replacement.
363 ProcessAlreadyExec,
364 /// On Windows, the volume does not contain a recognized file system. File
365 /// system drivers might not be loaded, or the volume may be corrupt.
366 UnrecognizedVolume,
367} || Io.File.OpenError || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
368
369pub const SpawnOptions = struct {
370 argv: []const []const u8,
371
372 /// Set to change the current working directory when spawning the child process.
373 cwd: Child.Cwd = .inherit,
374 /// Replaces the child environment when provided. The PATH value from here
375 /// is not used to resolve `argv[0]`; that resolution always uses parent
376 /// environment.
377 environ_map: ?*const Environ.Map = null,
378 expand_arg0: ArgExpansion = .no_expand,
379 /// When populated, a pipe will be created for the child process to
380 /// communicate progress back to the parent. The file descriptor of the
381 /// write end of the pipe will be specified in the `ZIG_PROGRESS`
382 /// environment variable inside the child process. The progress reported by
383 /// the child will be attached to this progress node in the parent process.
384 ///
385 /// The child's progress tree will be grafted into the parent's progress tree,
386 /// by substituting this node with the child's root node.
387 progress_node: std.Progress.Node = std.Progress.Node.none,
388
389 stdin: StdIo = .inherit,
390 stdout: StdIo = .inherit,
391 stderr: StdIo = .inherit,
392
393 /// Set to true to obtain rusage information for the child process.
394 /// Depending on the target platform and implementation status, the
395 /// requested statistics may or may not be available. If they are
396 /// available, then the `resource_usage_statistics` field will be populated
397 /// after calling `wait`.
398 /// On Linux and Darwin, this obtains rusage statistics from wait4().
399 request_resource_usage_statistics: bool = false,
400
401 /// Set to change the user id when spawning the child process.
402 uid: ?posix.uid_t = null,
403 /// Set to change the group id when spawning the child process.
404 gid: ?posix.gid_t = null,
405 /// Set to change the process group id when spawning the child process.
406 pgid: ?posix.pid_t = null,
407
408 /// Start child process in suspended state.
409 /// For Posix systems it's started as if SIGSTOP was sent.
410 start_suspended: bool = false,
411 /// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess.
412 create_no_window: bool = false,
413 /// Darwin-only. Disable ASLR for the child process.
414 disable_aslr: bool = false,
415
416 /// Behavior of the child process's standard input, output, and error streams.
417 pub const StdIo = union(enum) {
418 /// Inherit the corresponding stream from the parent process.
419 inherit,
420 /// Pass an already open file from the parent to the child.
421 ///
422 /// Nonblocking mode will be kept in the child process if present. This is
423 /// likely not supported by the child process. For example:
424 /// - Zig's std.Io.File.stdout() assumes blocking mode
425 /// - Rust explicity documents that nonblocking stdio may cause panics
426 /// - C++ standard streams do not support nonblocking file descriptors
427 file: File,
428 /// Pass a null stream to the child process by opening "/dev/null" on POSIX
429 /// and "NUL" on Windows.
430 ignore,
431 /// Create a new pipe for the stream.
432 ///
433 /// The corresponding field (`stdout`, `stderr`, or `stdin`) will be
434 /// assigned a `File` object that can be used to read from or write to the
435 /// pipe.
436 pipe,
437 /// Spawn the child process with the corresponding stream missing. This
438 /// will likely result in the child encountering EBADF if it tries to use
439 /// stdin, stdout, or stderr, or if only one stream is closed, it will
440 /// result in them getting mixed up. Generally, this option is for advanced
441 /// use cases only.
442 close,
443 };
444};
445
446/// Creates a child process.
447///
448/// `argv[0]` is the name of the program to execute. If it is not already a
449/// file path (i.e. it contains '/'), it is resolved into a file path based on
450/// PATH from the parent environment.
451pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child {
452 return io.vtable.processSpawn(io.userdata, options);
453}
454
455/// Creates a child process.
456///
457/// `argv[0]` is the file path of the program to execute, relative to `dir`. It
458/// is *always* treated as a file path, even if it does not contain '/'.
459pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child {
460 return io.vtable.processSpawnPath(io.userdata, dir, options);
461}
462
463pub const RunError = error{
464 StreamTooLong,
465} || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error;
466
467pub const RunOptions = struct {
468 argv: []const []const u8,
469 stderr_limit: Io.Limit = .unlimited,
470 stdout_limit: Io.Limit = .unlimited,
471 /// How many bytes to initially allocate for stderr and stdout.
472 reserve_amount: usize = 64,
473
474 /// Set to change the current working directory when spawning the child process.
475 cwd: Child.Cwd = .inherit,
476 /// Replaces the child environment when provided. The PATH value from here
477 /// is not used to resolve `argv[0]`; that resolution always uses parent
478 /// environment.
479 environ_map: ?*const Environ.Map = null,
480 expand_arg0: ArgExpansion = .no_expand,
481 /// When populated, a pipe will be created for the child process to
482 /// communicate progress back to the parent. The file descriptor of the
483 /// write end of the pipe will be specified in the `ZIG_PROGRESS`
484 /// environment variable inside the child process. The progress reported by
485 /// the child will be attached to this progress node in the parent process.
486 ///
487 /// The child's progress tree will be grafted into the parent's progress tree,
488 /// by substituting this node with the child's root node.
489 progress_node: std.Progress.Node = std.Progress.Node.none,
490 /// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess.
491 create_no_window: bool = true,
492 /// Darwin-only. Disable ASLR for the child process.
493 disable_aslr: bool = false,
494 timeout: Io.Timeout = .none,
495};
496
497pub const RunResult = struct {
498 term: Child.Term,
499 stdout: []u8,
500 stderr: []u8,
501};
502
503/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
504/// If it succeeds, the caller owns result.stdout and result.stderr memory.
505pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {
506 var child = try spawn(io, .{
507 .argv = options.argv,
508 .cwd = options.cwd,
509 .environ_map = options.environ_map,
510 .expand_arg0 = options.expand_arg0,
511 .progress_node = options.progress_node,
512 .create_no_window = options.create_no_window,
513 .disable_aslr = options.disable_aslr,
514
515 .stdin = .ignore,
516 .stdout = .pipe,
517 .stderr = .pipe,
518 });
519 defer child.kill(io);
520
521 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
522 var multi_reader: Io.File.MultiReader = undefined;
523 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
524 defer multi_reader.deinit();
525
526 const stdout_reader = multi_reader.reader(0);
527 const stderr_reader = multi_reader.reader(1);
528
529 while (multi_reader.fill(options.reserve_amount, options.timeout)) |_| {
530 if (options.stdout_limit.toInt()) |limit| {
531 if (stdout_reader.buffered().len > limit)
532 return error.StreamTooLong;
533 }
534 if (options.stderr_limit.toInt()) |limit| {
535 if (stderr_reader.buffered().len > limit)
536 return error.StreamTooLong;
537 }
538 } else |err| switch (err) {
539 error.EndOfStream => {},
540 else => |e| return e,
541 }
542
543 try multi_reader.checkAnyError();
544
545 const term = try child.wait(io);
546
547 const stdout_slice = try multi_reader.toOwnedSlice(0);
548 errdefer gpa.free(stdout_slice);
549
550 const stderr_slice = try multi_reader.toOwnedSlice(1);
551 errdefer gpa.free(stderr_slice);
552
553 return .{
554 .stdout = stdout_slice,
555 .stderr = stderr_slice,
556 .term = term,
557 };
558}
559
560pub const TotalSystemMemoryError = error{
561 UnknownTotalSystemMemory,
562};
563
564/// Returns the total system memory, in bytes as a u64.
565/// We return a u64 instead of usize due to PAE on ARM
566/// and Linux's /proc/meminfo reporting more memory when
567/// using QEMU user mode emulation.
568pub fn totalSystemMemory() TotalSystemMemoryError!u64 {
569 switch (native_os) {
570 .linux => {
571 var info: std.os.linux.Sysinfo = undefined;
572 const result: usize = std.os.linux.sysinfo(&info);
573 if (std.os.linux.errno(result) != .SUCCESS) {
574 return error.UnknownTotalSystemMemory;
575 }
576 // Promote to u64 to avoid overflow on systems where info.totalram is a 32-bit usize
577 return @as(u64, info.totalram) * info.mem_unit;
578 },
579 .dragonfly, .freebsd, .netbsd => {
580 const name = if (native_os == .netbsd) "hw.physmem64" else "hw.physmem";
581 var physmem: c_ulong = undefined;
582 var len: usize = @sizeOf(c_ulong);
583 switch (posix.errno(posix.system.sysctlbyname(name, &physmem, &len, null, 0))) {
584 .SUCCESS => return @intCast(physmem),
585 .FAULT => unreachable,
586 .PERM => unreachable, // only when setting values
587 .NOMEM => unreachable, // memory already on the stack
588 .NOENT => unreachable,
589 else => return error.UnknownTotalSystemMemory,
590 }
591 },
592 // whole Darwin family
593 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
594 // "hw.memsize" returns uint64_t
595 var physmem: u64 = undefined;
596 var len: usize = @sizeOf(u64);
597 switch (posix.errno(posix.system.sysctlbyname("hw.memsize", &physmem, &len, null, 0))) {
598 .SUCCESS => return physmem,
599 .FAULT => unreachable,
600 .PERM => unreachable, // only when setting values
601 .NOMEM => unreachable, // memory already on the stack
602 .NOENT => unreachable, // constant, known good value
603 else => return error.UnknownTotalSystemMemory,
604 }
605 },
606 .openbsd => {
607 const mib: [2]c_int = [_]c_int{
608 posix.CTL.HW,
609 posix.HW.PHYSMEM64,
610 };
611 var physmem: i64 = undefined;
612 var len: usize = @sizeOf(@TypeOf(physmem));
613 posix.sysctl(&mib, &physmem, &len, null, 0) catch |err| switch (err) {
614 error.NameTooLong => unreachable, // constant, known good value
615 error.PermissionDenied => unreachable, // only when setting values,
616 error.SystemResources => unreachable, // memory already on the stack
617 error.UnknownName => unreachable, // constant, known good value
618 else => return error.UnknownTotalSystemMemory,
619 };
620 assert(physmem >= 0);
621 return @as(u64, @bitCast(physmem));
622 },
623 .windows => {
624 var sbi: windows.SYSTEM.BASIC_INFORMATION = undefined;
625 const rc = windows.ntdll.NtQuerySystemInformation(
626 .Basic,
627 &sbi,
628 @sizeOf(windows.SYSTEM.BASIC_INFORMATION),
629 null,
630 );
631 if (rc != .SUCCESS) {
632 return error.UnknownTotalSystemMemory;
633 }
634 return @as(u64, sbi.NumberOfPhysicalPages) * sbi.PageSize;
635 },
636 else => return error.UnknownTotalSystemMemory,
637 }
638}
639
640/// Indicate intent to terminate with a successful exit code.
641///
642/// In debug builds, this is a no-op, so that the calling code's cleanup
643/// mechanisms are tested and so that external tools checking for resource
644/// leaks can be accurate. In release builds, this calls `exit` with code zero,
645/// and does not return.
646pub fn cleanExit(io: Io) void {
647 if (builtin.mode == .debug) return;
648 _ = io.lockStderr(&.{}, .no_color) catch {};
649 exit(0);
650}
651
652/// Request ability to have more open file descriptors simultaneously.
653///
654/// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded
655/// errors. On other systems, this does nothing.
656pub fn raiseFileDescriptorLimit() void {
657 const have_rlimit = posix.rlimit_resource != void;
658 if (!have_rlimit) return;
659
660 var lim = posix.getrlimit(.NOFILE) catch return; // Oh well; we tried.
661 if (native_os.isDarwin()) {
662 // On Darwin, `NOFILE` is bounded by a hardcoded value `OPEN_MAX`.
663 // According to the man pages for setrlimit():
664 // setrlimit() now returns with errno set to EINVAL in places that historically succeeded.
665 // It no longer accepts "rlim_cur = RLIM.INFINITY" for RLIM.NOFILE.
666 // Use "rlim_cur = min(OPEN_MAX, rlim_max)".
667 lim.max = @min(std.c.OPEN_MAX, lim.max);
668 }
669 if (lim.cur == lim.max) return;
670
671 // Do a binary search for the limit.
672 var min: posix.rlim_t = lim.cur;
673 var max: posix.rlim_t = 1 << 20;
674 // But if there's a defined upper bound, don't search, just set it.
675 if (lim.max != posix.RLIM.INFINITY) {
676 min = lim.max;
677 max = lim.max;
678 }
679
680 while (true) {
681 lim.cur = min + @divTrunc(max - min, 2); // on freebsd rlim_t is signed
682 if (posix.setrlimit(.NOFILE, lim)) |_| {
683 min = lim.cur;
684 } else |_| {
685 max = lim.cur;
686 }
687 if (min + 1 >= max) break;
688 }
689}
690
691test raiseFileDescriptorLimit {
692 raiseFileDescriptorLimit();
693}
694
695/// Logs an error and then terminates the process with exit code 1.
696pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
697 std.log.err(format, format_arguments);
698 exit(1);
699}
700
701pub const ExecutablePathBaseError = error{
702 FileNotFound,
703 AccessDenied,
704 /// The operating system does not support an executable learning its own
705 /// path.
706 OperationUnsupported,
707 NotDir,
708 SymLinkLoop,
709 InputOutput,
710 FileTooBig,
711 IsDir,
712 ProcessFdQuotaExceeded,
713 SystemFdQuotaExceeded,
714 NoDevice,
715 SystemResources,
716 NoSpaceLeft,
717 FileSystem,
718 BadPathName,
719 DeviceBusy,
720 PipeBusy,
721 NotLink,
722 PathAlreadyExists,
723 /// On Windows, `\\server` or `\\server\share` was not found.
724 NetworkNotFound,
725 ProcessNotFound,
726 /// On Windows, antivirus software is enabled by default. It can be
727 /// disabled, but Windows Update sometimes ignores the user's preference
728 /// and re-enables it. When enabled, antivirus software on Windows
729 /// intercepts file system operations and makes them significantly slower
730 /// in addition to possibly failing with this error code.
731 AntivirusInterference,
732 /// On Windows, the volume does not contain a recognized file system. File
733 /// system drivers might not be loaded, or the volume may be corrupt.
734 UnrecognizedVolume,
735 PermissionDenied,
736} || Io.Cancelable || Io.UnexpectedError;
737
738pub const ExecutablePathAllocError = ExecutablePathBaseError || Allocator.Error;
739
740pub fn executablePathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![:0]u8 {
741 var buffer: [max_path_bytes]u8 = undefined;
742 const n = executablePath(io, &buffer) catch |err| switch (err) {
743 error.NameTooLong => unreachable,
744 else => |e| return e,
745 };
746 return allocator.dupeSentinel(u8, buffer[0..n], 0);
747}
748
749pub const ExecutablePathError = ExecutablePathBaseError || error{NameTooLong};
750
751/// Get the path to the current executable, following symlinks.
752///
753/// This function may return an error if the current executable
754/// was deleted after spawning.
755///
756/// Returned value is a slice of out_buffer.
757///
758/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
759/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
760///
761/// On Linux, depends on procfs being mounted. If the currently executing binary has
762/// been deleted, the file path looks something like "/a/b/c/exe (deleted)".
763///
764/// See also:
765/// * `executableDirPath` - to obtain only the directory
766/// * `openExecutable` - to obtain only an open file handle
767pub fn executablePath(io: Io, out_buffer: []u8) ExecutablePathError!usize {
768 return io.vtable.processExecutablePath(io.userdata, out_buffer);
769}
770
771/// Get the directory path that contains the current executable.
772///
773/// Returns index into `out_buffer`.
774///
775/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
776/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
777pub fn executableDirPath(io: Io, out_buffer: []u8) ExecutablePathError!usize {
778 const n = try executablePath(io, out_buffer);
779 // Assert that the OS APIs return absolute paths, and therefore dirname
780 // will not return null.
781 return std.fs.path.dirname(out_buffer[0..n]).?.len;
782}
783
784/// Same as `executableDirPath` except allocates the result.
785pub fn executableDirPathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![]u8 {
786 var buffer: [max_path_bytes]u8 = undefined;
787 const dir_path_len = executableDirPath(io, &buffer) catch |err| switch (err) {
788 error.NameTooLong => unreachable,
789 else => |e| return e,
790 };
791 return allocator.dupe(u8, buffer[0..dir_path_len]);
792}
793
794pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.LockError;
795
796pub fn openExecutable(io: Io, flags: Dir.OpenFileOptions) OpenExecutableError!File {
797 return io.vtable.processExecutableOpen(io.userdata, flags);
798}
799
800/// Causes abnormal process termination.
801///
802/// If linking against libc, this calls `std.c.abort`. Otherwise it raises
803/// SIGABRT followed by SIGKILL.
804///
805/// Invokes the current signal handler for SIGABRT, if any.
806pub fn abort() noreturn {
807 @branchHint(.cold);
808 // MSVCRT abort() sometimes opens a popup window which is undesirable, so
809 // even when linking libc on Windows we use our own abort implementation.
810 // See https://github.com/ziglang/zig/issues/2071 for more details.
811 if (native_os == .windows) {
812 if (builtin.mode == .debug and windows.peb().BeingDebugged.toBool()) {
813 @breakpoint();
814 }
815 windows.ntdll.RtlExitUserProcess(3);
816 }
817 if (!builtin.link_libc and native_os == .linux) {
818 // The Linux man page says that the libc abort() function
819 // "first unblocks the SIGABRT signal", but this is a footgun
820 // for user-defined signal handlers that want to restore some state in
821 // some program sections and crash in others.
822 // So, the user-installed SIGABRT handler is run, if present.
823 posix.raise(.ABRT) catch {};
824
825 // Disable all signal handlers.
826 const filledset = std.os.linux.sigfillset();
827 posix.sigprocmask(posix.SIG.BLOCK, &filledset, null);
828
829 // Only one thread may proceed to the rest of abort().
830 if (!builtin.single_threaded) {
831 const global = struct {
832 var abort_entered: bool = false;
833 };
834 while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {}
835 }
836
837 // Install default handler so that the tkill below will terminate.
838 const sigact: posix.Sigaction = .{
839 .handler = .{ .handler = posix.SIG.DFL },
840 .mask = posix.sigemptyset(),
841 .flags = 0,
842 };
843 posix.sigaction(.ABRT, &sigact, null);
844
845 _ = std.os.linux.tkill(std.os.linux.gettid(), .ABRT);
846
847 var sigabrtmask = posix.sigemptyset();
848 posix.sigaddset(&sigabrtmask, .ABRT);
849 posix.sigprocmask(posix.SIG.UNBLOCK, &sigabrtmask, null);
850
851 // Beyond this point should be unreachable.
852 @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;
853 posix.raise(.KILL) catch {};
854 exit(127); // Pid 1 might not be signalled in some containers.
855 }
856 switch (native_os) {
857 .uefi, .wasi, .emscripten, .cuda, .amdhsa, .other, .freestanding => @trap(),
858 else => posix.system.abort(),
859 }
860}
861
862/// Exits all threads of the program with the specified status code.
863pub fn exit(status: u8) noreturn {
864 if (builtin.link_libc) {
865 std.c.exit(status);
866 } else switch (native_os) {
867 .windows => windows.ntdll.RtlExitUserProcess(status),
868 .wasi => std.os.wasi.proc_exit(status),
869 .linux => {
870 if (!builtin.single_threaded) std.os.linux.exit_group(status);
871 posix.system.exit(status);
872 },
873 .uefi => {
874 const uefi = std.os.uefi;
875 // exit() is only available if exitBootServices() has not been called yet.
876 // This call to exit should not fail, so we catch-ignore errors.
877 if (uefi.system_table.boot_services) |bs| {
878 bs.exit(uefi.handle, @fromBackingInt(@intCast(status)), null) catch {};
879 }
880 // If we can't exit, reboot the system instead.
881 uefi.system_table.runtime_services.resetSystem(.cold, @fromBackingInt(@intCast(status)), null);
882 },
883 else => posix.system.exit(status),
884 }
885}
886
887pub const SetCurrentDirError = error{
888 AccessDenied,
889 BadPathName,
890 FileNotFound,
891 FileSystem,
892 NameTooLong,
893 NoDevice,
894 NotDir,
895 OperationUnsupported,
896 UnrecognizedVolume,
897} || Io.Cancelable || Io.UnexpectedError;
898
899/// Changes the current working directory to the open directory handle.
900/// Corresponds to "fchdir" in libc.
901///
902/// This modifies global process state and can have surprising effects in
903/// multithreaded applications. Most applications and especially libraries
904/// should not call this function as a general rule, however it can have use
905/// cases in, for example, implementing a shell, or child process execution.
906///
907/// Calling this function makes code less portable and less reusable.
908pub fn setCurrentDir(io: Io, dir: Io.Dir) !void {
909 return io.vtable.processSetCurrentDir(io.userdata, dir);
910}
911
912pub const SetCurrentPathError = error{
913 AccessDenied,
914 SymLinkLoop,
915 SystemResources,
916 BadPathName,
917 FileNotFound,
918 FileSystem,
919 NoDevice,
920 NotDir,
921 NameTooLong,
922 OperationUnsupported,
923 /// Windows-only. The path is invalid WTF-8.
924 /// https://wtf-8.codeberg.page/
925 InvalidWtf8,
926} || Io.Cancelable || Io.UnexpectedError;
927
928/// Changes the current working directory to the given path.
929/// Corresponds to "chdir" in libc.
930///
931/// This modifies global process state and can have surprising effects in
932/// multithreaded applications. Most applications and especially libraries
933/// should not call this function as a general rule, however it can have use
934/// cases in, for example, implementing a shell, or child process execution.
935///
936/// Calling this function makes code less portable and less reusable.
937pub fn setCurrentPath(io: Io, path: []const u8) !void {
938 return io.vtable.processSetCurrentPath(io.userdata, path);
939}
940
941pub const LockMemoryError = error{
942 UnsupportedOperation,
943 PermissionDenied,
944 LockedMemoryLimitExceeded,
945 SystemResources,
946} || Io.UnexpectedError;
947
948pub const LockMemoryOptions = struct {
949 /// Lock pages that are currently resident and mark the entire range so
950 /// that the remaining nonresident pages are locked when they are populated
951 /// by a page fault.
952 on_fault: bool = false,
953};
954
955/// Request part of the calling process's virtual address space to be in RAM,
956/// preventing that memory from being paged to the swap area.
957///
958/// Corresponds to "mlock" or "mlock2" in libc.
959///
960/// See also:
961/// * unlockMemory
962pub fn lockMemory(memory: []align(std.heap.page_size_min) const u8, options: LockMemoryOptions) LockMemoryError!void {
963 if (native_os == .windows) {
964 // TODO call VirtualLock
965 }
966 if (!options.on_fault and @TypeOf(posix.system.mlock) != void) {
967 switch (posix.errno(posix.system.mlock(memory.ptr, memory.len))) {
968 .SUCCESS => return,
969 .INVAL => |err| return std.Io.Threaded.errnoBug(err), // unaligned, negative, runs off end of addrspace
970 .PERM => return error.PermissionDenied,
971 .NOMEM => return error.LockedMemoryLimitExceeded,
972 .AGAIN => return error.SystemResources,
973 else => |err| return posix.unexpectedErrno(err),
974 }
975 }
976 if (@TypeOf(posix.system.mlock2) != void) {
977 const flags: posix.MLOCK = .{ .ONFAULT = options.on_fault };
978 switch (posix.errno(posix.system.mlock2(memory.ptr, memory.len, flags))) {
979 .SUCCESS => return,
980 .INVAL => |err| return std.Io.Threaded.errnoBug(err), // unaligned, negative, runs off end of addrspace
981 .PERM => return error.PermissionDenied,
982 .NOMEM => return error.LockedMemoryLimitExceeded,
983 .AGAIN => return error.SystemResources,
984 else => |err| return posix.unexpectedErrno(err),
985 }
986 }
987 return error.UnsupportedOperation;
988}
989
990pub const UnlockMemoryError = error{
991 PermissionDenied,
992 OutOfMemory,
993 SystemResources,
994} || Io.UnexpectedError;
995
996/// Withdraw request for process's virtual address space to be in RAM.
997///
998/// Corresponds to "munlock" in libc.
999///
1000/// See also:
1001/// * `lockMemory`
1002pub fn unlockMemory(memory: []align(std.heap.page_size_min) const u8) UnlockMemoryError!void {
1003 if (@TypeOf(posix.system.munlock) == void) return;
1004 switch (posix.errno(posix.system.munlock(memory.ptr, memory.len))) {
1005 .SUCCESS => return,
1006 .INVAL => |err| return std.Io.Threaded.errnoBug(err), // unaligned or runs off end of addr space
1007 .PERM => return error.PermissionDenied,
1008 .NOMEM => return error.OutOfMemory,
1009 .AGAIN => return error.SystemResources,
1010 else => |err| return posix.unexpectedErrno(err),
1011 }
1012}
1013
1014pub const LockMemoryAllOptions = struct {
1015 current: bool = false,
1016 future: bool = false,
1017 /// Asserted to be used together with `current` or `future`, or both.
1018 on_fault: bool = false,
1019};
1020
1021pub fn lockMemoryAll(options: LockMemoryAllOptions) LockMemoryError!void {
1022 if (@TypeOf(posix.system.mlockall) == void) return error.UnsupportedOperation;
1023 var flags: posix.MCL = .{
1024 .CURRENT = options.current,
1025 .FUTURE = options.future,
1026 };
1027 if (options.on_fault) {
1028 assert(options.current or options.future);
1029 if (@hasField(posix.MCL, "ONFAULT")) {
1030 flags.ONFAULT = true;
1031 } else {
1032 return error.UnsupportedOperation;
1033 }
1034 }
1035 switch (posix.errno(posix.system.mlockall(flags))) {
1036 .SUCCESS => return,
1037 .INVAL => |err| return std.Io.Threaded.errnoBug(err),
1038 .PERM => return error.PermissionDenied,
1039 .NOMEM => return error.LockedMemoryLimitExceeded,
1040 .AGAIN => return error.SystemResources,
1041 else => |err| return posix.unexpectedErrno(err),
1042 }
1043}
1044
1045pub fn unlockMemoryAll() UnlockMemoryError!void {
1046 if (@TypeOf(posix.system.munlockall) == void) return;
1047 switch (posix.errno(posix.system.munlockall())) {
1048 .SUCCESS => return,
1049 .PERM => return error.PermissionDenied,
1050 .NOMEM => return error.OutOfMemory,
1051 .AGAIN => return error.SystemResources,
1052 else => |err| return posix.unexpectedErrno(err),
1053 }
1054}
1055
1056pub const ProtectMemoryError = error{
1057 UnsupportedOperation,
1058 /// OpenBSD will refuse to change memory protection if the specified region
1059 /// contains any pages that have previously been marked immutable using the
1060 /// `mimmutable` function.
1061 PermissionDenied,
1062 /// The memory cannot be given the specified access. This can happen, for
1063 /// example, if you memory map a file to which you have read-only access,
1064 /// then use `protectMemory` to mark it writable.
1065 AccessDenied,
1066 /// Changing the protection of a memory region would result in the total
1067 /// number of mappings with distinct attributes exceeding the allowed
1068 /// maximum.
1069 OutOfMemory,
1070} || Io.UnexpectedError;
1071
1072pub const MemoryProtection = packed struct(u3) {
1073 read: bool = false,
1074 write: bool = false,
1075 execute: bool = false,
1076};
1077
1078pub fn protectMemory(memory: []align(std.heap.page_size_min) u8, protection: MemoryProtection) ProtectMemoryError!void {
1079 if (native_os == .windows) {
1080 var addr = memory.ptr; // ntdll takes an extra level of indirection here
1081 var size = memory.len; // ntdll takes an extra level of indirection here
1082 var old: windows.PAGE = undefined;
1083 const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
1084 const new = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied;
1085 switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) {
1086 .SUCCESS => return,
1087 .INVALID_ADDRESS => return error.AccessDenied,
1088 else => |st| return windows.unexpectedStatus(st),
1089 }
1090 } else if (posix.PROT != void) {
1091 const flags: posix.PROT = .{
1092 .READ = protection.read,
1093 .WRITE = protection.write,
1094 .EXEC = protection.execute,
1095 };
1096 switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {
1097 .SUCCESS => return,
1098 .PERM => return error.PermissionDenied,
1099 .INVAL => |err| return std.Io.Threaded.errnoBug(err),
1100 .ACCES => return error.AccessDenied,
1101 .NOMEM => return error.OutOfMemory,
1102 else => |err| return posix.unexpectedErrno(err),
1103 }
1104 }
1105 return error.UnsupportedOperation;
1106}
1107
1108var test_page: [std.heap.page_size_max]u8 align(std.heap.page_size_max) = undefined;
1109
1110test lockMemory {
1111 lockMemory(&test_page, .{}) catch return error.SkipZigTest;
1112 unlockMemory(&test_page) catch return error.SkipZigTest;
1113}
1114
1115test lockMemoryAll {
1116 lockMemoryAll(.{ .current = true }) catch return error.SkipZigTest;
1117 unlockMemoryAll() catch return error.SkipZigTest;
1118}
1119
1120test protectMemory {
1121 protectMemory(&test_page, .{}) catch return error.SkipZigTest;
1122 protectMemory(&test_page, .{ .read = true, .write = true }) catch return error.SkipZigTest;
1123}
1124
1125test {
1126 _ = Child;
1127 _ = Args;
1128 _ = Environ;
1129 _ = Preopens;
1130}