    const std = @import("std.zig");
    const builtin = @import("builtin");
    const os = std.os;
    const fs = std.fs;
    const mem = std.mem;
    const math = std.math;
    const Allocator = mem.Allocator;
    const assert = std.debug.assert;
    const testing = std.testing;
    const child_process = @import("child_process.zig");

    pub const abort = os.abort;
    pub const exit = os.exit;
    pub const changeCurDir = os.chdir;
    pub const changeCurDirC = os.chdirC;

    /// The result is a slice of `out_buffer`, from index `0`.
    pub fn getCwd(out_buffer: []u8) ![]u8 {
        return os.getcwd(out_buffer);
    }

    /// Caller must free the returned memory.
    pub fn getCwdAlloc(allocator: Allocator) ![]u8 {
        // The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit

        // in stack_buf, avoiding an extra allocation in the common case.

        var stack_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
        var heap_buf: ?[]u8 = null;
        defer if (heap_buf) |buf| allocator.free(buf);

        var current_buf: []u8 = &stack_buf;
        while (true) {
            if (os.getcwd(current_buf)) |slice| {
                return allocator.dupe(u8, slice);
            } else |err| switch (err) {
                error.NameTooLong => {
                    // The path is too long to fit in stack_buf. Allocate geometrically

                    // increasing buffers until we find one that works

                    const new_capacity = current_buf.len * 2;
                    if (heap_buf) |buf| allocator.free(buf);
                    current_buf = try allocator.alloc(u8, new_capacity);
                    heap_buf = current_buf;
                },
                else => |e| return e,
            }
        }
    }

    test "getCwdAlloc" {
        if (builtin.os.tag == .wasi) return error.SkipZigTest;

        const cwd = try getCwdAlloc(testing.allocator);
        testing.allocator.free(cwd);
    }

    pub const EnvMap = struct {
        hash_map: HashMap,

        const HashMap = std.HashMap(
            []const u8,
            []const u8,
            EnvNameHashContext,
            std.hash_map.default_max_load_percentage,
        );

        pub const Size = HashMap.Size;

        pub const EnvNameHashContext = struct {
            fn upcase(c: u21) u21 {
                if (c <= std.math.maxInt(u16))
                    return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@intCast(u16, c));
                return c;
            }

            pub fn hash(self: @This(), s: []const u8) u64 {
                _ = self;
                if (builtin.os.tag == .windows) {
                    var h = std.hash.Wyhash.init(0);
                    var it = std.unicode.Utf8View.initUnchecked(s).iterator();
                    while (it.nextCodepoint()) |cp| {
                        const cp_upper = upcase(cp);
                        h.update(&[_]u8{
                            @intCast(u8, (cp_upper >> 16) & 0xff),
                            @intCast(u8, (cp_upper >> 8) & 0xff),
                            @intCast(u8, (cp_upper >> 0) & 0xff),
                        });
                    }
                    return h.final();
                }
                return std.hash_map.hashString(s);
            }

            pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
                _ = self;
                if (builtin.os.tag == .windows) {
                    var it_a = std.unicode.Utf8View.initUnchecked(a).iterator();
                    var it_b = std.unicode.Utf8View.initUnchecked(b).iterator();
                    while (true) {
                        const c_a = it_a.nextCodepoint() orelse break;
                        const c_b = it_b.nextCodepoint() orelse return false;
                        if (upcase(c_a) != upcase(c_b))
                            return false;
                    }
                    return if (it_b.nextCodepoint()) |_| false else true;
                }
                return std.hash_map.eqlString(a, b);
            }
        };

        /// Create a EnvMap backed by a specific allocator.
        /// That allocator will be used for both backing allocations
        /// and string deduplication.
        pub fn init(allocator: Allocator) EnvMap {
            return EnvMap{ .hash_map = HashMap.init(allocator) };
        }

        /// Free the backing storage of the map, as well as all
        /// of the stored keys and values.
        pub fn deinit(self: *EnvMap) void {
            var it = self.hash_map.iterator();
            while (it.next()) |entry| {
                self.free(entry.key_ptr.*);
                self.free(entry.value_ptr.*);
            }

            self.hash_map.deinit();
        }

        /// Same as `put` but the key and value become owned by the EnvMap rather
        /// than being copied.
        /// If `putMove` fails, the ownership of key and value does not transfer.
        /// On Windows `key` must be a valid UTF-8 string.
        pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
            const get_or_put = try self.hash_map.getOrPut(key);
            if (get_or_put.found_existing) {
                self.free(get_or_put.key_ptr.*);
                self.free(get_or_put.value_ptr.*);
                get_or_put.key_ptr.* = key;
            }
            get_or_put.value_ptr.* = value;
        }

        /// `key` and `value` are copied into the EnvMap.
        /// On Windows `key` must be a valid UTF-8 string.
        pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
            const value_copy = try self.copy(value);
            errdefer self.free(value_copy);
            const get_or_put = try self.hash_map.getOrPut(key);
            if (get_or_put.found_existing) {
                self.free(get_or_put.value_ptr.*);
            } else {
                get_or_put.key_ptr.* = self.copy(key) catch |err| {
                    _ = self.hash_map.remove(key);
                    return err;
                };
            }
            get_or_put.value_ptr.* = value_copy;
        }

        /// Find the address of the value associated with a key.
        /// The returned pointer is invalidated if the map resizes.
        /// On Windows `key` must be a valid UTF-8 string.
        pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
            return self.hash_map.getPtr(key);
        }

        /// Return the map's copy of the value associated with
        /// a key.  The returned string is invalidated if this
        /// key is removed from the map.
        /// On Windows `key` must be a valid UTF-8 string.
        pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
            return self.hash_map.get(key);
        }

        /// Removes the item from the map and frees its value.
        /// This invalidates the value returned by get() for this key.
        /// On Windows `key` must be a valid UTF-8 string.
        pub fn remove(self: *EnvMap, key: []const u8) void {
            const kv = self.hash_map.fetchRemove(key) orelse return;
            self.free(kv.key);
            self.free(kv.value);
        }

        /// Returns the number of KV pairs stored in the map.
        pub fn count(self: EnvMap) HashMap.Size {
            return self.hash_map.count();
        }

        /// Returns an iterator over entries in the map.
        pub fn iterator(self: *const EnvMap) HashMap.Iterator {
            return self.hash_map.iterator();
        }

        fn free(self: EnvMap, value: []const u8) void {
            self.hash_map.allocator.free(value);
        }

        fn copy(self: EnvMap, value: []const u8) ![]u8 {
            return self.hash_map.allocator.dupe(u8, value);
        }
    };

    test "EnvMap" {
        var env = EnvMap.init(testing.allocator);
        defer env.deinit();

        try env.put("SOMETHING_NEW", "hello");
        try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?);
        try testing.expectEqual(@as(EnvMap.Size, 1), env.count());

        // overwrite

        try env.put("SOMETHING_NEW", "something");
        try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?);
        try testing.expectEqual(@as(EnvMap.Size, 1), env.count());

        // a new longer name to test the Windows-specific conversion buffer

        try env.put("SOMETHING_NEW_AND_LONGER", "1");
        try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?);
        try testing.expectEqual(@as(EnvMap.Size, 2), env.count());

        // case insensitivity on Windows only

        if (builtin.os.tag == .windows) {
            try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?);
        } else {
            try testing.expect(null == env.get("something_New_aNd_LONGER"));
        }

        var it = env.iterator();
        var count: EnvMap.Size = 0;
        while (it.next()) |entry| {
            const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*);
            try testing.expect(is_an_expected_name);
            count += 1;
        }
        try testing.expectEqual(@as(EnvMap.Size, 2), count);

        env.remove("SOMETHING_NEW");
        try testing.expect(env.get("SOMETHING_NEW") == null);

        try testing.expectEqual(@as(EnvMap.Size, 1), env.count());

        // test Unicode case-insensitivity on Windows

        if (builtin.os.tag == .windows) {
            try env.put("КИРиллИЦА", "something else");
            try testing.expectEqualStrings("something else", env.get("кириллица").?);
        }
    }

    /// Returns a snapshot of the environment variables of the current process.
    /// Any modifications to the resulting EnvMap will not be not reflected in the environment, and
    /// likewise, any future modifications to the environment will not be reflected in the EnvMap.
    /// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
    pub fn getEnvMap(allocator: Allocator) !EnvMap {
        var result = EnvMap.init(allocator);
        errdefer result.deinit();

        if (builtin.os.tag == .windows) {
            const ptr = os.windows.peb().ProcessParameters.Environment;

            var i: usize = 0;
            while (ptr[i] != 0) {
                const key_start = i;

                // There are some special environment variables that start with =,

                // so we need a special case to not treat = as a key/value separator

                // if it's the first character.

                // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133

                if (ptr[key_start] == '=') i += 1;

                while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
                const key_w = ptr[key_start..i];
                const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w);
                errdefer allocator.free(key);

                if (ptr[i] == '=') i += 1;

                const value_start = i;
                while (ptr[i] != 0) : (i += 1) {}
                const value_w = ptr[value_start..i];
                const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w);
                errdefer allocator.free(value);

                i += 1; // skip over null byte

                try result.putMove(key, value);
            }
            return result;
        } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
            var environ_count: usize = undefined;
            var environ_buf_size: usize = undefined;

            const environ_sizes_get_ret = os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
            if (environ_sizes_get_ret != .SUCCESS) {
                return os.unexpectedErrno(environ_sizes_get_ret);
            }

            var environ = try allocator.alloc([*:0]u8, environ_count);
            defer allocator.free(environ);
            var environ_buf = try allocator.alloc(u8, environ_buf_size);
            defer allocator.free(environ_buf);

            const environ_get_ret = os.wasi.environ_get(environ.ptr, environ_buf.ptr);
            if (environ_get_ret != .SUCCESS) {
                return os.unexpectedErrno(environ_get_ret);
            }

            for (environ) |env| {
                const pair = mem.sliceTo(env, 0);
                var parts = mem.split(u8, pair, "=");
                const key = parts.first();
                const value = parts.next().?;
                try result.put(key, value);
            }
            return result;
        } else if (builtin.link_libc) {
            var ptr = std.c.environ;
            while (ptr[0]) |line| : (ptr += 1) {
                var line_i: usize = 0;
                while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
                const key = line[0..line_i];

                var end_i: usize = line_i;
                while (line[end_i] != 0) : (end_i += 1) {}
                const value = line[line_i + 1 .. end_i];

                try result.put(key, value);
            }
            return result;
        } else {
            for (os.environ) |line| {
                var line_i: usize = 0;
                while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
                const key = line[0..line_i];

                var end_i: usize = line_i;
                while (line[end_i] != 0) : (end_i += 1) {}
                const value = line[line_i + 1 .. end_i];

                try result.put(key, value);
            }
            return result;
        }
    }

    test "getEnvMap" {
        var env = try getEnvMap(testing.allocator);
        defer env.deinit();
    }

    pub const GetEnvVarOwnedError = error{
        OutOfMemory,
        EnvironmentVariableNotFound,

        /// See https://github.com/ziglang/zig/issues/1774
        InvalidUtf8,
    };

    /// Caller must free returned memory.
    pub fn getEnvVarOwned(allocator: mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
        if (builtin.os.tag == .windows) {
            const result_w = blk: {
                const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
                defer allocator.free(key_w);

                break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
            };
            return std.unicode.utf16leToUtf8Alloc(allocator, result_w) catch |err| switch (err) {
                error.DanglingSurrogateHalf => return error.InvalidUtf8,
                error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
                error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
                else => |e| return e,
            };
        } else {
            const result = os.getenv(key) orelse return error.EnvironmentVariableNotFound;
            return allocator.dupe(u8, result);
        }
    }

    pub fn hasEnvVarConstant(comptime key: []const u8) bool {
        if (builtin.os.tag == .windows) {
            const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);
            return std.os.getenvW(key_w) != null;
        } else {
            return os.getenv(key) != null;
        }
    }

    pub fn hasEnvVar(allocator: Allocator, key: []const u8) error{OutOfMemory}!bool {
        if (builtin.os.tag == .windows) {
            var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
            const key_w = try std.unicode.utf8ToUtf16LeWithNull(stack_alloc.get(), key);
            defer stack_alloc.allocator.free(key_w);
            return std.os.getenvW(key_w) != null;
        } else {
            return os.getenv(key) != null;
        }
    }

    test "os.getEnvVarOwned" {
        var ga = std.testing.allocator;
        try testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));
    }

    pub const ArgIteratorPosix = struct {
        index: usize,
        count: usize,

        pub const InitError = error{};

        pub fn init() ArgIteratorPosix {
            return ArgIteratorPosix{
                .index = 0,
                .count = os.argv.len,
            };
        }

        pub fn next(self: *ArgIteratorPosix) ?[:0]const u8 {
            if (self.index == self.count) return null;

            const s = os.argv[self.index];
            self.index += 1;
            return mem.sliceTo(s, 0);
        }

        pub fn skip(self: *ArgIteratorPosix) bool {
            if (self.index == self.count) return false;

            self.index += 1;
            return true;
        }
    };

    pub const ArgIteratorWasi = struct {
        allocator: mem.Allocator,
        index: usize,
        args: [][:0]u8,

        pub const InitError = error{OutOfMemory} || os.UnexpectedError;

        /// You must call deinit to free the internal buffer of the
        /// iterator after you are done.
        pub fn init(allocator: mem.Allocator) InitError!ArgIteratorWasi {
            const fetched_args = try ArgIteratorWasi.internalInit(allocator);
            return ArgIteratorWasi{
                .allocator = allocator,
                .index = 0,
                .args = fetched_args,
            };
        }

        fn internalInit(allocator: mem.Allocator) InitError![][:0]u8 {
            const w = os.wasi;
            var count: usize = undefined;
            var buf_size: usize = undefined;

            switch (w.args_sizes_get(&count, &buf_size)) {
                .SUCCESS => {},
                else => |err| return os.unexpectedErrno(err),
            }

            var argv = try allocator.alloc([*:0]u8, count);
            defer allocator.free(argv);

            var argv_buf = try allocator.alloc(u8, buf_size);

            switch (w.args_get(argv.ptr, argv_buf.ptr)) {
                .SUCCESS => {},
                else => |err| return os.unexpectedErrno(err),
            }

            var result_args = try allocator.alloc([:0]u8, count);
            var i: usize = 0;
            while (i < count) : (i += 1) {
                result_args[i] = mem.sliceTo(argv[i], 0);
            }

            return result_args;
        }

        pub fn next(self: *ArgIteratorWasi) ?[:0]const u8 {
            if (self.index == self.args.len) return null;

            const arg = self.args[self.index];
            self.index += 1;
            return arg;
        }

        pub fn skip(self: *ArgIteratorWasi) bool {
            if (self.index == self.args.len) return false;

            self.index += 1;
            return true;
        }

        /// Call to free the internal buffer of the iterator.
        pub fn deinit(self: *ArgIteratorWasi) void {
            const last_item = self.args[self.args.len - 1];
            const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated

            const first_item_ptr = self.args[0].ptr;
            const len = last_byte_addr - @ptrToInt(first_item_ptr);
            self.allocator.free(first_item_ptr[0..len]);
            self.allocator.free(self.args);
        }
    };

    /// Optional parameters for `ArgIteratorGeneral`
    pub const ArgIteratorGeneralOptions = struct {
        comments: bool = false,
        single_quotes: bool = false,
    };

    /// A general Iterator to parse a string into a set of arguments
    pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
        return struct {
            allocator: Allocator,
            index: usize = 0,
            cmd_line: []const u8,

            /// Should the cmd_line field be free'd (using the allocator) on deinit()?
            free_cmd_line_on_deinit: bool,

            /// buffer MUST be long enough to hold the cmd_line plus a null terminator.
            /// buffer will we free'd (using the allocator) on deinit()
            buffer: []u8,
            start: usize = 0,
            end: usize = 0,

            pub const Self = @This();

            pub const InitError = error{OutOfMemory};
            pub const InitUtf16leError = error{ OutOfMemory, InvalidCmdLine };

            /// cmd_line_utf8 MUST remain valid and constant while using this instance
            pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
                var buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
                errdefer allocator.free(buffer);

                return Self{
                    .allocator = allocator,
                    .cmd_line = cmd_line_utf8,
                    .free_cmd_line_on_deinit = false,
                    .buffer = buffer,
                };
            }

            /// cmd_line_utf8 will be free'd (with the allocator) on deinit()
            pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
                var buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
                errdefer allocator.free(buffer);

                return Self{
                    .allocator = allocator,
                    .cmd_line = cmd_line_utf8,
                    .free_cmd_line_on_deinit = true,
                    .buffer = buffer,
                };
            }

            /// cmd_line_utf16le MUST be encoded UTF16-LE, and is converted to UTF-8 in an internal buffer
            pub fn initUtf16le(allocator: Allocator, cmd_line_utf16le: [*:0]const u16) InitUtf16leError!Self {
                var utf16le_slice = mem.sliceTo(cmd_line_utf16le, 0);
                var cmd_line = std.unicode.utf16leToUtf8Alloc(allocator, utf16le_slice) catch |err| switch (err) {
                    error.ExpectedSecondSurrogateHalf,
                    error.DanglingSurrogateHalf,
                    error.UnexpectedSecondSurrogateHalf,
                    => return error.InvalidCmdLine,

                    error.OutOfMemory => return error.OutOfMemory,
                };
                errdefer allocator.free(cmd_line);

                var buffer = try allocator.alloc(u8, cmd_line.len + 1);
                errdefer allocator.free(buffer);

                return Self{
                    .allocator = allocator,
                    .cmd_line = cmd_line,
                    .free_cmd_line_on_deinit = true,
                    .buffer = buffer,
                };
            }

            // Skips over whitespace in the cmd_line.

            // Returns false if the terminating sentinel is reached, true otherwise.

            // Also skips over comments (if supported).

            fn skipWhitespace(self: *Self) bool {
                while (true) : (self.index += 1) {
                    const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
                    switch (character) {
                        0 => return false,
                        ' ', '\t', '\r', '\n' => continue,
                        '#' => {
                            if (options.comments) {
                                while (true) : (self.index += 1) {
                                    switch (self.cmd_line[self.index]) {
                                        '\n' => break,
                                        0 => return false,
                                        else => continue,
                                    }
                                }
                                continue;
                            } else {
                                break;
                            }
                        },
                        else => break,
                    }
                }
                return true;
            }

            pub fn skip(self: *Self) bool {
                if (!self.skipWhitespace()) {
                    return false;
                }

                var backslash_count: usize = 0;
                var in_quote = false;
                while (true) : (self.index += 1) {
                    const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
                    switch (character) {
                        0 => return true,
                        '"', '\'' => {
                            if (!options.single_quotes and character == '\'') {
                                backslash_count = 0;
                                continue;
                            }
                            const quote_is_real = backslash_count % 2 == 0;
                            if (quote_is_real) {
                                in_quote = !in_quote;
                            }
                        },
                        '\\' => {
                            backslash_count += 1;
                        },
                        ' ', '\t', '\r', '\n' => {
                            if (!in_quote) {
                                return true;
                            }
                            backslash_count = 0;
                        },
                        else => {
                            backslash_count = 0;
                            continue;
                        },
                    }
                }
            }

            /// Returns a slice of the internal buffer that contains the next argument.
            /// Returns null when it reaches the end.
            pub fn next(self: *Self) ?[:0]const u8 {
                if (!self.skipWhitespace()) {
                    return null;
                }

                var backslash_count: usize = 0;
                var in_quote = false;
                while (true) : (self.index += 1) {
                    const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
                    switch (character) {
                        0 => {
                            self.emitBackslashes(backslash_count);
                            self.buffer[self.end] = 0;
                            var token = self.buffer[self.start..self.end :0];
                            self.end += 1;
                            self.start = self.end;
                            return token;
                        },
                        '"', '\'' => {
                            if (!options.single_quotes and character == '\'') {
                                self.emitBackslashes(backslash_count);
                                backslash_count = 0;
                                self.emitCharacter(character);
                                continue;
                            }
                            const quote_is_real = backslash_count % 2 == 0;
                            self.emitBackslashes(backslash_count / 2);
                            backslash_count = 0;

                            if (quote_is_real) {
                                in_quote = !in_quote;
                            } else {
                                self.emitCharacter('"');
                            }
                        },
                        '\\' => {
                            backslash_count += 1;
                        },
                        ' ', '\t', '\r', '\n' => {
                            self.emitBackslashes(backslash_count);
                            backslash_count = 0;
                            if (in_quote) {
                                self.emitCharacter(character);
                            } else {
                                self.buffer[self.end] = 0;
                                var token = self.buffer[self.start..self.end :0];
                                self.end += 1;
                                self.start = self.end;
                                return token;
                            }
                        },
                        else => {
                            self.emitBackslashes(backslash_count);
                            backslash_count = 0;
                            self.emitCharacter(character);
                        },
                    }
                }
            }

            fn emitBackslashes(self: *Self, emit_count: usize) void {
                var i: usize = 0;
                while (i < emit_count) : (i += 1) {
                    self.emitCharacter('\\');
                }
            }

            fn emitCharacter(self: *Self, char: u8) void {
                self.buffer[self.end] = char;
                self.end += 1;
            }

            /// Call to free the internal buffer of the iterator.
            pub fn deinit(self: *Self) void {
                self.allocator.free(self.buffer);

                if (self.free_cmd_line_on_deinit) {
                    self.allocator.free(self.cmd_line);
                }
            }
        };
    }

    /// Cross-platform command line argument iterator.
    pub const ArgIterator = struct {
        const InnerType = switch (builtin.os.tag) {
            .windows => ArgIteratorGeneral(.{}),
            .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi,
            else => ArgIteratorPosix,
        };

        inner: InnerType,

        /// Initialize the args iterator. Consider using initWithAllocator() instead
        /// for cross-platform compatibility.
        pub fn init() ArgIterator {
            if (builtin.os.tag == .wasi) {
                @compileError("In WASI, use initWithAllocator instead.");
            }
            if (builtin.os.tag == .windows) {
                @compileError("In Windows, use initWithAllocator instead.");
            }

            return ArgIterator{ .inner = InnerType.init() };
        }

        pub const InitError = switch (builtin.os.tag) {
            .windows => InnerType.InitUtf16leError,
            else => InnerType.InitError,
        };

        /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
        pub fn initWithAllocator(allocator: mem.Allocator) InitError!ArgIterator {
            if (builtin.os.tag == .wasi and !builtin.link_libc) {
                return ArgIterator{ .inner = try InnerType.init(allocator) };
            }
            if (builtin.os.tag == .windows) {
                const cmd_line_w = os.windows.kernel32.GetCommandLineW();
                return ArgIterator{ .inner = try InnerType.initUtf16le(allocator, cmd_line_w) };
            }

            return ArgIterator{ .inner = InnerType.init() };
        }

        /// Get the next argument. Returns 'null' if we are at the end.
        /// Returned slice is pointing to the iterator's internal buffer.
        pub fn next(self: *ArgIterator) ?([:0]const u8) {
            return self.inner.next();
        }

        /// Parse past 1 argument without capturing it.
        /// Returns `true` if skipped an arg, `false` if we are at the end.
        pub fn skip(self: *ArgIterator) bool {
            return self.inner.skip();
        }

        /// Call this to free the iterator's internal buffer if the iterator
        /// was created with `initWithAllocator` function.
        pub fn deinit(self: *ArgIterator) void {
            // Unless we're targeting WASI or Windows, this is a no-op.

            if (builtin.os.tag == .wasi and !builtin.link_libc) {
                self.inner.deinit();
            }

            if (builtin.os.tag == .windows) {
                self.inner.deinit();
            }
        }
    };

    /// Use argsWithAllocator() for cross-platform code
    pub fn args() ArgIterator {
        return ArgIterator.init();
    }

    /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
    pub fn argsWithAllocator(allocator: mem.Allocator) ArgIterator.InitError!ArgIterator {
        return ArgIterator.initWithAllocator(allocator);
    }

    test "args iterator" {
        var ga = std.testing.allocator;
        var it = try argsWithAllocator(ga);
        defer it.deinit(); // no-op unless WASI or Windows

        const prog_name = it.next() orelse unreachable;
        const expected_suffix = switch (builtin.os.tag) {
            .wasi => "test.wasm",
            .windows => "test.exe",
            else => "test",
        };
        const given_suffix = std.fs.path.basename(prog_name);

        try testing.expect(mem.eql(u8, expected_suffix, given_suffix));
        try testing.expect(it.next() == null);
        try testing.expect(!it.skip());
    }

    /// Caller must call argsFree on result.
    pub fn argsAlloc(allocator: mem.Allocator) ![][:0]u8 {
        // TODO refactor to only make 1 allocation.

        var it = try argsWithAllocator(allocator);
        defer it.deinit();

        var contents = std.ArrayList(u8).init(allocator);
        defer contents.deinit();

        var slice_list = std.ArrayList(usize).init(allocator);
        defer slice_list.deinit();

        while (it.next()) |arg| {
            try contents.appendSlice(arg[0 .. arg.len + 1]);
            try slice_list.append(arg.len);
        }

        const contents_slice = contents.items;
        const slice_sizes = slice_list.items;
        const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len);
        const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len);
        const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes);
        errdefer allocator.free(buf);

        const result_slice_list = mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]);
        const result_contents = buf[slice_list_bytes..];
        mem.copy(u8, result_contents, contents_slice);

        var contents_index: usize = 0;
        for (slice_sizes) |len, i| {
            const new_index = contents_index + len;
            result_slice_list[i] = result_contents[contents_index..new_index :0];
            contents_index = new_index + 1;
        }

        return result_slice_list;
    }

    pub fn argsFree(allocator: mem.Allocator, args_alloc: []const [:0]u8) void {
        var total_bytes: usize = 0;
        for (args_alloc) |arg| {
            total_bytes += @sizeOf([]u8) + arg.len + 1;
        }
        const unaligned_allocated_buf = @ptrCast([*]const u8, args_alloc.ptr)[0..total_bytes];
        const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf);
        return allocator.free(aligned_allocated_buf);
    }

    test "general arg parsing" {
        try testGeneralCmdLine("a   b\tc d", &.{ "a", "b", "c", "d" });
        try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" });
        try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" });
        try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" });
        try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" });
        try testGeneralCmdLine("a   b\tc \"d f", &.{ "a", "b", "c", "d f" });
        try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" });
        try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" });

        try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{
            ".\\..\\zig-cache\\build",
            "bin\\zig.exe",
            ".\\..",
            ".\\..\\zig-cache",
            "--help",
        });

        try testGeneralCmdLine(
            \\ 'foo' "bar"

        , &.{ "'foo'", "bar" });
    }

    fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
        var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line);
        defer it.deinit();
        for (expected_args) |expected_arg| {
            const arg = it.next().?;
            try testing.expectEqualStrings(expected_arg, arg);
        }
        try testing.expect(it.next() == null);
    }

    test "response file arg parsing" {
        try testResponseFileCmdLine(
            \\a b

            \\c d\

        , &.{ "a", "b", "c", "d\\" });
        try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" });

        try testResponseFileCmdLine(
            \\j

            \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\"

            \\ "m" #another comment

            \\

        , &.{ "j", "k", "l", "m" });

        try testResponseFileCmdLine(
            \\ "" q ""

            \\ "r s # t" "u\" v" #another comment

            \\

        , &.{ "", "q", "", "r s # t", "u\" v" });

        try testResponseFileCmdLine(
            \\ -l"advapi32" a# b#c d#

            \\e\\\

        , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" });

        try testResponseFileCmdLine(
            \\ 'foo' "bar"

        , &.{ "foo", "bar" });
    }

    fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
        var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true })
            .init(std.testing.allocator, input_cmd_line);
        defer it.deinit();
        for (expected_args) |expected_arg| {
            const arg = it.next().?;
            try testing.expectEqualStrings(expected_arg, arg);
        }
        try testing.expect(it.next() == null);
    }

    pub const UserInfo = struct {
        uid: os.uid_t,
        gid: os.gid_t,
    };

    /// POSIX function which gets a uid from username.
    pub fn getUserInfo(name: []const u8) !UserInfo {
        return switch (builtin.os.tag) {
            .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd, .openbsd, .haiku, .solaris => posixGetUserInfo(name),
            else => @compileError("Unsupported OS"),
        };
    }

    /// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
    /// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
    pub fn posixGetUserInfo(name: []const u8) !UserInfo {
        const file = try std.fs.openFileAbsolute("/etc/passwd", .{});
        defer file.close();

        const reader = file.reader();

        const State = enum {
            Start,
            WaitForNextLine,
            SkipPassword,
            ReadUserId,
            ReadGroupId,
        };

        var buf: [std.mem.page_size]u8 = undefined;
        var name_index: usize = 0;
        var state = State.Start;
        var uid: os.uid_t = 0;
        var gid: os.gid_t = 0;

        while (true) {
            const amt_read = try reader.read(buf[0..]);
            for (buf[0..amt_read]) |byte| {
                switch (state) {
                    .Start => switch (byte) {
                        ':' => {
                            state = if (name_index == name.len) State.SkipPassword else State.WaitForNextLine;
                        },
                        '\n' => return error.CorruptPasswordFile,
                        else => {
                            if (name_index == name.len or name[name_index] != byte) {
                                state = .WaitForNextLine;
                            }
                            name_index += 1;
                        },
                    },
                    .WaitForNextLine => switch (byte) {
                        '\n' => {
                            name_index = 0;
                            state = .Start;
                        },
                        else => continue,
                    },
                    .SkipPassword => switch (byte) {
                        '\n' => return error.CorruptPasswordFile,
                        ':' => {
                            state = .ReadUserId;
                        },
                        else => continue,
                    },
                    .ReadUserId => switch (byte) {
                        ':' => {
                            state = .ReadGroupId;
                        },
                        '\n' => return error.CorruptPasswordFile,
                        else => {
                            const digit = switch (byte) {
                                '0'...'9' => byte - '0',
                                else => return error.CorruptPasswordFile,
                            };
                            if (@mulWithOverflow(u32, uid, 10, &uid)) return error.CorruptPasswordFile;
                            if (@addWithOverflow(u32, uid, digit, &uid)) return error.CorruptPasswordFile;
                        },
                    },
                    .ReadGroupId => switch (byte) {
                        '\n', ':' => {
                            return UserInfo{
                                .uid = uid,
                                .gid = gid,
                            };
                        },
                        else => {
                            const digit = switch (byte) {
                                '0'...'9' => byte - '0',
                                else => return error.CorruptPasswordFile,
                            };
                            if (@mulWithOverflow(u32, gid, 10, &gid)) return error.CorruptPasswordFile;
                            if (@addWithOverflow(u32, gid, digit, &gid)) return error.CorruptPasswordFile;
                        },
                    },
                }
            }
            if (amt_read < buf.len) return error.UserNotFound;
        }
    }

    pub fn getBaseAddress() usize {
        switch (builtin.os.tag) {
            .linux => {
                const base = os.system.getauxval(std.elf.AT_BASE);
                if (base != 0) {
                    return base;
                }
                const phdr = os.system.getauxval(std.elf.AT_PHDR);
                return phdr - @sizeOf(std.elf.Ehdr);
            },
            .macos, .freebsd, .netbsd => {
                return @ptrToInt(&std.c._mh_execute_header);
            },
            .windows => return @ptrToInt(os.windows.kernel32.GetModuleHandleW(null)),
            else => @compileError("Unsupported OS"),
        }
    }

    /// Caller owns the result value and each inner slice.
    /// TODO Remove the `Allocator` requirement from this API, which will remove the `Allocator`
    /// requirement from `std.zig.system.NativeTargetInfo.detect`. Most likely this will require
    /// introducing a new, lower-level function which takes a callback function, and then this
    /// function which takes an allocator can exist on top of it.
    pub fn getSelfExeSharedLibPaths(allocator: Allocator) error{OutOfMemory}![][:0]u8 {
        switch (builtin.link_mode) {
            .Static => return &[_][:0]u8{},
            .Dynamic => {},
        }
        const List = std.ArrayList([:0]u8);
        switch (builtin.os.tag) {
            .linux,
            .freebsd,
            .netbsd,
            .dragonfly,
            .openbsd,
            .solaris,
            => {
                var paths = List.init(allocator);
                errdefer {
                    const slice = paths.toOwnedSlice();
                    for (slice) |item| {
                        allocator.free(item);
                    }
                    allocator.free(slice);
                }
                try os.dl_iterate_phdr(&paths, error{OutOfMemory}, struct {
                    fn callback(info: *os.dl_phdr_info, size: usize, list: *List) !void {
                        _ = size;
                        const name = info.dlpi_name orelse return;
                        if (name[0] == '/') {
                            const item = try list.allocator.dupeZ(u8, mem.sliceTo(name, 0));
                            errdefer list.allocator.free(item);
                            try list.append(item);
                        }
                    }
                }.callback);
                return paths.toOwnedSlice();
            },
            .macos, .ios, .watchos, .tvos => {
                var paths = List.init(allocator);
                errdefer {
                    const slice = paths.toOwnedSlice();
                    for (slice) |item| {
                        allocator.free(item);
                    }
                    allocator.free(slice);
                }
                const img_count = std.c._dyld_image_count();
                var i: u32 = 0;
                while (i < img_count) : (i += 1) {
                    const name = std.c._dyld_get_image_name(i);
                    const item = try allocator.dupeZ(u8, mem.sliceTo(name, 0));
                    errdefer allocator.free(item);
                    try paths.append(item);
                }
                return paths.toOwnedSlice();
            },
            // revisit if Haiku implements dl_iterat_phdr (https://dev.haiku-os.org/ticket/15743)

            .haiku => {
                var paths = List.init(allocator);
                errdefer {
                    const slice = paths.toOwnedSlice();
                    for (slice) |item| {
                        allocator.free(item);
                    }
                    allocator.free(slice);
                }

                var b = "/boot/system/runtime_loader";
                const item = try allocator.dupeZ(u8, mem.sliceTo(b, 0));
                errdefer allocator.free(item);
                try paths.append(item);

                return paths.toOwnedSlice();
            },
            else => @compileError("getSelfExeSharedLibPaths unimplemented for this target"),
        }
    }

    /// Tells whether calling the `execv` or `execve` functions will be a compile error.
    pub const can_execv = switch (builtin.os.tag) {
        .windows, .haiku, .wasi => false,
        else => true,
    };

    /// Tells whether spawning child processes is supported (e.g. via ChildProcess)
    pub const can_spawn = switch (builtin.os.tag) {
        .wasi => false,
        else => true,
    };

    pub const ExecvError = std.os.ExecveError || error{OutOfMemory};

    /// Replaces the current process image with the executed process.
    /// This function must allocate memory to add a null terminating bytes on path and each arg.
    /// It must also convert to KEY=VALUE\0 format for environment variables, and include null
    /// pointers after the args and after the environment variables.
    /// `argv[0]` is the executable path.
    /// This function also uses the PATH environment variable to get the full path to the executable.
    /// Due to the heap-allocation, it is illegal to call this function in a fork() child.
    /// For that use case, use the `std.os` functions directly.
    pub fn execv(allocator: mem.Allocator, argv: []const []const u8) ExecvError {
        return execve(allocator, argv, null);
    }

    /// Replaces the current process image with the executed process.
    /// This function must allocate memory to add a null terminating bytes on path and each arg.
    /// It must also convert to KEY=VALUE\0 format for environment variables, and include null
    /// pointers after the args and after the environment variables.
    /// `argv[0]` is the executable path.
    /// This function also uses the PATH environment variable to get the full path to the executable.
    /// Due to the heap-allocation, it is illegal to call this function in a fork() child.
    /// For that use case, use the `std.os` functions directly.
    pub fn execve(
        allocator: mem.Allocator,
        argv: []const []const u8,
        env_map: ?*const EnvMap,
    ) ExecvError {
        if (!can_execv) @compileError("The target OS does not support execv");

        var arena_allocator = std.heap.ArenaAllocator.init(allocator);
        defer arena_allocator.deinit();
        const arena = arena_allocator.allocator();

        const argv_buf = try arena.allocSentinel(?[*:0]u8, argv.len, null);
        for (argv) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;

        const envp = m: {
            if (env_map) |m| {
                const envp_buf = try child_process.createNullDelimitedEnvMap(arena, m);
                break :m envp_buf.ptr;
            } else if (builtin.link_libc) {
                break :m std.c.environ;
            } else if (builtin.output_mode == .Exe) {
                // Then we have Zig start code and this works.

                // TODO type-safety for null-termination of `os.environ`.

                break :m @ptrCast([*:null]?[*:0]u8, os.environ.ptr);
            } else {
                // TODO come up with a solution for this.

                @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process");
            }
        };

        return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);
    }