diff --git a/lib/std/os.zig b/lib/std/os.zig index a4f148ae00..cdb48a05a5 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1104,10 +1104,6 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) /// Get an environment variable. /// See also `getenvZ`. pub fn getenv(key: []const u8) ?[]const u8 { - if (builtin.os == .windows) { - // TODO update this to use the ProcessEnvironmentBlock - @compileError("TODO implement std.os.getenv for Windows"); - } if (builtin.link_libc) { var small_key_buf: [64]u8 = undefined; if (key.len < small_key_buf.len) { @@ -1133,6 +1129,9 @@ pub fn getenv(key: []const u8) ?[]const u8 { } return null; } + if (builtin.os == .windows) { + @compileError("std.os.getenv is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API."); + } // TODO see https://github.com/ziglang/zig/issues/4524 for (environ) |ptr| { var line_i: usize = 0; @@ -1155,17 +1154,44 @@ pub const getenvC = getenvZ; /// Get an environment variable with a null-terminated name. /// See also `getenv`. pub fn getenvZ(key: [*:0]const u8) ?[]const u8 { - if (builtin.os == .windows) { - // TODO update this to use the ProcessEnvironmentBlock - @compileError("TODO implement std.os.getenv for Windows"); - } if (builtin.link_libc) { const value = system.getenv(key) orelse return null; return mem.toSliceConst(u8, value); } + if (builtin.os == .windows) { + @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API."); + } return getenv(mem.toSliceConst(u8, key)); } +/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. +/// See also `getenv`. +pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { + if (builtin.os != .windows) { + @compileError("std.os.getenvW is a Windows-only API"); + } + const key_slice = mem.toSliceConst(u16, key); + const ptr = windows.peb().ProcessParameters.Environment; + var i: usize = 0; + while (ptr[i] != 0) { + const key_start = i; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const this_key = ptr[key_start..i]; + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const this_value = ptr[value_start..i :0]; + + if (mem.eql(u16, key_slice, this_key)) return this_value; + + i += 1; // skip over null byte + } + return null; +} + pub const GetCwdError = error{ NameTooLong, CurrentWorkingDirectoryUnlinked, diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 055d049f69..488a557ed6 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -351,3 +351,11 @@ test "mmap" { try fs.cwd().deleteFile(test_out_file); } + +test "getenv" { + if (builtin.os == .windows) { + expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null); + } else { + expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null); + } +} diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 265d3439e2..4e153d40ed 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1187,7 +1187,7 @@ pub const RTL_USER_PROCESS_PARAMETERS = extern struct { DllPath: UNICODE_STRING, ImagePathName: UNICODE_STRING, CommandLine: UNICODE_STRING, - Environment: [*]WCHAR, + Environment: [*:0]WCHAR, dwX: ULONG, dwY: ULONG, dwXSize: ULONG, diff --git a/lib/std/process.zig b/lib/std/process.zig index ba124ff262..ac75eff725 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -37,14 +37,11 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { errdefer result.deinit(); if (builtin.os == .windows) { - // TODO update this to use the ProcessEnvironmentBlock - const ptr = try os.windows.GetEnvironmentStringsW(); + const ptr = windows.peb().ProcessParameters.Environment; defer os.windows.FreeEnvironmentStringsW(ptr); var i: usize = 0; - while (true) { - if (ptr[i] == 0) return result; - + while (ptr[i] != 0) { const key_start = i; while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} @@ -64,6 +61,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { try result.setMove(key, value); } + return result; } else if (builtin.os == .wasi) { var environ_count: usize = undefined; var environ_buf_size: usize = undefined; @@ -141,34 +139,18 @@ pub const GetEnvVarOwnedError = error{ /// Caller must free returned memory. pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { if (builtin.os == .windows) { - const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); - defer allocator.free(key_with_null); + const result_w = blk: { + const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); + defer allocator.free(key_w); - var buf = try allocator.alloc(u16, 256); - defer allocator.free(buf); - - while (true) { - const windows_buf_len = math.cast(os.windows.DWORD, buf.len) catch return error.OutOfMemory; - const result = os.windows.GetEnvironmentVariableW( - key_with_null.ptr, - buf.ptr, - windows_buf_len, - ) catch |err| switch (err) { - error.Unexpected => return error.EnvironmentVariableNotFound, - else => |e| return e, - }; - if (result > buf.len) { - buf = try allocator.realloc(buf, result); - continue; - } - - return std.unicode.utf16leToUtf8Alloc(allocator, buf[0..result]) catch |err| switch (err) { - error.DanglingSurrogateHalf => return error.InvalidUtf8, - error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8, - error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8, - else => |e| return e, - }; - } + 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 mem.dupe(allocator, u8, result); diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 74a9ffdc70..3931e362c6 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -5,6 +5,8 @@ const ArrayList = std.ArrayList; const assert = std.debug.assert; const process = std.process; +const is_windows = std.Target.current.isWindows(); + pub const NativePaths = struct { include_dirs: ArrayList([:0]u8), lib_dirs: ArrayList([:0]u8), @@ -21,7 +23,9 @@ pub const NativePaths = struct { errdefer self.deinit(); var is_nix = false; - if (std.os.getenvZ("NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { + if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { + defer allocator.free(nix_cflags_compile); + is_nix = true; var it = mem.tokenize(nix_cflags_compile, " "); while (true) { @@ -37,8 +41,14 @@ pub const NativePaths = struct { break; } } + } else |err| switch (err) { + error.InvalidUtf8 => {}, + error.EnvironmentVariableNotFound => {}, + error.OutOfMemory => |e| return e, } - if (std.os.getenvZ("NIX_LDFLAGS")) |nix_ldflags| { + if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| { + defer allocator.free(nix_ldflags); + is_nix = true; var it = mem.tokenize(nix_ldflags, " "); while (true) { @@ -57,39 +67,40 @@ pub const NativePaths = struct { break; } } + } else |err| switch (err) { + error.InvalidUtf8 => {}, + error.EnvironmentVariableNotFound => {}, + error.OutOfMemory => |e| return e, } if (is_nix) { return self; } - switch (std.builtin.os) { - .windows => {}, - else => { - const triple = try std.Target.current.linuxTriple(allocator); + if (!is_windows) { + const triple = try std.Target.current.linuxTriple(allocator); - // TODO: $ ld --verbose | grep SEARCH_DIR - // the output contains some paths that end with lib64, maybe include them too? - // TODO: what is the best possible order of things? - // TODO: some of these are suspect and should only be added on some systems. audit needed. + // TODO: $ ld --verbose | grep SEARCH_DIR + // the output contains some paths that end with lib64, maybe include them too? + // TODO: what is the best possible order of things? + // TODO: some of these are suspect and should only be added on some systems. audit needed. - try self.addIncludeDir("/usr/local/include"); - try self.addLibDir("/usr/local/lib"); - try self.addLibDir("/usr/local/lib64"); + try self.addIncludeDir("/usr/local/include"); + try self.addLibDir("/usr/local/lib"); + try self.addLibDir("/usr/local/lib64"); - try self.addIncludeDirFmt("/usr/include/{}", .{triple}); - try self.addLibDirFmt("/usr/lib/{}", .{triple}); + try self.addIncludeDirFmt("/usr/include/{}", .{triple}); + try self.addLibDirFmt("/usr/lib/{}", .{triple}); - try self.addIncludeDir("/usr/include"); - try self.addLibDir("/lib"); - try self.addLibDir("/lib64"); - try self.addLibDir("/usr/lib"); - try self.addLibDir("/usr/lib64"); + try self.addIncludeDir("/usr/include"); + try self.addLibDir("/lib"); + try self.addLibDir("/lib64"); + try self.addLibDir("/usr/lib"); + try self.addLibDir("/usr/lib64"); - // example: on a 64-bit debian-based linux distro, with zlib installed from apt: - // zlib.h is in /usr/include (added above) - // libz.so.1 is in /lib/x86_64-linux-gnu (added here) - try self.addLibDirFmt("/lib/{}", .{triple}); - }, + // example: on a 64-bit debian-based linux distro, with zlib installed from apt: + // zlib.h is in /usr/include (added above) + // libz.so.1 is in /lib/x86_64-linux-gnu (added here) + try self.addLibDirFmt("/lib/{}", .{triple}); } return self;