mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
implement std.os.ChildProcess for windows
This commit is contained in:
parent
7f9dc4ebc1
commit
8d3eaab871
8 changed files with 533 additions and 69 deletions
|
|
@ -18,7 +18,7 @@ clarity.
|
|||
writing buggy code.
|
||||
* Debug mode optimizes for fast compilation time and crashing with a stack trace
|
||||
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.
|
||||
* Compatible with C libraries with no wrapper necessary. Directly include
|
||||
C .h files and get access to the functions and symbols therein.
|
||||
|
|
@ -36,16 +36,13 @@ clarity.
|
|||
a preprocessor or macros.
|
||||
* The binaries produced by Zig have complete debugging information so you can,
|
||||
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
|
||||
process carefully documented. Issues filed by package maintainers are
|
||||
considered especially important.
|
||||
* Cross-compiling is a primary use case.
|
||||
* In addition to creating executables, creating a C library is a primary use
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ pub const BufSet = struct {
|
|||
|
||||
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 {
|
||||
.hash_map = BufSetHashMap.init(allocator),
|
||||
.hash_map = BufSetHashMap.init(a),
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
|
@ -45,6 +45,10 @@ pub const BufSet = struct {
|
|||
return self.hash_map.iterator();
|
||||
}
|
||||
|
||||
pub fn allocator(self: &const BufSet) -> &Allocator {
|
||||
return self.hash_map.allocator;
|
||||
}
|
||||
|
||||
fn free(self: &BufSet, value: []const u8) {
|
||||
// remove the const
|
||||
const mut_value = @ptrCast(&u8, value.ptr)[0..value.len];
|
||||
|
|
|
|||
|
|
@ -91,8 +91,17 @@ pub const Buffer = struct {
|
|||
}
|
||||
|
||||
pub fn appendByte(self: &Buffer, byte: u8) -> %void {
|
||||
%return self.resize(self.len() + 1);
|
||||
self.list.items[self.len() - 1] = byte;
|
||||
return self.appendByteNTimes(byte, 1);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
11
std/cstr.zig
11
std/cstr.zig
|
|
@ -1,4 +1,5 @@
|
|||
const debug = @import("debug.zig");
|
||||
const mem = @import("mem.zig");
|
||||
const assert = debug.assert;
|
||||
|
||||
pub fn len(ptr: &const u8) -> usize {
|
||||
|
|
@ -36,3 +37,13 @@ fn testCStrFnsImpl() {
|
|||
assert(cmp(c"aoeu", c"aoez") == -1);
|
||||
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];
|
||||
}
|
||||
|
|
|
|||
15
std/mem.zig
15
std/mem.zig
|
|
@ -254,6 +254,21 @@ pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize,
|
|||
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 {
|
||||
return indexOfPos(T, haystack, 0, needle);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,30 @@
|
|||
const io = @import("../io.zig");
|
||||
const os = @import("index.zig");
|
||||
const std = @import("../index.zig");
|
||||
const cstr = std.cstr;
|
||||
const io = std.io;
|
||||
const os = std.os;
|
||||
const posix = os.posix;
|
||||
const mem = @import("../mem.zig");
|
||||
const windows = os.windows;
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
const debug = @import("../debug.zig");
|
||||
const debug = std.debug;
|
||||
const assert = debug.assert;
|
||||
const BufMap = @import("../buf_map.zig").BufMap;
|
||||
const BufMap = std.BufMap;
|
||||
const Buffer = std.Buffer;
|
||||
const builtin = @import("builtin");
|
||||
const Os = builtin.Os;
|
||||
const LinkedList = @import("../linked_list.zig").LinkedList;
|
||||
const LinkedList = std.LinkedList;
|
||||
|
||||
error PermissionDenied;
|
||||
error ProcessNotFound;
|
||||
|
||||
var children_nodes = LinkedList(&ChildProcess).init();
|
||||
|
||||
const is_windows = builtin.os == Os.windows;
|
||||
|
||||
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 stdin: ?&io.OutStream,
|
||||
|
|
@ -38,16 +46,16 @@ pub const ChildProcess = struct {
|
|||
pub stderr_behavior: StdIo,
|
||||
|
||||
/// 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.
|
||||
pub gid: ?u32,
|
||||
pub gid: if (is_windows) void else ?u32,
|
||||
|
||||
/// Set to change the current working directory when spawning the child process.
|
||||
pub cwd: ?[]const u8,
|
||||
|
||||
err_pipe: [2]i32,
|
||||
llnode: LinkedList(&ChildProcess).Node,
|
||||
err_pipe: if (is_windows) void else [2]i32,
|
||||
llnode: if (is_windows) void else LinkedList(&ChildProcess).Node,
|
||||
|
||||
pub const Term = enum {
|
||||
Exited: i32,
|
||||
|
|
@ -73,14 +81,15 @@ pub const ChildProcess = struct {
|
|||
.allocator = allocator,
|
||||
.argv = argv,
|
||||
.pid = undefined,
|
||||
.handle = undefined,
|
||||
.err_pipe = undefined,
|
||||
.llnode = undefined,
|
||||
.term = null,
|
||||
.onTerm = null,
|
||||
.env_map = null,
|
||||
.cwd = null,
|
||||
.uid = null,
|
||||
.gid = null,
|
||||
.uid = if (is_windows) {} else null,
|
||||
.gid = if (is_windows) {} else null,
|
||||
.stdin = null,
|
||||
.stdout = null,
|
||||
.stderr = null,
|
||||
|
|
@ -101,9 +110,10 @@ pub const ChildProcess = struct {
|
|||
/// onTerm can be called before `spawn` returns.
|
||||
/// On success must call `kill` or `wait`.
|
||||
pub fn spawn(self: &ChildProcess) -> %void {
|
||||
return switch (builtin.os) {
|
||||
Os.linux, Os.macosx, Os.ios, Os.darwin => self.spawnPosix(),
|
||||
else => @compileError("Unsupported OS"),
|
||||
if (is_windows) {
|
||||
return self.spawnWindows();
|
||||
} else {
|
||||
return self.spawnPosix();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +124,30 @@ pub const ChildProcess = struct {
|
|||
|
||||
/// Forcibly terminates child process and then cleans up all resources.
|
||||
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();
|
||||
defer restore_SIGCHLD();
|
||||
|
||||
|
|
@ -137,6 +171,24 @@ pub const ChildProcess = struct {
|
|||
|
||||
/// Blocks until child process terminates and then cleans up all resources.
|
||||
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();
|
||||
defer restore_SIGCHLD();
|
||||
|
||||
|
|
@ -153,6 +205,23 @@ pub const ChildProcess = struct {
|
|||
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) {
|
||||
var status: i32 = undefined;
|
||||
while (true) {
|
||||
|
|
@ -262,16 +331,21 @@ pub const ChildProcess = struct {
|
|||
} 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);
|
||||
|
||||
block_SIGCHLD();
|
||||
const pid_result = posix.fork();
|
||||
|
|
@ -355,6 +429,195 @@ pub const ChildProcess = struct {
|
|||
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 {
|
||||
switch (stdio) {
|
||||
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 {
|
||||
var fds: [2]i32 = undefined;
|
||||
const err = posix.getErrno(posix.pipe(&fds));
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
|
||||
/// 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;
|
||||
|
||||
const envp_count = env_map.count();
|
||||
const envp_buf = %return allocator.alloc(?&u8, envp_count + 1);
|
||||
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 envp_buf = %return createNullDelimitedEnvMap(allocator, env_map);
|
||||
defer freeNullDelimitedEnvMap(allocator, envp_buf);
|
||||
|
||||
const exe_path = argv[0];
|
||||
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);
|
||||
}
|
||||
|
||||
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" {
|
||||
_ = @import("child_process.zig");
|
||||
_ = @import("darwin_errno.zig");
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ pub extern "kernel32" stdcallcc fn CreateFileA(lpFileName: LPCSTR, dwDesiredAcce
|
|||
dwShareMode: DWORD, lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, dwCreationDisposition: DWORD,
|
||||
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 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;
|
||||
|
||||
/// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis.
|
||||
/// Multiple threads do not overwrite each other's last-error code.
|
||||
pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: &DWORD) -> BOOL;
|
||||
|
||||
pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;
|
||||
|
||||
/// Retrieves file information for the specified file.
|
||||
pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE,
|
||||
in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, out_lpFileInformation: &c_void,
|
||||
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,
|
||||
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 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 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;
|
||||
|
||||
|
|
@ -88,7 +98,7 @@ pub const INT = c_int;
|
|||
pub const ULONG_PTR = usize;
|
||||
pub const WCHAR = u16;
|
||||
pub const LPCVOID = &const c_void;
|
||||
|
||||
pub const LPBYTE = &BYTE;
|
||||
|
||||
/// The standard input device. Initially, this is the console input buffer, CONIN$.
|
||||
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 {
|
||||
nLength: DWORD,
|
||||
lpSecurityDescriptor: LPVOID,
|
||||
lpSecurityDescriptor: ?LPVOID,
|
||||
bInheritHandle: BOOL,
|
||||
};
|
||||
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_SYSTEM = 0x4;
|
||||
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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue