authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-31 15:06:13-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 00:27:08-08:00
log3e6d6150d98658e6c46ad5c378f90fb628f97d2a
tree0cbbcc777c632c6c241aaf5072254df40e0c2bc2
parent384bfc5f99d0d46521d62e09858facc2edd3bc74

std.process.Environ: fix compile errors on POSIX


8 files changed, 267 insertions(+), 227 deletions(-)

lib/compiler/test_runner.zig+2-3
......@@ -29,7 +29,7 @@ const need_simple = switch (builtin.zig_backend) {
2929 else => false,
3030};
3131
32pub fn main() void {
32pub fn main(init: std.process.Init.Minimal) void {
3333 @disableInstrumentation();
3434
3535 if (builtin.cpu.arch.isSpirV()) {
......@@ -41,8 +41,7 @@ pub fn main() void {
4141 return mainSimple() catch @panic("test failure\n");
4242 }
4343
44 const args = std.process.argsAlloc(fba.allocator()) catch
45 @panic("unable to parse command line args");
44 const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args");
4645
4746 var listen = false;
4847 var opt_cache_dir: ?[]const u8 = null;
lib/std/Io/Threaded.zig+2-2
......@@ -12836,11 +12836,11 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
1283612836 const envp: [*:null]const ?[*:0]const u8 = m: {
1283712837 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
1283812838 if (options.env_map) |env_map| {
12839 break :m (try env_map.createBlock(arena, .{
12839 break :m (try env_map.createBlockPosix(arena, .{
1284012840 .zig_progress_fd = prog_fd,
1284112841 })).ptr;
1284212842 }
12843 break :m (try process.Environ.createBlock(.{ .block = t.environ.block }, arena, .{
12843 break :m (try process.Environ.createBlockPosix(.{ .block = t.environ.block }, arena, .{
1284412844 .zig_progress_fd = prog_fd,
1284512845 })).ptr;
1284612846 };
lib/std/dynamic_library.zig+18-10
......@@ -31,12 +31,20 @@ pub const DynLib = struct {
3131
3232 /// Trusts the file. Malicious file will be able to execute arbitrary code.
3333 pub fn open(path: []const u8) Error!DynLib {
34 return .{ .inner = try InnerType.open(path) };
34 if (InnerType == ElfDynLib) {
35 return .{ .inner = try InnerType.open(path, null) };
36 } else {
37 return .{ .inner = try InnerType.open(path) };
38 }
3539 }
3640
3741 /// Trusts the file. Malicious file will be able to execute arbitrary code.
3842 pub fn openZ(path_c: [*:0]const u8) Error!DynLib {
39 return .{ .inner = try InnerType.openZ(path_c) };
43 if (InnerType == ElfDynLib) {
44 return .{ .inner = try InnerType.openZ(path_c, null) };
45 } else {
46 return .{ .inner = try InnerType.openZ(path_c) };
47 }
4048 }
4149
4250 /// Trusts the file.
......@@ -197,7 +205,7 @@ pub const ElfDynLib = struct {
197205 // - DT_RPATH of the calling binary is not used as a search path
198206 // - DT_RUNPATH of the calling binary is not used as a search path
199207 // - /etc/ld.so.cache is not read
200 fn resolveFromName(io: Io, path_or_name: []const u8) !posix.fd_t {
208 fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !posix.fd_t {
201209 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname
202210 if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {
203211 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
......@@ -207,7 +215,7 @@ pub const ElfDynLib = struct {
207215 if (std.os.linux.geteuid() == std.os.linux.getuid() and
208216 std.os.linux.getegid() == std.os.linux.getgid())
209217 {
210 if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| {
218 if (LD_LIBRARY_PATH) |ld_library_path| {
211219 if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| {
212220 return fd;
213221 }
......@@ -221,10 +229,10 @@ pub const ElfDynLib = struct {
221229 }
222230
223231 /// Trusts the file. Malicious file will be able to execute arbitrary code.
224 pub fn open(path: []const u8) Error!ElfDynLib {
232 pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
225233 const io = std.Options.debug_io;
226234
227 const fd = try resolveFromName(io, path);
235 const fd = try resolveFromName(io, path, LD_LIBRARY_PATH);
228236 defer posix.close(fd);
229237
230238 const file: Io.File = .{ .handle = fd };
......@@ -371,8 +379,8 @@ pub const ElfDynLib = struct {
371379 }
372380
373381 /// Trusts the file. Malicious file will be able to execute arbitrary code.
374 pub fn openZ(path_c: [*:0]const u8) Error!ElfDynLib {
375 return open(mem.sliceTo(path_c, 0));
382 pub fn openZ(path_c: [*:0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
383 return open(mem.sliceTo(path_c, 0), LD_LIBRARY_PATH);
376384 }
377385
378386 /// Trusts the file
......@@ -554,8 +562,8 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str
554562
555563test "ElfDynLib" {
556564 if (native_os != .linux) return error.SkipZigTest;
557 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so"));
558 try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so"));
565 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so", null));
566 try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so", null));
559567}
560568
561569/// Separated to avoid referencing `WindowsDynLib`, because its field types may not
lib/std/posix/test.zig+1-6
......@@ -140,11 +140,6 @@ test "pipe" {
140140 posix.close(fds[0]);
141141}
142142
143test "argsAlloc" {
144 const args = try std.process.argsAlloc(std.testing.allocator);
145 std.process.argsFree(std.testing.allocator, args);
146}
147
148143test "memfd_create" {
149144 const io = testing.io;
150145
......@@ -473,7 +468,7 @@ test "getpid" {
473468 if (native_os == .wasi) return error.SkipZigTest;
474469 if (native_os == .windows) return error.SkipZigTest;
475470
476 try expect(posix.getpid() != 0);
471 try expect(posix.system.getpid() != 0);
477472}
478473
479474test "getppid" {
lib/std/process/Args.zig+1-1
......@@ -6,7 +6,7 @@ const native_os = builtin.os.tag;
66const std = @import("../std.zig");
77const Allocator = std.mem.Allocator;
88const assert = std.debug.assert;
9const testing = std.debug.testing;
9const testing = std.testing;
1010
1111vector: Vector,
1212
lib/std/process/Environ.zig+218-182
......@@ -6,15 +6,25 @@ const native_os = builtin.os.tag;
66const std = @import("../std.zig");
77const Allocator = std.mem.Allocator;
88const assert = std.debug.assert;
9const testing = std.debug.testing;
9const testing = std.testing;
1010const unicode = std.unicode;
1111const posix = std.posix;
1212const mem = std.mem;
1313
14/// Unmodified, unprocessed data provided by the operating system.
15///
16/// On Windows this might point to memory in the PEB.
17///
18/// On WASI without libc, this is void because the environment has to be
19/// queried and heap-allocated at runtime.
1420block: Block,
1521
1622pub const Block = switch (native_os) {
1723 .windows => []const u16,
24 .wasi => switch (builtin.link_libc) {
25 false => void,
26 true => [:null]const ?[*:0]const u8,
27 },
1828 else => [:null]const ?[*:0]const u8,
1929};
2030
......@@ -89,11 +99,11 @@ pub const Map = struct {
8999 self.* = undefined;
90100 }
91101
92 pub fn keys(m: *Map) [][]const u8 {
102 pub fn keys(m: *const Map) [][]const u8 {
93103 return m.array_hash_map.keys();
94104 }
95105
96 pub fn values(m: *Map) [][]const u8 {
106 pub fn values(m: *const Map) [][]const u8 {
97107 return m.array_hash_map.values();
98108 }
99109
......@@ -214,10 +224,10 @@ pub const Map = struct {
214224
215225 /// Creates a null-delimited environment variable block in the format
216226 /// expected by POSIX, from a hash map plus options.
217 pub fn createBlock(
227 pub fn createBlockPosix(
218228 map: *const Map,
219229 arena: Allocator,
220 options: CreateBlockOptions,
230 options: CreateBlockPosixOptions,
221231 ) Allocator.Error![:null]?[*:0]u8 {
222232 const ZigProgressAction = enum { nothing, edit, delete, add };
223233 const zig_progress_action: ZigProgressAction = a: {
......@@ -273,6 +283,46 @@ pub const Map = struct {
273283 assert(i == envp_count);
274284 return envp_buf;
275285 }
286
287 /// Caller must free result.
288 pub fn createBlockWindows(map: *const Map, gpa: Allocator) Allocator.Error![]u16 {
289 // count bytes needed
290 const max_chars_needed = x: {
291 // Only need 2 trailing NUL code units for an empty environment
292 var max_chars_needed: usize = if (map.count() == 0) 2 else 1;
293 var it = map.iterator();
294 while (it.next()) |pair| {
295 // +1 for '='
296 // +1 for null byte
297 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
298 }
299 break :x max_chars_needed;
300 };
301 const result = try gpa.alloc(u16, max_chars_needed);
302 errdefer gpa.free(result);
303
304 var it = map.iterator();
305 var i: usize = 0;
306 while (it.next()) |pair| {
307 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
308 result[i] = '=';
309 i += 1;
310 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
311 result[i] = 0;
312 i += 1;
313 }
314 result[i] = 0;
315 i += 1;
316 // An empty environment is a special case that requires a redundant
317 // NUL terminator. CreateProcess will read the second code unit even
318 // though theoretically the first should be enough to recognize that the
319 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
320 if (map.count() == 0) {
321 result[i] = 0;
322 i += 1;
323 }
324 return try gpa.realloc(result, i);
325 }
276326};
277327
278328pub const CreateMapError = error{
......@@ -380,162 +430,131 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
380430 }
381431}
382432
383test createMap {
384 var env = try createMap(testing.allocator);
385 defer env.deinit();
386}
387
388pub const GetEnvVarOwnedError = error{
433pub const ContainsError = error{
389434 OutOfMemory,
390 EnvironmentVariableNotFound,
391
392 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
393 /// https://wtf-8.codeberg.page/
435 /// On Windows, environment variable keys provided by the user must be
436 /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable
437 /// if the key is statically known to be valid.
394438 InvalidWtf8,
439 /// WASI-only. `environ_sizes_get` or `environ_get` failed for an
440 /// unexpected reason.
441 Unexpected,
395442};
396443
397/// Caller must free returned memory.
398444/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
399445/// then `error.InvalidWtf8` is returned.
400/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/).
401/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
402pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
403 if (native_os == .windows) {
404 const result_w = blk: {
405 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
406 const stack_allocator = stack_alloc.get();
407 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
408 defer stack_allocator.free(key_w);
446///
447/// See also:
448/// * `createMap`
449/// * `containsConstant`
450/// * `containsUnempty`
451pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
452 var map = try createMap(environ, gpa);
453 defer map.deinit();
454 return map.contains(key);
455}
409456
410 break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
411 };
412 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
413 return unicode.wtf16LeToWtf8Alloc(allocator, result_w);
414 } else if (native_os == .wasi and !builtin.link_libc) {
415 var envmap = createMap(allocator) catch return error.OutOfMemory;
416 defer envmap.deinit();
417 const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound;
418 return allocator.dupe(u8, val);
419 } else {
420 const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
421 return allocator.dupe(u8, result);
422 }
457/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
458/// then `error.InvalidWtf8` is returned.
459///
460/// See also:
461/// * `createMap`
462/// * `containsUnemptyConstant`
463/// * `contains`
464pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
465 var map = try createMap(environ, gpa);
466 defer map.deinit();
467 const value = map.get(key) orelse return false;
468 return value.len != 0;
423469}
424470
425/// On Windows, `key` must be valid WTF-8.
426pub inline fn hasEnvVarConstant(comptime key: []const u8) bool {
471/// This function is unavailable on WASI without libc due to the memory
472/// allocation requirement.
473///
474/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/),
475///
476/// See also:
477/// * `contains`
478/// * `containsUnemptyConstant`
479/// * `createMap`
480pub inline fn containsConstant(environ: Environ, comptime key: []const u8) bool {
427481 if (native_os == .windows) {
428482 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
429 return getenvW(key_w) != null;
430 } else if (native_os == .wasi and !builtin.link_libc) {
431 return false;
483 return getWindows(environ, key_w) != null;
432484 } else {
433 return posix.getenv(key) != null;
485 return getPosix(environ, key) != null;
434486 }
435487}
436488
437/// On Windows, `key` must be valid WTF-8.
438pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
489/// This function is unavailable on WASI without libc due to the memory
490/// allocation requirement.
491///
492/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/),
493///
494/// See also:
495/// * `containsUnempty`
496/// * `containsConstant`
497/// * `createMap`
498pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8) bool {
439499 if (native_os == .windows) {
440500 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
441 const value = getenvW(key_w) orelse return false;
501 const value = getWindows(environ, key_w) orelse return false;
442502 return value.len != 0;
443 } else if (native_os == .wasi and !builtin.link_libc) {
444 return false;
445503 } else {
446 const value = posix.getenv(key) orelse return false;
504 const value = getPosix(environ, key) orelse return false;
447505 return value.len != 0;
448506 }
449507}
450508
451pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound};
452
453/// Parses an environment variable as an integer.
509/// This function is unavailable on WASI without libc due to the memory
510/// allocation requirement.
454511///
455/// On Windows, `key` must be valid WTF-8.
456pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I {
457 const text = io.environ(key) orelse return error.EnvironmentVariableNotFound;
458 return std.fmt.parseInt(I, text, base);
459}
460
461pub const HasEnvVarError = error{
462 OutOfMemory,
463
464 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
465 /// https://wtf-8.codeberg.page/
466 InvalidWtf8,
467};
468
469/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
470/// then `error.InvalidWtf8` is returned.
471pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
472 if (native_os == .windows) {
473 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
474 const stack_allocator = stack_alloc.get();
475 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
476 defer stack_allocator.free(key_w);
477 return getenvW(key_w) != null;
478 } else if (native_os == .wasi and !builtin.link_libc) {
479 var envmap = createMap(allocator) catch return error.OutOfMemory;
480 defer envmap.deinit();
481 return envmap.getPtr(key) != null;
482 } else {
483 return posix.getenv(key) != null;
484 }
485}
512/// See also:
513/// * `getWindows`
514/// * `createMap`
515pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
516 if (mem.findScalar(u8, key, '=') != null) return null;
517 for (environ.block) |opt_line| {
518 const line = opt_line.?;
519 var line_i: usize = 0;
520 while (line[line_i] != 0) : (line_i += 1) {
521 if (line_i == key.len) break;
522 if (line[line_i] != key[line_i]) break;
523 }
524 if ((line_i != key.len) or (line[line_i] != '=')) continue;
486525
487/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
488/// then `error.InvalidWtf8` is returned.
489pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
490 if (native_os == .windows) {
491 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
492 const stack_allocator = stack_alloc.get();
493 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
494 defer stack_allocator.free(key_w);
495 const value = getenvW(key_w) orelse return false;
496 return value.len != 0;
497 } else if (native_os == .wasi and !builtin.link_libc) {
498 var envmap = createMap(allocator) catch return error.OutOfMemory;
499 defer envmap.deinit();
500 const value = envmap.getPtr(key) orelse return false;
501 return value.len != 0;
502 } else {
503 const value = posix.getenv(key) orelse return false;
504 return value.len != 0;
526 return mem.sliceTo(line + line_i + 1, 0);
505527 }
528 return null;
506529}
507530
508/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
509/// The returned slice points to memory in the PEB.
531/// Windows-only. Get an environment variable with a null-terminated, WTF-16
532/// encoded name.
510533///
511/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString.
534/// This function performs a Unicode-aware case-insensitive lookup using
535/// RtlEqualUnicodeString.
512536///
513537/// See also:
514/// * `std.posix.getenv`
515538/// * `createMap`
516/// * `getEnvVarOwned`
517/// * `hasEnvVarConstant`
518/// * `hasEnvVar`
519pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
520 if (native_os != .windows) {
521 @compileError("Windows-only");
522 }
539/// * `containsConstant`
540/// * `contains`
541pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
542 comptime assert(native_os == .windows);
543
544 // '=' anywhere but the start makes this an invalid environment variable name.
523545 const key_slice = mem.sliceTo(key, 0);
524 // '=' anywhere but the start makes this an invalid environment variable name
525 if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) {
526 return null;
527 }
528 const ptr = std.os.windows.peb().ProcessParameters.Environment;
546 if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
547
529548 var i: usize = 0;
530 while (ptr[i] != 0) {
531 const key_value = mem.sliceTo(ptr[i..], 0);
549 while (environ.block[i] != 0) {
550 const key_value = mem.sliceTo(environ.block[i..], 0);
532551
533552 // There are some special environment variables that start with =,
534553 // so we need a special case to not treat = as a key/value separator
535554 // if it's the first character.
536555 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
537556 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
538 const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
557 const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
539558 // This is enforced by CreateProcess.
540559 // If violated, CreateProcess will fail with INVALID_PARAMETER.
541560 unreachable; // must contain a =
......@@ -552,25 +571,35 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
552571 return null;
553572}
554573
555test getEnvVarOwned {
556 try testing.expectError(
557 error.EnvironmentVariableNotFound,
558 getEnvVarOwned(std.testing.allocator, "BADENV"),
559 );
560}
561
562test hasEnvVarConstant {
563 if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
564
565 try testing.expect(!hasEnvVarConstant("BADENV"));
566}
574pub const GetAllocError = error{
575 OutOfMemory,
576 EnvironmentVariableMissing,
577 /// On Windows, environment variable keys provided by the user must be
578 /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable
579 /// if the key is statically known to be valid.
580 InvalidWtf8,
581};
567582
568test hasEnvVar {
569 const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
570 try testing.expect(!has_env);
583/// Caller owns returned memory.
584///
585/// On Windows:
586/// * If `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), then
587/// `error.InvalidWtf8` is returned.
588/// * The returned value is encoded as [WTF-8](https://wtf-8.codeberg.page/).
589///
590/// On other platforms, the value is an opaque sequence of bytes with no
591/// particular encoding.
592///
593/// See also:
594/// * `createMap`
595pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {
596 var map = createMap(environ, gpa) catch return error.OutOfMemory;
597 defer map.deinit();
598 const val = map.get(key) orelse return error.EnvironmentVariableMissing;
599 return gpa.dupe(u8, val);
571600}
572601
573pub const CreateBlockOptions = struct {
602pub const CreateBlockPosixOptions = struct {
574603 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
575604 /// If non-null, negative means to remove the environment variable, and >= 0
576605 /// means to provide it with the given integer.
......@@ -579,7 +608,11 @@ pub const CreateBlockOptions = struct {
579608
580609/// Creates a null-delimited environment variable block in the format expected
581610/// by POSIX, from a different one.
582pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 {
611pub fn createBlockPosix(
612 existing: Environ,
613 arena: Allocator,
614 options: CreateBlockPosixOptions,
615) Allocator.Error![:null]?[*:0]u8 {
583616 const contains_zig_progress = for (existing.block) |opt_line| {
584617 if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true;
585618 } else false;
......@@ -646,7 +679,7 @@ test "Map.createBlock" {
646679
647680 var arena = std.heap.ArenaAllocator.init(allocator);
648681 defer arena.deinit();
649 const environ = try envmap.createBlock(arena.allocator(), .{});
682 const environ = try envmap.createBlockPosix(arena.allocator(), .{});
650683
651684 try testing.expectEqual(@as(usize, 5), environ.len);
652685
......@@ -665,46 +698,6 @@ test "Map.createBlock" {
665698 }
666699}
667700
668/// Caller must free result.
669pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const Map) ![]u16 {
670 // count bytes needed
671 const max_chars_needed = x: {
672 // Only need 2 trailing NUL code units for an empty environment
673 var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1;
674 var it = env_map.iterator();
675 while (it.next()) |pair| {
676 // +1 for '='
677 // +1 for null byte
678 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
679 }
680 break :x max_chars_needed;
681 };
682 const result = try allocator.alloc(u16, max_chars_needed);
683 errdefer allocator.free(result);
684
685 var it = env_map.iterator();
686 var i: usize = 0;
687 while (it.next()) |pair| {
688 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
689 result[i] = '=';
690 i += 1;
691 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
692 result[i] = 0;
693 i += 1;
694 }
695 result[i] = 0;
696 i += 1;
697 // An empty environment is a special case that requires a redundant
698 // NUL terminator. CreateProcess will read the second code unit even
699 // though theoretically the first should be enough to recognize that the
700 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
701 if (env_map.count() == 0) {
702 result[i] = 0;
703 i += 1;
704 }
705 return try allocator.realloc(result, i);
706}
707
708701test Map {
709702 var env = Map.init(testing.allocator);
710703 defer env.deinit();
......@@ -733,13 +726,14 @@ test Map {
733726 var it = env.iterator();
734727 var count: Map.Size = 0;
735728 while (it.next()) |entry| {
736 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.*);
729 const is_an_expected_name = mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*);
737730 try testing.expect(is_an_expected_name);
738731 count += 1;
739732 }
740733 try testing.expectEqual(@as(Map.Size, 2), count);
741734
742 env.remove("SOMETHING_NEW");
735 try testing.expect(env.swapRemove("SOMETHING_NEW"));
736 try testing.expect(!env.swapRemove("SOMETHING_NEW"));
743737 try testing.expect(env.get("SOMETHING_NEW") == null);
744738
745739 try testing.expectEqual(@as(Map.Size, 1), env.count());
......@@ -751,7 +745,7 @@ test Map {
751745
752746 // and WTF-8 that's not valid UTF-8
753747 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
754 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
748 mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
755749 });
756750 defer testing.allocator.free(wtf8_with_surrogate_pair);
757751
......@@ -759,3 +753,45 @@ test Map {
759753 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
760754 }
761755}
756
757test "convert from Environ to Map and back again" {
758 const gpa = testing.allocator;
759
760 var map: Map = .init(gpa);
761 defer map.deinit();
762 try map.put("FOO", "BAR");
763 try map.put("A", "");
764 try map.put("", "B");
765
766 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
767 defer arena_allocator.deinit();
768 const arena = arena_allocator.allocator();
769
770 const environ: Environ = switch (native_os) {
771 .windows => .{ .block = try map.createBlockWindows(arena) },
772 .wasi => if (!builtin.libc) return error.SkipZigTest,
773 else => .{ .block = try map.createBlockPosix(arena, .{}) },
774 };
775
776 try testing.expectEqual(true, environ.contains(gpa, "FOO"));
777 try testing.expectEqual(false, environ.contains(gpa, "BAR"));
778 try testing.expectEqual(true, environ.contains(gpa, "A"));
779 try testing.expectEqual(true, environ.containsConstant("A"));
780 try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));
781 try testing.expectEqual(false, environ.containsUnemptyConstant("A"));
782 try testing.expectEqual(true, environ.contains(gpa, ""));
783 try testing.expectEqual(false, environ.contains(gpa, "B"));
784
785 try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));
786 {
787 const value = try environ.getAlloc(gpa, "FOO");
788 defer gpa.free(value);
789 try testing.expectEqualStrings("BAR", value);
790 }
791
792 var map2 = try environ.createMap(gpa);
793 defer map2.deinit();
794
795 try testing.expectEqualSlices([]const u8, map.keys(), map2.keys());
796 try testing.expectEqualSlices([]const u8, map.values(), map2.values());
797}
lib/std/zig.zig+2
......@@ -739,6 +739,8 @@ pub const EnvVar = enum {
739739 ZIG_VERBOSE_CC,
740740 ZIG_BTRFS_WORKAROUND,
741741 ZIG_DEBUG_CMD,
742 ZIG_IS_DETECTING_LIBC_PATHS,
743 ZIG_IS_TRYING_TO_NOT_CALL_ITSELF,
742744 CC,
743745 NO_COLOR,
744746 CLICOLOR_FORCE,
src/main.zig+23-23
......@@ -168,7 +168,7 @@ const use_debug_allocator = build_options.debug_gpa or
168168 .ReleaseFast, .ReleaseSmall => false,
169169 });
170170
171pub fn main() anyerror!void {
171pub fn main(init: std.process.Init.Minimal) anyerror!void {
172172 const gpa = gpa: {
173173 if (use_debug_allocator) break :gpa debug_allocator.allocator();
174174 if (native_os == .wasi) break :gpa std.heap.wasm_allocator;
......@@ -182,26 +182,25 @@ pub fn main() anyerror!void {
182182 defer arena_instance.deinit();
183183 const arena = arena_instance.allocator();
184184
185 const args = try process.argsAlloc(arena);
185 const args = try init.args.toSlice(arena);
186186
187187 if (args.len > 0) crash_report.zig_argv0 = args[0];
188188
189 var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err});
190
189191 if (tracy.enable_allocation) {
190192 var gpa_tracy = tracy.tracyAllocator(gpa);
191 return mainArgs(gpa_tracy.allocator(), arena, args);
193 return mainArgs(gpa_tracy.allocator(), arena, args, &env_map);
192194 }
193195
194196 if (native_os == .wasi) {
195197 wasi_preopens = try fs.wasi.preopensAlloc(arena);
196198 }
197199
198 return mainArgs(gpa, arena, args);
200 return mainArgs(gpa, arena, args, &env_map);
199201}
200202
201fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void {
202 const tr = tracy.trace(@src());
203 defer tr.end();
204
203fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_map: *process.Environ.Map) !void {
205204 Compilation.setMainThread();
206205
207206 if (args.len <= 1) {
......@@ -209,7 +208,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void
209208 fatal("expected command argument", .{});
210209 }
211210
212 if (process.can_execv and std.posix.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) {
211 if (process.can_replace and std.zig.EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) {
213212 dev.check(.cc_command);
214213 // In this case we have accidentally invoked ourselves as "the system C compiler"
215214 // 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
217216 // Here we ignore the CC environment variable and exec `cc` as a child process.
218217 // However it's possible Zig is installed as *that* C compiler as well, which is
219218 // why we have this additional environment variable here to check.
220 var env_map = try process.getEnvMap(arena);
221
222 const inf_loop_env_key = "ZIG_IS_TRYING_TO_NOT_CALL_ITSELF";
223 if (env_map.get(inf_loop_env_key) != null) {
224 fatal("The compilation links against libc, but Zig is unable to provide a libc " ++
225 "for this operating system, and no --libc " ++
226 "parameter was provided, so Zig attempted to invoke the system C compiler " ++
227 "in order to determine where libc is installed. However the system C " ++
228 "compiler is `zig cc`, so no libc installation was found.", .{});
219
220 const inf_loop_env_key: std.zig.EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF;
221 if (inf_loop_env_key.isSet(env_map)) {
222 fatal("{s}", .{
223 "The compilation links against libc, but Zig is unable to provide a libc " ++
224 "for this operating system, and no --libc " ++
225 "parameter was provided, so Zig attempted to invoke the system C compiler " ++
226 "in order to determine where libc is installed. However the system C " ++
227 "compiler is `zig cc`, so no libc installation was found.",
228 });
229229 }
230 try env_map.put(inf_loop_env_key, "1");
230 try env_map.put(@tagName(inf_loop_env_key), "1");
231231
232232 // Some programs such as CMake will strip the `cc` and subsequent args from the
233233 // CC environment variable. We detect and support this scenario here because of
234234 // the ZIG_IS_DETECTING_LIBC_PATHS environment variable.
235235 if (mem.eql(u8, args[1], "cc")) {
236 return process.execve(arena, args[1..], &env_map);
236 return process.replace(.{ .argv = args[1..], .env_map = env_map });
237237 } else {
238238 const modified_args = try arena.dupe([]const u8, args);
239239 modified_args[0] = "cc";
240 return process.execve(arena, modified_args, &env_map);
240 return process.replace(.{ .argv = modified_args, .env_map = env_map });
241241 }
242242 }
243243
......@@ -4431,7 +4431,7 @@ fn runOrTest(
44314431
44324432 // We do not execve for tests because if the test fails we want to print
44334433 // the error message and invocation below.
4434 if (process.can_execv and arg_mode == .run) {
4434 if (process.can_replace and arg_mode == .run) {
44354435 // execv releases the locks; no need to destroy the Compilation here.
44364436 _ = try io.lockStderr(&.{}, .no_color);
44374437 const err = process.execve(gpa, argv.items, &env_map);
......@@ -5668,7 +5668,7 @@ fn jitCmd(
56685668
56695669 child_argv.appendSliceAssumeCapacity(args);
56705670
5671 if (process.can_execv and options.capture == null) {
5671 if (process.can_replace and options.capture == null) {
56725672 if (EnvVar.ZIG_DEBUG_CMD.isSet()) {
56735673 const cmd = try std.mem.join(arena, " ", child_argv.items);
56745674 std.debug.print("{s}\n", .{cmd});