Merge pull request #4525 from ziglang/environ

update std lib to integrate with libc for environ
This commit is contained in:
Andrew Kelley 2020-02-23 01:38:03 -05:00 committed by GitHub
commit d1243bf272
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 162 additions and 80 deletions

View File

@ -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;

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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 {
for (os.environ) |ptr| {
} else if (builtin.link_libc) {
var ptr = std.c.environ;
while (ptr.*) |line| : (ptr += 1) {
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);
}
return result;
} else {
for (os.environ) |line| {
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);
}
@ -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);
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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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