Commit 936d0b18b1

Andrew Kelley <andrew@ziglang.org>
2020-02-22 21:59:13
update std lib to integrate with libc for environ
closes #3511
1 parent 0cd89e9
Changed files (5)
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;
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);
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);
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);
     }
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