From 936d0b18b116ea126893d2f165f9be72f8bef845 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 22 Feb 2020 15:59:13 -0500 Subject: [PATCH] update std lib to integrate with libc for environ closes #3511 --- lib/std/c.zig | 2 ++ lib/std/os.zig | 49 ++++++++++++++++++++++++++++++++++++++++----- lib/std/process.zig | 31 +++++++++++++++++++--------- lib/std/start.zig | 10 +++++++-- src/os.cpp | 6 +----- 5 files changed, 77 insertions(+), 21 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 93c6c246641f6b5183f14fb450ed6613e31a40cd..f6c0e07dbd94c8421343dac74c6ce89c45b15034 100644 --- 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 index 766b678c46b704929806919053f1155f62a944fb..a4f148ae00c2be5506589dcaa962371820799069 100644 --- 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,37 @@ 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.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) { + 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; + } + // 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) {} @@ -1120,6 +1155,10 @@ 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); diff --git a/lib/std/process.zig b/lib/std/process.zig index 89307b44da66d49f57bb347ec7c83f64d1ba44cd..ba124ff262a2bfafab3a5ed8ddbdf54d4026ab26 100644 --- 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,13 +31,13 @@ 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) { + // TODO update this to use the ProcessEnvironmentBlock const ptr = try os.windows.GetEnvironmentStringsW(); defer os.windows.FreeEnvironmentStringsW(ptr); @@ -95,15 +95,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,7 +139,6 @@ 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); diff --git a/lib/std/start.zig b/lib/std/start.zig index e6c21ba9b8af4c7a09f27957983d200f33731fd5..b58b6e8144354e475e8e83bcdc12c94b414a8f18 100644 --- 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/src/os.cpp b/src/os.cpp index 268e8120502d10a67d119ec91e17f26a4944acdf..8e0bcc433b89622772dc0d3ce5494c1d31623344 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -81,11 +81,7 @@ static clock_serv_t macos_monotonic_clock; #include #include -// Apple doesn't provide the environ global variable -#if defined(__APPLE__) && !defined(environ) -#include -#define environ (*_NSGetEnviron()) -#elif defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY) +#if !defined(environ) extern char **environ; #endif -- 2.54.0