zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 3e6d6150d98658e6c46ad5c378f90fb628f97d2a (tree)
parent 384bfc5f99d0d46521d62e09858facc2edd3bc74
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Wed, 31 Dec 2025 15:06:13 -0800

std.process.Environ: fix compile errors on POSIX

Diffstat:
Mlib/compiler/test_runner.zig | 5++---
Mlib/std/Io/Threaded.zig | 4++--
Mlib/std/dynamic_library.zig | 28++++++++++++++++++----------
Mlib/std/posix/test.zig | 7+------
Mlib/std/process/Args.zig | 2+-
Mlib/std/process/Environ.zig | 400+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mlib/std/zig.zig | 2++
Msrc/main.zig | 46+++++++++++++++++++++++-----------------------
8 files changed, 267 insertions(+), 227 deletions(-)

diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig @@ -29,7 +29,7 @@ const need_simple = switch (builtin.zig_backend) { else => false, }; -pub fn main() void { +pub fn main(init: std.process.Init.Minimal) void { @disableInstrumentation(); if (builtin.cpu.arch.isSpirV()) { @@ -41,8 +41,7 @@ pub fn main() void { return mainSimple() catch @panic("test failure\n"); } - const args = std.process.argsAlloc(fba.allocator()) catch - @panic("unable to parse command line args"); + const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args"); var listen = false; var opt_cache_dir: ?[]const u8 = null; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig @@ -12836,11 +12836,11 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce const envp: [*:null]const ?[*:0]const u8 = m: { const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; if (options.env_map) |env_map| { - break :m (try env_map.createBlock(arena, .{ + break :m (try env_map.createBlockPosix(arena, .{ .zig_progress_fd = prog_fd, })).ptr; } - break :m (try process.Environ.createBlock(.{ .block = t.environ.block }, arena, .{ + break :m (try process.Environ.createBlockPosix(.{ .block = t.environ.block }, arena, .{ .zig_progress_fd = prog_fd, })).ptr; }; diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig @@ -31,12 +31,20 @@ pub const DynLib = struct { /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn open(path: []const u8) Error!DynLib { - return .{ .inner = try InnerType.open(path) }; + if (InnerType == ElfDynLib) { + return .{ .inner = try InnerType.open(path, null) }; + } else { + return .{ .inner = try InnerType.open(path) }; + } } /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn openZ(path_c: [*:0]const u8) Error!DynLib { - return .{ .inner = try InnerType.openZ(path_c) }; + if (InnerType == ElfDynLib) { + return .{ .inner = try InnerType.openZ(path_c, null) }; + } else { + return .{ .inner = try InnerType.openZ(path_c) }; + } } /// Trusts the file. @@ -197,7 +205,7 @@ pub const ElfDynLib = struct { // - DT_RPATH of the calling binary is not used as a search path // - DT_RUNPATH of the calling binary is not used as a search path // - /etc/ld.so.cache is not read - fn resolveFromName(io: Io, path_or_name: []const u8) !posix.fd_t { + fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !posix.fd_t { // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| { return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); @@ -207,7 +215,7 @@ pub const ElfDynLib = struct { if (std.os.linux.geteuid() == std.os.linux.getuid() and std.os.linux.getegid() == std.os.linux.getgid()) { - if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| { + if (LD_LIBRARY_PATH) |ld_library_path| { if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| { return fd; } @@ -221,10 +229,10 @@ pub const ElfDynLib = struct { } /// Trusts the file. Malicious file will be able to execute arbitrary code. - pub fn open(path: []const u8) Error!ElfDynLib { + pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib { const io = std.Options.debug_io; - const fd = try resolveFromName(io, path); + const fd = try resolveFromName(io, path, LD_LIBRARY_PATH); defer posix.close(fd); const file: Io.File = .{ .handle = fd }; @@ -371,8 +379,8 @@ pub const ElfDynLib = struct { } /// Trusts the file. Malicious file will be able to execute arbitrary code. - pub fn openZ(path_c: [*:0]const u8) Error!ElfDynLib { - return open(mem.sliceTo(path_c, 0)); + pub fn openZ(path_c: [*:0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib { + return open(mem.sliceTo(path_c, 0), LD_LIBRARY_PATH); } /// Trusts the file @@ -554,8 +562,8 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str test "ElfDynLib" { if (native_os != .linux) return error.SkipZigTest; - try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so")); - try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so")); + try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so", null)); + try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so", null)); } /// Separated to avoid referencing `WindowsDynLib`, because its field types may not diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig @@ -140,11 +140,6 @@ test "pipe" { posix.close(fds[0]); } -test "argsAlloc" { - const args = try std.process.argsAlloc(std.testing.allocator); - std.process.argsFree(std.testing.allocator, args); -} - test "memfd_create" { const io = testing.io; @@ -473,7 +468,7 @@ test "getpid" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; - try expect(posix.getpid() != 0); + try expect(posix.system.getpid() != 0); } test "getppid" { diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig @@ -6,7 +6,7 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const testing = std.debug.testing; +const testing = std.testing; vector: Vector, diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig @@ -6,15 +6,25 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const testing = std.debug.testing; +const testing = std.testing; const unicode = std.unicode; const posix = std.posix; const mem = std.mem; +/// Unmodified, unprocessed data provided by the operating system. +/// +/// On Windows this might point to memory in the PEB. +/// +/// On WASI without libc, this is void because the environment has to be +/// queried and heap-allocated at runtime. block: Block, pub const Block = switch (native_os) { .windows => []const u16, + .wasi => switch (builtin.link_libc) { + false => void, + true => [:null]const ?[*:0]const u8, + }, else => [:null]const ?[*:0]const u8, }; @@ -89,11 +99,11 @@ pub const Map = struct { self.* = undefined; } - pub fn keys(m: *Map) [][]const u8 { + pub fn keys(m: *const Map) [][]const u8 { return m.array_hash_map.keys(); } - pub fn values(m: *Map) [][]const u8 { + pub fn values(m: *const Map) [][]const u8 { return m.array_hash_map.values(); } @@ -214,10 +224,10 @@ pub const Map = struct { /// Creates a null-delimited environment variable block in the format /// expected by POSIX, from a hash map plus options. - pub fn createBlock( + pub fn createBlockPosix( map: *const Map, arena: Allocator, - options: CreateBlockOptions, + options: CreateBlockPosixOptions, ) Allocator.Error![:null]?[*:0]u8 { const ZigProgressAction = enum { nothing, edit, delete, add }; const zig_progress_action: ZigProgressAction = a: { @@ -273,6 +283,46 @@ pub const Map = struct { assert(i == envp_count); return envp_buf; } + + /// Caller must free result. + pub fn createBlockWindows(map: *const Map, gpa: Allocator) Allocator.Error![]u16 { + // count bytes needed + const max_chars_needed = x: { + // Only need 2 trailing NUL code units for an empty environment + var max_chars_needed: usize = if (map.count() == 0) 2 else 1; + var it = map.iterator(); + while (it.next()) |pair| { + // +1 for '=' + // +1 for null byte + max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; + } + break :x max_chars_needed; + }; + const result = try gpa.alloc(u16, max_chars_needed); + errdefer gpa.free(result); + + var it = map.iterator(); + var i: usize = 0; + while (it.next()) |pair| { + i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); + result[i] = '='; + i += 1; + i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); + result[i] = 0; + i += 1; + } + result[i] = 0; + i += 1; + // An empty environment is a special case that requires a redundant + // NUL terminator. CreateProcess will read the second code unit even + // though theoretically the first should be enough to recognize that the + // environment is empty (see https://nullprogram.com/blog/2023/08/23/) + if (map.count() == 0) { + result[i] = 0; + i += 1; + } + return try gpa.realloc(result, i); + } }; pub const CreateMapError = error{ @@ -380,162 +430,131 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { } } -test createMap { - var env = try createMap(testing.allocator); - defer env.deinit(); -} - -pub const GetEnvVarOwnedError = error{ +pub const ContainsError = error{ OutOfMemory, - EnvironmentVariableNotFound, - - /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ + /// On Windows, environment variable keys provided by the user must be + /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable + /// if the key is statically known to be valid. InvalidWtf8, + /// WASI-only. `environ_sizes_get` or `environ_get` failed for an + /// unexpected reason. + Unexpected, }; -/// Caller must free returned memory. /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), /// then `error.InvalidWtf8` is returned. -/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the value is an opaque sequence of bytes with no particular encoding. -pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { - if (native_os == .windows) { - const result_w = blk: { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); +/// +/// See also: +/// * `createMap` +/// * `containsConstant` +/// * `containsUnempty` +pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { + var map = try createMap(environ, gpa); + defer map.deinit(); + return map.contains(key); +} - break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; - }; - // wtf16LeToWtf8Alloc can only fail with OutOfMemory - return unicode.wtf16LeToWtf8Alloc(allocator, result_w); - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = createMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; - return allocator.dupe(u8, val); - } else { - const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; - return allocator.dupe(u8, result); - } +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), +/// then `error.InvalidWtf8` is returned. +/// +/// See also: +/// * `createMap` +/// * `containsUnemptyConstant` +/// * `contains` +pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { + var map = try createMap(environ, gpa); + defer map.deinit(); + const value = map.get(key) orelse return false; + return value.len != 0; } -/// On Windows, `key` must be valid WTF-8. -pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { +/// This function is unavailable on WASI without libc due to the memory +/// allocation requirement. +/// +/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/), +/// +/// See also: +/// * `contains` +/// * `containsUnemptyConstant` +/// * `createMap` +pub inline fn containsConstant(environ: Environ, 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) { - return false; + return getWindows(environ, key_w) != null; } else { - return posix.getenv(key) != null; + return getPosix(environ, key) != null; } } -/// On Windows, `key` must be valid WTF-8. -pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { +/// This function is unavailable on WASI without libc due to the memory +/// allocation requirement. +/// +/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/), +/// +/// See also: +/// * `containsUnempty` +/// * `containsConstant` +/// * `createMap` +pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8) bool { if (native_os == .windows) { const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); - const value = getenvW(key_w) orelse return false; + const value = getWindows(environ, key_w) orelse return false; return value.len != 0; - } else if (native_os == .wasi and !builtin.link_libc) { - return false; } else { - const value = posix.getenv(key) orelse return false; + const value = getPosix(environ, key) orelse return false; return value.len != 0; } } -pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; - -/// Parses an environment variable as an integer. +/// This function is unavailable on WASI without libc due to the memory +/// allocation requirement. /// -/// On Windows, `key` must be valid WTF-8. -pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I { - const text = io.environ(key) orelse return error.EnvironmentVariableNotFound; - return std.fmt.parseInt(I, text, base); -} - -pub const HasEnvVarError = error{ - OutOfMemory, - - /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - InvalidWtf8, -}; - -/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), -/// then `error.InvalidWtf8` is returned. -pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { - if (native_os == .windows) { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); - return getenvW(key_w) != null; - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = createMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - return envmap.getPtr(key) != null; - } else { - return posix.getenv(key) != null; - } -} +/// See also: +/// * `getWindows` +/// * `createMap` +pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { + if (mem.findScalar(u8, key, '=') != null) return null; + for (environ.block) |opt_line| { + const line = opt_line.?; + var line_i: usize = 0; + while (line[line_i] != 0) : (line_i += 1) { + if (line_i == key.len) break; + if (line[line_i] != key[line_i]) break; + } + if ((line_i != key.len) or (line[line_i] != '=')) continue; -/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), -/// then `error.InvalidWtf8` is returned. -pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { - if (native_os == .windows) { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); - const value = getenvW(key_w) orelse return false; - return value.len != 0; - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = createMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - const value = envmap.getPtr(key) orelse return false; - return value.len != 0; - } else { - const value = posix.getenv(key) orelse return false; - return value.len != 0; + return mem.sliceTo(line + line_i + 1, 0); } + return null; } -/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. -/// The returned slice points to memory in the PEB. +/// Windows-only. Get an environment variable with a null-terminated, WTF-16 +/// encoded name. /// -/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. +/// This function performs a Unicode-aware case-insensitive lookup using +/// RtlEqualUnicodeString. /// /// See also: -/// * `std.posix.getenv` /// * `createMap` -/// * `getEnvVarOwned` -/// * `hasEnvVarConstant` -/// * `hasEnvVar` -pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { - if (native_os != .windows) { - @compileError("Windows-only"); - } +/// * `containsConstant` +/// * `contains` +pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { + comptime assert(native_os == .windows); + + // '=' anywhere but the start makes this an invalid environment variable name. const key_slice = mem.sliceTo(key, 0); - // '=' anywhere but the start makes this an invalid environment variable name - if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { - return null; - } - const ptr = std.os.windows.peb().ProcessParameters.Environment; + if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null; + var i: usize = 0; - while (ptr[i] != 0) { - const key_value = mem.sliceTo(ptr[i..], 0); + while (environ.block[i] != 0) { + const key_value = mem.sliceTo(environ.block[i..], 0); // There are some special environment variables that start with =, // so we need a special case to not treat = as a key/value separator // if it's the first character. // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; - const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { + const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { // This is enforced by CreateProcess. // If violated, CreateProcess will fail with INVALID_PARAMETER. unreachable; // must contain a = @@ -552,25 +571,35 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { return null; } -test getEnvVarOwned { - try testing.expectError( - error.EnvironmentVariableNotFound, - getEnvVarOwned(std.testing.allocator, "BADENV"), - ); -} - -test hasEnvVarConstant { - if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; - - try testing.expect(!hasEnvVarConstant("BADENV")); -} +pub const GetAllocError = error{ + OutOfMemory, + EnvironmentVariableMissing, + /// On Windows, environment variable keys provided by the user must be + /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable + /// if the key is statically known to be valid. + InvalidWtf8, +}; -test hasEnvVar { - const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); - try testing.expect(!has_env); +/// Caller owns returned memory. +/// +/// On Windows: +/// * If `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), then +/// `error.InvalidWtf8` is returned. +/// * The returned value is encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// +/// On other platforms, the value is an opaque sequence of bytes with no +/// particular encoding. +/// +/// See also: +/// * `createMap` +pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 { + var map = createMap(environ, gpa) catch return error.OutOfMemory; + defer map.deinit(); + const val = map.get(key) orelse return error.EnvironmentVariableMissing; + return gpa.dupe(u8, val); } -pub const CreateBlockOptions = struct { +pub const CreateBlockPosixOptions = struct { /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. /// If non-null, negative means to remove the environment variable, and >= 0 /// means to provide it with the given integer. @@ -579,7 +608,11 @@ pub const CreateBlockOptions = struct { /// Creates a null-delimited environment variable block in the format expected /// by POSIX, from a different one. -pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 { +pub fn createBlockPosix( + existing: Environ, + arena: Allocator, + options: CreateBlockPosixOptions, +) Allocator.Error![:null]?[*:0]u8 { const contains_zig_progress = for (existing.block) |opt_line| { if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; } else false; @@ -646,7 +679,7 @@ test "Map.createBlock" { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - const environ = try envmap.createBlock(arena.allocator(), .{}); + const environ = try envmap.createBlockPosix(arena.allocator(), .{}); try testing.expectEqual(@as(usize, 5), environ.len); @@ -665,46 +698,6 @@ test "Map.createBlock" { } } -/// Caller must free result. -pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const Map) ![]u16 { - // count bytes needed - const max_chars_needed = x: { - // Only need 2 trailing NUL code units for an empty environment - var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1; - var it = env_map.iterator(); - while (it.next()) |pair| { - // +1 for '=' - // +1 for null byte - max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; - } - break :x max_chars_needed; - }; - const result = try allocator.alloc(u16, max_chars_needed); - errdefer allocator.free(result); - - var it = env_map.iterator(); - var i: usize = 0; - while (it.next()) |pair| { - i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); - result[i] = '='; - i += 1; - i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); - result[i] = 0; - i += 1; - } - result[i] = 0; - i += 1; - // An empty environment is a special case that requires a redundant - // NUL terminator. CreateProcess will read the second code unit even - // though theoretically the first should be enough to recognize that the - // environment is empty (see https://nullprogram.com/blog/2023/08/23/) - if (env_map.count() == 0) { - result[i] = 0; - i += 1; - } - return try allocator.realloc(result, i); -} - test Map { var env = Map.init(testing.allocator); defer env.deinit(); @@ -733,13 +726,14 @@ test Map { var it = env.iterator(); var count: Map.Size = 0; while (it.next()) |entry| { - const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); + const is_an_expected_name = mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); try testing.expect(is_an_expected_name); count += 1; } try testing.expectEqual(@as(Map.Size, 2), count); - env.remove("SOMETHING_NEW"); + try testing.expect(env.swapRemove("SOMETHING_NEW")); + try testing.expect(!env.swapRemove("SOMETHING_NEW")); try testing.expect(env.get("SOMETHING_NEW") == null); try testing.expectEqual(@as(Map.Size, 1), env.count()); @@ -751,7 +745,7 @@ test Map { // and WTF-8 that's not valid UTF-8 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ - std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate + mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate }); defer testing.allocator.free(wtf8_with_surrogate_pair); @@ -759,3 +753,45 @@ test Map { try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); } } + +test "convert from Environ to Map and back again" { + const gpa = testing.allocator; + + var map: Map = .init(gpa); + defer map.deinit(); + try map.put("FOO", "BAR"); + try map.put("A", ""); + try map.put("", "B"); + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const environ: Environ = switch (native_os) { + .windows => .{ .block = try map.createBlockWindows(arena) }, + .wasi => if (!builtin.libc) return error.SkipZigTest, + else => .{ .block = try map.createBlockPosix(arena, .{}) }, + }; + + try testing.expectEqual(true, environ.contains(gpa, "FOO")); + try testing.expectEqual(false, environ.contains(gpa, "BAR")); + try testing.expectEqual(true, environ.contains(gpa, "A")); + try testing.expectEqual(true, environ.containsConstant("A")); + try testing.expectEqual(false, environ.containsUnempty(gpa, "A")); + try testing.expectEqual(false, environ.containsUnemptyConstant("A")); + try testing.expectEqual(true, environ.contains(gpa, "")); + try testing.expectEqual(false, environ.contains(gpa, "B")); + + try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS")); + { + const value = try environ.getAlloc(gpa, "FOO"); + defer gpa.free(value); + try testing.expectEqualStrings("BAR", value); + } + + var map2 = try environ.createMap(gpa); + defer map2.deinit(); + + try testing.expectEqualSlices([]const u8, map.keys(), map2.keys()); + try testing.expectEqualSlices([]const u8, map.values(), map2.values()); +} diff --git a/lib/std/zig.zig b/lib/std/zig.zig @@ -739,6 +739,8 @@ pub const EnvVar = enum { ZIG_VERBOSE_CC, ZIG_BTRFS_WORKAROUND, ZIG_DEBUG_CMD, + ZIG_IS_DETECTING_LIBC_PATHS, + ZIG_IS_TRYING_TO_NOT_CALL_ITSELF, CC, NO_COLOR, CLICOLOR_FORCE, diff --git a/src/main.zig b/src/main.zig @@ -168,7 +168,7 @@ const use_debug_allocator = build_options.debug_gpa or .ReleaseFast, .ReleaseSmall => false, }); -pub fn main() anyerror!void { +pub fn main(init: std.process.Init.Minimal) anyerror!void { const gpa = gpa: { if (use_debug_allocator) break :gpa debug_allocator.allocator(); if (native_os == .wasi) break :gpa std.heap.wasm_allocator; @@ -182,26 +182,25 @@ pub fn main() anyerror!void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - const args = try process.argsAlloc(arena); + const args = try init.args.toSlice(arena); if (args.len > 0) crash_report.zig_argv0 = args[0]; + var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err}); + if (tracy.enable_allocation) { var gpa_tracy = tracy.tracyAllocator(gpa); - return mainArgs(gpa_tracy.allocator(), arena, args); + return mainArgs(gpa_tracy.allocator(), arena, args, &env_map); } if (native_os == .wasi) { wasi_preopens = try fs.wasi.preopensAlloc(arena); } - return mainArgs(gpa, arena, args); + return mainArgs(gpa, arena, args, &env_map); } -fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void { - const tr = tracy.trace(@src()); - defer tr.end(); - +fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_map: *process.Environ.Map) !void { Compilation.setMainThread(); if (args.len <= 1) { @@ -209,7 +208,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void fatal("expected command argument", .{}); } - if (process.can_execv and std.posix.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) { + if (process.can_replace and std.zig.EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) { dev.check(.cc_command); // In this case we have accidentally invoked ourselves as "the system C compiler" // to figure out where libc is installed. This is essentially infinite recursion @@ -217,27 +216,28 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void // Here we ignore the CC environment variable and exec `cc` as a child process. // However it's possible Zig is installed as *that* C compiler as well, which is // why we have this additional environment variable here to check. - var env_map = try process.getEnvMap(arena); - - const inf_loop_env_key = "ZIG_IS_TRYING_TO_NOT_CALL_ITSELF"; - if (env_map.get(inf_loop_env_key) != null) { - fatal("The compilation links against libc, but Zig is unable to provide a libc " ++ - "for this operating system, and no --libc " ++ - "parameter was provided, so Zig attempted to invoke the system C compiler " ++ - "in order to determine where libc is installed. However the system C " ++ - "compiler is `zig cc`, so no libc installation was found.", .{}); + + const inf_loop_env_key: std.zig.EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF; + if (inf_loop_env_key.isSet(env_map)) { + fatal("{s}", .{ + "The compilation links against libc, but Zig is unable to provide a libc " ++ + "for this operating system, and no --libc " ++ + "parameter was provided, so Zig attempted to invoke the system C compiler " ++ + "in order to determine where libc is installed. However the system C " ++ + "compiler is `zig cc`, so no libc installation was found.", + }); } - try env_map.put(inf_loop_env_key, "1"); + try env_map.put(@tagName(inf_loop_env_key), "1"); // Some programs such as CMake will strip the `cc` and subsequent args from the // CC environment variable. We detect and support this scenario here because of // the ZIG_IS_DETECTING_LIBC_PATHS environment variable. if (mem.eql(u8, args[1], "cc")) { - return process.execve(arena, args[1..], &env_map); + return process.replace(.{ .argv = args[1..], .env_map = env_map }); } else { const modified_args = try arena.dupe([]const u8, args); modified_args[0] = "cc"; - return process.execve(arena, modified_args, &env_map); + return process.replace(.{ .argv = modified_args, .env_map = env_map }); } } @@ -4431,7 +4431,7 @@ fn runOrTest( // We do not execve for tests because if the test fails we want to print // the error message and invocation below. - if (process.can_execv and arg_mode == .run) { + if (process.can_replace and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. _ = try io.lockStderr(&.{}, .no_color); const err = process.execve(gpa, argv.items, &env_map); @@ -5668,7 +5668,7 @@ fn jitCmd( child_argv.appendSliceAssumeCapacity(args); - if (process.can_execv and options.capture == null) { + if (process.can_replace and options.capture == null) { if (EnvVar.ZIG_DEBUG_CMD.isSet()) { const cmd = try std.mem.join(arena, " ", child_argv.items); std.debug.print("{s}\n", .{cmd});