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:
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});