authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-23 01:38:03-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-02-23 01:38:03-05:00
logd1243bf272b591a8deba5abedb56050edf3c3d6a
treea9ca11c601631de614c0fdca478f730a2cc4dc01
parent0cd89e9176ab36fc5e267120dc4d75cb79d32684
parent94c3dbf9e3f9fcc63b2495adec36e6b4acb1813e
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #4525 from ziglang/environ

update std lib to integrate with libc for environ

8 files changed, 166 insertions(+), 84 deletions(-)

lib/std/c.zig+2
......@@ -62,6 +62,8 @@ pub fn versionCheck(glibc_version: builtin.Version) type {
6262 };
6363}
6464
65pub extern "c" var environ: [*:null]?[*:0]u8;
66
6567pub extern "c" fn fopen(filename: [*:0]const u8, modes: [*:0]const u8) ?*FILE;
6668pub extern "c" fn fclose(stream: *FILE) c_int;
6769pub extern "c" fn fwrite(ptr: [*]const u8, size_of_type: usize, item_count: usize, stream: *FILE) usize;
lib/std/os.zig+70-5
......@@ -70,6 +70,8 @@ else switch (builtin.os) {
7070pub usingnamespace @import("os/bits.zig");
7171
7272/// See also `getenv`. Populated by startup code before main().
73/// TODO this is a footgun because the value will be undefined when using `zig build-lib`.
74/// https://github.com/ziglang/zig/issues/4524
7375pub var environ: [][*:0]u8 = undefined;
7476
7577/// Populated by startup code before main().
......@@ -922,7 +924,11 @@ pub const execveC = execveZ;
922924/// Like `execve` except the parameters are null-terminated,
923925/// matching the syscall API on all targets. This removes the need for an allocator.
924926/// This function ignores PATH environment variable. See `execvpeZ` for that.
925pub fn execveZ(path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) ExecveError {
927pub fn execveZ(
928 path: [*:0]const u8,
929 child_argv: [*:null]const ?[*:0]const u8,
930 envp: [*:null]const ?[*:0]const u8,
931) ExecveError {
926932 switch (errno(system.execve(path, child_argv, envp))) {
927933 0 => unreachable,
928934 EFAULT => unreachable,
......@@ -966,7 +972,7 @@ pub fn execvpeZ_expandArg0(
966972 envp: [*:null]const ?[*:0]const u8,
967973) ExecveError {
968974 const file_slice = mem.toSliceConst(u8, file);
969 if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp);
975 if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
970976
971977 const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
972978 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
......@@ -993,7 +999,7 @@ pub fn execvpeZ_expandArg0(
993999 .expand => child_argv[0] = full_path,
9941000 .no_expand => {},
9951001 }
996 err = execveC(full_path, child_argv, envp);
1002 err = execveZ(full_path, child_argv, envp);
9971003 switch (err) {
9981004 error.AccessDenied => seen_eacces = true,
9991005 error.FileNotFound, error.NotDir => {},
......@@ -1007,7 +1013,7 @@ pub fn execvpeZ_expandArg0(
10071013/// Like `execvpe` except the parameters are null-terminated,
10081014/// matching the syscall API on all targets. This removes the need for an allocator.
10091015/// This function also uses the PATH environment variable to get the full path to the executable.
1010/// If `file` is an absolute path, this is the same as `execveC`.
1016/// If `file` is an absolute path, this is the same as `execveZ`.
10111017pub fn execvpeZ(
10121018 file: [*:0]const u8,
10131019 argv: [*:null]const ?[*:0]const u8,
......@@ -1097,8 +1103,36 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8)
10971103
10981104/// Get an environment variable.
10991105/// See also `getenvZ`.
1100/// TODO make this go through libc when we have it
11011106pub fn getenv(key: []const u8) ?[]const u8 {
1107 if (builtin.link_libc) {
1108 var small_key_buf: [64]u8 = undefined;
1109 if (key.len < small_key_buf.len) {
1110 mem.copy(u8, &small_key_buf, key);
1111 small_key_buf[key.len] = 0;
1112 const key0 = small_key_buf[0..key.len :0];
1113 return getenvZ(key0);
1114 }
1115 // Search the entire `environ` because we don't have a null terminated pointer.
1116 var ptr = std.c.environ;
1117 while (ptr.*) |line| : (ptr += 1) {
1118 var line_i: usize = 0;
1119 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
1120 const this_key = line[0..line_i];
1121
1122 if (!mem.eql(u8, this_key, key)) continue;
1123
1124 var end_i: usize = line_i;
1125 while (line[end_i] != 0) : (end_i += 1) {}
1126 const value = line[line_i + 1 .. end_i];
1127
1128 return value;
1129 }
1130 return null;
1131 }
1132 if (builtin.os == .windows) {
1133 @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.");
1134 }
1135 // TODO see https://github.com/ziglang/zig/issues/4524
11021136 for (environ) |ptr| {
11031137 var line_i: usize = 0;
11041138 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 {
11241158 const value = system.getenv(key) orelse return null;
11251159 return mem.toSliceConst(u8, value);
11261160 }
1161 if (builtin.os == .windows) {
1162 @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.");
1163 }
11271164 return getenv(mem.toSliceConst(u8, key));
11281165}
11291166
1167/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
1168/// See also `getenv`.
1169pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
1170 if (builtin.os != .windows) {
1171 @compileError("std.os.getenvW is a Windows-only API");
1172 }
1173 const key_slice = mem.toSliceConst(u16, key);
1174 const ptr = windows.peb().ProcessParameters.Environment;
1175 var i: usize = 0;
1176 while (ptr[i] != 0) {
1177 const key_start = i;
1178
1179 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
1180 const this_key = ptr[key_start..i];
1181
1182 if (ptr[i] == '=') i += 1;
1183
1184 const value_start = i;
1185 while (ptr[i] != 0) : (i += 1) {}
1186 const this_value = ptr[value_start..i :0];
1187
1188 if (mem.eql(u16, key_slice, this_key)) return this_value;
1189
1190 i += 1; // skip over null byte
1191 }
1192 return null;
1193}
1194
11301195pub const GetCwdError = error{
11311196 NameTooLong,
11321197 CurrentWorkingDirectoryUnlinked,
lib/std/os/test.zig+8
......@@ -351,3 +351,11 @@ test "mmap" {
351351
352352 try fs.cwd().deleteFile(test_out_file);
353353}
354
355test "getenv" {
356 if (builtin.os == .windows) {
357 expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null);
358 } else {
359 expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null);
360 }
361}
lib/std/os/windows/bits.zig+1-1
......@@ -1187,7 +1187,7 @@ pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
11871187 DllPath: UNICODE_STRING,
11881188 ImagePathName: UNICODE_STRING,
11891189 CommandLine: UNICODE_STRING,
1190 Environment: [*]WCHAR,
1190 Environment: [*:0]WCHAR,
11911191 dwX: ULONG,
11921192 dwY: ULONG,
11931193 dwXSize: ULONG,
lib/std/process.zig+35-41
......@@ -1,5 +1,5 @@
1const builtin = @import("builtin");
21const std = @import("std.zig");
2const builtin = std.builtin;
33const os = std.os;
44const fs = std.fs;
55const BufMap = std.BufMap;
......@@ -31,20 +31,16 @@ test "getCwdAlloc" {
3131 testing.allocator.free(cwd);
3232}
3333
34/// Caller must free result when done.
35/// TODO make this go through libc when we have it
34/// Caller owns resulting `BufMap`.
3635pub fn getEnvMap(allocator: *Allocator) !BufMap {
3736 var result = BufMap.init(allocator);
3837 errdefer result.deinit();
3938
4039 if (builtin.os == .windows) {
41 const ptr = try os.windows.GetEnvironmentStringsW();
42 defer os.windows.FreeEnvironmentStringsW(ptr);
40 const ptr = os.windows.peb().ProcessParameters.Environment;
4341
4442 var i: usize = 0;
45 while (true) {
46 if (ptr[i] == 0) return result;
47
43 while (ptr[i] != 0) {
4844 const key_start = i;
4945
5046 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
......@@ -64,6 +60,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
6460
6561 try result.setMove(key, value);
6662 }
63 return result;
6764 } else if (builtin.os == .wasi) {
6865 var environ_count: usize = undefined;
6966 var environ_buf_size: usize = undefined;
......@@ -95,15 +92,29 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
9592 }
9693 }
9794 return result;
95 } else if (builtin.link_libc) {
96 var ptr = std.c.environ;
97 while (ptr.*) |line| : (ptr += 1) {
98 var line_i: usize = 0;
99 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
100 const key = line[0..line_i];
101
102 var end_i: usize = line_i;
103 while (line[end_i] != 0) : (end_i += 1) {}
104 const value = line[line_i + 1 .. end_i];
105
106 try result.set(key, value);
107 }
108 return result;
98109 } else {
99 for (os.environ) |ptr| {
110 for (os.environ) |line| {
100111 var line_i: usize = 0;
101 while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
102 const key = ptr[0..line_i];
112 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
113 const key = line[0..line_i];
103114
104115 var end_i: usize = line_i;
105 while (ptr[end_i] != 0) : (end_i += 1) {}
106 const value = ptr[line_i + 1 .. end_i];
116 while (line[end_i] != 0) : (end_i += 1) {}
117 const value = line[line_i + 1 .. end_i];
107118
108119 try result.set(key, value);
109120 }
......@@ -125,37 +136,20 @@ pub const GetEnvVarOwnedError = error{
125136};
126137
127138/// Caller must free returned memory.
128/// TODO make this go through libc when we have it
129139pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
130140 if (builtin.os == .windows) {
131 const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
132 defer allocator.free(key_with_null);
133
134 var buf = try allocator.alloc(u16, 256);
135 defer allocator.free(buf);
136
137 while (true) {
138 const windows_buf_len = math.cast(os.windows.DWORD, buf.len) catch return error.OutOfMemory;
139 const result = os.windows.GetEnvironmentVariableW(
140 key_with_null.ptr,
141 buf.ptr,
142 windows_buf_len,
143 ) catch |err| switch (err) {
144 error.Unexpected => return error.EnvironmentVariableNotFound,
145 else => |e| return e,
146 };
147 if (result > buf.len) {
148 buf = try allocator.realloc(buf, result);
149 continue;
150 }
141 const result_w = blk: {
142 const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
143 defer allocator.free(key_w);
151144
152 return std.unicode.utf16leToUtf8Alloc(allocator, buf[0..result]) catch |err| switch (err) {
153 error.DanglingSurrogateHalf => return error.InvalidUtf8,
154 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
155 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
156 else => |e| return e,
157 };
158 }
145 break :blk std.os.getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
146 };
147 return std.unicode.utf16leToUtf8Alloc(allocator, result_w) catch |err| switch (err) {
148 error.DanglingSurrogateHalf => return error.InvalidUtf8,
149 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
150 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
151 else => |e| return e,
152 };
159153 } else {
160154 const result = os.getenv(key) orelse return error.EnvironmentVariableNotFound;
161155 return mem.dupe(allocator, u8, result);
lib/std/start.zig+8-2
......@@ -21,7 +21,9 @@ comptime {
2121 @export(main, .{ .name = "main", .linkage = .Weak });
2222 }
2323 } else if (builtin.os == .windows) {
24 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) {
24 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
25 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
26 {
2527 @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" });
2628 }
2729 } else if (builtin.os == .uefi) {
......@@ -34,7 +36,11 @@ comptime {
3436 }
3537}
3638
37fn _DllMainCRTStartup(hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, lpReserved: std.os.windows.LPVOID) callconv(.Stdcall) std.os.windows.BOOL {
39fn _DllMainCRTStartup(
40 hinstDLL: std.os.windows.HINSTANCE,
41 fdwReason: std.os.windows.DWORD,
42 lpReserved: std.os.windows.LPVOID,
43) callconv(.Stdcall) std.os.windows.BOOL {
3844 if (@hasDecl(root, "DllMain")) {
3945 return root.DllMain(hinstDLL, fdwReason, lpReserved);
4046 }
lib/std/zig/system.zig+41-30
......@@ -5,6 +5,8 @@ const ArrayList = std.ArrayList;
55const assert = std.debug.assert;
66const process = std.process;
77
8const is_windows = std.Target.current.isWindows();
9
810pub const NativePaths = struct {
911 include_dirs: ArrayList([:0]u8),
1012 lib_dirs: ArrayList([:0]u8),
......@@ -21,7 +23,9 @@ pub const NativePaths = struct {
2123 errdefer self.deinit();
2224
2325 var is_nix = false;
24 if (std.os.getenvZ("NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
26 if (process.getEnvVarOwned(allocator, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
27 defer allocator.free(nix_cflags_compile);
28
2529 is_nix = true;
2630 var it = mem.tokenize(nix_cflags_compile, " ");
2731 while (true) {
......@@ -37,8 +41,14 @@ pub const NativePaths = struct {
3741 break;
3842 }
3943 }
44 } else |err| switch (err) {
45 error.InvalidUtf8 => {},
46 error.EnvironmentVariableNotFound => {},
47 error.OutOfMemory => |e| return e,
4048 }
41 if (std.os.getenvZ("NIX_LDFLAGS")) |nix_ldflags| {
49 if (process.getEnvVarOwned(allocator, "NIX_LDFLAGS")) |nix_ldflags| {
50 defer allocator.free(nix_ldflags);
51
4252 is_nix = true;
4353 var it = mem.tokenize(nix_ldflags, " ");
4454 while (true) {
......@@ -57,39 +67,40 @@ pub const NativePaths = struct {
5767 break;
5868 }
5969 }
70 } else |err| switch (err) {
71 error.InvalidUtf8 => {},
72 error.EnvironmentVariableNotFound => {},
73 error.OutOfMemory => |e| return e,
6074 }
6175 if (is_nix) {
6276 return self;
6377 }
6478
65 switch (std.builtin.os) {
66 .windows => {},
67 else => {
68 const triple = try std.Target.current.linuxTriple(allocator);
69
70 // TODO: $ ld --verbose | grep SEARCH_DIR
71 // the output contains some paths that end with lib64, maybe include them too?
72 // TODO: what is the best possible order of things?
73 // TODO: some of these are suspect and should only be added on some systems. audit needed.
74
75 try self.addIncludeDir("/usr/local/include");
76 try self.addLibDir("/usr/local/lib");
77 try self.addLibDir("/usr/local/lib64");
78
79 try self.addIncludeDirFmt("/usr/include/{}", .{triple});
80 try self.addLibDirFmt("/usr/lib/{}", .{triple});
81
82 try self.addIncludeDir("/usr/include");
83 try self.addLibDir("/lib");
84 try self.addLibDir("/lib64");
85 try self.addLibDir("/usr/lib");
86 try self.addLibDir("/usr/lib64");
87
88 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
89 // zlib.h is in /usr/include (added above)
90 // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
91 try self.addLibDirFmt("/lib/{}", .{triple});
92 },
79 if (!is_windows) {
80 const triple = try std.Target.current.linuxTriple(allocator);
81
82 // TODO: $ ld --verbose | grep SEARCH_DIR
83 // the output contains some paths that end with lib64, maybe include them too?
84 // TODO: what is the best possible order of things?
85 // TODO: some of these are suspect and should only be added on some systems. audit needed.
86
87 try self.addIncludeDir("/usr/local/include");
88 try self.addLibDir("/usr/local/lib");
89 try self.addLibDir("/usr/local/lib64");
90
91 try self.addIncludeDirFmt("/usr/include/{}", .{triple});
92 try self.addLibDirFmt("/usr/lib/{}", .{triple});
93
94 try self.addIncludeDir("/usr/include");
95 try self.addLibDir("/lib");
96 try self.addLibDir("/lib64");
97 try self.addLibDir("/usr/lib");
98 try self.addLibDir("/usr/lib64");
99
100 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
101 // zlib.h is in /usr/include (added above)
102 // libz.so.1 is in /lib/x86_64-linux-gnu (added here)
103 try self.addLibDirFmt("/lib/{}", .{triple});
93104 }
94105
95106 return self;
src/os.cpp+1-5
......@@ -81,11 +81,7 @@ static clock_serv_t macos_monotonic_clock;
8181#include <errno.h>
8282#include <time.h>
8383
84// Apple doesn't provide the environ global variable
85#if defined(__APPLE__) && !defined(environ)
86#include <crt_externs.h>
87#define environ (*_NSGetEnviron())
88#elif defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD) || defined(ZIG_OS_DRAGONFLY)
84#if !defined(environ)
8985extern char **environ;
9086#endif
9187