authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-13 09:31:03-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-14 15:32:18-04:00
log8d3eaab8717381023ba8b6837fc5840ad8be1004
treea53bba1b992fe8c84abd2f108c56cf87dd852c4b
parent7f9dc4ebc10eb13e73466224f28ba62747693df9

implement std.os.ChildProcess for windows


8 files changed, 528 insertions(+), 64 deletions(-)

README.md+2-5
...@@ -18,7 +18,7 @@ clarity....@@ -18,7 +18,7 @@ clarity.
18 writing buggy code.18 writing buggy code.
19 * Debug mode optimizes for fast compilation time and crashing with a stack trace19 * Debug mode optimizes for fast compilation time and crashing with a stack trace
20 when undefined behavior *would* happen.20 when undefined behavior *would* happen.
21 * Release mode produces heavily optimized code. What other projects call21 * ReleaseFast mode produces heavily optimized code. What other projects call
22 "Link Time Optimization" Zig does automatically.22 "Link Time Optimization" Zig does automatically.
23 * Compatible with C libraries with no wrapper necessary. Directly include23 * Compatible with C libraries with no wrapper necessary. Directly include
24 C .h files and get access to the functions and symbols therein.24 C .h files and get access to the functions and symbols therein.
...@@ -36,16 +36,13 @@ clarity....@@ -36,16 +36,13 @@ clarity.
36 a preprocessor or macros.36 a preprocessor or macros.
37 * The binaries produced by Zig have complete debugging information so you can,37 * The binaries produced by Zig have complete debugging information so you can,
38 for example, use GDB to debug your software.38 for example, use GDB to debug your software.
39 * Mark functions as tests and automatically run them with `zig test`.39 * Built-in unit tests with `zig test`.
40 * Friendly toward package maintainers. Reproducible build, bootstrapping40 * Friendly toward package maintainers. Reproducible build, bootstrapping
41 process carefully documented. Issues filed by package maintainers are41 process carefully documented. Issues filed by package maintainers are
42 considered especially important.42 considered especially important.
43 * Cross-compiling is a primary use case.43 * Cross-compiling is a primary use case.
44 * In addition to creating executables, creating a C library is a primary use44 * In addition to creating executables, creating a C library is a primary use
45 case. You can export an auto-generated .h file.45 case. You can export an auto-generated .h file.
46 * For OS development, Zig supports all architectures that LLVM does. All the
47 standard library that does not depend on an OS is available to you in
48 freestanding mode.
4946
50### Support Table47### Support Table
5148
std/buf_set.zig+6-2
...@@ -7,9 +7,9 @@ pub const BufSet = struct {...@@ -7,9 +7,9 @@ pub const BufSet = struct {
77
8 const BufSetHashMap = HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);8 const BufSetHashMap = HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
99
10 pub fn init(allocator: &Allocator) -> BufSet {10 pub fn init(a: &Allocator) -> BufSet {
11 var self = BufSet {11 var self = BufSet {
12 .hash_map = BufSetHashMap.init(allocator),12 .hash_map = BufSetHashMap.init(a),
13 };13 };
14 return self;14 return self;
15 }15 }
...@@ -45,6 +45,10 @@ pub const BufSet = struct {...@@ -45,6 +45,10 @@ pub const BufSet = struct {
45 return self.hash_map.iterator();45 return self.hash_map.iterator();
46 }46 }
4747
48 pub fn allocator(self: &const BufSet) -> &Allocator {
49 return self.hash_map.allocator;
50 }
51
48 fn free(self: &BufSet, value: []const u8) {52 fn free(self: &BufSet, value: []const u8) {
49 // remove the const53 // remove the const
50 const mut_value = @ptrCast(&u8, value.ptr)[0..value.len];54 const mut_value = @ptrCast(&u8, value.ptr)[0..value.len];
std/buffer.zig+11-2
...@@ -91,8 +91,17 @@ pub const Buffer = struct {...@@ -91,8 +91,17 @@ pub const Buffer = struct {
91 }91 }
9292
93 pub fn appendByte(self: &Buffer, byte: u8) -> %void {93 pub fn appendByte(self: &Buffer, byte: u8) -> %void {
94 %return self.resize(self.len() + 1);94 return self.appendByteNTimes(byte, 1);
95 self.list.items[self.len() - 1] = byte;95 }
96
97 pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) -> %void {
98 var prev_size: usize = self.len();
99 %return self.resize(prev_size + count);
100
101 var i: usize = 0;
102 while (i < count) : (i += 1) {
103 self.list.items[prev_size + i] = byte;
104 }
96 }105 }
97106
98 pub fn eql(self: &const Buffer, m: []const u8) -> bool {107 pub fn eql(self: &const Buffer, m: []const u8) -> bool {
std/cstr.zig+11
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const debug = @import("debug.zig");1const debug = @import("debug.zig");
2const mem = @import("mem.zig");
2const assert = debug.assert;3const assert = debug.assert;
34
4pub fn len(ptr: &const u8) -> usize {5pub fn len(ptr: &const u8) -> usize {
...@@ -36,3 +37,13 @@ fn testCStrFnsImpl() {...@@ -36,3 +37,13 @@ fn testCStrFnsImpl() {
36 assert(cmp(c"aoeu", c"aoez") == -1);37 assert(cmp(c"aoeu", c"aoez") == -1);
37 assert(len(c"123456789") == 9);38 assert(len(c"123456789") == 9);
38}39}
40
41/// Returns a mutable slice with exactly the same size which is guaranteed to
42/// have a null byte after it.
43/// Caller owns the returned memory.
44pub fn addNullByte(allocator: &mem.Allocator, slice: []const u8) -> %[]u8 {
45 const result = %return allocator.alloc(u8, slice.len + 1);
46 mem.copy(u8, result, slice);
47 result[slice.len] = 0;
48 return result[0..slice.len];
49}
std/mem.zig+15
...@@ -254,6 +254,21 @@ pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize,...@@ -254,6 +254,21 @@ pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize,
254 return null;254 return null;
255}255}
256256
257pub fn indexOfAny(comptime T: type, slice: []const T, values: []const T) -> ?usize {
258 return indexOfAnyPos(T, slice, 0, values);
259}
260
261pub fn indexOfAnyPos(comptime T: type, slice: []const T, start_index: usize, values: []const T) -> ?usize {
262 var i: usize = start_index;
263 while (i < slice.len) : (i += 1) {
264 for (values) |value| {
265 if (slice[i] == value)
266 return i;
267 }
268 }
269 return null;
270}
271
257pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {272pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {
258 return indexOfPos(T, haystack, 0, needle);273 return indexOfPos(T, haystack, 0, needle);
259}274}
std/os/child_process.zig+357-16
...@@ -1,22 +1,30 @@...@@ -1,22 +1,30 @@
1const io = @import("../io.zig");1const std = @import("../index.zig");
2const os = @import("index.zig");2const cstr = std.cstr;
3const io = std.io;
4const os = std.os;
3const posix = os.posix;5const posix = os.posix;
4const mem = @import("../mem.zig");6const windows = os.windows;
7const mem = std.mem;
5const Allocator = mem.Allocator;8const Allocator = mem.Allocator;
6const debug = @import("../debug.zig");9const debug = std.debug;
7const assert = debug.assert;10const assert = debug.assert;
8const BufMap = @import("../buf_map.zig").BufMap;11const BufMap = std.BufMap;
12const Buffer = std.Buffer;
9const builtin = @import("builtin");13const builtin = @import("builtin");
10const Os = builtin.Os;14const Os = builtin.Os;
11const LinkedList = @import("../linked_list.zig").LinkedList;15const LinkedList = std.LinkedList;
1216
13error PermissionDenied;17error PermissionDenied;
14error ProcessNotFound;18error ProcessNotFound;
1519
16var children_nodes = LinkedList(&ChildProcess).init();20var children_nodes = LinkedList(&ChildProcess).init();
1721
22const is_windows = builtin.os == Os.windows;
23
18pub const ChildProcess = struct {24pub const ChildProcess = struct {
19 pub pid: i32,25 pub pid: if (is_windows) void else i32,
26 pub handle: if (is_windows) windows.HANDLE else void,
27
20 pub allocator: &mem.Allocator,28 pub allocator: &mem.Allocator,
2129
22 pub stdin: ?&io.OutStream,30 pub stdin: ?&io.OutStream,
...@@ -38,16 +46,16 @@ pub const ChildProcess = struct {...@@ -38,16 +46,16 @@ pub const ChildProcess = struct {
38 pub stderr_behavior: StdIo,46 pub stderr_behavior: StdIo,
3947
40 /// Set to change the user id when spawning the child process.48 /// Set to change the user id when spawning the child process.
41 pub uid: ?u32,49 pub uid: if (is_windows) void else ?u32,
4250
43 /// Set to change the group id when spawning the child process.51 /// Set to change the group id when spawning the child process.
44 pub gid: ?u32,52 pub gid: if (is_windows) void else ?u32,
4553
46 /// Set to change the current working directory when spawning the child process.54 /// Set to change the current working directory when spawning the child process.
47 pub cwd: ?[]const u8,55 pub cwd: ?[]const u8,
4856
49 err_pipe: [2]i32,57 err_pipe: if (is_windows) void else [2]i32,
50 llnode: LinkedList(&ChildProcess).Node,58 llnode: if (is_windows) void else LinkedList(&ChildProcess).Node,
5159
52 pub const Term = enum {60 pub const Term = enum {
53 Exited: i32,61 Exited: i32,
...@@ -73,14 +81,15 @@ pub const ChildProcess = struct {...@@ -73,14 +81,15 @@ pub const ChildProcess = struct {
73 .allocator = allocator,81 .allocator = allocator,
74 .argv = argv,82 .argv = argv,
75 .pid = undefined,83 .pid = undefined,
84 .handle = undefined,
76 .err_pipe = undefined,85 .err_pipe = undefined,
77 .llnode = undefined,86 .llnode = undefined,
78 .term = null,87 .term = null,
79 .onTerm = null,88 .onTerm = null,
80 .env_map = null,89 .env_map = null,
81 .cwd = null,90 .cwd = null,
82 .uid = null,91 .uid = if (is_windows) {} else null,
83 .gid = null,92 .gid = if (is_windows) {} else null,
84 .stdin = null,93 .stdin = null,
85 .stdout = null,94 .stdout = null,
86 .stderr = null,95 .stderr = null,
...@@ -101,9 +110,10 @@ pub const ChildProcess = struct {...@@ -101,9 +110,10 @@ pub const ChildProcess = struct {
101 /// onTerm can be called before `spawn` returns.110 /// onTerm can be called before `spawn` returns.
102 /// On success must call `kill` or `wait`.111 /// On success must call `kill` or `wait`.
103 pub fn spawn(self: &ChildProcess) -> %void {112 pub fn spawn(self: &ChildProcess) -> %void {
104 return switch (builtin.os) {113 if (is_windows) {
105 Os.linux, Os.macosx, Os.ios, Os.darwin => self.spawnPosix(),114 return self.spawnWindows();
106 else => @compileError("Unsupported OS"),115 } else {
116 return self.spawnPosix();
107 };117 };
108 }118 }
109119
...@@ -114,6 +124,30 @@ pub const ChildProcess = struct {...@@ -114,6 +124,30 @@ pub const ChildProcess = struct {
114124
115 /// Forcibly terminates child process and then cleans up all resources.125 /// Forcibly terminates child process and then cleans up all resources.
116 pub fn kill(self: &ChildProcess) -> %Term {126 pub fn kill(self: &ChildProcess) -> %Term {
127 if (is_windows) {
128 return self.killWindows(1);
129 } else {
130 return self.killPosix();
131 }
132 }
133
134 pub fn killWindows(self: &ChildProcess, exit_code: windows.UINT) -> %Term {
135 if (self.term) |term| {
136 self.cleanupStreams();
137 return term;
138 }
139
140 if (!windows.TerminateProcess(self.handle, exit_code)) {
141 const err = windows.GetLastError();
142 return switch (err) {
143 else => error.Unexpected,
144 };
145 }
146 self.waitUnwrappedWindows();
147 return ??self.term;
148 }
149
150 pub fn killPosix(self: &ChildProcess) -> %Term {
117 block_SIGCHLD();151 block_SIGCHLD();
118 defer restore_SIGCHLD();152 defer restore_SIGCHLD();
119153
...@@ -137,6 +171,24 @@ pub const ChildProcess = struct {...@@ -137,6 +171,24 @@ pub const ChildProcess = struct {
137171
138 /// Blocks until child process terminates and then cleans up all resources.172 /// Blocks until child process terminates and then cleans up all resources.
139 pub fn wait(self: &ChildProcess) -> %Term {173 pub fn wait(self: &ChildProcess) -> %Term {
174 if (is_windows) {
175 return self.waitWindows();
176 } else {
177 return self.waitPosix();
178 }
179 }
180
181 fn waitWindows(self: &ChildProcess) -> %Term {
182 if (self.term) |term| {
183 self.cleanupStreams();
184 return term;
185 }
186
187 %return self.waitUnwrappedWindows();
188 return ??self.term;
189 }
190
191 fn waitPosix(self: &ChildProcess) -> %Term {
140 block_SIGCHLD();192 block_SIGCHLD();
141 defer restore_SIGCHLD();193 defer restore_SIGCHLD();
142194
...@@ -153,6 +205,23 @@ pub const ChildProcess = struct {...@@ -153,6 +205,23 @@ pub const ChildProcess = struct {
153 self.allocator.destroy(self);205 self.allocator.destroy(self);
154 }206 }
155207
208 fn waitUnwrappedWindows(self: &ChildProcess) -> %void {
209 const result = os.windowsWaitSingle(self.handle, windows.INFINITE);
210
211 self.term = (%Term)({
212 var exit_code: windows.DWORD = undefined;
213 if (!windows.GetExitCodeProcess(self.handle, &exit_code)) {
214 Term.Unknown{0}
215 } else {
216 Term.Exited {@bitCast(i32, exit_code)}
217 }
218 });
219
220 os.windowsClose(self.handle);
221 self.cleanupStreams();
222 return result;
223 }
224
156 fn waitUnwrapped(self: &ChildProcess) {225 fn waitUnwrapped(self: &ChildProcess) {
157 var status: i32 = undefined;226 var status: i32 = undefined;
158 while (true) {227 while (true) {
...@@ -262,16 +331,21 @@ pub const ChildProcess = struct {...@@ -262,16 +331,21 @@ pub const ChildProcess = struct {
262 } else {331 } else {
263 null332 null
264 };333 };
334 %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
335
265 const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {336 const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
266 %return self.allocator.create(io.InStream)337 %return self.allocator.create(io.InStream)
267 } else {338 } else {
268 null339 null
269 };340 };
341 %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
342
270 const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {343 const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
271 %return self.allocator.create(io.InStream)344 %return self.allocator.create(io.InStream)
272 } else {345 } else {
273 null346 null
274 };347 };
348 %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
275349
276 block_SIGCHLD();350 block_SIGCHLD();
277 const pid_result = posix.fork();351 const pid_result = posix.fork();
...@@ -355,6 +429,195 @@ pub const ChildProcess = struct {...@@ -355,6 +429,195 @@ pub const ChildProcess = struct {
355 if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); }429 if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); }
356 }430 }
357431
432 fn spawnWindows(self: &ChildProcess) -> %void {
433 var saAttr: windows.SECURITY_ATTRIBUTES = undefined;
434 saAttr.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES);
435 saAttr.bInheritHandle = true;
436 saAttr.lpSecurityDescriptor = null;
437
438 const any_ignore = (self.stdin_behavior == StdIo.Ignore or
439 self.stdout_behavior == StdIo.Ignore or
440 self.stderr_behavior == StdIo.Ignore);
441
442 const nul_handle = if (any_ignore) {
443 %return os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ,
444 windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null)
445 } else {
446 undefined
447 };
448 defer { if (any_ignore) os.windowsClose(nul_handle); };
449 if (any_ignore) {
450 %return windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0);
451 }
452
453
454 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
455 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
456 switch (self.stdin_behavior) {
457 StdIo.Pipe => {
458 %return windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
459 },
460 StdIo.Ignore => {
461 g_hChildStd_IN_Rd = nul_handle;
462 },
463 StdIo.Inherit => {
464 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE);
465 },
466 StdIo.Close => {
467 g_hChildStd_IN_Rd = null;
468 },
469 }
470 %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); };
471
472 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
473 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
474 switch (self.stdout_behavior) {
475 StdIo.Pipe => {
476 %return windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
477 },
478 StdIo.Ignore => {
479 g_hChildStd_OUT_Wr = nul_handle;
480 },
481 StdIo.Inherit => {
482 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE);
483 },
484 StdIo.Close => {
485 g_hChildStd_OUT_Wr = null;
486 },
487 }
488 %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); };
489
490 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
491 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
492 switch (self.stderr_behavior) {
493 StdIo.Pipe => {
494 %return windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
495 },
496 StdIo.Ignore => {
497 g_hChildStd_ERR_Wr = nul_handle;
498 },
499 StdIo.Inherit => {
500 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE);
501 },
502 StdIo.Close => {
503 g_hChildStd_ERR_Wr = null;
504 },
505 }
506 %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); };
507
508 const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) {
509 %return self.allocator.create(io.OutStream)
510 } else {
511 null
512 };
513 %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr);
514
515 const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) {
516 %return self.allocator.create(io.InStream)
517 } else {
518 null
519 };
520 %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr);
521
522 const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) {
523 %return self.allocator.create(io.InStream)
524 } else {
525 null
526 };
527 %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr);
528
529 const cmd_line = %return windowsCreateCommandLine(self.allocator, self.argv);
530 defer self.allocator.free(cmd_line);
531
532 var siStartInfo = windows.STARTUPINFOA {
533 .cb = @sizeOf(windows.STARTUPINFOA),
534 .hStdError = g_hChildStd_ERR_Wr,
535 .hStdOutput = g_hChildStd_OUT_Wr,
536 .hStdInput = g_hChildStd_IN_Rd,
537 .dwFlags = windows.STARTF_USESTDHANDLES,
538
539 .lpReserved = null,
540 .lpDesktop = null,
541 .lpTitle = null,
542 .dwX = 0,
543 .dwY = 0,
544 .dwXSize = 0,
545 .dwYSize = 0,
546 .dwXCountChars = 0,
547 .dwYCountChars = 0,
548 .dwFillAttribute = 0,
549 .wShowWindow = 0,
550 .cbReserved2 = 0,
551 .lpReserved2 = null,
552 };
553 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
554
555 const app_name = %return cstr.addNullByte(self.allocator, self.argv[0]);
556 defer self.allocator.free(app_name);
557
558 const cwd_slice = if (self.cwd) |cwd| {
559 %return cstr.addNullByte(self.allocator, cwd)
560 } else {
561 null
562 };
563 defer if (cwd_slice) |cwd| self.allocator.free(cwd);
564 const cwd_ptr = if (cwd_slice) |cwd| cwd.ptr else null;
565
566 const maybe_envp_buf = if (self.env_map) |env_map| {
567 %return os.createNullDelimitedEnvMap(self.allocator, env_map)
568 } else {
569 null
570 };
571 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
572 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
573
574 if (!windows.CreateProcessA(app_name.ptr, cmd_line.ptr, null, null, true, 0,
575 @ptrCast(?&c_void, envp_ptr),
576 cwd_ptr, &siStartInfo, &piProcInfo))
577 {
578 const err = windows.GetLastError();
579 return switch (err) {
580 windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
581 else => error.Unexpected,
582 };
583 }
584 os.windowsClose(piProcInfo.hThread);
585
586 if (stdin_ptr) |outstream| {
587 *outstream = io.OutStream {
588 .fd = {},
589 .handle = g_hChildStd_IN_Wr,
590 .handle_id = undefined,
591 .buffer = undefined,
592 .index = 0,
593 };
594 }
595 if (stdout_ptr) |instream| {
596 *instream = io.InStream {
597 .fd = {},
598 .handle = g_hChildStd_OUT_Rd,
599 .handle_id = undefined,
600 };
601 }
602 if (stderr_ptr) |instream| {
603 *instream = io.InStream {
604 .fd = {},
605 .handle = g_hChildStd_ERR_Rd,
606 .handle_id = undefined,
607 };
608 }
609
610 self.handle = piProcInfo.hProcess;
611 self.term = null;
612 self.stdin = stdin_ptr;
613 self.stdout = stdout_ptr;
614 self.stderr = stderr_ptr;
615
616 if (self.stdin_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_IN_Rd); }
617 if (self.stderr_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_ERR_Wr); }
618 if (self.stdout_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_OUT_Wr); }
619 }
620
358 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {621 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {
359 switch (stdio) {622 switch (stdio) {
360 StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno),623 StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno),
...@@ -365,6 +628,84 @@ pub const ChildProcess = struct {...@@ -365,6 +628,84 @@ pub const ChildProcess = struct {
365 }628 }
366};629};
367630
631/// Caller must dealloc.
632/// Guarantees a null byte at result[result.len].
633fn windowsCreateCommandLine(allocator: &Allocator, argv: []const []const u8) -> %[]u8 {
634 var buf = %return Buffer.initSize(allocator, 0);
635 defer buf.deinit();
636
637 for (argv) |arg, arg_i| {
638 if (arg_i != 0)
639 %return buf.appendByte(' ');
640 if (mem.indexOfAny(u8, arg, " \t\n\"") == null) {
641 %return buf.append(arg);
642 continue;
643 }
644 %return buf.appendByte('"');
645 var backslash_count: usize = 0;
646 for (arg) |byte| {
647 switch (byte) {
648 '\\' => backslash_count += 1,
649 '"' => {
650 %return buf.appendByteNTimes('\\', backslash_count * 2 + 1);
651 %return buf.appendByte('"');
652 backslash_count = 0;
653 },
654 else => {
655 %return buf.appendByteNTimes('\\', backslash_count);
656 %return buf.appendByte(byte);
657 backslash_count = 0;
658 },
659 }
660 }
661 %return buf.appendByteNTimes('\\', backslash_count * 2);
662 %return buf.appendByte('"');
663 }
664
665 return buf.toOwnedSlice();
666}
667
668fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) {
669 if (rd) |h| os.windowsClose(h);
670 if (wr) |h| os.windowsClose(h);
671}
672
673fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
674 if (!windows.CreatePipe(rd, wr, sattr, 0)) {
675 const err = windows.GetLastError();
676 return switch (err) {
677 else => error.Unexpected,
678 };
679 }
680}
681
682fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.DWORD) -> %void {
683 if (!windows.SetHandleInformation(h, mask, flags)) {
684 const err = windows.GetLastError();
685 return switch (err) {
686 else => error.Unexpected,
687 };
688 }
689}
690
691fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
692 var rd_h: windows.HANDLE = undefined;
693 var wr_h: windows.HANDLE = undefined;
694 %return windowsMakePipe(&rd_h, &wr_h, sattr);
695 %return windowsSetHandleInfo(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
696 *rd = rd_h;
697 *wr = wr_h;
698}
699
700fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &windows.SECURITY_ATTRIBUTES) -> %void {
701 var rd_h: windows.HANDLE = undefined;
702 var wr_h: windows.HANDLE = undefined;
703 %return windowsMakePipe(&rd_h, &wr_h, sattr);
704 %return windowsSetHandleInfo(rd_h, windows.HANDLE_FLAG_INHERIT, 0);
705 *rd = rd_h;
706 *wr = wr_h;
707}
708
368fn makePipe() -> %[2]i32 {709fn makePipe() -> %[2]i32 {
369 var fds: [2]i32 = undefined;710 var fds: [2]i32 = undefined;
370 const err = posix.getErrno(posix.pipe(&fds));711 const err = posix.getErrno(posix.pipe(&fds));
std/os/index.zig+49-25
...@@ -382,6 +382,37 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {...@@ -382,6 +382,37 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void {
382 }382 }
383}383}
384384
385pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) -> %[]?&u8 {
386 const envp_count = env_map.count();
387 const envp_buf = %return allocator.alloc(?&u8, envp_count + 1);
388 mem.set(?&u8, envp_buf, null);
389 %defer freeNullDelimitedEnvMap(allocator, envp_buf);
390 {
391 var it = env_map.iterator();
392 var i: usize = 0;
393 while (it.next()) |pair| : (i += 1) {
394 const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
395 @memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
396 env_buf[pair.key.len] = '=';
397 @memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
398 env_buf[env_buf.len - 1] = 0;
399
400 envp_buf[i] = env_buf.ptr;
401 }
402 assert(i == envp_count);
403 }
404 assert(envp_buf[envp_count] == null);
405 return envp_buf;
406}
407
408pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) {
409 for (envp_buf) |env| {
410 const env_buf = if (env) |ptr| cstr.toSlice(ptr) else break;
411 allocator.free(env_buf);
412 }
413 allocator.free(envp_buf);
414}
415
385/// This function must allocate memory to add a null terminating bytes on path and each arg.416/// This function must allocate memory to add a null terminating bytes on path and each arg.
386/// It must also convert to KEY=VALUE\0 format for environment variables, and include null417/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
387/// pointers after the args and after the environment variables.418/// pointers after the args and after the environment variables.
...@@ -408,31 +439,8 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,...@@ -408,31 +439,8 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
408 }439 }
409 argv_buf[argv.len] = null;440 argv_buf[argv.len] = null;
410441
411 const envp_count = env_map.count();442 const envp_buf = %return createNullDelimitedEnvMap(allocator, env_map);
412 const envp_buf = %return allocator.alloc(?&u8, envp_count + 1);443 defer freeNullDelimitedEnvMap(allocator, envp_buf);
413 mem.set(?&u8, envp_buf, null);
414 defer {
415 for (envp_buf) |env| {
416 const env_buf = if (env) |ptr| cstr.toSlice(ptr) else break;
417 allocator.free(env_buf);
418 }
419 allocator.free(envp_buf);
420 }
421 {
422 var it = env_map.iterator();
423 var i: usize = 0;
424 while (it.next()) |pair| : (i += 1) {
425 const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
426 @memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
427 env_buf[pair.key.len] = '=';
428 @memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
429 env_buf[env_buf.len - 1] = 0;
430
431 envp_buf[i] = env_buf.ptr;
432 }
433 assert(i == envp_count);
434 }
435 envp_buf[envp_count] = null;
436444
437 const exe_path = argv[0];445 const exe_path = argv[0];
438 if (mem.indexOfScalar(u8, exe_path, '/') != null) {446 if (mem.indexOfScalar(u8, exe_path, '/') != null) {
...@@ -1367,6 +1375,22 @@ fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const...@@ -1367,6 +1375,22 @@ fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const
1367 assert(it.next(&debug.global_allocator) == null);1375 assert(it.next(&debug.global_allocator) == null);
1368}1376}
13691377
1378error WaitAbandoned;
1379error WaitTimeOut;
1380
1381pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) -> %void {
1382 const result = windows.WaitForSingleObject(handle, milliseconds);
1383 return switch (result) {
1384 windows.WAIT_ABANDONED => error.WaitAbandoned,
1385 windows.WAIT_OBJECT_0 => {},
1386 windows.WAIT_TIMEOUT => error.WaitTimeOut,
1387 windows.WAIT_FAILED => switch (windows.GetLastError()) {
1388 else => error.Unexpected,
1389 },
1390 else => error.Unexpected,
1391 };
1392}
1393
1370test "std.os" {1394test "std.os" {
1371 _ = @import("child_process.zig");1395 _ = @import("child_process.zig");
1372 _ = @import("darwin_errno.zig");1396 _ = @import("darwin_errno.zig");
std/os/windows/index.zig+77-14
...@@ -14,6 +14,14 @@ pub extern "kernel32" stdcallcc fn CreateFileA(lpFileName: LPCSTR, dwDesiredAcce...@@ -14,6 +14,14 @@ pub extern "kernel32" stdcallcc fn CreateFileA(lpFileName: LPCSTR, dwDesiredAcce
14 dwShareMode: DWORD, lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, dwCreationDisposition: DWORD,14 dwShareMode: DWORD, lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, dwCreationDisposition: DWORD,
15 dwFlagsAndAttributes: DWORD, hTemplateFile: ?HANDLE) -> HANDLE;15 dwFlagsAndAttributes: DWORD, hTemplateFile: ?HANDLE) -> HANDLE;
1616
17pub extern "kernel32" stdcallcc fn CreatePipe(hReadPipe: &HANDLE, hWritePipe: &HANDLE,
18 lpPipeAttributes: &SECURITY_ATTRIBUTES, nSize: DWORD) -> BOOL;
19
20pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lpCommandLine: LPSTR,
21 lpProcessAttributes: ?&SECURITY_ATTRIBUTES, lpThreadAttributes: ?&SECURITY_ATTRIBUTES, bInheritHandles: BOOL,
22 dwCreationFlags: DWORD, lpEnvironment: ?LPVOID, lpCurrentDirectory: ?LPCSTR, lpStartupInfo: &STARTUPINFOA,
23 lpProcessInformation: &PROCESS_INFORMATION) -> BOOL;
24
17pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool;25pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) -> bool;
1826
19pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn;27pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) -> noreturn;
...@@ -24,11 +32,10 @@ pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out...@@ -24,11 +32,10 @@ pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out
2432
25pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) -> DWORD;33pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) -> DWORD;
2634
27/// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis.35pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: &DWORD) -> BOOL;
28/// Multiple threads do not overwrite each other's last-error code.36
29pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;37pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;
3038
31/// Retrieves file information for the specified file.
32pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE,39pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE,
33 in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, out_lpFileInformation: &c_void,40 in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, out_lpFileInformation: &c_void,
34 in_dwBufferSize: DWORD) -> bool;41 in_dwBufferSize: DWORD) -> bool;
...@@ -36,26 +43,29 @@ pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE...@@ -36,26 +43,29 @@ pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(in_hFile: HANDLE
36pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(hFile: HANDLE, lpszFilePath: LPSTR,43pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(hFile: HANDLE, lpszFilePath: LPSTR,
37 cchFilePath: DWORD, dwFlags: DWORD) -> DWORD;44 cchFilePath: DWORD, dwFlags: DWORD) -> DWORD;
3845
39/// Retrieves a handle to the specified standard device (standard input, standard output, or standard error).46pub extern "kernel32" stdcallcc fn GetProcessHeap() -> HANDLE;
47
40pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) -> ?HANDLE;48pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) -> ?HANDLE;
4149
50pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID;
51
52pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL;
53
42pub extern "kernel32" stdcallcc fn ReadFile(in_hFile: HANDLE, out_lpBuffer: LPVOID,54pub extern "kernel32" stdcallcc fn ReadFile(in_hFile: HANDLE, out_lpBuffer: LPVOID,
43 in_nNumberOfBytesToRead: DWORD, out_lpNumberOfBytesRead: &DWORD,55 in_nNumberOfBytesToRead: DWORD, out_lpNumberOfBytesRead: &DWORD,
44 in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;56 in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
4557
46/// Writes data to the specified file or input/output (I/O) device.58pub extern "kernel32" stdcallcc fn SetHandleInformation(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) -> BOOL;
47/// This function is designed for both synchronous and asynchronous operation. For a similar function designed solely for asynchronous operation, see WriteFileEx.
48pub extern "kernel32" stdcallcc fn WriteFile(in_hFile: HANDLE, in_lpBuffer: &const c_void,
49 in_nNumberOfBytesToWrite: DWORD, out_lpNumberOfBytesWritten: ?&DWORD,
50 in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
5159
52pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD);60pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD);
5361
54pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID;62pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
5563
56pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL;64pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
5765
58pub extern "kernel32" stdcallcc fn GetProcessHeap() -> HANDLE;66pub extern "kernel32" stdcallcc fn WriteFile(in_hFile: HANDLE, in_lpBuffer: &const c_void,
67 in_nNumberOfBytesToWrite: DWORD, out_lpNumberOfBytesWritten: ?&DWORD,
68 in_out_lpOverlapped: ?&OVERLAPPED) -> BOOL;
5969
60pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) -> c_int;70pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) -> c_int;
6171
...@@ -88,7 +98,7 @@ pub const INT = c_int;...@@ -88,7 +98,7 @@ pub const INT = c_int;
88pub const ULONG_PTR = usize;98pub const ULONG_PTR = usize;
89pub const WCHAR = u16;99pub const WCHAR = u16;
90pub const LPCVOID = &const c_void;100pub const LPCVOID = &const c_void;
91101pub const LPBYTE = &BYTE;
92102
93/// The standard input device. Initially, this is the console input buffer, CONIN$.103/// The standard input device. Initially, this is the console input buffer, CONIN$.
94pub const STD_INPUT_HANDLE = @maxValue(DWORD) - 10 + 1;104pub const STD_INPUT_HANDLE = @maxValue(DWORD) - 10 + 1;
...@@ -158,7 +168,7 @@ pub const VOLUME_NAME_NT = 0x2;...@@ -158,7 +168,7 @@ pub const VOLUME_NAME_NT = 0x2;
158168
159pub const SECURITY_ATTRIBUTES = extern struct {169pub const SECURITY_ATTRIBUTES = extern struct {
160 nLength: DWORD,170 nLength: DWORD,
161 lpSecurityDescriptor: LPVOID,171 lpSecurityDescriptor: ?LPVOID,
162 bInheritHandle: BOOL,172 bInheritHandle: BOOL,
163};173};
164pub const PSECURITY_ATTRIBUTES = &SECURITY_ATTRIBUTES;174pub const PSECURITY_ATTRIBUTES = &SECURITY_ATTRIBUTES;
...@@ -189,3 +199,56 @@ pub const FILE_ATTRIBUTE_OFFLINE = 0x1000;...@@ -189,3 +199,56 @@ pub const FILE_ATTRIBUTE_OFFLINE = 0x1000;
189pub const FILE_ATTRIBUTE_READONLY = 0x1;199pub const FILE_ATTRIBUTE_READONLY = 0x1;
190pub const FILE_ATTRIBUTE_SYSTEM = 0x4;200pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
191pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;201pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
202
203pub const PROCESS_INFORMATION = extern struct {
204 hProcess: HANDLE,
205 hThread: HANDLE,
206 dwProcessId: DWORD,
207 dwThreadId: DWORD,
208};
209
210pub const STARTUPINFOA = extern struct {
211 cb: DWORD,
212 lpReserved: ?LPSTR,
213 lpDesktop: ?LPSTR,
214 lpTitle: ?LPSTR,
215 dwX: DWORD,
216 dwY: DWORD,
217 dwXSize: DWORD,
218 dwYSize: DWORD,
219 dwXCountChars: DWORD,
220 dwYCountChars: DWORD,
221 dwFillAttribute: DWORD,
222 dwFlags: DWORD,
223 wShowWindow: WORD,
224 cbReserved2: WORD,
225 lpReserved2: ?LPBYTE,
226 hStdInput: ?HANDLE,
227 hStdOutput: ?HANDLE,
228 hStdError: ?HANDLE,
229};
230
231pub const STARTF_FORCEONFEEDBACK = 0x00000040;
232pub const STARTF_FORCEOFFFEEDBACK = 0x00000080;
233pub const STARTF_PREVENTPINNING = 0x00002000;
234pub const STARTF_RUNFULLSCREEN = 0x00000020;
235pub const STARTF_TITLEISAPPID = 0x00001000;
236pub const STARTF_TITLEISLINKNAME = 0x00000800;
237pub const STARTF_UNTRUSTEDSOURCE = 0x00008000;
238pub const STARTF_USECOUNTCHARS = 0x00000008;
239pub const STARTF_USEFILLATTRIBUTE = 0x00000010;
240pub const STARTF_USEHOTKEY = 0x00000200;
241pub const STARTF_USEPOSITION = 0x00000004;
242pub const STARTF_USESHOWWINDOW = 0x00000001;
243pub const STARTF_USESIZE = 0x00000002;
244pub const STARTF_USESTDHANDLES = 0x00000100;
245
246pub const INFINITE = 4294967295;
247
248pub const WAIT_ABANDONED = 0x00000080;
249pub const WAIT_OBJECT_0 = 0x00000000;
250pub const WAIT_TIMEOUT = 0x00000102;
251pub const WAIT_FAILED = 0xFFFFFFFF;
252
253pub const HANDLE_FLAG_INHERIT = 0x00000001;
254pub const HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;