master
   1const std = @import("std.zig");
   2const builtin = @import("builtin");
   3const fs = std.fs;
   4const mem = std.mem;
   5const math = std.math;
   6const Allocator = mem.Allocator;
   7const assert = std.debug.assert;
   8const testing = std.testing;
   9const native_os = builtin.os.tag;
  10const posix = std.posix;
  11const windows = std.os.windows;
  12const unicode = std.unicode;
  13
  14pub const Child = @import("process/Child.zig");
  15pub const abort = posix.abort;
  16pub const exit = posix.exit;
  17pub const changeCurDir = posix.chdir;
  18pub const changeCurDirZ = posix.chdirZ;
  19
  20pub const GetCwdError = posix.GetCwdError;
  21
  22/// The result is a slice of `out_buffer`, from index `0`.
  23/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
  24/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
  25pub fn getCwd(out_buffer: []u8) GetCwdError![]u8 {
  26    return posix.getcwd(out_buffer);
  27}
  28
  29// Same as GetCwdError, minus error.NameTooLong + Allocator.Error
  30pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnlinked} || posix.UnexpectedError;
  31
  32/// Caller must free the returned memory.
  33/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
  34/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
  35pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 {
  36    // The use of max_path_bytes here is just a heuristic: most paths will fit
  37    // in stack_buf, avoiding an extra allocation in the common case.
  38    var stack_buf: [fs.max_path_bytes]u8 = undefined;
  39    var heap_buf: ?[]u8 = null;
  40    defer if (heap_buf) |buf| allocator.free(buf);
  41
  42    var current_buf: []u8 = &stack_buf;
  43    while (true) {
  44        if (posix.getcwd(current_buf)) |slice| {
  45            return allocator.dupe(u8, slice);
  46        } else |err| switch (err) {
  47            error.NameTooLong => {
  48                // The path is too long to fit in stack_buf. Allocate geometrically
  49                // increasing buffers until we find one that works
  50                const new_capacity = current_buf.len * 2;
  51                if (heap_buf) |buf| allocator.free(buf);
  52                current_buf = try allocator.alloc(u8, new_capacity);
  53                heap_buf = current_buf;
  54            },
  55            else => |e| return e,
  56        }
  57    }
  58}
  59
  60test getCwdAlloc {
  61    if (native_os == .wasi) return error.SkipZigTest;
  62
  63    const cwd = try getCwdAlloc(testing.allocator);
  64    testing.allocator.free(cwd);
  65}
  66
  67pub const EnvMap = struct {
  68    hash_map: HashMap,
  69
  70    const HashMap = std.HashMap(
  71        []const u8,
  72        []const u8,
  73        EnvNameHashContext,
  74        std.hash_map.default_max_load_percentage,
  75    );
  76
  77    pub const Size = HashMap.Size;
  78
  79    pub const EnvNameHashContext = struct {
  80        fn upcase(c: u21) u21 {
  81            if (c <= std.math.maxInt(u16))
  82                return windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
  83            return c;
  84        }
  85
  86        pub fn hash(self: @This(), s: []const u8) u64 {
  87            _ = self;
  88            if (native_os == .windows) {
  89                var h = std.hash.Wyhash.init(0);
  90                var it = unicode.Wtf8View.initUnchecked(s).iterator();
  91                while (it.nextCodepoint()) |cp| {
  92                    const cp_upper = upcase(cp);
  93                    h.update(&[_]u8{
  94                        @as(u8, @intCast((cp_upper >> 16) & 0xff)),
  95                        @as(u8, @intCast((cp_upper >> 8) & 0xff)),
  96                        @as(u8, @intCast((cp_upper >> 0) & 0xff)),
  97                    });
  98                }
  99                return h.final();
 100            }
 101            return std.hash_map.hashString(s);
 102        }
 103
 104        pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
 105            _ = self;
 106            if (native_os == .windows) {
 107                var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
 108                var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
 109                while (true) {
 110                    const c_a = it_a.nextCodepoint() orelse break;
 111                    const c_b = it_b.nextCodepoint() orelse return false;
 112                    if (upcase(c_a) != upcase(c_b))
 113                        return false;
 114                }
 115                return if (it_b.nextCodepoint()) |_| false else true;
 116            }
 117            return std.hash_map.eqlString(a, b);
 118        }
 119    };
 120
 121    /// Create a EnvMap backed by a specific allocator.
 122    /// That allocator will be used for both backing allocations
 123    /// and string deduplication.
 124    pub fn init(allocator: Allocator) EnvMap {
 125        return EnvMap{ .hash_map = HashMap.init(allocator) };
 126    }
 127
 128    /// Free the backing storage of the map, as well as all
 129    /// of the stored keys and values.
 130    pub fn deinit(self: *EnvMap) void {
 131        var it = self.hash_map.iterator();
 132        while (it.next()) |entry| {
 133            self.free(entry.key_ptr.*);
 134            self.free(entry.value_ptr.*);
 135        }
 136
 137        self.hash_map.deinit();
 138    }
 139
 140    /// Same as `put` but the key and value become owned by the EnvMap rather
 141    /// than being copied.
 142    /// If `putMove` fails, the ownership of key and value does not transfer.
 143    /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
 144    pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
 145        assert(unicode.wtf8ValidateSlice(key));
 146        const get_or_put = try self.hash_map.getOrPut(key);
 147        if (get_or_put.found_existing) {
 148            self.free(get_or_put.key_ptr.*);
 149            self.free(get_or_put.value_ptr.*);
 150            get_or_put.key_ptr.* = key;
 151        }
 152        get_or_put.value_ptr.* = value;
 153    }
 154
 155    /// `key` and `value` are copied into the EnvMap.
 156    /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
 157    pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
 158        assert(unicode.wtf8ValidateSlice(key));
 159        const value_copy = try self.copy(value);
 160        errdefer self.free(value_copy);
 161        const get_or_put = try self.hash_map.getOrPut(key);
 162        if (get_or_put.found_existing) {
 163            self.free(get_or_put.value_ptr.*);
 164        } else {
 165            get_or_put.key_ptr.* = self.copy(key) catch |err| {
 166                _ = self.hash_map.remove(key);
 167                return err;
 168            };
 169        }
 170        get_or_put.value_ptr.* = value_copy;
 171    }
 172
 173    /// Find the address of the value associated with a key.
 174    /// The returned pointer is invalidated if the map resizes.
 175    /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
 176    pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
 177        assert(unicode.wtf8ValidateSlice(key));
 178        return self.hash_map.getPtr(key);
 179    }
 180
 181    /// Return the map's copy of the value associated with
 182    /// a key.  The returned string is invalidated if this
 183    /// key is removed from the map.
 184    /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
 185    pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
 186        assert(unicode.wtf8ValidateSlice(key));
 187        return self.hash_map.get(key);
 188    }
 189
 190    /// Removes the item from the map and frees its value.
 191    /// This invalidates the value returned by get() for this key.
 192    /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
 193    pub fn remove(self: *EnvMap, key: []const u8) void {
 194        assert(unicode.wtf8ValidateSlice(key));
 195        const kv = self.hash_map.fetchRemove(key) orelse return;
 196        self.free(kv.key);
 197        self.free(kv.value);
 198    }
 199
 200    /// Returns the number of KV pairs stored in the map.
 201    pub fn count(self: EnvMap) HashMap.Size {
 202        return self.hash_map.count();
 203    }
 204
 205    /// Returns an iterator over entries in the map.
 206    pub fn iterator(self: *const EnvMap) HashMap.Iterator {
 207        return self.hash_map.iterator();
 208    }
 209
 210    /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily
 211    /// the same allocator used to allocate `em`.
 212    pub fn clone(em: *const EnvMap, gpa: Allocator) Allocator.Error!EnvMap {
 213        var new: EnvMap = .init(gpa);
 214        errdefer new.deinit();
 215        // Since we need to dupe the keys and values, the only way for error handling to not be a
 216        // nightmare is to add keys to an empty map one-by-one. This could be avoided if this
 217        // abstraction were a bit less... OOP-esque.
 218        try new.hash_map.ensureUnusedCapacity(em.hash_map.count());
 219        var it = em.hash_map.iterator();
 220        while (it.next()) |entry| {
 221            try new.put(entry.key_ptr.*, entry.value_ptr.*);
 222        }
 223        return new;
 224    }
 225
 226    fn free(self: EnvMap, value: []const u8) void {
 227        self.hash_map.allocator.free(value);
 228    }
 229
 230    fn copy(self: EnvMap, value: []const u8) ![]u8 {
 231        return self.hash_map.allocator.dupe(u8, value);
 232    }
 233};
 234
 235test EnvMap {
 236    var env = EnvMap.init(testing.allocator);
 237    defer env.deinit();
 238
 239    try env.put("SOMETHING_NEW", "hello");
 240    try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?);
 241    try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
 242
 243    // overwrite
 244    try env.put("SOMETHING_NEW", "something");
 245    try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?);
 246    try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
 247
 248    // a new longer name to test the Windows-specific conversion buffer
 249    try env.put("SOMETHING_NEW_AND_LONGER", "1");
 250    try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?);
 251    try testing.expectEqual(@as(EnvMap.Size, 2), env.count());
 252
 253    // case insensitivity on Windows only
 254    if (native_os == .windows) {
 255        try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?);
 256    } else {
 257        try testing.expect(null == env.get("something_New_aNd_LONGER"));
 258    }
 259
 260    var it = env.iterator();
 261    var count: EnvMap.Size = 0;
 262    while (it.next()) |entry| {
 263        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.*);
 264        try testing.expect(is_an_expected_name);
 265        count += 1;
 266    }
 267    try testing.expectEqual(@as(EnvMap.Size, 2), count);
 268
 269    env.remove("SOMETHING_NEW");
 270    try testing.expect(env.get("SOMETHING_NEW") == null);
 271
 272    try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
 273
 274    if (native_os == .windows) {
 275        // test Unicode case-insensitivity on Windows
 276        try env.put("КИРиллИЦА", "something else");
 277        try testing.expectEqualStrings("something else", env.get("кириллица").?);
 278
 279        // and WTF-8 that's not valid UTF-8
 280        const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
 281            std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
 282        });
 283        defer testing.allocator.free(wtf8_with_surrogate_pair);
 284
 285        try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
 286        try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
 287    }
 288}
 289
 290pub const GetEnvMapError = error{
 291    OutOfMemory,
 292    /// WASI-only. `environ_sizes_get` or `environ_get`
 293    /// failed for an unexpected reason.
 294    Unexpected,
 295};
 296
 297/// Returns a snapshot of the environment variables of the current process.
 298/// Any modifications to the resulting EnvMap will not be reflected in the environment, and
 299/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
 300/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
 301pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
 302    var result = EnvMap.init(allocator);
 303    errdefer result.deinit();
 304
 305    if (native_os == .windows) {
 306        const ptr = windows.peb().ProcessParameters.Environment;
 307
 308        var i: usize = 0;
 309        while (ptr[i] != 0) {
 310            const key_start = i;
 311
 312            // There are some special environment variables that start with =,
 313            // so we need a special case to not treat = as a key/value separator
 314            // if it's the first character.
 315            // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
 316            if (ptr[key_start] == '=') i += 1;
 317
 318            while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
 319            const key_w = ptr[key_start..i];
 320            const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w);
 321            errdefer allocator.free(key);
 322
 323            if (ptr[i] == '=') i += 1;
 324
 325            const value_start = i;
 326            while (ptr[i] != 0) : (i += 1) {}
 327            const value_w = ptr[value_start..i];
 328            const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w);
 329            errdefer allocator.free(value);
 330
 331            i += 1; // skip over null byte
 332
 333            try result.putMove(key, value);
 334        }
 335        return result;
 336    } else if (native_os == .wasi and !builtin.link_libc) {
 337        var environ_count: usize = undefined;
 338        var environ_buf_size: usize = undefined;
 339
 340        const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
 341        if (environ_sizes_get_ret != .SUCCESS) {
 342            return posix.unexpectedErrno(environ_sizes_get_ret);
 343        }
 344
 345        if (environ_count == 0) {
 346            return result;
 347        }
 348
 349        const environ = try allocator.alloc([*:0]u8, environ_count);
 350        defer allocator.free(environ);
 351        const environ_buf = try allocator.alloc(u8, environ_buf_size);
 352        defer allocator.free(environ_buf);
 353
 354        const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr);
 355        if (environ_get_ret != .SUCCESS) {
 356            return posix.unexpectedErrno(environ_get_ret);
 357        }
 358
 359        for (environ) |env| {
 360            const pair = mem.sliceTo(env, 0);
 361            var parts = mem.splitScalar(u8, pair, '=');
 362            const key = parts.first();
 363            const value = parts.rest();
 364            try result.put(key, value);
 365        }
 366        return result;
 367    } else if (builtin.link_libc) {
 368        var ptr = std.c.environ;
 369        while (ptr[0]) |line| : (ptr += 1) {
 370            var line_i: usize = 0;
 371            while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
 372            const key = line[0..line_i];
 373
 374            var end_i: usize = line_i;
 375            while (line[end_i] != 0) : (end_i += 1) {}
 376            const value = line[line_i + 1 .. end_i];
 377
 378            try result.put(key, value);
 379        }
 380        return result;
 381    } else {
 382        for (std.os.environ) |line| {
 383            var line_i: usize = 0;
 384            while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
 385            const key = line[0..line_i];
 386
 387            var end_i: usize = line_i;
 388            while (line[end_i] != 0) : (end_i += 1) {}
 389            const value = line[line_i + 1 .. end_i];
 390
 391            try result.put(key, value);
 392        }
 393        return result;
 394    }
 395}
 396
 397test getEnvMap {
 398    var env = try getEnvMap(testing.allocator);
 399    defer env.deinit();
 400}
 401
 402pub const GetEnvVarOwnedError = error{
 403    OutOfMemory,
 404    EnvironmentVariableNotFound,
 405
 406    /// On Windows, environment variable keys provided by the user must be valid WTF-8.
 407    /// https://wtf-8.codeberg.page/
 408    InvalidWtf8,
 409};
 410
 411/// Caller must free returned memory.
 412/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
 413/// then `error.InvalidWtf8` is returned.
 414/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/).
 415/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
 416pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
 417    if (native_os == .windows) {
 418        const result_w = blk: {
 419            var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
 420            const stack_allocator = stack_alloc.get();
 421            const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
 422            defer stack_allocator.free(key_w);
 423
 424            break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
 425        };
 426        // wtf16LeToWtf8Alloc can only fail with OutOfMemory
 427        return unicode.wtf16LeToWtf8Alloc(allocator, result_w);
 428    } else if (native_os == .wasi and !builtin.link_libc) {
 429        var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
 430        defer envmap.deinit();
 431        const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound;
 432        return allocator.dupe(u8, val);
 433    } else {
 434        const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
 435        return allocator.dupe(u8, result);
 436    }
 437}
 438
 439/// On Windows, `key` must be valid WTF-8.
 440pub fn hasEnvVarConstant(comptime key: []const u8) bool {
 441    if (native_os == .windows) {
 442        const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
 443        return getenvW(key_w) != null;
 444    } else if (native_os == .wasi and !builtin.link_libc) {
 445        @compileError("hasEnvVarConstant is not supported for WASI without libc");
 446    } else {
 447        return posix.getenv(key) != null;
 448    }
 449}
 450
 451/// On Windows, `key` must be valid WTF-8.
 452pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
 453    if (native_os == .windows) {
 454        const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
 455        const value = getenvW(key_w) orelse return false;
 456        return value.len != 0;
 457    } else if (native_os == .wasi and !builtin.link_libc) {
 458        @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc");
 459    } else {
 460        const value = posix.getenv(key) orelse return false;
 461        return value.len != 0;
 462    }
 463}
 464
 465pub const ParseEnvVarIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound};
 466
 467/// Parses an environment variable as an integer.
 468///
 469/// Since the key is comptime-known, no allocation is needed.
 470///
 471/// On Windows, `key` must be valid WTF-8.
 472pub fn parseEnvVarInt(comptime key: []const u8, comptime I: type, base: u8) ParseEnvVarIntError!I {
 473    if (native_os == .windows) {
 474        const key_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(key);
 475        const text = getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
 476        return std.fmt.parseIntWithGenericCharacter(I, u16, text, base);
 477    } else if (native_os == .wasi and !builtin.link_libc) {
 478        @compileError("parseEnvVarInt is not supported for WASI without libc");
 479    } else {
 480        const text = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
 481        return std.fmt.parseInt(I, text, base);
 482    }
 483}
 484
 485pub const HasEnvVarError = error{
 486    OutOfMemory,
 487
 488    /// On Windows, environment variable keys provided by the user must be valid WTF-8.
 489    /// https://wtf-8.codeberg.page/
 490    InvalidWtf8,
 491};
 492
 493/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
 494/// then `error.InvalidWtf8` is returned.
 495pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
 496    if (native_os == .windows) {
 497        var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
 498        const stack_allocator = stack_alloc.get();
 499        const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
 500        defer stack_allocator.free(key_w);
 501        return getenvW(key_w) != null;
 502    } else if (native_os == .wasi and !builtin.link_libc) {
 503        var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
 504        defer envmap.deinit();
 505        return envmap.getPtr(key) != null;
 506    } else {
 507        return posix.getenv(key) != null;
 508    }
 509}
 510
 511/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
 512/// then `error.InvalidWtf8` is returned.
 513pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
 514    if (native_os == .windows) {
 515        var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
 516        const stack_allocator = stack_alloc.get();
 517        const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
 518        defer stack_allocator.free(key_w);
 519        const value = getenvW(key_w) orelse return false;
 520        return value.len != 0;
 521    } else if (native_os == .wasi and !builtin.link_libc) {
 522        var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
 523        defer envmap.deinit();
 524        const value = envmap.getPtr(key) orelse return false;
 525        return value.len != 0;
 526    } else {
 527        const value = posix.getenv(key) orelse return false;
 528        return value.len != 0;
 529    }
 530}
 531
 532/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
 533/// The returned slice points to memory in the PEB.
 534///
 535/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString.
 536///
 537/// See also:
 538/// * `std.posix.getenv`
 539/// * `getEnvMap`
 540/// * `getEnvVarOwned`
 541/// * `hasEnvVarConstant`
 542/// * `hasEnvVar`
 543pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
 544    if (native_os != .windows) {
 545        @compileError("Windows-only");
 546    }
 547    const key_slice = mem.sliceTo(key, 0);
 548    // '=' anywhere but the start makes this an invalid environment variable name
 549    if (key_slice.len > 0 and std.mem.indexOfScalar(u16, key_slice[1..], '=') != null) {
 550        return null;
 551    }
 552    const ptr = windows.peb().ProcessParameters.Environment;
 553    var i: usize = 0;
 554    while (ptr[i] != 0) {
 555        const key_value = mem.sliceTo(ptr[i..], 0);
 556
 557        // There are some special environment variables that start with =,
 558        // so we need a special case to not treat = as a key/value separator
 559        // if it's the first character.
 560        // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
 561        const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
 562        const equal_index = std.mem.indexOfScalarPos(u16, key_value, equal_search_start, '=') orelse {
 563            // This is enforced by CreateProcess.
 564            // If violated, CreateProcess will fail with INVALID_PARAMETER.
 565            unreachable; // must contain a =
 566        };
 567
 568        const this_key = key_value[0..equal_index];
 569        if (windows.eqlIgnoreCaseWtf16(key_slice, this_key)) {
 570            return key_value[equal_index + 1 ..];
 571        }
 572
 573        // skip past the NUL terminator
 574        i += key_value.len + 1;
 575    }
 576    return null;
 577}
 578
 579test getEnvVarOwned {
 580    try testing.expectError(
 581        error.EnvironmentVariableNotFound,
 582        getEnvVarOwned(std.testing.allocator, "BADENV"),
 583    );
 584}
 585
 586test hasEnvVarConstant {
 587    if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
 588
 589    try testing.expect(!hasEnvVarConstant("BADENV"));
 590}
 591
 592test hasEnvVar {
 593    const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
 594    try testing.expect(!has_env);
 595}
 596
 597pub const ArgIteratorPosix = struct {
 598    index: usize,
 599    count: usize,
 600
 601    pub const InitError = error{};
 602
 603    pub fn init() ArgIteratorPosix {
 604        return ArgIteratorPosix{
 605            .index = 0,
 606            .count = std.os.argv.len,
 607        };
 608    }
 609
 610    pub fn next(self: *ArgIteratorPosix) ?[:0]const u8 {
 611        if (self.index == self.count) return null;
 612
 613        const s = std.os.argv[self.index];
 614        self.index += 1;
 615        return mem.sliceTo(s, 0);
 616    }
 617
 618    pub fn skip(self: *ArgIteratorPosix) bool {
 619        if (self.index == self.count) return false;
 620
 621        self.index += 1;
 622        return true;
 623    }
 624};
 625
 626pub const ArgIteratorWasi = struct {
 627    allocator: Allocator,
 628    index: usize,
 629    args: [][:0]u8,
 630
 631    pub const InitError = error{OutOfMemory} || posix.UnexpectedError;
 632
 633    /// You must call deinit to free the internal buffer of the
 634    /// iterator after you are done.
 635    pub fn init(allocator: Allocator) InitError!ArgIteratorWasi {
 636        const fetched_args = try ArgIteratorWasi.internalInit(allocator);
 637        return ArgIteratorWasi{
 638            .allocator = allocator,
 639            .index = 0,
 640            .args = fetched_args,
 641        };
 642    }
 643
 644    fn internalInit(allocator: Allocator) InitError![][:0]u8 {
 645        var count: usize = undefined;
 646        var buf_size: usize = undefined;
 647
 648        switch (std.os.wasi.args_sizes_get(&count, &buf_size)) {
 649            .SUCCESS => {},
 650            else => |err| return posix.unexpectedErrno(err),
 651        }
 652
 653        if (count == 0) {
 654            return &[_][:0]u8{};
 655        }
 656
 657        const argv = try allocator.alloc([*:0]u8, count);
 658        defer allocator.free(argv);
 659
 660        const argv_buf = try allocator.alloc(u8, buf_size);
 661
 662        switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) {
 663            .SUCCESS => {},
 664            else => |err| return posix.unexpectedErrno(err),
 665        }
 666
 667        var result_args = try allocator.alloc([:0]u8, count);
 668        var i: usize = 0;
 669        while (i < count) : (i += 1) {
 670            result_args[i] = mem.sliceTo(argv[i], 0);
 671        }
 672
 673        return result_args;
 674    }
 675
 676    pub fn next(self: *ArgIteratorWasi) ?[:0]const u8 {
 677        if (self.index == self.args.len) return null;
 678
 679        const arg = self.args[self.index];
 680        self.index += 1;
 681        return arg;
 682    }
 683
 684    pub fn skip(self: *ArgIteratorWasi) bool {
 685        if (self.index == self.args.len) return false;
 686
 687        self.index += 1;
 688        return true;
 689    }
 690
 691    /// Call to free the internal buffer of the iterator.
 692    pub fn deinit(self: *ArgIteratorWasi) void {
 693        const last_item = self.args[self.args.len - 1];
 694        const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated
 695        const first_item_ptr = self.args[0].ptr;
 696        const len = last_byte_addr - @intFromPtr(first_item_ptr);
 697        self.allocator.free(first_item_ptr[0..len]);
 698        self.allocator.free(self.args);
 699    }
 700};
 701
 702/// Iterator that implements the Windows command-line parsing algorithm.
 703/// The implementation is intended to be compatible with the post-2008 C runtime,
 704/// but is *not* intended to be compatible with `CommandLineToArgvW` since
 705/// `CommandLineToArgvW` uses the pre-2008 parsing rules.
 706///
 707/// This iterator faithfully implements the parsing behavior observed from the C runtime with
 708/// one exception: if the command-line string is empty, the iterator will immediately complete
 709/// without returning any arguments (whereas the C runtime will return a single argument
 710/// representing the name of the current executable).
 711///
 712/// The essential parts of the algorithm are described in Microsoft's documentation:
 713///
 714/// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments
 715///
 716/// David Deley explains some additional undocumented quirks in great detail:
 717///
 718/// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES
 719pub const ArgIteratorWindows = struct {
 720    allocator: Allocator,
 721    /// Encoded as WTF-16 LE.
 722    cmd_line: []const u16,
 723    index: usize = 0,
 724    /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices
 725    /// of each argument encoded as WTF-8.
 726    buffer: []u8,
 727    start: usize = 0,
 728    end: usize = 0,
 729
 730    pub const InitError = error{OutOfMemory};
 731
 732    /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
 733    ///
 734    /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for
 735    /// at least as long as the returned ArgIteratorWindows.
 736    pub fn init(allocator: Allocator, cmd_line_w: []const u16) InitError!ArgIteratorWindows {
 737        const wtf8_len = unicode.calcWtf8Len(cmd_line_w);
 738
 739        // This buffer must be large enough to contain contiguous NUL-terminated slices
 740        // of each argument.
 741        // - During parsing, the length of a parsed argument will always be equal to
 742        //   to less than its unparsed length
 743        // - The first argument needs one extra byte of space allocated for its NUL
 744        //   terminator, but for each subsequent argument the necessary whitespace
 745        //   between arguments guarantees room for their NUL terminator(s).
 746        const buffer = try allocator.alloc(u8, wtf8_len + 1);
 747        errdefer allocator.free(buffer);
 748
 749        return .{
 750            .allocator = allocator,
 751            .cmd_line = cmd_line_w,
 752            .buffer = buffer,
 753        };
 754    }
 755
 756    /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
 757    /// command-line string. The iterator owns the returned slice.
 758    /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
 759    pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
 760        return self.nextWithStrategy(next_strategy);
 761    }
 762
 763    /// Skips the next argument and advances the iterator. Returns `true` if an argument was
 764    /// skipped, `false` if at the end of the command-line string.
 765    pub fn skip(self: *ArgIteratorWindows) bool {
 766        return self.nextWithStrategy(skip_strategy);
 767    }
 768
 769    const next_strategy = struct {
 770        const T = ?[:0]const u8;
 771
 772        const eof = null;
 773
 774        /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`.
 775        fn emitBackslashes(self: *ArgIteratorWindows, count: usize, last_emitted_code_unit: ?u16) ?u16 {
 776            for (0..count) |_| {
 777                self.buffer[self.end] = '\\';
 778                self.end += 1;
 779            }
 780            return if (count != 0) '\\' else last_emitted_code_unit;
 781        }
 782
 783        /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then
 784        /// the previously emitted high surrogate is overwritten by the codepoint encoded
 785        /// by the surrogate pair, and `null` is returned.
 786        /// Otherwise, `code_unit` is emitted and returned.
 787        fn emitCharacter(self: *ArgIteratorWindows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 {
 788            // Because we are emitting WTF-8, we need to
 789            // check to see if we've emitted two consecutive surrogate
 790            // codepoints that form a valid surrogate pair in order
 791            // to ensure that we're always emitting well-formed WTF-8
 792            // (https://wtf-8.codeberg.page/#concatenating).
 793            //
 794            // If we do have a valid surrogate pair, we need to emit
 795            // the UTF-8 sequence for the codepoint that they encode
 796            // instead of the WTF-8 encoding for the two surrogate pairs
 797            // separately.
 798            //
 799            // This is relevant when dealing with a WTF-16 encoded
 800            // command line like this:
 801            // "<0xD801>"<0xDC37>
 802            // which would get parsed and converted to WTF-8 as:
 803            // <0xED><0xA0><0x81><0xED><0xB0><0xB7>
 804            // but instead, we need to recognize the surrogate pair
 805            // and emit the codepoint it encodes, which in this
 806            // example is U+10437 (𐐷), which is encoded in UTF-8 as:
 807            // <0xF0><0x90><0x90><0xB7>
 808            if (last_emitted_code_unit != null and
 809                std.unicode.utf16IsLowSurrogate(code_unit) and
 810                std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?))
 811            {
 812                const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable;
 813
 814                // Unpaired surrogate is 3 bytes long
 815                const dest = self.buffer[self.end - 3 ..];
 816                const len = unicode.utf8Encode(codepoint, dest) catch unreachable;
 817                // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes
 818                assert(len == 4);
 819                self.end += 1;
 820                return null;
 821            }
 822
 823            const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable;
 824            self.end += wtf8_len;
 825            return code_unit;
 826        }
 827
 828        fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 {
 829            self.buffer[self.end] = 0;
 830            const arg = self.buffer[self.start..self.end :0];
 831            self.end += 1;
 832            self.start = self.end;
 833            return arg;
 834        }
 835    };
 836
 837    const skip_strategy = struct {
 838        const T = bool;
 839
 840        const eof = false;
 841
 842        fn emitBackslashes(_: *ArgIteratorWindows, _: usize, last_emitted_code_unit: ?u16) ?u16 {
 843            return last_emitted_code_unit;
 844        }
 845
 846        fn emitCharacter(_: *ArgIteratorWindows, _: u16, last_emitted_code_unit: ?u16) ?u16 {
 847            return last_emitted_code_unit;
 848        }
 849
 850        fn yieldArg(_: *ArgIteratorWindows) bool {
 851            return true;
 852        }
 853    };
 854
 855    fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T {
 856        var last_emitted_code_unit: ?u16 = null;
 857        // The first argument (the executable name) uses different parsing rules.
 858        if (self.index == 0) {
 859            if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) {
 860                // Immediately complete the iterator.
 861                // The C runtime would return the name of the current executable here.
 862                return strategy.eof;
 863            }
 864
 865            var inside_quotes = false;
 866            while (true) : (self.index += 1) {
 867                const char = if (self.index != self.cmd_line.len)
 868                    mem.littleToNative(u16, self.cmd_line[self.index])
 869                else
 870                    0;
 871                switch (char) {
 872                    0 => {
 873                        return strategy.yieldArg(self);
 874                    },
 875                    '"' => {
 876                        inside_quotes = !inside_quotes;
 877                    },
 878                    ' ', '\t' => {
 879                        if (inside_quotes) {
 880                            last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
 881                        } else {
 882                            self.index += 1;
 883                            return strategy.yieldArg(self);
 884                        }
 885                    },
 886                    else => {
 887                        last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
 888                    },
 889                }
 890            }
 891        }
 892
 893        // Skip spaces and tabs. The iterator completes if we reach the end of the string here.
 894        while (true) : (self.index += 1) {
 895            const char = if (self.index != self.cmd_line.len)
 896                mem.littleToNative(u16, self.cmd_line[self.index])
 897            else
 898                0;
 899            switch (char) {
 900                0 => return strategy.eof,
 901                ' ', '\t' => continue,
 902                else => break,
 903            }
 904        }
 905
 906        // Parsing rules for subsequent arguments:
 907        //
 908        // - The end of the string always terminates the current argument.
 909        // - When not in 'inside_quotes' mode, a space or tab terminates the current argument.
 910        // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero).
 911        //   If in 'inside_quotes' and the quote is immediately followed by a second quote,
 912        //   one quote is emitted and the other is skipped, otherwise, the quote is skipped
 913        //   and 'inside_quotes' is toggled.
 914        // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote.
 915        // - n backslashes not followed by a quote emit n backslashes.
 916        var backslash_count: usize = 0;
 917        var inside_quotes = false;
 918        while (true) : (self.index += 1) {
 919            const char = if (self.index != self.cmd_line.len)
 920                mem.littleToNative(u16, self.cmd_line[self.index])
 921            else
 922                0;
 923            switch (char) {
 924                0 => {
 925                    last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
 926                    return strategy.yieldArg(self);
 927                },
 928                ' ', '\t' => {
 929                    last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
 930                    backslash_count = 0;
 931                    if (inside_quotes) {
 932                        last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
 933                    } else return strategy.yieldArg(self);
 934                },
 935                '"' => {
 936                    const char_is_escaped_quote = backslash_count % 2 != 0;
 937                    last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit);
 938                    backslash_count = 0;
 939                    if (char_is_escaped_quote) {
 940                        last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
 941                    } else {
 942                        if (inside_quotes and
 943                            self.index + 1 != self.cmd_line.len and
 944                            mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"')
 945                        {
 946                            last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
 947                            self.index += 1;
 948                        } else {
 949                            inside_quotes = !inside_quotes;
 950                        }
 951                    }
 952                },
 953                '\\' => {
 954                    backslash_count += 1;
 955                },
 956                else => {
 957                    last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
 958                    backslash_count = 0;
 959                    last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
 960                },
 961            }
 962        }
 963    }
 964
 965    /// Frees the iterator's copy of the command-line string and all previously returned
 966    /// argument slices.
 967    pub fn deinit(self: *ArgIteratorWindows) void {
 968        self.allocator.free(self.buffer);
 969    }
 970};
 971
 972/// Optional parameters for `ArgIteratorGeneral`
 973pub const ArgIteratorGeneralOptions = struct {
 974    comments: bool = false,
 975    single_quotes: bool = false,
 976};
 977
 978/// A general Iterator to parse a string into a set of arguments
 979pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
 980    return struct {
 981        allocator: Allocator,
 982        index: usize = 0,
 983        cmd_line: []const u8,
 984
 985        /// Should the cmd_line field be free'd (using the allocator) on deinit()?
 986        free_cmd_line_on_deinit: bool,
 987
 988        /// buffer MUST be long enough to hold the cmd_line plus a null terminator.
 989        /// buffer will we free'd (using the allocator) on deinit()
 990        buffer: []u8,
 991        start: usize = 0,
 992        end: usize = 0,
 993
 994        pub const Self = @This();
 995
 996        pub const InitError = error{OutOfMemory};
 997
 998        /// cmd_line_utf8 MUST remain valid and constant while using this instance
 999        pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
1000            const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
1001            errdefer allocator.free(buffer);
1002
1003            return Self{
1004                .allocator = allocator,
1005                .cmd_line = cmd_line_utf8,
1006                .free_cmd_line_on_deinit = false,
1007                .buffer = buffer,
1008            };
1009        }
1010
1011        /// cmd_line_utf8 will be free'd (with the allocator) on deinit()
1012        pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
1013            const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
1014            errdefer allocator.free(buffer);
1015
1016            return Self{
1017                .allocator = allocator,
1018                .cmd_line = cmd_line_utf8,
1019                .free_cmd_line_on_deinit = true,
1020                .buffer = buffer,
1021            };
1022        }
1023
1024        // Skips over whitespace in the cmd_line.
1025        // Returns false if the terminating sentinel is reached, true otherwise.
1026        // Also skips over comments (if supported).
1027        fn skipWhitespace(self: *Self) bool {
1028            while (true) : (self.index += 1) {
1029                const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1030                switch (character) {
1031                    0 => return false,
1032                    ' ', '\t', '\r', '\n' => continue,
1033                    '#' => {
1034                        if (options.comments) {
1035                            while (true) : (self.index += 1) {
1036                                switch (self.cmd_line[self.index]) {
1037                                    '\n' => break,
1038                                    0 => return false,
1039                                    else => continue,
1040                                }
1041                            }
1042                            continue;
1043                        } else {
1044                            break;
1045                        }
1046                    },
1047                    else => break,
1048                }
1049            }
1050            return true;
1051        }
1052
1053        pub fn skip(self: *Self) bool {
1054            if (!self.skipWhitespace()) {
1055                return false;
1056            }
1057
1058            var backslash_count: usize = 0;
1059            var in_quote = false;
1060            while (true) : (self.index += 1) {
1061                const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1062                switch (character) {
1063                    0 => return true,
1064                    '"', '\'' => {
1065                        if (!options.single_quotes and character == '\'') {
1066                            backslash_count = 0;
1067                            continue;
1068                        }
1069                        const quote_is_real = backslash_count % 2 == 0;
1070                        if (quote_is_real) {
1071                            in_quote = !in_quote;
1072                        }
1073                    },
1074                    '\\' => {
1075                        backslash_count += 1;
1076                    },
1077                    ' ', '\t', '\r', '\n' => {
1078                        if (!in_quote) {
1079                            return true;
1080                        }
1081                        backslash_count = 0;
1082                    },
1083                    else => {
1084                        backslash_count = 0;
1085                        continue;
1086                    },
1087                }
1088            }
1089        }
1090
1091        /// Returns a slice of the internal buffer that contains the next argument.
1092        /// Returns null when it reaches the end.
1093        pub fn next(self: *Self) ?[:0]const u8 {
1094            if (!self.skipWhitespace()) {
1095                return null;
1096            }
1097
1098            var backslash_count: usize = 0;
1099            var in_quote = false;
1100            while (true) : (self.index += 1) {
1101                const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1102                switch (character) {
1103                    0 => {
1104                        self.emitBackslashes(backslash_count);
1105                        self.buffer[self.end] = 0;
1106                        const token = self.buffer[self.start..self.end :0];
1107                        self.end += 1;
1108                        self.start = self.end;
1109                        return token;
1110                    },
1111                    '"', '\'' => {
1112                        if (!options.single_quotes and character == '\'') {
1113                            self.emitBackslashes(backslash_count);
1114                            backslash_count = 0;
1115                            self.emitCharacter(character);
1116                            continue;
1117                        }
1118                        const quote_is_real = backslash_count % 2 == 0;
1119                        self.emitBackslashes(backslash_count / 2);
1120                        backslash_count = 0;
1121
1122                        if (quote_is_real) {
1123                            in_quote = !in_quote;
1124                        } else {
1125                            self.emitCharacter('"');
1126                        }
1127                    },
1128                    '\\' => {
1129                        backslash_count += 1;
1130                    },
1131                    ' ', '\t', '\r', '\n' => {
1132                        self.emitBackslashes(backslash_count);
1133                        backslash_count = 0;
1134                        if (in_quote) {
1135                            self.emitCharacter(character);
1136                        } else {
1137                            self.buffer[self.end] = 0;
1138                            const token = self.buffer[self.start..self.end :0];
1139                            self.end += 1;
1140                            self.start = self.end;
1141                            return token;
1142                        }
1143                    },
1144                    else => {
1145                        self.emitBackslashes(backslash_count);
1146                        backslash_count = 0;
1147                        self.emitCharacter(character);
1148                    },
1149                }
1150            }
1151        }
1152
1153        fn emitBackslashes(self: *Self, emit_count: usize) void {
1154            var i: usize = 0;
1155            while (i < emit_count) : (i += 1) {
1156                self.emitCharacter('\\');
1157            }
1158        }
1159
1160        fn emitCharacter(self: *Self, char: u8) void {
1161            self.buffer[self.end] = char;
1162            self.end += 1;
1163        }
1164
1165        /// Call to free the internal buffer of the iterator.
1166        pub fn deinit(self: *Self) void {
1167            self.allocator.free(self.buffer);
1168
1169            if (self.free_cmd_line_on_deinit) {
1170                self.allocator.free(self.cmd_line);
1171            }
1172        }
1173    };
1174}
1175
1176/// Cross-platform command line argument iterator.
1177pub const ArgIterator = struct {
1178    const InnerType = switch (native_os) {
1179        .windows => ArgIteratorWindows,
1180        .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi,
1181        else => ArgIteratorPosix,
1182    };
1183
1184    inner: InnerType,
1185
1186    /// Initialize the args iterator. Consider using initWithAllocator() instead
1187    /// for cross-platform compatibility.
1188    pub fn init() ArgIterator {
1189        if (native_os == .wasi) {
1190            @compileError("In WASI, use initWithAllocator instead.");
1191        }
1192        if (native_os == .windows) {
1193            @compileError("In Windows, use initWithAllocator instead.");
1194        }
1195
1196        return ArgIterator{ .inner = InnerType.init() };
1197    }
1198
1199    pub const InitError = InnerType.InitError;
1200
1201    /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
1202    pub fn initWithAllocator(allocator: Allocator) InitError!ArgIterator {
1203        if (native_os == .wasi and !builtin.link_libc) {
1204            return ArgIterator{ .inner = try InnerType.init(allocator) };
1205        }
1206        if (native_os == .windows) {
1207            const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
1208            const cmd_line_w = cmd_line.Buffer.?[0 .. cmd_line.Length / 2];
1209            return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) };
1210        }
1211
1212        return ArgIterator{ .inner = InnerType.init() };
1213    }
1214
1215    /// Get the next argument. Returns 'null' if we are at the end.
1216    /// Returned slice is pointing to the iterator's internal buffer.
1217    /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
1218    /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1219    pub fn next(self: *ArgIterator) ?([:0]const u8) {
1220        return self.inner.next();
1221    }
1222
1223    /// Parse past 1 argument without capturing it.
1224    /// Returns `true` if skipped an arg, `false` if we are at the end.
1225    pub fn skip(self: *ArgIterator) bool {
1226        return self.inner.skip();
1227    }
1228
1229    /// Call this to free the iterator's internal buffer if the iterator
1230    /// was created with `initWithAllocator` function.
1231    pub fn deinit(self: *ArgIterator) void {
1232        // Unless we're targeting WASI or Windows, this is a no-op.
1233        if (native_os == .wasi and !builtin.link_libc) {
1234            self.inner.deinit();
1235        }
1236
1237        if (native_os == .windows) {
1238            self.inner.deinit();
1239        }
1240    }
1241};
1242
1243/// Holds the command-line arguments, with the program name as the first entry.
1244/// Use argsWithAllocator() for cross-platform code.
1245pub fn args() ArgIterator {
1246    return ArgIterator.init();
1247}
1248
1249/// You must deinitialize iterator's internal buffers by calling `deinit` when done.
1250pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator {
1251    return ArgIterator.initWithAllocator(allocator);
1252}
1253
1254/// Caller must call argsFree on result.
1255/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
1256/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1257pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
1258    // TODO refactor to only make 1 allocation.
1259    var it = try argsWithAllocator(allocator);
1260    defer it.deinit();
1261
1262    var contents = std.array_list.Managed(u8).init(allocator);
1263    defer contents.deinit();
1264
1265    var slice_list = std.array_list.Managed(usize).init(allocator);
1266    defer slice_list.deinit();
1267
1268    while (it.next()) |arg| {
1269        try contents.appendSlice(arg[0 .. arg.len + 1]);
1270        try slice_list.append(arg.len);
1271    }
1272
1273    const contents_slice = contents.items;
1274    const slice_sizes = slice_list.items;
1275    const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len);
1276    const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len);
1277    const buf = try allocator.alignedAlloc(u8, .of([]u8), total_bytes);
1278    errdefer allocator.free(buf);
1279
1280    const result_slice_list = mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]);
1281    const result_contents = buf[slice_list_bytes..];
1282    @memcpy(result_contents[0..contents_slice.len], contents_slice);
1283
1284    var contents_index: usize = 0;
1285    for (slice_sizes, 0..) |len, i| {
1286        const new_index = contents_index + len;
1287        result_slice_list[i] = result_contents[contents_index..new_index :0];
1288        contents_index = new_index + 1;
1289    }
1290
1291    return result_slice_list;
1292}
1293
1294pub fn argsFree(allocator: Allocator, args_alloc: []const [:0]u8) void {
1295    var total_bytes: usize = 0;
1296    for (args_alloc) |arg| {
1297        total_bytes += @sizeOf([]u8) + arg.len + 1;
1298    }
1299    const unaligned_allocated_buf = @as([*]const u8, @ptrCast(args_alloc.ptr))[0..total_bytes];
1300    const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf);
1301    return allocator.free(aligned_allocated_buf);
1302}
1303
1304test ArgIteratorWindows {
1305    const t = testArgIteratorWindows;
1306
1307    try t(
1308        \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 --eval="new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")"
1309    , &.{
1310        \\C:\Program Files\zig\zig.exe
1311        ,
1312        \\run
1313        ,
1314        \\.\src\main.zig
1315        ,
1316        \\-target
1317        ,
1318        \\x86_64-windows-gnu
1319        ,
1320        \\-O
1321        ,
1322        \\ReleaseSafe
1323        ,
1324        \\--
1325        ,
1326        \\--emoji=🗿
1327        ,
1328        \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
1329        ,
1330    });
1331
1332    // Empty
1333    try t("", &.{});
1334
1335    // Separators
1336    try t("aa bb cc", &.{ "aa", "bb", "cc" });
1337    try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" });
1338    try t("aa\nbb\ncc", &.{"aa\nbb\ncc"});
1339    try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"});
1340    try t("aa\rbb\rcc", &.{"aa\rbb\rcc"});
1341    try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"});
1342    try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"});
1343    try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"});
1344
1345    // Leading/trailing whitespace
1346    try t("  ", &.{""});
1347    try t("  aa  bb  ", &.{ "", "aa", "bb" });
1348    try t("\t\t", &.{""});
1349    try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" });
1350    try t("\n\n", &.{"\n\n"});
1351    try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"});
1352
1353    // Executable name with quotes/backslashes
1354    try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"});
1355    try t("\"", &.{""});
1356    try t("\"\"", &.{""});
1357    try t("\"\"\"", &.{""});
1358    try t("\"\"\"\"", &.{""});
1359    try t("\"\"\"\"\"", &.{""});
1360    try t("aa\"bb\"cc\"dd", &.{"aabbccdd"});
1361    try t("aa\"bb cc\"dd", &.{"aabb ccdd"});
1362    try t("\"aa\\\"bb\"", &.{"aa\\bb"});
1363    try t("\"aa\\\\\"", &.{"aa\\\\"});
1364    try t("aa\\\"bb", &.{"aa\\bb"});
1365    try t("aa\\\\\"bb", &.{"aa\\\\bb"});
1366
1367    // Arguments with quotes/backslashes
1368    try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" });
1369    try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" });
1370    try t(". ", &.{"."});
1371    try t(". \"", &.{ ".", "" });
1372    try t(". \"\"", &.{ ".", "" });
1373    try t(". \"\"\"", &.{ ".", "\"" });
1374    try t(". \"\"\"\"", &.{ ".", "\"" });
1375    try t(". \"\"\"\"\"", &.{ ".", "\"\"" });
1376    try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" });
1377    try t(". \" \"", &.{ ".", " " });
1378    try t(". \" \"\"", &.{ ".", " \"" });
1379    try t(". \" \"\"\"", &.{ ".", " \"" });
1380    try t(". \" \"\"\"\"", &.{ ".", " \"\"" });
1381    try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" });
1382    try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" });
1383    try t(". \\\"", &.{ ".", "\"" });
1384    try t(". \\\"\"", &.{ ".", "\"" });
1385    try t(". \\\"\"\"", &.{ ".", "\"" });
1386    try t(". \\\"\"\"\"", &.{ ".", "\"\"" });
1387    try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" });
1388    try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" });
1389    try t(". \" \\\"", &.{ ".", " \"" });
1390    try t(". \" \\\"\"", &.{ ".", " \"" });
1391    try t(". \" \\\"\"\"", &.{ ".", " \"\"" });
1392    try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" });
1393    try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" });
1394    try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" });
1395    try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" });
1396    try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" });
1397    try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" });
1398
1399    // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
1400    try t(
1401        \\foo.exe "abc" d e
1402    , &.{ "foo.exe", "abc", "d", "e" });
1403    try t(
1404        \\foo.exe a\\b d"e f"g h
1405    , &.{ "foo.exe", "a\\\\b", "de fg", "h" });
1406    try t(
1407        \\foo.exe a\\\"b c d
1408    , &.{ "foo.exe", "a\\\"b", "c", "d" });
1409    try t(
1410        \\foo.exe a\\\\"b c" d e
1411    , &.{ "foo.exe", "a\\\\b c", "d", "e" });
1412    try t(
1413        \\foo.exe a"b"" c d
1414    , &.{ "foo.exe", "ab\" c d" });
1415
1416    // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX
1417    try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" });
1418    try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" });
1419    try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" });
1420    try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" });
1421    try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" });
1422    try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" });
1423    try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" });
1424    try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" });
1425    try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" });
1426
1427    // Surrogate pair encoding of 𐐷 separated by quotes.
1428    // Encoded as WTF-16:
1429    // "<0xD801>"<0xDC37>
1430    // Encoded as WTF-8:
1431    // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7>
1432    // During parsing, the quotes drop out and the surrogate pair
1433    // should end up encoded as its normal UTF-8 representation.
1434    try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" });
1435}
1436
1437fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
1438    const cmd_line_w = try unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
1439    defer testing.allocator.free(cmd_line_w);
1440
1441    // next
1442    {
1443        var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w);
1444        defer it.deinit();
1445
1446        for (expected_args) |expected| {
1447            if (it.next()) |actual| {
1448                try testing.expectEqualStrings(expected, actual);
1449            } else {
1450                return error.TestUnexpectedResult;
1451            }
1452        }
1453        try testing.expect(it.next() == null);
1454    }
1455
1456    // skip
1457    {
1458        var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w);
1459        defer it.deinit();
1460
1461        for (0..expected_args.len) |_| {
1462            try testing.expect(it.skip());
1463        }
1464        try testing.expect(!it.skip());
1465    }
1466}
1467
1468test "general arg parsing" {
1469    try testGeneralCmdLine("a   b\tc d", &.{ "a", "b", "c", "d" });
1470    try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" });
1471    try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" });
1472    try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" });
1473    try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" });
1474    try testGeneralCmdLine("a   b\tc \"d f", &.{ "a", "b", "c", "d f" });
1475    try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" });
1476    try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" });
1477
1478    try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{
1479        ".\\..\\zig-cache\\build",
1480        "bin\\zig.exe",
1481        ".\\..",
1482        ".\\..\\zig-cache",
1483        "--help",
1484    });
1485
1486    try testGeneralCmdLine(
1487        \\ 'foo' "bar"
1488    , &.{ "'foo'", "bar" });
1489}
1490
1491fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
1492    var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line);
1493    defer it.deinit();
1494    for (expected_args) |expected_arg| {
1495        const arg = it.next().?;
1496        try testing.expectEqualStrings(expected_arg, arg);
1497    }
1498    try testing.expect(it.next() == null);
1499}
1500
1501test "response file arg parsing" {
1502    try testResponseFileCmdLine(
1503        \\a b
1504        \\c d\
1505    , &.{ "a", "b", "c", "d\\" });
1506    try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" });
1507
1508    try testResponseFileCmdLine(
1509        \\j
1510        \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\"
1511        \\ "m" #another comment
1512        \\
1513    , &.{ "j", "k", "l", "m" });
1514
1515    try testResponseFileCmdLine(
1516        \\ "" q ""
1517        \\ "r s # t" "u\" v" #another comment
1518        \\
1519    , &.{ "", "q", "", "r s # t", "u\" v" });
1520
1521    try testResponseFileCmdLine(
1522        \\ -l"advapi32" a# b#c d#
1523        \\e\\\
1524    , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" });
1525
1526    try testResponseFileCmdLine(
1527        \\ 'foo' "bar"
1528    , &.{ "foo", "bar" });
1529}
1530
1531fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
1532    var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true })
1533        .init(std.testing.allocator, input_cmd_line);
1534    defer it.deinit();
1535    for (expected_args) |expected_arg| {
1536        const arg = it.next().?;
1537        try testing.expectEqualStrings(expected_arg, arg);
1538    }
1539    try testing.expect(it.next() == null);
1540}
1541
1542pub const UserInfo = struct {
1543    uid: posix.uid_t,
1544    gid: posix.gid_t,
1545};
1546
1547/// POSIX function which gets a uid from username.
1548pub fn getUserInfo(name: []const u8) !UserInfo {
1549    return switch (native_os) {
1550        .linux,
1551        .driverkit,
1552        .ios,
1553        .maccatalyst,
1554        .macos,
1555        .tvos,
1556        .visionos,
1557        .watchos,
1558        .freebsd,
1559        .netbsd,
1560        .openbsd,
1561        .haiku,
1562        .illumos,
1563        .serenity,
1564        => posixGetUserInfo(name),
1565        else => @compileError("Unsupported OS"),
1566    };
1567}
1568
1569/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
1570/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
1571pub fn posixGetUserInfo(name: []const u8) !UserInfo {
1572    const file = try std.fs.openFileAbsolute("/etc/passwd", .{});
1573    defer file.close();
1574    var buffer: [4096]u8 = undefined;
1575    var file_reader = file.reader(&buffer);
1576    return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) {
1577        error.ReadFailed => return file_reader.err.?,
1578        error.EndOfStream => return error.UserNotFound,
1579        error.CorruptPasswordFile => return error.CorruptPasswordFile,
1580    };
1581}
1582
1583fn posixGetUserInfoPasswdStream(name: []const u8, reader: *std.Io.Reader) !UserInfo {
1584    const State = enum {
1585        start,
1586        wait_for_next_line,
1587        skip_password,
1588        read_user_id,
1589        read_group_id,
1590    };
1591
1592    var name_index: usize = 0;
1593    var uid: posix.uid_t = 0;
1594    var gid: posix.gid_t = 0;
1595
1596    sw: switch (State.start) {
1597        .start => switch (try reader.takeByte()) {
1598            ':' => {
1599                if (name_index == name.len) {
1600                    continue :sw .skip_password;
1601                } else {
1602                    continue :sw .wait_for_next_line;
1603                }
1604            },
1605            '\n' => return error.CorruptPasswordFile,
1606            else => |byte| {
1607                if (name_index == name.len or name[name_index] != byte) {
1608                    continue :sw .wait_for_next_line;
1609                }
1610                name_index += 1;
1611                continue :sw .start;
1612            },
1613        },
1614        .wait_for_next_line => switch (try reader.takeByte()) {
1615            '\n' => {
1616                name_index = 0;
1617                continue :sw .start;
1618            },
1619            else => continue :sw .wait_for_next_line,
1620        },
1621        .skip_password => switch (try reader.takeByte()) {
1622            '\n' => return error.CorruptPasswordFile,
1623            ':' => {
1624                continue :sw .read_user_id;
1625            },
1626            else => continue :sw .skip_password,
1627        },
1628        .read_user_id => switch (try reader.takeByte()) {
1629            ':' => {
1630                continue :sw .read_group_id;
1631            },
1632            '\n' => return error.CorruptPasswordFile,
1633            else => |byte| {
1634                const digit = switch (byte) {
1635                    '0'...'9' => byte - '0',
1636                    else => return error.CorruptPasswordFile,
1637                };
1638                {
1639                    const ov = @mulWithOverflow(uid, 10);
1640                    if (ov[1] != 0) return error.CorruptPasswordFile;
1641                    uid = ov[0];
1642                }
1643                {
1644                    const ov = @addWithOverflow(uid, digit);
1645                    if (ov[1] != 0) return error.CorruptPasswordFile;
1646                    uid = ov[0];
1647                }
1648                continue :sw .read_user_id;
1649            },
1650        },
1651        .read_group_id => switch (try reader.takeByte()) {
1652            '\n', ':' => return .{
1653                .uid = uid,
1654                .gid = gid,
1655            },
1656            else => |byte| {
1657                const digit = switch (byte) {
1658                    '0'...'9' => byte - '0',
1659                    else => return error.CorruptPasswordFile,
1660                };
1661                {
1662                    const ov = @mulWithOverflow(gid, 10);
1663                    if (ov[1] != 0) return error.CorruptPasswordFile;
1664                    gid = ov[0];
1665                }
1666                {
1667                    const ov = @addWithOverflow(gid, digit);
1668                    if (ov[1] != 0) return error.CorruptPasswordFile;
1669                    gid = ov[0];
1670                }
1671                continue :sw .read_group_id;
1672            },
1673        },
1674    }
1675    comptime unreachable;
1676}
1677
1678pub fn getBaseAddress() usize {
1679    switch (native_os) {
1680        .linux => {
1681            const phdrs = std.posix.getSelfPhdrs();
1682            var base: usize = 0;
1683            for (phdrs) |phdr| switch (phdr.type) {
1684                .LOAD => return base + phdr.vaddr,
1685                .PHDR => base = @intFromPtr(phdrs.ptr) - phdr.vaddr,
1686                else => {},
1687            } else unreachable;
1688        },
1689        .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1690            return @intFromPtr(&std.c._mh_execute_header);
1691        },
1692        .windows => return @intFromPtr(windows.kernel32.GetModuleHandleW(null)),
1693        else => @compileError("Unsupported OS"),
1694    }
1695}
1696
1697/// Tells whether calling the `execv` or `execve` functions will be a compile error.
1698pub const can_execv = switch (native_os) {
1699    .windows, .haiku, .wasi => false,
1700    else => true,
1701};
1702
1703/// Tells whether spawning child processes is supported (e.g. via Child)
1704pub const can_spawn = switch (native_os) {
1705    .wasi, .ios, .tvos, .visionos, .watchos => false,
1706    else => true,
1707};
1708
1709pub const ExecvError = std.posix.ExecveError || error{OutOfMemory};
1710
1711/// Replaces the current process image with the executed process.
1712/// This function must allocate memory to add a null terminating bytes on path and each arg.
1713/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
1714/// pointers after the args and after the environment variables.
1715/// `argv[0]` is the executable path.
1716/// This function also uses the PATH environment variable to get the full path to the executable.
1717/// Due to the heap-allocation, it is illegal to call this function in a fork() child.
1718/// For that use case, use the `std.posix` functions directly.
1719pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError {
1720    return execve(allocator, argv, null);
1721}
1722
1723/// Replaces the current process image with the executed process.
1724/// This function must allocate memory to add a null terminating bytes on path and each arg.
1725/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
1726/// pointers after the args and after the environment variables.
1727/// `argv[0]` is the executable path.
1728/// This function also uses the PATH environment variable to get the full path to the executable.
1729/// Due to the heap-allocation, it is illegal to call this function in a fork() child.
1730/// For that use case, use the `std.posix` functions directly.
1731pub fn execve(
1732    allocator: Allocator,
1733    argv: []const []const u8,
1734    env_map: ?*const EnvMap,
1735) ExecvError {
1736    if (!can_execv) @compileError("The target OS does not support execv");
1737
1738    var arena_allocator = std.heap.ArenaAllocator.init(allocator);
1739    defer arena_allocator.deinit();
1740    const arena = arena_allocator.allocator();
1741
1742    const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null);
1743    for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1744
1745    const envp = m: {
1746        if (env_map) |m| {
1747            const envp_buf = try createNullDelimitedEnvMap(arena, m);
1748            break :m envp_buf.ptr;
1749        } else if (builtin.link_libc) {
1750            break :m std.c.environ;
1751        } else if (builtin.output_mode == .Exe) {
1752            // Then we have Zig start code and this works.
1753            // TODO type-safety for null-termination of `os.environ`.
1754            break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));
1755        } else {
1756            // TODO come up with a solution for this.
1757            @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process");
1758        }
1759    };
1760
1761    return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);
1762}
1763
1764pub const TotalSystemMemoryError = error{
1765    UnknownTotalSystemMemory,
1766};
1767
1768/// Returns the total system memory, in bytes as a u64.
1769/// We return a u64 instead of usize due to PAE on ARM
1770/// and Linux's /proc/meminfo reporting more memory when
1771/// using QEMU user mode emulation.
1772pub fn totalSystemMemory() TotalSystemMemoryError!u64 {
1773    switch (native_os) {
1774        .linux => {
1775            var info: std.os.linux.Sysinfo = undefined;
1776            const result: usize = std.os.linux.sysinfo(&info);
1777            if (std.os.linux.errno(result) != .SUCCESS) {
1778                return error.UnknownTotalSystemMemory;
1779            }
1780            // Promote to u64 to avoid overflow on systems where info.totalram is a 32-bit usize
1781            return @as(u64, info.totalram) * info.mem_unit;
1782        },
1783        .freebsd => {
1784            var physmem: c_ulong = undefined;
1785            var len: usize = @sizeOf(c_ulong);
1786            posix.sysctlbynameZ("hw.physmem", &physmem, &len, null, 0) catch |err| switch (err) {
1787                error.UnknownName => unreachable,
1788                else => return error.UnknownTotalSystemMemory,
1789            };
1790            return @as(u64, @intCast(physmem));
1791        },
1792        // whole Darwin family
1793        .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1794            // "hw.memsize" returns uint64_t
1795            var physmem: u64 = undefined;
1796            var len: usize = @sizeOf(u64);
1797            posix.sysctlbynameZ("hw.memsize", &physmem, &len, null, 0) catch |err| switch (err) {
1798                error.PermissionDenied => unreachable, // only when setting values,
1799                error.SystemResources => unreachable, // memory already on the stack
1800                error.UnknownName => unreachable, // constant, known good value
1801                else => return error.UnknownTotalSystemMemory,
1802            };
1803            return physmem;
1804        },
1805        .openbsd => {
1806            const mib: [2]c_int = [_]c_int{
1807                posix.CTL.HW,
1808                posix.HW.PHYSMEM64,
1809            };
1810            var physmem: i64 = undefined;
1811            var len: usize = @sizeOf(@TypeOf(physmem));
1812            posix.sysctl(&mib, &physmem, &len, null, 0) catch |err| switch (err) {
1813                error.NameTooLong => unreachable, // constant, known good value
1814                error.PermissionDenied => unreachable, // only when setting values,
1815                error.SystemResources => unreachable, // memory already on the stack
1816                error.UnknownName => unreachable, // constant, known good value
1817                else => return error.UnknownTotalSystemMemory,
1818            };
1819            assert(physmem >= 0);
1820            return @as(u64, @bitCast(physmem));
1821        },
1822        .windows => {
1823            var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined;
1824            const rc = windows.ntdll.NtQuerySystemInformation(
1825                .SystemBasicInformation,
1826                &sbi,
1827                @sizeOf(windows.SYSTEM_BASIC_INFORMATION),
1828                null,
1829            );
1830            if (rc != .SUCCESS) {
1831                return error.UnknownTotalSystemMemory;
1832            }
1833            return @as(u64, sbi.NumberOfPhysicalPages) * sbi.PageSize;
1834        },
1835        else => return error.UnknownTotalSystemMemory,
1836    }
1837}
1838
1839/// Indicate that we are now terminating with a successful exit code.
1840/// In debug builds, this is a no-op, so that the calling code's
1841/// cleanup mechanisms are tested and so that external tools that
1842/// check for resource leaks can be accurate. In release builds, this
1843/// calls exit(0), and does not return.
1844pub fn cleanExit() void {
1845    if (builtin.mode == .Debug) {
1846        return;
1847    } else {
1848        std.debug.lockStdErr();
1849        exit(0);
1850    }
1851}
1852
1853/// Raise the open file descriptor limit.
1854///
1855/// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded
1856/// errors. On other systems, this does nothing.
1857pub fn raiseFileDescriptorLimit() void {
1858    const have_rlimit = posix.rlimit_resource != void;
1859    if (!have_rlimit) return;
1860
1861    var lim = posix.getrlimit(.NOFILE) catch return; // Oh well; we tried.
1862    if (native_os.isDarwin()) {
1863        // On Darwin, `NOFILE` is bounded by a hardcoded value `OPEN_MAX`.
1864        // According to the man pages for setrlimit():
1865        //   setrlimit() now returns with errno set to EINVAL in places that historically succeeded.
1866        //   It no longer accepts "rlim_cur = RLIM.INFINITY" for RLIM.NOFILE.
1867        //   Use "rlim_cur = min(OPEN_MAX, rlim_max)".
1868        lim.max = @min(std.c.OPEN_MAX, lim.max);
1869    }
1870    if (lim.cur == lim.max) return;
1871
1872    // Do a binary search for the limit.
1873    var min: posix.rlim_t = lim.cur;
1874    var max: posix.rlim_t = 1 << 20;
1875    // But if there's a defined upper bound, don't search, just set it.
1876    if (lim.max != posix.RLIM.INFINITY) {
1877        min = lim.max;
1878        max = lim.max;
1879    }
1880
1881    while (true) {
1882        lim.cur = min + @divTrunc(max - min, 2); // on freebsd rlim_t is signed
1883        if (posix.setrlimit(.NOFILE, lim)) |_| {
1884            min = lim.cur;
1885        } else |_| {
1886            max = lim.cur;
1887        }
1888        if (min + 1 >= max) break;
1889    }
1890}
1891
1892test raiseFileDescriptorLimit {
1893    raiseFileDescriptorLimit();
1894}
1895
1896pub const CreateEnvironOptions = struct {
1897    /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
1898    /// If non-null, negative means to remove the environment variable, and >= 0
1899    /// means to provide it with the given integer.
1900    zig_progress_fd: ?i32 = null,
1901};
1902
1903/// Creates a null-delimited environment variable block in the format
1904/// expected by POSIX, from a hash map plus options.
1905pub fn createEnvironFromMap(
1906    arena: Allocator,
1907    map: *const EnvMap,
1908    options: CreateEnvironOptions,
1909) Allocator.Error![:null]?[*:0]u8 {
1910    const ZigProgressAction = enum { nothing, edit, delete, add };
1911    const zig_progress_action: ZigProgressAction = a: {
1912        const fd = options.zig_progress_fd orelse break :a .nothing;
1913        const contains = map.get("ZIG_PROGRESS") != null;
1914        if (fd >= 0) {
1915            break :a if (contains) .edit else .add;
1916        } else {
1917            if (contains) break :a .delete;
1918        }
1919        break :a .nothing;
1920    };
1921
1922    const envp_count: usize = c: {
1923        var count: usize = map.count();
1924        switch (zig_progress_action) {
1925            .add => count += 1,
1926            .delete => count -= 1,
1927            .nothing, .edit => {},
1928        }
1929        break :c count;
1930    };
1931
1932    const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1933    var i: usize = 0;
1934
1935    if (zig_progress_action == .add) {
1936        envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
1937        i += 1;
1938    }
1939
1940    {
1941        var it = map.iterator();
1942        while (it.next()) |pair| {
1943            if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
1944                .add => unreachable,
1945                .delete => continue,
1946                .edit => {
1947                    envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
1948                        pair.key_ptr.*, options.zig_progress_fd.?,
1949                    }, 0);
1950                    i += 1;
1951                    continue;
1952                },
1953                .nothing => {},
1954            };
1955
1956            envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
1957            i += 1;
1958        }
1959    }
1960
1961    assert(i == envp_count);
1962    return envp_buf;
1963}
1964
1965/// Creates a null-delimited environment variable block in the format
1966/// expected by POSIX, from a hash map plus options.
1967pub fn createEnvironFromExisting(
1968    arena: Allocator,
1969    existing: [*:null]const ?[*:0]const u8,
1970    options: CreateEnvironOptions,
1971) Allocator.Error![:null]?[*:0]u8 {
1972    const existing_count, const contains_zig_progress = c: {
1973        var count: usize = 0;
1974        var contains = false;
1975        while (existing[count]) |line| : (count += 1) {
1976            contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS");
1977        }
1978        break :c .{ count, contains };
1979    };
1980    const ZigProgressAction = enum { nothing, edit, delete, add };
1981    const zig_progress_action: ZigProgressAction = a: {
1982        const fd = options.zig_progress_fd orelse break :a .nothing;
1983        if (fd >= 0) {
1984            break :a if (contains_zig_progress) .edit else .add;
1985        } else {
1986            if (contains_zig_progress) break :a .delete;
1987        }
1988        break :a .nothing;
1989    };
1990
1991    const envp_count: usize = c: {
1992        var count: usize = existing_count;
1993        switch (zig_progress_action) {
1994            .add => count += 1,
1995            .delete => count -= 1,
1996            .nothing, .edit => {},
1997        }
1998        break :c count;
1999    };
2000
2001    const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
2002    var i: usize = 0;
2003    var existing_index: usize = 0;
2004
2005    if (zig_progress_action == .add) {
2006        envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
2007        i += 1;
2008    }
2009
2010    while (existing[existing_index]) |line| : (existing_index += 1) {
2011        if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
2012            .add => unreachable,
2013            .delete => continue,
2014            .edit => {
2015                envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
2016                i += 1;
2017                continue;
2018            },
2019            .nothing => {},
2020        };
2021        envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
2022        i += 1;
2023    }
2024
2025    assert(i == envp_count);
2026    return envp_buf;
2027}
2028
2029pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 {
2030    return createEnvironFromMap(arena, env_map, .{});
2031}
2032
2033test createNullDelimitedEnvMap {
2034    const allocator = testing.allocator;
2035    var envmap = EnvMap.init(allocator);
2036    defer envmap.deinit();
2037
2038    try envmap.put("HOME", "/home/ifreund");
2039    try envmap.put("WAYLAND_DISPLAY", "wayland-1");
2040    try envmap.put("DISPLAY", ":1");
2041    try envmap.put("DEBUGINFOD_URLS", " ");
2042    try envmap.put("XCURSOR_SIZE", "24");
2043
2044    var arena = std.heap.ArenaAllocator.init(allocator);
2045    defer arena.deinit();
2046    const environ = try createNullDelimitedEnvMap(arena.allocator(), &envmap);
2047
2048    try testing.expectEqual(@as(usize, 5), environ.len);
2049
2050    inline for (.{
2051        "HOME=/home/ifreund",
2052        "WAYLAND_DISPLAY=wayland-1",
2053        "DISPLAY=:1",
2054        "DEBUGINFOD_URLS= ",
2055        "XCURSOR_SIZE=24",
2056    }) |target| {
2057        for (environ) |variable| {
2058            if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
2059        } else {
2060            try testing.expect(false); // Environment variable not found
2061        }
2062    }
2063}
2064
2065/// Caller must free result.
2066pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 {
2067    // count bytes needed
2068    const max_chars_needed = x: {
2069        // Only need 2 trailing NUL code units for an empty environment
2070        var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1;
2071        var it = env_map.iterator();
2072        while (it.next()) |pair| {
2073            // +1 for '='
2074            // +1 for null byte
2075            max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
2076        }
2077        break :x max_chars_needed;
2078    };
2079    const result = try allocator.alloc(u16, max_chars_needed);
2080    errdefer allocator.free(result);
2081
2082    var it = env_map.iterator();
2083    var i: usize = 0;
2084    while (it.next()) |pair| {
2085        i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
2086        result[i] = '=';
2087        i += 1;
2088        i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
2089        result[i] = 0;
2090        i += 1;
2091    }
2092    result[i] = 0;
2093    i += 1;
2094    // An empty environment is a special case that requires a redundant
2095    // NUL terminator. CreateProcess will read the second code unit even
2096    // though theoretically the first should be enough to recognize that the
2097    // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
2098    if (env_map.count() == 0) {
2099        result[i] = 0;
2100        i += 1;
2101    }
2102    return try allocator.realloc(result, i);
2103}
2104
2105/// Logs an error and then terminates the process with exit code 1.
2106pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
2107    std.log.err(format, format_arguments);
2108    exit(1);
2109}