aboutsummaryrefslogtreecommitdiff
path: root/lib/std/process.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrewrk@noreply.codeberg.org>2025-12-27 14:10:46 +0100
committerAndrew Kelley <andrewrk@noreply.codeberg.org>2025-12-27 14:10:46 +0100
commite55e6b5528bb2f01de242fcf32b172e244e98e74 (patch)
tree3a5eb3193d3d192c54ab0c2b7295a7f21861c27e /lib/std/process.zig
parentc3f2de5e519926eb0029062fe8e782a6f9df9c05 (diff)
parent60a1ba0a8f3517356fa2941462f002a7f580545b (diff)
downloadzig-e55e6b5528bb2f01de242fcf32b172e244e98e74.tar.gz
zig-e55e6b5528bb2f01de242fcf32b172e244e98e74.zip
Merge pull request 'std: migrate all `fs` APIs to `Io`' (#30232) from std.Io-fs into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30232
Diffstat (limited to 'lib/std/process.zig')
-rw-r--r--lib/std/process.zig271
1 files changed, 245 insertions, 26 deletions
diff --git a/lib/std/process.zig b/lib/std/process.zig
index a0a26c766f..865376d907 100644
--- a/lib/std/process.zig
+++ b/lib/std/process.zig
@@ -1,24 +1,33 @@
-const std = @import("std.zig");
const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+
+const std = @import("std.zig");
+const Io = std.Io;
+const File = std.Io.File;
const fs = std.fs;
const mem = std.mem;
const math = std.math;
-const Allocator = mem.Allocator;
+const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const testing = std.testing;
-const native_os = builtin.os.tag;
const posix = std.posix;
const windows = std.os.windows;
const unicode = std.unicode;
+const max_path_bytes = std.fs.max_path_bytes;
pub const Child = @import("process/Child.zig");
-pub const abort = posix.abort;
-pub const exit = posix.exit;
pub const changeCurDir = posix.chdir;
pub const changeCurDirZ = posix.chdirZ;
pub const GetCwdError = posix.GetCwdError;
+/// This is the global, process-wide protection to coordinate stderr writes.
+///
+/// The primary motivation for recursive mutex here is so that a panic while
+/// stderr mutex is held still dumps the stack trace and other debug
+/// information.
+pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
+
/// The result is a slice of `out_buffer`, from index `0`.
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
@@ -35,7 +44,7 @@ pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnl
pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 {
// The use of max_path_bytes here is just a heuristic: most paths will fit
// in stack_buf, avoiding an extra allocation in the common case.
- var stack_buf: [fs.max_path_bytes]u8 = undefined;
+ var stack_buf: [max_path_bytes]u8 = undefined;
var heap_buf: ?[]u8 = null;
defer if (heap_buf) |buf| allocator.free(buf);
@@ -437,25 +446,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
}
/// On Windows, `key` must be valid WTF-8.
-pub fn hasEnvVarConstant(comptime key: []const u8) bool {
+pub inline fn hasEnvVarConstant(comptime key: []const u8) bool {
if (native_os == .windows) {
const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
return getenvW(key_w) != null;
} else if (native_os == .wasi and !builtin.link_libc) {
- @compileError("hasEnvVarConstant is not supported for WASI without libc");
+ return false;
} else {
return posix.getenv(key) != null;
}
}
/// On Windows, `key` must be valid WTF-8.
-pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
+pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
if (native_os == .windows) {
const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
const value = getenvW(key_w) orelse return false;
return value.len != 0;
} else if (native_os == .wasi and !builtin.link_libc) {
- @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc");
+ return false;
} else {
const value = posix.getenv(key) orelse return false;
return value.len != 0;
@@ -1571,9 +1580,9 @@ pub fn getUserInfo(name: []const u8) !UserInfo {
/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
-pub fn posixGetUserInfo(name: []const u8) !UserInfo {
- const file = try std.fs.openFileAbsolute("/etc/passwd", .{});
- defer file.close();
+pub fn posixGetUserInfo(io: Io, name: []const u8) !UserInfo {
+ const file = try Io.Dir.openFileAbsolute(io, "/etc/passwd", .{});
+ defer file.close(io);
var buffer: [4096]u8 = undefined;
var file_reader = file.reader(&buffer);
return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) {
@@ -1839,21 +1848,19 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 {
}
}
-/// Indicate that we are now terminating with a successful exit code.
-/// In debug builds, this is a no-op, so that the calling code's
-/// cleanup mechanisms are tested and so that external tools that
-/// check for resource leaks can be accurate. In release builds, this
-/// calls exit(0), and does not return.
-pub fn cleanExit() void {
- if (builtin.mode == .Debug) {
- return;
- } else {
- std.debug.lockStdErr();
- exit(0);
- }
+/// Indicate intent to terminate with a successful exit code.
+///
+/// In debug builds, this is a no-op, so that the calling code's cleanup
+/// mechanisms are tested and so that external tools checking for resource
+/// leaks can be accurate. In release builds, this calls `exit` with code zero,
+/// and does not return.
+pub fn cleanExit(io: Io) void {
+ if (builtin.mode == .Debug) return;
+ _ = io.lockStderr(&.{}, .no_color) catch {};
+ exit(0);
}
-/// Raise the open file descriptor limit.
+/// Request ability to have more open file descriptors simultaneously.
///
/// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded
/// errors. On other systems, this does nothing.
@@ -2110,3 +2117,215 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
std.log.err(format, format_arguments);
exit(1);
}
+
+pub const ExecutablePathBaseError = error{
+ FileNotFound,
+ AccessDenied,
+ /// The operating system does not support an executable learning its own
+ /// path.
+ OperationUnsupported,
+ NotDir,
+ SymLinkLoop,
+ InputOutput,
+ FileTooBig,
+ IsDir,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ SystemResources,
+ NoSpaceLeft,
+ FileSystem,
+ BadPathName,
+ DeviceBusy,
+ SharingViolation,
+ PipeBusy,
+ NotLink,
+ PathAlreadyExists,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+ ProcessNotFound,
+ /// On Windows, antivirus software is enabled by default. It can be
+ /// disabled, but Windows Update sometimes ignores the user's preference
+ /// and re-enables it. When enabled, antivirus software on Windows
+ /// intercepts file system operations and makes them significantly slower
+ /// in addition to possibly failing with this error code.
+ AntivirusInterference,
+ /// On Windows, the volume does not contain a recognized file system. File
+ /// system drivers might not be loaded, or the volume may be corrupt.
+ UnrecognizedVolume,
+ PermissionDenied,
+} || Io.Cancelable || Io.UnexpectedError;
+
+pub const ExecutablePathAllocError = ExecutablePathBaseError || Allocator.Error;
+
+pub fn executablePathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![:0]u8 {
+ var buffer: [max_path_bytes]u8 = undefined;
+ const n = executablePath(io, &buffer) catch |err| switch (err) {
+ error.NameTooLong => unreachable,
+ else => |e| return e,
+ };
+ return allocator.dupeZ(u8, buffer[0..n]);
+}
+
+pub const ExecutablePathError = ExecutablePathBaseError || error{NameTooLong};
+
+/// Get the path to the current executable, following symlinks.
+///
+/// This function may return an error if the current executable
+/// was deleted after spawning.
+///
+/// Returned value is a slice of out_buffer.
+///
+/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
+///
+/// On Linux, depends on procfs being mounted. If the currently executing binary has
+/// been deleted, the file path looks something like "/a/b/c/exe (deleted)".
+///
+/// See also:
+/// * `executableDirPath` - to obtain only the directory
+/// * `openExecutable` - to obtain only an open file handle
+pub fn executablePath(io: Io, out_buffer: []u8) ExecutablePathError!usize {
+ return io.vtable.processExecutablePath(io.userdata, out_buffer);
+}
+
+/// Get the directory path that contains the current executable.
+///
+/// Returns index into `out_buffer`.
+///
+/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
+/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
+pub fn executableDirPath(io: Io, out_buffer: []u8) ExecutablePathError!usize {
+ const n = try executablePath(io, out_buffer);
+ // Assert that the OS APIs return absolute paths, and therefore dirname
+ // will not return null.
+ return std.fs.path.dirname(out_buffer[0..n]).?.len;
+}
+
+/// Same as `executableDirPath` except allocates the result.
+pub fn executableDirPathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![]u8 {
+ var buffer: [max_path_bytes]u8 = undefined;
+ const dir_path_len = executableDirPath(io, &buffer) catch |err| switch (err) {
+ error.NameTooLong => unreachable,
+ else => |e| return e,
+ };
+ return allocator.dupe(u8, buffer[0..dir_path_len]);
+}
+
+pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.LockError;
+
+pub fn openExecutable(io: Io, flags: File.OpenFlags) OpenExecutableError!File {
+ return io.vtable.processExecutableOpen(io.userdata, flags);
+}
+
+/// Causes abnormal process termination.
+///
+/// If linking against libc, this calls `std.c.abort`. Otherwise it raises
+/// SIGABRT followed by SIGKILL.
+///
+/// Invokes the current signal handler for SIGABRT, if any.
+pub fn abort() noreturn {
+ @branchHint(.cold);
+ // MSVCRT abort() sometimes opens a popup window which is undesirable, so
+ // even when linking libc on Windows we use our own abort implementation.
+ // See https://github.com/ziglang/zig/issues/2071 for more details.
+ if (native_os == .windows) {
+ if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) {
+ @breakpoint();
+ }
+ windows.ntdll.RtlExitUserProcess(3);
+ }
+ if (!builtin.link_libc and native_os == .linux) {
+ // The Linux man page says that the libc abort() function
+ // "first unblocks the SIGABRT signal", but this is a footgun
+ // for user-defined signal handlers that want to restore some state in
+ // some program sections and crash in others.
+ // So, the user-installed SIGABRT handler is run, if present.
+ posix.raise(.ABRT) catch {};
+
+ // Disable all signal handlers.
+ const filledset = std.os.linux.sigfillset();
+ posix.sigprocmask(posix.SIG.BLOCK, &filledset, null);
+
+ // Only one thread may proceed to the rest of abort().
+ if (!builtin.single_threaded) {
+ const global = struct {
+ var abort_entered: bool = false;
+ };
+ while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {}
+ }
+
+ // Install default handler so that the tkill below will terminate.
+ const sigact: posix.Sigaction = .{
+ .handler = .{ .handler = posix.SIG.DFL },
+ .mask = posix.sigemptyset(),
+ .flags = 0,
+ };
+ posix.sigaction(.ABRT, &sigact, null);
+
+ _ = std.os.linux.tkill(std.os.linux.gettid(), .ABRT);
+
+ var sigabrtmask = posix.sigemptyset();
+ posix.sigaddset(&sigabrtmask, .ABRT);
+ posix.sigprocmask(posix.SIG.UNBLOCK, &sigabrtmask, null);
+
+ // Beyond this point should be unreachable.
+ @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;
+ posix.raise(.KILL) catch {};
+ exit(127); // Pid 1 might not be signalled in some containers.
+ }
+ switch (native_os) {
+ .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(),
+ else => posix.system.abort(),
+ }
+}
+
+/// Exits all threads of the program with the specified status code.
+pub fn exit(status: u8) noreturn {
+ if (builtin.link_libc) {
+ std.c.exit(status);
+ } else switch (native_os) {
+ .windows => windows.ntdll.RtlExitUserProcess(status),
+ .wasi => std.os.wasi.proc_exit(status),
+ .linux => {
+ if (!builtin.single_threaded) std.os.linux.exit_group(status);
+ posix.system.exit(status);
+ },
+ .uefi => {
+ const uefi = std.os.uefi;
+ // exit() is only available if exitBootServices() has not been called yet.
+ // This call to exit should not fail, so we catch-ignore errors.
+ if (uefi.system_table.boot_services) |bs| {
+ bs.exit(uefi.handle, @enumFromInt(status), null) catch {};
+ }
+ // If we can't exit, reboot the system instead.
+ uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null);
+ },
+ else => posix.system.exit(status),
+ }
+}
+
+pub const SetCurrentDirError = error{
+ AccessDenied,
+ BadPathName,
+ FileNotFound,
+ FileSystem,
+ NameTooLong,
+ NoDevice,
+ NotDir,
+ OperationUnsupported,
+ UnrecognizedVolume,
+} || Io.Cancelable || Io.UnexpectedError;
+
+/// Changes the current working directory to the open directory handle.
+/// Corresponds to "fchdir" in libc.
+///
+/// This modifies global process state and can have surprising effects in
+/// multithreaded applications. Most applications and especially libraries
+/// should not call this function as a general rule, however it can have use
+/// cases in, for example, implementing a shell, or child process execution.
+///
+/// Calling this function makes code less portable and less reusable.
+pub fn setCurrentDir(io: Io, dir: Io.Dir) !void {
+ return io.vtable.processSetCurrentDir(io.userdata, dir);
+}