zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit d1243bf272b591a8deba5abedb56050edf3c3d6a (tree)
parent 0cd89e9176ab36fc5e267120dc4d75cb79d32684
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Sun, 23 Feb 2020 01:38:03 -0500

Merge pull request #4525 from ziglang/environ

update std lib to integrate with libc for environ
Diffstat:
Mlib/std/c.zig | 2++
Mlib/std/os.zig | 75++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mlib/std/os/test.zig | 8++++++++
Mlib/std/os/windows/bits.zig | 2+-
Mlib/std/process.zig | 76+++++++++++++++++++++++++++++++++++-----------------------------------------
Mlib/std/start.zig | 10++++++++--
Mlib/std/zig/system.zig | 71+++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/os.cpp | 6+-----
8 files changed, 166 insertions(+), 84 deletions(-)

diff --git a/lib/std/c.zig b/lib/std/c.zig @@ -62,6 +62,8 @@ pub fn versionCheck(glibc_version: builtin.Version) type { }; } +pub extern "c" var environ: [*:null]?[*:0]u8; + pub extern "c" fn fopen(filename: [*:0]const u8, modes: [*:0]const u8) ?*FILE; pub extern "c" fn fclose(stream: *FILE) c_int; pub extern "c" fn fwrite(ptr: [*]const u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; diff --git a/lib/std/os.zig b/lib/std/os.zig @@ -70,6 +70,8 @@ else switch (builtin.os) { pub usingnamespace @import("os/bits.zig"); /// See also `getenv`. Populated by startup code before main(). +/// TODO this is a footgun because the value will be undefined when using `zig build-lib`. +/// https://github.com/ziglang/zig/issues/4524 pub var environ: [][*:0]u8 = undefined; /// Populated by startup code before main(). @@ -922,7 +924,11 @@ pub const execveC = execveZ; /// Like `execve` except the parameters are null-terminated, /// matching the syscall API on all targets. This removes the need for an allocator. /// This function ignores PATH environment variable. See `execvpeZ` for that. -pub fn execveZ(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError { +pub fn execveZ( + path: [*:0]const u8, + child_argv: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, +) ExecveError { switch (errno(system.execve(path, child_argv, envp))) { 0 => unreachable, EFAULT => unreachable, @@ -966,7 +972,7 @@ pub fn execvpeZ_expandArg0( envp: [*:null]const ?[*:0]const u8, ) ExecveError { const file_slice = mem.toSliceConst(u8, file); - if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp); + if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; var path_buf: [MAX_PATH_BYTES]u8 = undefined; @@ -993,7 +999,7 @@ pub fn execvpeZ_expandArg0( .expand => child_argv[0] = full_path, .no_expand => {}, } - err = execveC(full_path, child_argv, envp); + err = execveZ(full_path, child_argv, envp); switch (err) { error.AccessDenied => seen_eacces = true, error.FileNotFound, error.NotDir => {}, @@ -1007,7 +1013,7 @@ pub fn execvpeZ_expandArg0( /// Like `execvpe` except the parameters are null-terminated, /// matching the syscall API on all targets. This removes the need for an allocator. /// This function also uses the PATH environment variable to get the full path to the executable. -/// If `file` is an absolute path, this is the same as `execveC`. +/// If `file` is an absolute path, this is the same as `execveZ`. pub fn execvpeZ( file: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, @@ -1097,8 +1103,36 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) /// Get an environment variable. /// See also `getenvZ`. -/// TODO make this go through libc when we have it pub fn getenv(key: []const u8) ?[]const u8 { + if (builtin.link_libc) { + var small_key_buf: [64]u8 = undefined; + if (key.len < small_key_buf.len) { + mem.copy(u8, &small_key_buf, key); + small_key_buf[key.len] = 0; + const key0 = small_key_buf[0..key.len :0]; + return getenvZ(key0); + } + // Search the entire `environ` because we don't have a null terminated pointer. + var ptr = std.c.environ; + while (ptr.*) |line| : (ptr += 1) { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const this_key = line[0..line_i]; + + if (!mem.eql(u8, this_key, key)) continue; + + var end_i: usize = line_i; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; + + return value; + } + 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; while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} @@ -1124,9 +1158,40 @@ pub fn getenvZ(key: [*:0]const u8) ?[]const u8 { 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 @@ -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 @@ -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 @@ -1,5 +1,5 @@ -const builtin = @import("builtin"); const std = @import("std.zig"); +const builtin = std.builtin; const os = std.os; const fs = std.fs; const BufMap = std.BufMap; @@ -31,20 +31,16 @@ test "getCwdAlloc" { testing.allocator.free(cwd); } -/// Caller must free result when done. -/// TODO make this go through libc when we have it +/// Caller owns resulting `BufMap`. pub fn getEnvMap(allocator: *Allocator) !BufMap { var result = BufMap.init(allocator); errdefer result.deinit(); if (builtin.os == .windows) { - const ptr = try os.windows.GetEnvironmentStringsW(); - defer os.windows.FreeEnvironmentStringsW(ptr); + const ptr = os.windows.peb().ProcessParameters.Environment; 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 +60,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; @@ -95,15 +92,29 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { } } return result; + } else if (builtin.link_libc) { + var ptr = std.c.environ; + while (ptr.*) |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.set(key, value); + } + return result; } else { - for (os.environ) |ptr| { + for (os.environ) |line| { var line_i: usize = 0; - while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} - const key = ptr[0..line_i]; + 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 (ptr[end_i] != 0) : (end_i += 1) {} - const value = ptr[line_i + 1 .. end_i]; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; try result.set(key, value); } @@ -125,37 +136,20 @@ pub const GetEnvVarOwnedError = error{ }; /// Caller must free returned memory. -/// TODO make this go through libc when we have it 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); - - 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; - } + const result_w = blk: { + const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); + defer allocator.free(key_w); - 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/start.zig b/lib/std/start.zig @@ -21,7 +21,9 @@ comptime { @export(main, .{ .name = "main", .linkage = .Weak }); } } else if (builtin.os == .windows) { - if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) { + if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and + !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) + { @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" }); } } else if (builtin.os == .uefi) { @@ -34,7 +36,11 @@ comptime { } } -fn _DllMainCRTStartup(hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, lpReserved: std.os.windows.LPVOID) callconv(.Stdcall) std.os.windows.BOOL { +fn _DllMainCRTStartup( + hinstDLL: std.os.windows.HINSTANCE, + fdwReason: std.os.windows.DWORD, + lpReserved: std.os.windows.LPVOID, +) callconv(.Stdcall) std.os.windows.BOOL { if (@hasDecl(root, "DllMain")) { return root.DllMain(hinstDLL, fdwReason, lpReserved); } diff --git 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); - - // 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.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"); - - // 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}); - }, + 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. + + 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.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}); } return self; diff --git a/src/os.cpp b/src/os.cpp @@ -81,11 +81,7 @@ static clock_serv_t macos_monotonic_clock; #include <errno.h> #include <time.h> -// Apple doesn't provide the environ global variable -#if defined(__APPLE__) && !defined(environ) -#include <crt_externs.h> -#define environ (*_NSGetEnviron()) -#elif defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) +#if !defined(environ) extern char **environ; #endif