implement std.os.ChildProcess for windows

This commit is contained in:
Andrew Kelley 2017-10-13 09:31:03 -04:00
parent 7f9dc4ebc1
commit 8d3eaab871
8 changed files with 533 additions and 69 deletions

View file

@ -18,7 +18,7 @@ clarity.
writing buggy code. writing buggy code.
* Debug mode optimizes for fast compilation time and crashing with a stack trace * Debug mode optimizes for fast compilation time and crashing with a stack trace
when undefined behavior *would* happen. when undefined behavior *would* happen.
* Release mode produces heavily optimized code. What other projects call * ReleaseFast mode produces heavily optimized code. What other projects call
"Link Time Optimization" Zig does automatically. "Link Time Optimization" Zig does automatically.
* Compatible with C libraries with no wrapper necessary. Directly include * Compatible with C libraries with no wrapper necessary. Directly include
C .h files and get access to the functions and symbols therein. C .h files and get access to the functions and symbols therein.
@ -36,16 +36,13 @@ clarity.
a preprocessor or macros. a preprocessor or macros.
* The binaries produced by Zig have complete debugging information so you can, * The binaries produced by Zig have complete debugging information so you can,
for example, use GDB to debug your software. for example, use GDB to debug your software.
* Mark functions as tests and automatically run them with `zig test`. * Built-in unit tests with `zig test`.
* Friendly toward package maintainers. Reproducible build, bootstrapping * Friendly toward package maintainers. Reproducible build, bootstrapping
process carefully documented. Issues filed by package maintainers are process carefully documented. Issues filed by package maintainers are
considered especially important. considered especially important.
* Cross-compiling is a primary use case. * Cross-compiling is a primary use case.
* In addition to creating executables, creating a C library is a primary use * In addition to creating executables, creating a C library is a primary use
case. You can export an auto-generated .h file. case. You can export an auto-generated .h file.
* For OS development, Zig supports all architectures that LLVM does. All the
standard library that does not depend on an OS is available to you in
freestanding mode.
### Support Table ### Support Table

View file

@ -7,9 +7,9 @@ pub const BufSet = struct {
const BufSetHashMap = HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); const BufSetHashMap = HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
pub fn init(allocator: &Allocator) -> BufSet { pub fn init(a: &Allocator) -> BufSet {
var self = BufSet { var self = BufSet {
.hash_map = BufSetHashMap.init(allocator), .hash_map = BufSetHashMap.init(a),
}; };
return self; return self;
} }
@ -45,6 +45,10 @@ pub const BufSet = struct {
return self.hash_map.iterator(); return self.hash_map.iterator();
} }
pub fn allocator(self: &const BufSet) -> &Allocator {
return self.hash_map.allocator;
}
fn free(self: &BufSet, value: []const u8) { fn free(self: &BufSet, value: []const u8) {
// remove the const // remove the const
const mut_value = @ptrCast(&u8, value.ptr)[0..value.len]; const mut_value = @ptrCast(&u8, value.ptr)[0..value.len];

View file

@ -91,8 +91,17 @@ pub const Buffer = struct {
} }
pub fn appendByte(self: &Buffer, byte: u8) -> %void { pub fn appendByte(self: &Buffer, byte: u8) -> %void {
%return self.resize(self.len() + 1); return self.appendByteNTimes(byte, 1);
self.list.items[self.len() - 1] = byte; }
pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) -> %void {
var prev_size: usize = self.len();
%return self.resize(prev_size + count);
var i: usize = 0;
while (i < count) : (i += 1) {
self.list.items[prev_size + i] = byte;
}
} }
pub fn eql(self: &const Buffer, m: []const u8) -> bool { pub fn eql(self: &const Buffer, m: []const u8) -> bool {

View file

@ -1,4 +1,5 @@
const debug = @import("debug.zig"); const debug = @import("debug.zig");
const mem = @import("mem.zig");
const assert = debug.assert; const assert = debug.assert;
pub fn len(ptr: &const u8) -> usize { pub fn len(ptr: &const u8) -> usize {
@ -36,3 +37,13 @@ fn testCStrFnsImpl() {
assert(cmp(c"aoeu", c"aoez") == -1); assert(cmp(c"aoeu", c"aoez") == -1);
assert(len(c"123456789") == 9); assert(len(c"123456789") == 9);
} }
/// Returns a mutable slice with exactly the same size which is guaranteed to
/// have a null byte after it.
/// Caller owns the returned memory.
pub fn addNullByte(allocator: &mem.Allocator, slice: []const u8) -> %[]u8 {
const result = %return allocator.alloc(u8, slice.len + 1);
mem.copy(u8, result, slice);
result[slice.len] = 0;
return result[0..slice.len];
}

View file

@ -254,6 +254,21 @@ pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize,
return null; return null;
} }
pub fn indexOfAny(comptime T: type, slice: []const T, values: []const T) -> ?usize {
return indexOfAnyPos(T, slice, 0, values);
}
pub fn indexOfAnyPos(comptime T: type, slice: []const T, start_index: usize, values: []const T) -> ?usize {
var i: usize = start_index;
while (i < slice.len) : (i += 1) {
for (values) |value| {
if (slice[i] == value)
return i;
}
}
return null;
}
pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize { pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {
return indexOfPos(T, haystack, 0, needle); return indexOfPos(T, haystack, 0, needle);
} }

View file

@ -1,22 +1,30 @@
const io = @import("../io.zig"); const std = @import("../index.zig");
const os = @import("index.zig"); const cstr = std.cstr;
const io = std.io;
const os = std.os;
const posix = os.posix; const posix = os.posix;
const mem = @import("../mem.zig"); const windows = os.windows;
const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const debug = @import("../debug.zig"); const debug = std.debug;
const assert = debug.assert; const assert = debug.assert;
const BufMap = @import("../buf_map.zig").BufMap; const BufMap = std.BufMap;
const Buffer = std.Buffer;
const builtin = @import("builtin"); const builtin = @import("builtin");
const Os = builtin.Os; const Os = builtin.Os;
const LinkedList = @import("../linked_list.zig").LinkedList; const LinkedList = std.LinkedList;
error PermissionDenied; error PermissionDenied;
error ProcessNotFound; error ProcessNotFound;
var children_nodes = LinkedList(&ChildProcess).init(); var children_nodes = LinkedList(&ChildProcess).init();
const is_windows = builtin.os == Os.windows;
pub const ChildProcess = struct { pub const ChildProcess = struct {
pub pid: i32, pub pid: if (is_windows) void else i32,
pub handle: if (is_windows) windows.HANDLE else void,
pub allocator: &mem.Allocator, pub allocator: &mem.Allocator,
pub stdin: ?&io.OutStream, pub stdin: ?&io.OutStream,
@ -38,16 +46,16 @@ pub const ChildProcess = struct {
pub stderr_behavior: StdIo, pub stderr_behavior: StdIo,
/// Set to change the user id when spawning the child process. /// Set to change the user id when spawning the child process.
pub uid: ?u32, pub uid: if (is_windows) void else ?u32,
/// Set to change the group id when spawning the child process. /// Set to change the group id when spawning the child process.
pub gid: ?u32, pub gid: if (is_windows) void else ?u32,
/// Set to change the current working directory when spawning the child process. /// Set to change the current working directory when spawning the child process.
pub cwd: ?[]const u8, pub cwd: ?[]const u8,
err_pipe: [2]i32, err_pipe: if (is_windows) void else [2]i32,
llnode: LinkedList(&ChildProcess).Node, llnode: if (is_windows) void else LinkedList(&ChildProcess).Node,
pub const Term = enum { pub const Term = enum {
Exited: i32, Exited: i32,
@ -73,14 +81,15 @@ pub const ChildProcess = struct {
.allocator = allocator, .allocator = allocator,
.argv = argv, .argv = argv,
.pid = undefined, .pid = undefined,
.handle = undefined,
.err_pipe = undefined, .err_pipe = undefined,
.llnode = undefined, .llnode = undefined,
.term = null, .term = null,
.onTerm = null, .onTerm = null,
.env_map = null, .env_map = null,
.cwd = null, .cwd = null,
.uid = null, .uid = if (is_windows) {} else null,
.gid = null, .gid = if (is_windows) {} else null,
.stdin = null, .stdin = null,
.stdout = null, .stdout = null,
.stderr = null, .stderr = null,
@ -101,9 +110,10 @@ pub const ChildProcess = struct {
/// onTerm can be called before `spawn` returns. /// onTerm can be called before `spawn` returns.
/// On success must call `kill` or `wait`. /// On success must call `kill` or `wait`.
pub fn spawn(self: &ChildProcess) -> %void { pub fn spawn(self: &ChildProcess) -> %void {
return switch (builtin.os) { if (is_windows) {
Os.linux, Os.macosx, Os.ios, Os.darwin => self.spawnPosix(), return self.spawnWindows();
else => @compileError("Unsupported OS"), } else {
return self.spawnPosix();
}; };
} }
@ -114,6 +124,30 @@ pub const ChildProcess = struct {
/// Forcibly terminates child process and then cleans up all resources. /// Forcibly terminates child process and then cleans up all resources.
pub fn kill(self: &ChildProcess) -> %Term { pub fn kill(self: &ChildProcess) -> %Term {
if (is_windows) {
return self.killWindows(1);
} else {
return self.killPosix();
}
}
pub fn killWindows(self: &ChildProcess, exit_code: windows.UINT) -> %Term {
if (self.term) |term| {
self.cleanupStreams();
return term;
}
if (!windows.TerminateProcess(self.handle, exit_code)) {
const err = windows.GetLastError();
return switch (err) {
else => error.Unexpected,
};
}
self.waitUnwrappedWindows();
return ??self.term;
}
pub fn killPosix(self: &ChildProcess) -> %Term {
block_SIGCHLD(); block_SIGCHLD();
defer restore_SIGCHLD(); defer restore_SIGCHLD();
@ -137,6 +171,24 @@ pub const ChildProcess = struct {
/// Blocks until child process terminates and then cleans up all resources. /// Blocks until child process terminates and then cleans up all resources.
pub fn wait(self: &ChildProcess) -> %Term { pub fn wait(self: &ChildProcess) -> %Term {
if (is_windows) {
return self.waitWindows();
} else {
return self.waitPosix();
}
}
fn waitWindows(self: &ChildProcess) -> %Term {
if (self.term) |term| {
self.cleanupStreams();
return term;
}
%return self.waitUnwrappedWindows();
return ??self.term;
}
fn waitPosix(self: &ChildProcess) -> %Term {
block_SIGCHLD(); block_SIGCHLD();
defer restore_SIGCHLD(); defer restore_SIGCHLD();
@ -153,6 +205,23 @@ pub const ChildProcess = struct {
self.allocator.destroy(self); self.allocator.destroy(self);
} }
fn waitUnwrappedWindows(self: &ChildProcess) -> %void {
const result = os.windowsWaitSingle(self.handle, windows.INFINITE);
self.term = (%Term)({
var exit_code: windows.DWORD = undefined;
if (!windows.GetExitCodeProcess(self.handle, &exit_code)) {
Term.Unknown{0}
} else {
Term.Exited {@bitCast(i32, exit_code)}
}
});
os.windowsClose(self.handle);
self.cleanupStreams();
return result;
}
fn waitUnwrapped(self: &ChildProcess) { fn waitUnwrapped(self: &ChildProcess) {
var status: i32 = undefined; var status: i32 = undefined;
while (true) { while (true) {
@ -262,16 +331,21 @@ pub const ChildProcess = struct {
} else { } else {
null null
}; };
%defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) { const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
%return self.allocator.create(io.InStream) %return self.allocator.create(io.InStream)
} else { } else {
null null
}; };
%defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) { const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
%return self.allocator.create(io.InStream) %return self.allocator.create(io.InStream)
} else { } else {
null null
}; };
%defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
block_SIGCHLD(); block_SIGCHLD();
const pid_result = posix.fork(); const pid_result = posix.fork();
@ -355,6 +429,195 @@ pub const ChildProcess = struct {
if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); } if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); }
} }
fn spawnWindows(self: &ChildProcess) -> %void {
var saAttr: windows.SECURITY_ATTRIBUTES = undefined;
saAttr.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = true;
saAttr.lpSecurityDescriptor = null;
const any_ignore = (self.stdin_behavior == StdIo.Ignore or
self.stdout_behavior == StdIo.Ignore or
self.stderr_behavior == StdIo.Ignore);
const nul_handle = if (any_ignore) {
%return os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ,
windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null)
} else {
undefined
};
defer { if (any_ignore) os.windowsClose(nul_handle); };
if (any_ignore) {
%return windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0);
}
var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
switch (self.stdin_behavior) {
StdIo.Pipe => {
%return windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
},
StdIo.Ignore => {
g_hChildStd_IN_Rd = nul_handle;
},
StdIo.Inherit => {
g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE);
},
StdIo.Close => {
g_hChildStd_IN_Rd = null;
},
}
%defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); };
var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
switch (self.stdout_behavior) {
StdIo.Pipe => {
%return windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
},
StdIo.Ignore => {
g_hChildStd_OUT_Wr = nul_handle;
},
StdIo.Inherit => {
g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE);
},
StdIo.Close => {
g_hChildStd_OUT_Wr = null;
},
}
%defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); };
var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
switch (self.stderr_behavior) {
StdIo.Pipe => {
%return windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
},
StdIo.Ignore => {
g_hChildStd_ERR_Wr = nul_handle;
},
StdIo.Inherit => {
g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE);
},
StdIo.Close => {
g_hChildStd_ERR_Wr = null;
},
}
%defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); };
const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) {
%return self.allocator.create(io.OutStream)
} else {
null
};
%defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
%return self.allocator.create(io.InStream)
} else {
null
};
%defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
%return self.allocator.create(io.InStream)
} else {
null
};
%defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
const cmd_line = %return windowsCreateCommandLine(self.allocator, self.argv);
defer self.allocator.free(cmd_line);
var siStartInfo = windows.STARTUPINFOA {
.cb = @sizeOf(windows.STARTUPINFOA),
.hStdError = g_hChildStd_ERR_Wr,
.hStdOutput = g_hChildStd_OUT_Wr,
.hStdInput = g_hChildStd_IN_Rd,
.dwFlags = windows.STARTF_USESTDHANDLES,
.lpReserved = null,
.lpDesktop = null,
.lpTitle = null,
.dwX = 0,
.dwY = 0,
.dwXSize = 0,
.dwYSize = 0,
.dwXCountChars = 0,
.dwYCountChars = 0,
.dwFillAttribute = 0,
.wShowWindow = 0,
.cbReserved2 = 0,
.lpReserved2 = null,
};
var piProcInfo: windows.PROCESS_INFORMATION = undefined;
const app_name = %return cstr.addNullByte(self.allocator, self.argv[0]);
defer self.allocator.free(app_name);
const cwd_slice = if (self.cwd) |cwd| {
%return cstr.addNullByte(self.allocator, cwd)
} else {
null
};
defer if (cwd_slice) |cwd| self.allocator.free(cwd);
const cwd_ptr = if (cwd_slice) |cwd| cwd.ptr else null;
const maybe_envp_buf = if (self.env_map) |env_map| {
%return os.createNullDelimitedEnvMap(self.allocator, env_map)
} else {
null
};
defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
if (!windows.CreateProcessA(app_name.ptr, cmd_line.ptr, null, null, true, 0,
@ptrCast(?&c_void, envp_ptr),
cwd_ptr, &siStartInfo, &piProcInfo))
{
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
else => error.Unexpected,
};
}
os.windowsClose(piProcInfo.hThread);
if (stdin_ptr) |outstream| {
*outstream = io.OutStream {
.fd = {},
.handle = g_hChildStd_IN_Wr,
.handle_id = undefined,
.buffer = undefined,
.index = 0,
};
}
if (stdout_ptr) |instream| {
*instream = io.InStream {
.fd = {},
.handle = g_hChildStd_OUT_Rd,
.handle_id = undefined,
};
}
if (stderr_ptr) |instream| {
*instream = io.InStream {
.fd = {},
.handle = g_hChildStd_ERR_Rd,
.handle_id = undefined,
};
}
self.handle = piProcInfo.hProcess;
self.term = null;
self.stdin = stdin_ptr;
self.stdout = stdout_ptr;
self.stderr = stderr_ptr;
if (self.stdin_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_IN_Rd); }
if (self.stderr_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_ERR_Wr); }
if (self.stdout_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_OUT_Wr); }
}
fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void { fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {
switch (stdio) { switch (stdio) {
StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno), StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno),
@ -365,6 +628,84 @@ pub const ChildProcess = struct {
} }
}; };
/// Caller must dealloc.
/// Guarantees a null byte at result[result.len].
fn windowsCreateCommandLine(allocator: &Allocator, argv: []const []const u8) -> %[]u8 {
var buf = %return Buffer.initSize(allocator, 0);
defer buf.deinit();
for (argv) |arg, arg_i| {
if (arg_i != 0)
%return buf.appendByte(' ');
if (mem.indexOfAny(u8, arg, " \t\n\"") == null) {
%return buf.append(arg);
continue;
}
%return buf.appendByte('"');
var backslash_count: usize = 0;
for (arg) |byte| {
switch (byte) {
'\\' => backslash_count += 1,
'"' => {
%return buf.appendByteNTimes('\\', backslash_count * 2 + 1);
%return buf.appendByte('"');
backslash_count = 0;
},
else => {
%return buf.appendByteNTimes('\\', backslash_count);
%return buf.appendByte(byte);
backslash_count = 0;
},
}
}
%return buf.appendByteNTimes('\\', backslash_count * 2);
%return buf.appendByte('"');
}
return buf.toOwnedSlice();
}
fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) {
if (rd) |h| os.windowsClose(h);
if (wr) |h| os.windowsClose(h);
}
fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
if (!windows.CreatePipe(rd, wr, sattr, 0)) {
const err = windows.GetLastError();
return switch (err) {
else => error.Unexpected,
};
}
}
fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.DWORD) -> %void {
if (!windows.SetHandleInformation(h, mask, flags)) {
const err = windows.GetLastError();
return switch (err) {
else => error.Unexpected,
};
}
}
fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
var rd_h: windows.HANDLE = undefined;
var wr_h: windows.HANDLE = undefined;
%return windowsMakePipe(&rd_h, &wr_h, sattr);
%return windowsSetHandleInfo(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
*rd = rd_h;
*wr = wr_h;
}
fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
var rd_h: windows.HANDLE = undefined;
var wr_h: windows.HANDLE = undefined;
%return windowsMakePipe(&rd_h, &wr_h, sattr);
%return windowsSetHandleInfo(rd_h, windows.HANDLE_FLAG_INHERIT, 0);
*rd = rd_h;
*wr = wr_h;
}
fn makePipe() -> %[2]i32 { fn makePipe() -> %[2]i32 {
var fds: [2]i32 = undefined; var fds: [2]i32 = undefined;
const err = posix.getErrno(posix.pipe(&fds)); const err = posix.getErrno(posix.pipe(&fds));

View file

@ -382,6 +382,37 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {
} }
} }
pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) -> %[]?&u8 {
const envp_count = env_map.count();
const envp_buf = %return allocator.alloc(?&u8, envp_count + 1);
mem.set(?&u8, envp_buf, null);
%defer freeNullDelimitedEnvMap(allocator, envp_buf);
{
var it = env_map.iterator();
var i: usize = 0;
while (it.next()) |pair| : (i += 1) {
const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
@memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
env_buf[pair.key.len] = '=';
@memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
env_buf[env_buf.len - 1] = 0;
envp_buf[i] = env_buf.ptr;
}
assert(i == envp_count);
}
assert(envp_buf[envp_count] == null);
return envp_buf;
}
pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) {
for (envp_buf) |env| {
const env_buf = if (env) |ptr| cstr.toSlice(ptr) else break;
allocator.free(env_buf);
}
allocator.free(envp_buf);
}
/// This function must allocate memory to add a null terminating bytes on path and each arg. /// This function must allocate memory to add a null terminating bytes on path and each arg.
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null /// It must also convert to KEY=VALUE\0 format for environment variables, and include null
/// pointers after the args and after the environment variables. /// pointers after the args and after the environment variables.
@ -408,31 +439,8 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
} }
argv_buf[argv.len] = null; argv_buf[argv.len] = null;
const envp_count = env_map.count(); const envp_buf = %return createNullDelimitedEnvMap(allocator, env_map);
const envp_buf = %return allocator.alloc(?&u8, envp_count + 1); defer freeNullDelimitedEnvMap(allocator, envp_buf);
mem.set(?&u8, envp_buf, null);
defer {
for (envp_buf) |env| {
const env_buf = if (env) |ptr| cstr.toSlice(ptr) else break;
allocator.free(env_buf);
}
allocator.free(envp_buf);
}
{
var it = env_map.iterator();
var i: usize = 0;
while (it.next()) |pair| : (i += 1) {
const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
@memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
env_buf[pair.key.len] = '=';
@memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
env_buf[env_buf.len - 1] = 0;
envp_buf[i] = env_buf.ptr;
}
assert(i == envp_count);
}
envp_buf[envp_count] = null;
const exe_path = argv[0]; const exe_path = argv[0];
if (mem.indexOfScalar(u8, exe_path, '/') != null) { if (mem.indexOfScalar(u8, exe_path, '/') != null) {
@ -1367,6 +1375,22 @@ fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const
assert(it.next(&debug.global_allocator) == null); assert(it.next(&debug.global_allocator) == null);
} }
error WaitAbandoned;
error WaitTimeOut;
pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) -> %void {
const result = windows.WaitForSingleObject(handle, milliseconds);
return switch (result) {
windows.WAIT_ABANDONED => error.WaitAbandoned,
windows.WAIT_OBJECT_0 => {},
windows.WAIT_TIMEOUT => error.WaitTimeOut,
windows.WAIT_FAILED => switch (windows.GetLastError()) {
else => error.Unexpected,
},
else => error.Unexpected,
};
}
test "std.os" { test "std.os" {
_ = @import("child_process.zig"); _ = @import("child_process.zig");
_ = @import("darwin_errno.zig"); _ = @import("darwin_errno.zig");

View file

@ -14,6 +14,14 @@ pub extern "kernel32" stdcallcc fn CreateFileA(lpFileName: LPCSTR, dwDesiredAcce
dwShareMode: DWORD, lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, dwCreationDisposition: DWORD, dwShareMode: DWORD, lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, dwCreationDisposition: DWORD,
dwFlagsAndAttributes: DWORD, hTemplateFile: ?HANDLE) -> HANDLE; dwFlagsAndAttributes: DWORD, hTemplateFile: ?HANDLE) -> HANDLE;
pub extern "kernel32" stdcallcc fn CreatePipe(hReadPipe: &HANDLE, hWritePipe: &HANDLE,
lpPipeAttributes: &SECURITY_ATTRIBUTES, nSize: DWORD) -> BOOL;
pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lpCommandLine: LPSTR,
lpProcessAttributes: ?&SECURITY_ATTRIBUTES, lpThreadAttributes: ?&SECURITY_ATTRIBUTES, bInheritHandles: BOOL,
dwCreationFlags: DWORD, lpEnvironment: ?LPVOID, lpCurrentDirectory: ?LPCSTR, lpStartupInfo: &STARTUPINFOA,
lpProcessInformation: &PROCESS_INFORMATION) -> BOOL;
pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool; pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool;
pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn; pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn;
@ -24,11 +32,10 @@ pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out
pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) -> DWORD; pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) -> DWORD;
/// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis. pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: &DWORD) -> BOOL;
/// Multiple threads do not overwrite each other's last-error code.
pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD; pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;
/// Retrieves file information for the specified file.
pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE, pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE,
in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, out_lpFileInformation: &c_void, in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, out_lpFileInformation: &c_void,
in_dwBufferSize: DWORD) -> bool; in_dwBufferSize: DWORD) -> bool;
@ -36,26 +43,29 @@ pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE
pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(hFile: HANDLE, lpszFilePath: LPSTR, pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(hFile: HANDLE, lpszFilePath: LPSTR,
cchFilePath: DWORD, dwFlags: DWORD) -> DWORD; cchFilePath: DWORD, dwFlags: DWORD) -> DWORD;
/// Retrieves a handle to the specified standard device (standard input, standard output, or standard error). pub extern "kernel32" stdcallcc fn GetProcessHeap() -> HANDLE;
pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) -> ?HANDLE; pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) -> ?HANDLE;
pub extern "kernel32" stdcallcc fn ReadFile(in_hFile: HANDLE, out_lpBuffer: LPVOID,
in_nNumberOfBytesToRead: DWORD, out_lpNumberOfBytesRead: &DWORD,
in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
/// Writes data to the specified file or input/output (I/O) device.
/// This function is designed for both synchronous and asynchronous operation. For a similar function designed solely for asynchronous operation, see WriteFileEx.
pub extern "kernel32" stdcallcc fn WriteFile(in_hFile: HANDLE, in_lpBuffer: &const c_void,
in_nNumberOfBytesToWrite: DWORD, out_lpNumberOfBytesWritten: ?&DWORD,
in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD);
pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID; pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID;
pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL; pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL;
pub extern "kernel32" stdcallcc fn GetProcessHeap() -> HANDLE; pub extern "kernel32" stdcallcc fn ReadFile(in_hFile: HANDLE, out_lpBuffer: LPVOID,
in_nNumberOfBytesToRead: DWORD, out_lpNumberOfBytesRead: &DWORD,
in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
pub extern "kernel32" stdcallcc fn SetHandleInformation(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) -> BOOL;
pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD);
pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
pub extern "kernel32" stdcallcc fn WriteFile(in_hFile: HANDLE, in_lpBuffer: &const c_void,
in_nNumberOfBytesToWrite: DWORD, out_lpNumberOfBytesWritten: ?&DWORD,
in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) -> c_int; pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) -> c_int;
@ -88,7 +98,7 @@ pub const INT = c_int;
pub const ULONG_PTR = usize; pub const ULONG_PTR = usize;
pub const WCHAR = u16; pub const WCHAR = u16;
pub const LPCVOID = &const c_void; pub const LPCVOID = &const c_void;
pub const LPBYTE = &BYTE;
/// The standard input device. Initially, this is the console input buffer, CONIN$. /// The standard input device. Initially, this is the console input buffer, CONIN$.
pub const STD_INPUT_HANDLE = @maxValue(DWORD) - 10 + 1; pub const STD_INPUT_HANDLE = @maxValue(DWORD) - 10 + 1;
@ -158,7 +168,7 @@ pub const VOLUME_NAME_NT = 0x2;
pub const SECURITY_ATTRIBUTES = extern struct { pub const SECURITY_ATTRIBUTES = extern struct {
nLength: DWORD, nLength: DWORD,
lpSecurityDescriptor: LPVOID, lpSecurityDescriptor: ?LPVOID,
bInheritHandle: BOOL, bInheritHandle: BOOL,
}; };
pub const PSECURITY_ATTRIBUTES = &SECURITY_ATTRIBUTES; pub const PSECURITY_ATTRIBUTES = &SECURITY_ATTRIBUTES;
@ -189,3 +199,56 @@ pub const FILE_ATTRIBUTE_OFFLINE = 0x1000;
pub const FILE_ATTRIBUTE_READONLY = 0x1; pub const FILE_ATTRIBUTE_READONLY = 0x1;
pub const FILE_ATTRIBUTE_SYSTEM = 0x4; pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
pub const FILE_ATTRIBUTE_TEMPORARY = 0x100; pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
pub const PROCESS_INFORMATION = extern struct {
hProcess: HANDLE,
hThread: HANDLE,
dwProcessId: DWORD,
dwThreadId: DWORD,
};
pub const STARTUPINFOA = extern struct {
cb: DWORD,
lpReserved: ?LPSTR,
lpDesktop: ?LPSTR,
lpTitle: ?LPSTR,
dwX: DWORD,
dwY: DWORD,
dwXSize: DWORD,
dwYSize: DWORD,
dwXCountChars: DWORD,
dwYCountChars: DWORD,
dwFillAttribute: DWORD,
dwFlags: DWORD,
wShowWindow: WORD,
cbReserved2: WORD,
lpReserved2: ?LPBYTE,
hStdInput: ?HANDLE,
hStdOutput: ?HANDLE,
hStdError: ?HANDLE,
};
pub const STARTF_FORCEONFEEDBACK = 0x00000040;
pub const STARTF_FORCEOFFFEEDBACK = 0x00000080;
pub const STARTF_PREVENTPINNING = 0x00002000;
pub const STARTF_RUNFULLSCREEN = 0x00000020;
pub const STARTF_TITLEISAPPID = 0x00001000;
pub const STARTF_TITLEISLINKNAME = 0x00000800;
pub const STARTF_UNTRUSTEDSOURCE = 0x00008000;
pub const STARTF_USECOUNTCHARS = 0x00000008;
pub const STARTF_USEFILLATTRIBUTE = 0x00000010;
pub const STARTF_USEHOTKEY = 0x00000200;
pub const STARTF_USEPOSITION = 0x00000004;
pub const STARTF_USESHOWWINDOW = 0x00000001;
pub const STARTF_USESIZE = 0x00000002;
pub const STARTF_USESTDHANDLES = 0x00000100;
pub const INFINITE = 4294967295;
pub const WAIT_ABANDONED = 0x00000080;
pub const WAIT_OBJECT_0 = 0x00000000;
pub const WAIT_TIMEOUT = 0x00000102;
pub const WAIT_FAILED = 0xFFFFFFFF;
pub const HANDLE_FLAG_INHERIT = 0x00000001;
pub const HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;