diff options
Diffstat (limited to 'lib/std/process.zig')
| -rw-r--r-- | lib/std/process.zig | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/lib/std/process.zig b/lib/std/process.zig new file mode 100644 index 0000000000..c74e8c43be --- /dev/null +++ b/lib/std/process.zig @@ -0,0 +1,616 @@ +const builtin = @import("builtin"); +const std = @import("std.zig"); +const os = std.os; +const fs = std.fs; +const BufMap = std.BufMap; +const Buffer = std.Buffer; +const mem = std.mem; +const math = std.math; +const Allocator = mem.Allocator; +const assert = std.debug.assert; +const testing = std.testing; + +pub const abort = os.abort; +pub const exit = os.exit; +pub const changeCurDir = os.chdir; +pub const changeCurDirC = os.chdirC; + +/// The result is a slice of `out_buffer`, from index `0`. +pub fn getCwd(out_buffer: *[fs.MAX_PATH_BYTES]u8) ![]u8 { + return os.getcwd(out_buffer); +} + +/// Caller must free the returned memory. +pub fn getCwdAlloc(allocator: *Allocator) ![]u8 { + var buf: [fs.MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try os.getcwd(&buf)); +} + +test "getCwdAlloc" { + // at least call it so it gets compiled + var buf: [1000]u8 = undefined; + const allocator = &std.heap.FixedBufferAllocator.init(&buf).allocator; + _ = getCwdAlloc(allocator) catch undefined; +} + +/// Caller must free result when done. +/// TODO make this go through libc when we have it +pub fn getEnvMap(allocator: *Allocator) !BufMap { + var result = BufMap.init(allocator); + errdefer result.deinit(); + + if (os.windows.is_the_target) { + const ptr = try os.windows.GetEnvironmentStringsW(); + defer os.windows.FreeEnvironmentStringsW(ptr); + + var i: usize = 0; + while (true) { + if (ptr[i] == 0) return result; + + const key_start = i; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w); + errdefer allocator.free(key); + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const value_w = ptr[value_start..i]; + const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w); + errdefer allocator.free(value); + + i += 1; // skip over null byte + + try result.setMove(key, value); + } + } else if (builtin.os == .wasi) { + var environ_count: usize = undefined; + var environ_buf_size: usize = undefined; + + const environ_sizes_get_ret = os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); + if (environ_sizes_get_ret != os.wasi.ESUCCESS) { + return os.unexpectedErrno(environ_sizes_get_ret); + } + + // TODO: Verify that the documentation is incorrect + // https://github.com/WebAssembly/WASI/issues/27 + var environ = try allocator.alloc(?[*]u8, environ_count + 1); + defer allocator.free(environ); + var environ_buf = try std.heap.wasm_allocator.alloc(u8, environ_buf_size); + defer allocator.free(environ_buf); + + const environ_get_ret = os.wasi.environ_get(environ.ptr, environ_buf.ptr); + if (environ_get_ret != os.wasi.ESUCCESS) { + return os.unexpectedErrno(environ_get_ret); + } + + for (environ) |env| { + if (env) |ptr| { + const pair = mem.toSlice(u8, ptr); + var parts = mem.separate(pair, "="); + const key = parts.next().?; + const value = parts.next().?; + try result.set(key, value); + } + } + return result; + } else { + for (os.environ) |ptr| { + var line_i: usize = 0; + while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} + const key = ptr[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]; + + try result.set(key, value); + } + return result; + } +} + +test "os.getEnvMap" { + var env = try getEnvMap(std.debug.global_allocator); + defer env.deinit(); +} + +pub const GetEnvVarOwnedError = error{ + OutOfMemory, + EnvironmentVariableNotFound, + + /// See https://github.com/ziglang/zig/issues/1774 + InvalidUtf8, +}; + +/// 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 (os.windows.is_the_target) { + 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; + } + + 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, + }; + } + } else { + const result = os.getenv(key) orelse return error.EnvironmentVariableNotFound; + return mem.dupe(allocator, u8, result); + } +} + +test "os.getEnvVarOwned" { + var ga = std.debug.global_allocator; + testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV")); +} + +pub const ArgIteratorPosix = struct { + index: usize, + count: usize, + + pub fn init() ArgIteratorPosix { + return ArgIteratorPosix{ + .index = 0, + .count = os.argv.len, + }; + } + + pub fn next(self: *ArgIteratorPosix) ?[]const u8 { + if (self.index == self.count) return null; + + const s = os.argv[self.index]; + self.index += 1; + return mem.toSlice(u8, s); + } + + pub fn skip(self: *ArgIteratorPosix) bool { + if (self.index == self.count) return false; + + self.index += 1; + return true; + } +}; + +pub const ArgIteratorWindows = struct { + index: usize, + cmd_line: [*]const u8, + in_quote: bool, + quote_count: usize, + seen_quote_count: usize, + + pub const NextError = error{OutOfMemory}; + + pub fn init() ArgIteratorWindows { + return initWithCmdLine(os.windows.kernel32.GetCommandLineA()); + } + + pub fn initWithCmdLine(cmd_line: [*]const u8) ArgIteratorWindows { + return ArgIteratorWindows{ + .index = 0, + .cmd_line = cmd_line, + .in_quote = false, + .quote_count = countQuotes(cmd_line), + .seen_quote_count = 0, + }; + } + + /// You must free the returned memory when done. + pub fn next(self: *ArgIteratorWindows, allocator: *Allocator) ?(NextError![]u8) { + // march forward over whitespace + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return null, + ' ', '\t' => continue, + else => break, + } + } + + return self.internalNext(allocator); + } + + pub fn skip(self: *ArgIteratorWindows) bool { + // march forward over whitespace + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return false, + ' ', '\t' => continue, + else => break, + } + } + + var backslash_count: usize = 0; + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return true, + '"' => { + const quote_is_real = backslash_count % 2 == 0; + if (quote_is_real) { + self.seen_quote_count += 1; + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t' => { + if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) { + return true; + } + backslash_count = 0; + }, + else => { + backslash_count = 0; + continue; + }, + } + } + } + + fn internalNext(self: *ArgIteratorWindows, allocator: *Allocator) NextError![]u8 { + var buf = try Buffer.initSize(allocator, 0); + defer buf.deinit(); + + var backslash_count: usize = 0; + while (true) : (self.index += 1) { + const byte = self.cmd_line[self.index]; + switch (byte) { + 0 => return buf.toOwnedSlice(), + '"' => { + const quote_is_real = backslash_count % 2 == 0; + try self.emitBackslashes(&buf, backslash_count / 2); + backslash_count = 0; + + if (quote_is_real) { + self.seen_quote_count += 1; + if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) { + try buf.appendByte('"'); + } + } else { + try buf.appendByte('"'); + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t' => { + try self.emitBackslashes(&buf, backslash_count); + backslash_count = 0; + if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) { + try buf.appendByte(byte); + } else { + return buf.toOwnedSlice(); + } + }, + else => { + try self.emitBackslashes(&buf, backslash_count); + backslash_count = 0; + try buf.appendByte(byte); + }, + } + } + } + + fn emitBackslashes(self: *ArgIteratorWindows, buf: *Buffer, emit_count: usize) !void { + var i: usize = 0; + while (i < emit_count) : (i += 1) { + try buf.appendByte('\\'); + } + } + + fn countQuotes(cmd_line: [*]const u8) usize { + var result: usize = 0; + var backslash_count: usize = 0; + var index: usize = 0; + while (true) : (index += 1) { + const byte = cmd_line[index]; + switch (byte) { + 0 => return result, + '\\' => backslash_count += 1, + '"' => { + result += 1 - (backslash_count % 2); + backslash_count = 0; + }, + else => { + backslash_count = 0; + }, + } + } + } +}; + +pub const ArgIterator = struct { + const InnerType = if (builtin.os == .windows) ArgIteratorWindows else ArgIteratorPosix; + + inner: InnerType, + + pub fn init() ArgIterator { + if (builtin.os == .wasi) { + // TODO: Figure out a compatible interface accomodating WASI + @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead."); + } + + return ArgIterator{ .inner = InnerType.init() }; + } + + pub const NextError = ArgIteratorWindows.NextError; + + /// You must free the returned memory when done. + pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) { + if (builtin.os == .windows) { + return self.inner.next(allocator); + } else { + return mem.dupe(allocator, u8, self.inner.next() orelse return null); + } + } + + /// If you only are targeting posix you can call this and not need an allocator. + pub fn nextPosix(self: *ArgIterator) ?[]const u8 { + return self.inner.next(); + } + + /// Parse past 1 argument without capturing it. + /// Returns `true` if skipped an arg, `false` if we are at the end. + pub fn skip(self: *ArgIterator) bool { + return self.inner.skip(); + } +}; + +pub fn args() ArgIterator { + return ArgIterator.init(); +} + +/// Caller must call argsFree on result. +pub fn argsAlloc(allocator: *mem.Allocator) ![][]u8 { + if (builtin.os == .wasi) { + var count: usize = undefined; + var buf_size: usize = undefined; + + const args_sizes_get_ret = os.wasi.args_sizes_get(&count, &buf_size); + if (args_sizes_get_ret != os.wasi.ESUCCESS) { + return os.unexpectedErrno(args_sizes_get_ret); + } + + var argv = try allocator.alloc([*]u8, count); + defer allocator.free(argv); + + var argv_buf = try allocator.alloc(u8, buf_size); + const args_get_ret = os.wasi.args_get(argv.ptr, argv_buf.ptr); + if (args_get_ret != os.wasi.ESUCCESS) { + return os.unexpectedErrno(args_get_ret); + } + + var result_slice = try allocator.alloc([]u8, count); + + var i: usize = 0; + while (i < count) : (i += 1) { + result_slice[i] = mem.toSlice(u8, argv[i]); + } + + return result_slice; + } + + // TODO refactor to only make 1 allocation. + var it = args(); + var contents = try Buffer.initSize(allocator, 0); + defer contents.deinit(); + + var slice_list = std.ArrayList(usize).init(allocator); + defer slice_list.deinit(); + + while (it.next(allocator)) |arg_or_err| { + const arg = try arg_or_err; + defer allocator.free(arg); + try contents.append(arg); + try slice_list.append(arg.len); + } + + const contents_slice = contents.toSliceConst(); + const slice_sizes = slice_list.toSliceConst(); + const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len); + const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len); + const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes); + errdefer allocator.free(buf); + + const result_slice_list = @bytesToSlice([]u8, buf[0..slice_list_bytes]); + const result_contents = buf[slice_list_bytes..]; + mem.copy(u8, result_contents, contents_slice); + + var contents_index: usize = 0; + for (slice_sizes) |len, i| { + const new_index = contents_index + len; + result_slice_list[i] = result_contents[contents_index..new_index]; + contents_index = new_index; + } + + return result_slice_list; +} + +pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void { + if (builtin.os == .wasi) { + const last_item = args_alloc[args_alloc.len - 1]; + const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated + const first_item_ptr = args_alloc[0].ptr; + const len = last_byte_addr - @ptrToInt(first_item_ptr); + allocator.free(first_item_ptr[0..len]); + + return allocator.free(args_alloc); + } + + var total_bytes: usize = 0; + for (args_alloc) |arg| { + total_bytes += @sizeOf([]u8) + arg.len; + } + const unaligned_allocated_buf = @ptrCast([*]const u8, args_alloc.ptr)[0..total_bytes]; + const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf); + return allocator.free(aligned_allocated_buf); +} + +test "windows arg parsing" { + testWindowsCmdLine(c"a b\tc d", [_][]const u8{ "a", "b", "c", "d" }); + testWindowsCmdLine(c"\"abc\" d e", [_][]const u8{ "abc", "d", "e" }); + testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [_][]const u8{ "a\\\\\\b", "de fg", "h" }); + testWindowsCmdLine(c"a\\\\\\\"b c d", [_][]const u8{ "a\\\"b", "c", "d" }); + testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [_][]const u8{ "a\\\\b c", "d", "e" }); + testWindowsCmdLine(c"a b\tc \"d f", [_][]const u8{ "a", "b", "c", "\"d", "f" }); + + testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [_][]const u8{ + ".\\..\\zig-cache\\build", + "bin\\zig.exe", + ".\\..", + ".\\..\\zig-cache", + "--help", + }); +} + +fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void { + var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line); + for (expected_args) |expected_arg| { + const arg = it.next(std.debug.global_allocator).? catch unreachable; + testing.expectEqualSlices(u8, expected_arg, arg); + } + testing.expect(it.next(std.debug.global_allocator) == null); +} + +pub const UserInfo = struct { + uid: u32, + gid: u32, +}; + +/// POSIX function which gets a uid from username. +pub fn getUserInfo(name: []const u8) !UserInfo { + return switch (builtin.os) { + .linux, .macosx, .watchos, .tvos, .ios, .freebsd, .netbsd => posixGetUserInfo(name), + else => @compileError("Unsupported OS"), + }; +} + +/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else +/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`. +pub fn posixGetUserInfo(name: []const u8) !UserInfo { + var in_stream = try io.InStream.open("/etc/passwd", null); + defer in_stream.close(); + + const State = enum { + Start, + WaitForNextLine, + SkipPassword, + ReadUserId, + ReadGroupId, + }; + + var buf: [std.mem.page_size]u8 = undefined; + var name_index: usize = 0; + var state = State.Start; + var uid: u32 = 0; + var gid: u32 = 0; + + while (true) { + const amt_read = try in_stream.read(buf[0..]); + for (buf[0..amt_read]) |byte| { + switch (state) { + .Start => switch (byte) { + ':' => { + state = if (name_index == name.len) State.SkipPassword else State.WaitForNextLine; + }, + '\n' => return error.CorruptPasswordFile, + else => { + if (name_index == name.len or name[name_index] != byte) { + state = .WaitForNextLine; + } + name_index += 1; + }, + }, + .WaitForNextLine => switch (byte) { + '\n' => { + name_index = 0; + state = .Start; + }, + else => continue, + }, + .SkipPassword => switch (byte) { + '\n' => return error.CorruptPasswordFile, + ':' => { + state = .ReadUserId; + }, + else => continue, + }, + .ReadUserId => switch (byte) { + ':' => { + state = .ReadGroupId; + }, + '\n' => return error.CorruptPasswordFile, + else => { + const digit = switch (byte) { + '0'...'9' => byte - '0', + else => return error.CorruptPasswordFile, + }; + if (@mulWithOverflow(u32, uid, 10, *uid)) return error.CorruptPasswordFile; + if (@addWithOverflow(u32, uid, digit, *uid)) return error.CorruptPasswordFile; + }, + }, + .ReadGroupId => switch (byte) { + '\n', ':' => { + return UserInfo{ + .uid = uid, + .gid = gid, + }; + }, + else => { + const digit = switch (byte) { + '0'...'9' => byte - '0', + else => return error.CorruptPasswordFile, + }; + if (@mulWithOverflow(u32, gid, 10, *gid)) return error.CorruptPasswordFile; + if (@addWithOverflow(u32, gid, digit, *gid)) return error.CorruptPasswordFile; + }, + }, + } + } + if (amt_read < buf.len) return error.UserNotFound; + } +} + +pub fn getBaseAddress() usize { + switch (builtin.os) { + .linux => { + const base = os.system.getauxval(std.elf.AT_BASE); + if (base != 0) { + return base; + } + const phdr = os.system.getauxval(std.elf.AT_PHDR); + return phdr - @sizeOf(std.elf.Ehdr); + }, + .macosx, .freebsd, .netbsd => { + return @ptrToInt(&std.c._mh_execute_header); + }, + .windows => return @ptrToInt(os.windows.kernel32.GetModuleHandleW(null)), + else => @compileError("Unsupported OS"), + } +} |
