| author | |
| committer | |
| log | fcef9905ae859601d085576012b81dc05f67c46f |
| tree | 1c45d66e0a8e011c178a7d5d6db87a31a6681713 |
| parent | c3edf0ba641fcaf9ccc93e27ca3bf140d4b8e84c |
| parent | 71156aff806856d5d48e72cd8aeb9315b9ae0b62 |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31113
Reviewed-by: Andrew Kelley <andrew@ziglang.org>17 files changed, 1449 insertions(+), 1561 deletions(-)
build.zig+1| ... | ... | @@ -1498,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1498 | 1498 | defer dir.close(io); |
| 1499 | 1499 | |
| 1500 | 1500 | var wf = b.addWriteFiles(); |
| 1501 | b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step); | |
| 1501 | 1502 | |
| 1502 | 1503 | var it = dir.iterateAssumeFirstIteration(); |
| 1503 | 1504 | while (it.next(io) catch @panic("failed to read dir")) |entry| { |
lib/std/Build/Step.zig+20-32| ... | ... | @@ -386,10 +386,14 @@ pub const ZigProcess = struct { |
| 386 | 386 | child: std.process.Child, |
| 387 | 387 | multi_reader_buffer: Io.File.MultiReader.Buffer(2), |
| 388 | 388 | multi_reader: Io.File.MultiReader, |
| 389 | progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void, | |
| 389 | progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn, | |
| 390 | 390 | |
| 391 | 391 | pub const StreamEnum = enum { stdout, stderr }; |
| 392 | 392 | |
| 393 | pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void { | |
| 394 | zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null; | |
| 395 | } | |
| 396 | ||
| 393 | 397 | pub fn deinit(zp: *ZigProcess, io: Io) void { |
| 394 | 398 | zp.child.kill(io); |
| 395 | 399 | zp.multi_reader.deinit(); |
| ... | ... | @@ -417,7 +421,14 @@ pub fn evalZigProcess( |
| 417 | 421 | |
| 418 | 422 | if (s.getZigProcess()) |zp| update: { |
| 419 | 423 | assert(watch); |
| 420 | if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd); | |
| 424 | if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index); | |
| 425 | zp.progress_ipc_index = null; | |
| 426 | var exited = false; | |
| 427 | defer if (exited) { | |
| 428 | s.cast(Compile).?.zig_process = null; | |
| 429 | zp.deinit(io); | |
| 430 | gpa.destroy(zp); | |
| 431 | } else zp.saveState(prog_node); | |
| 421 | 432 | const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { |
| 422 | 433 | error.BrokenPipe, error.EndOfStream => |reason| { |
| 423 | 434 | std.log.info("{s} restart required: {t}", .{ argv[0], reason }); |
| ... | ... | @@ -426,7 +437,7 @@ pub fn evalZigProcess( |
| 426 | 437 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); |
| 427 | 438 | }; |
| 428 | 439 | _ = term; |
| 429 | s.clearZigProcess(gpa); | |
| 440 | exited = true; | |
| 430 | 441 | break :update; |
| 431 | 442 | }, |
| 432 | 443 | else => |e| return e, |
| ... | ... | @@ -442,7 +453,7 @@ pub fn evalZigProcess( |
| 442 | 453 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); |
| 443 | 454 | }; |
| 444 | 455 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; |
| 445 | s.clearZigProcess(gpa); | |
| 456 | exited = true; | |
| 446 | 457 | try handleChildProcessTerm(s, term); |
| 447 | 458 | return error.MakeFailed; |
| 448 | 459 | } |
| ... | ... | @@ -467,19 +478,16 @@ pub fn evalZigProcess( |
| 467 | 478 | .progress_node = prog_node, |
| 468 | 479 | }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); |
| 469 | 480 | |
| 470 | zp.* = .{ | |
| 471 | .child = zp.child, | |
| 472 | .multi_reader_buffer = undefined, | |
| 473 | .multi_reader = undefined, | |
| 474 | .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {}, | |
| 475 | }; | |
| 476 | 481 | zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{ |
| 477 | 482 | zp.child.stdout.?, zp.child.stderr.?, |
| 478 | 483 | }); |
| 479 | if (watch) s.setZigProcess(zp); | |
| 484 | if (watch) s.cast(Compile).?.zig_process = zp; | |
| 480 | 485 | defer if (!watch) zp.deinit(io); |
| 481 | 486 | |
| 482 | const result = try zigProcessUpdate(s, zp, watch, web_server, gpa); | |
| 487 | const result = result: { | |
| 488 | defer if (watch) zp.saveState(prog_node); | |
| 489 | break :result try zigProcessUpdate(s, zp, watch, web_server, gpa); | |
| 490 | }; | |
| 483 | 491 | |
| 484 | 492 | if (!watch) { |
| 485 | 493 | // Send EOF to stdin. |
| ... | ... | @@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess { |
| 670 | 678 | }; |
| 671 | 679 | } |
| 672 | 680 | |
| 673 | fn setZigProcess(s: *Step, zp: *ZigProcess) void { | |
| 674 | switch (s.id) { | |
| 675 | .compile => s.cast(Compile).?.zig_process = zp, | |
| 676 | else => unreachable, | |
| 677 | } | |
| 678 | } | |
| 679 | ||
| 680 | fn clearZigProcess(s: *Step, gpa: Allocator) void { | |
| 681 | switch (s.id) { | |
| 682 | .compile => { | |
| 683 | const compile = s.cast(Compile).?; | |
| 684 | if (compile.zig_process) |zp| { | |
| 685 | gpa.destroy(zp); | |
| 686 | compile.zig_process = null; | |
| 687 | } | |
| 688 | }, | |
| 689 | else => unreachable, | |
| 690 | } | |
| 691 | } | |
| 692 | ||
| 693 | 681 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { |
| 694 | 682 | const header: std.zig.Client.Message.Header = .{ |
| 695 | 683 | .tag = tag, |
lib/std/Build/Watch.zig+7-11| ... | ... | @@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) { |
| 366 | 366 | .MaximumLength = @intCast(path_len_bytes), |
| 367 | 367 | .Buffer = @constCast(sub_path_w.span().ptr), |
| 368 | 368 | }; |
| 369 | var attr = windows.OBJECT_ATTRIBUTES{ | |
| 370 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 371 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, | |
| 372 | .Attributes = .{}, | |
| 373 | .ObjectName = &nt_name, | |
| 374 | .SecurityDescriptor = null, | |
| 375 | .SecurityQualityOfService = null, | |
| 376 | }; | |
| 377 | var io: windows.IO_STATUS_BLOCK = undefined; | |
| 369 | var iosb: windows.IO_STATUS_BLOCK = undefined; | |
| 378 | 370 | |
| 379 | 371 | switch (windows.ntdll.NtCreateFile( |
| 380 | 372 | &dir_handle, |
| ... | ... | @@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) { |
| 385 | 377 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 386 | 378 | .GENERIC = .{ .READ = true }, |
| 387 | 379 | }, |
| 388 | &attr, | |
| 389 | &io, | |
| 380 | &.{ | |
| 381 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, | |
| 382 | .ObjectName = &nt_name, | |
| 383 | }, | |
| 384 | &iosb, | |
| 390 | 385 | null, |
| 391 | 386 | .{}, |
| 392 | 387 | .VALID_FLAGS, |
| 393 | 388 | .OPEN, |
| 394 | 389 | .{ |
| 395 | 390 | .DIRECTORY_FILE = true, |
| 391 | .IO = .ASYNCHRONOUS, | |
| 396 | 392 | .OPEN_FOR_BACKUP_INTENT = true, |
| 397 | 393 | }, |
| 398 | 394 | null, |
lib/std/Io/Threaded.zig+416-367| ... | ... | @@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment; |
| 19 | 19 | const assert = std.debug.assert; |
| 20 | 20 | const posix = std.posix; |
| 21 | 21 | const windows = std.os.windows; |
| 22 | const ws2_32 = std.os.windows.ws2_32; | |
| 22 | const ws2_32 = windows.ws2_32; | |
| 23 | 23 | |
| 24 | 24 | /// Thread-safe. |
| 25 | 25 | /// |
| ... | ... | @@ -76,6 +76,7 @@ environ: Environ, |
| 76 | 76 | |
| 77 | 77 | null_file: NullFile = .{}, |
| 78 | 78 | random_file: RandomFile = .{}, |
| 79 | pipe_file: PipeFile = .{}, | |
| 79 | 80 | |
| 80 | 81 | csprng: Csprng = .{}, |
| 81 | 82 | |
| ... | ... | @@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) { |
| 121 | 122 | |
| 122 | 123 | const Environ = struct { |
| 123 | 124 | /// Unmodified data directly from the OS. |
| 124 | process_environ: process.Environ = .empty, | |
| 125 | process_environ: process.Environ, | |
| 125 | 126 | /// Protected by `mutex`. Determines whether the other fields have been |
| 126 | 127 | /// memoized based on `process_environ`. |
| 127 | 128 | initialized: bool = false, |
| ... | ... | @@ -131,13 +132,15 @@ const Environ = struct { |
| 131 | 132 | /// Protected by `mutex`. Memoized based on `process_environ`. |
| 132 | 133 | string: String = .{}, |
| 133 | 134 | /// ZIG_PROGRESS |
| 134 | zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing, | |
| 135 | zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing, | |
| 135 | 136 | /// Protected by `mutex`. Tracks the problem, if any, that occurred when |
| 136 | 137 | /// trying to scan environment variables. |
| 137 | 138 | /// |
| 138 | 139 | /// Errors are only possible on WASI. |
| 139 | 140 | err: ?Error = null, |
| 140 | 141 | |
| 142 | pub const empty: Environ = .{ .process_environ = .empty }; | |
| 143 | ||
| 141 | 144 | pub const Error = Allocator.Error || Io.UnexpectedError; |
| 142 | 145 | |
| 143 | 146 | pub const Exist = struct { |
| ... | ... | @@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) { |
| 193 | 196 | }, |
| 194 | 197 | }; |
| 195 | 198 | |
| 199 | pub const PipeFile = switch (native_os) { | |
| 200 | .windows => struct { | |
| 201 | handle: ?windows.HANDLE = null, | |
| 202 | ||
| 203 | fn deinit(this: *@This()) void { | |
| 204 | if (this.handle) |handle| { | |
| 205 | windows.CloseHandle(handle); | |
| 206 | this.handle = null; | |
| 207 | } | |
| 208 | } | |
| 209 | }, | |
| 210 | else => struct { | |
| 211 | fn deinit(this: @This()) void { | |
| 212 | _ = this; | |
| 213 | } | |
| 214 | }, | |
| 215 | }; | |
| 216 | ||
| 196 | 217 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 197 | 218 | unknown = 0, |
| 198 | 219 | _, |
| ... | ... | @@ -1498,7 +1519,9 @@ pub const init_single_threaded: Threaded = .{ |
| 1498 | 1519 | .old_sig_pipe = undefined, |
| 1499 | 1520 | .have_signal_handler = false, |
| 1500 | 1521 | .argv0 = .empty, |
| 1501 | .environ = .{}, | |
| 1522 | .environ = .{ .process_environ = .{ | |
| 1523 | .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty, | |
| 1524 | } }, | |
| 1502 | 1525 | .worker_threads = .init(null), |
| 1503 | 1526 | .disable_memory_mapping = false, |
| 1504 | 1527 | }; |
| ... | ... | @@ -1533,6 +1556,7 @@ pub fn deinit(t: *Threaded) void { |
| 1533 | 1556 | } |
| 1534 | 1557 | t.null_file.deinit(); |
| 1535 | 1558 | t.random_file.deinit(); |
| 1559 | t.pipe_file.deinit(); | |
| 1536 | 1560 | t.* = undefined; |
| 1537 | 1561 | } |
| 1538 | 1562 | |
| ... | ... | @@ -1576,14 +1600,7 @@ fn worker(t: *Threaded) void { |
| 1576 | 1600 | }, |
| 1577 | 1601 | }, |
| 1578 | 1602 | }, |
| 1579 | &.{ | |
| 1580 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 1581 | .RootDirectory = null, | |
| 1582 | .ObjectName = null, | |
| 1583 | .Attributes = .{}, | |
| 1584 | .SecurityDescriptor = null, | |
| 1585 | .SecurityQualityOfService = null, | |
| 1586 | }, | |
| 1603 | &.{ .ObjectName = null }, | |
| 1587 | 1604 | &windows.teb().ClientId, |
| 1588 | 1605 | ) == .SUCCESS); |
| 1589 | 1606 | } |
| ... | ... | @@ -2595,8 +2612,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { |
| 2595 | 2612 | // opportunity to find additional ready operations. |
| 2596 | 2613 | break :t 0; |
| 2597 | 2614 | } |
| 2598 | const max_poll_ms = std.math.maxInt(i32); | |
| 2599 | break :t max_poll_ms; | |
| 2615 | break :t std.math.maxInt(i32); | |
| 2600 | 2616 | }; |
| 2601 | 2617 | const syscall = try Syscall.start(); |
| 2602 | 2618 | const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); |
| ... | ... | @@ -2716,6 +2732,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout |
| 2716 | 2732 | break :allocation allocation; |
| 2717 | 2733 | }; |
| 2718 | 2734 | @memcpy(slice[0..poll_buffer_len], storage.slice); |
| 2735 | storage.slice = slice; | |
| 2719 | 2736 | } |
| 2720 | 2737 | storage.slice[len] = .{ |
| 2721 | 2738 | .fd = file.handle, |
| ... | ... | @@ -2769,9 +2786,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout |
| 2769 | 2786 | } |
| 2770 | 2787 | const d = deadline orelse break :t -1; |
| 2771 | 2788 | const duration = d.durationFromNow(t_io); |
| 2772 | if (duration.raw.nanoseconds <= 0) return error.Timeout; | |
| 2773 | const max_poll_ms = std.math.maxInt(i32); | |
| 2774 | break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); | |
| 2789 | break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32)); | |
| 2775 | 2790 | }; |
| 2776 | 2791 | const syscall = try Syscall.start(); |
| 2777 | 2792 | const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms); |
| ... | ... | @@ -3379,12 +3394,8 @@ fn dirCreateDirPathOpenWindows( |
| 3379 | 3394 | }, |
| 3380 | 3395 | }, |
| 3381 | 3396 | &.{ |
| 3382 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | |
| 3383 | 3397 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 3384 | .Attributes = .{}, | |
| 3385 | 3398 | .ObjectName = &nt_name, |
| 3386 | .SecurityDescriptor = null, | |
| 3387 | .SecurityQualityOfService = null, | |
| 3388 | 3399 | }, |
| 3389 | 3400 | &io_status_block, |
| 3390 | 3401 | null, |
| ... | ... | @@ -4066,13 +4077,9 @@ fn dirAccessWindows( |
| 4066 | 4077 | .MaximumLength = path_len_bytes, |
| 4067 | 4078 | .Buffer = @constCast(sub_path_w.ptr), |
| 4068 | 4079 | }; |
| 4069 | var attr: windows.OBJECT_ATTRIBUTES = .{ | |
| 4070 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 4080 | const attr: windows.OBJECT_ATTRIBUTES = .{ | |
| 4071 | 4081 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 4072 | .Attributes = .{}, | |
| 4073 | 4082 | .ObjectName = &nt_name, |
| 4074 | .SecurityDescriptor = null, | |
| 4075 | .SecurityQualityOfService = null, | |
| 4076 | 4083 | }; |
| 4077 | 4084 | var basic_info: windows.FILE.BASIC_INFORMATION = undefined; |
| 4078 | 4085 | const syscall: Syscall = try .start(); |
| ... | ... | @@ -4288,14 +4295,8 @@ fn dirCreateFileWindows( |
| 4288 | 4295 | .Buffer = @constCast(sub_path_w.ptr), |
| 4289 | 4296 | }; |
| 4290 | 4297 | const attr: windows.OBJECT_ATTRIBUTES = .{ |
| 4291 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 4292 | 4298 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 4293 | .Attributes = .{ | |
| 4294 | .INHERIT = false, | |
| 4295 | }, | |
| 4296 | 4299 | .ObjectName = &nt_name, |
| 4297 | .SecurityDescriptor = null, | |
| 4298 | .SecurityQualityOfService = null, | |
| 4299 | 4300 | }; |
| 4300 | 4301 | const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) |
| 4301 | 4302 | .CREATE |
| ... | ... | @@ -4908,17 +4909,6 @@ pub fn dirOpenFileWtf16( |
| 4908 | 4909 | .MaximumLength = path_len_bytes, |
| 4909 | 4910 | .Buffer = @constCast(sub_path_w.ptr), |
| 4910 | 4911 | }; |
| 4911 | var attr: w.OBJECT_ATTRIBUTES = .{ | |
| 4912 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | |
| 4913 | .RootDirectory = dir_handle, | |
| 4914 | .Attributes = .{ | |
| 4915 | // TODO should we set INHERIT=false? | |
| 4916 | //.INHERIT = false, | |
| 4917 | }, | |
| 4918 | .ObjectName = &nt_name, | |
| 4919 | .SecurityDescriptor = null, | |
| 4920 | .SecurityQualityOfService = null, | |
| 4921 | }; | |
| 4922 | 4912 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 4923 | 4913 | |
| 4924 | 4914 | // There are multiple kernel bugs being worked around with retries. |
| ... | ... | @@ -4937,7 +4927,10 @@ pub fn dirOpenFileWtf16( |
| 4937 | 4927 | .WRITE = flags.isWrite(), |
| 4938 | 4928 | }, |
| 4939 | 4929 | }, |
| 4940 | &attr, | |
| 4930 | &.{ | |
| 4931 | .RootDirectory = dir_handle, | |
| 4932 | .ObjectName = &nt_name, | |
| 4933 | }, | |
| 4941 | 4934 | &io_status_block, |
| 4942 | 4935 | null, |
| 4943 | 4936 | .{ .NORMAL = true }, |
| ... | ... | @@ -5305,12 +5298,8 @@ pub fn dirOpenDirWindows( |
| 5305 | 5298 | }, |
| 5306 | 5299 | }, |
| 5307 | 5300 | &.{ |
| 5308 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | |
| 5309 | 5301 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 5310 | .Attributes = .{}, | |
| 5311 | 5302 | .ObjectName = &nt_name, |
| 5312 | .SecurityDescriptor = null, | |
| 5313 | .SecurityQualityOfService = null, | |
| 5314 | 5303 | }, |
| 5315 | 5304 | &io_status_block, |
| 5316 | 5305 | null, |
| ... | ... | @@ -6520,12 +6509,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 6520 | 6509 | .SYNCHRONIZE = true, |
| 6521 | 6510 | } }, |
| 6522 | 6511 | &.{ |
| 6523 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | |
| 6524 | 6512 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 6525 | .Attributes = .{}, | |
| 6526 | 6513 | .ObjectName = &nt_name, |
| 6527 | .SecurityDescriptor = null, | |
| 6528 | .SecurityQualityOfService = null, | |
| 6529 | 6514 | }, |
| 6530 | 6515 | &io_status_block, |
| 6531 | 6516 | null, |
| ... | ... | @@ -6534,6 +6519,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 6534 | 6519 | .OPEN, |
| 6535 | 6520 | .{ |
| 6536 | 6521 | .DIRECTORY_FILE = remove_dir, |
| 6522 | .IO = .SYNCHRONOUS_NONALERT, | |
| 6537 | 6523 | .NON_DIRECTORY_FILE = !remove_dir, |
| 6538 | 6524 | .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? |
| 6539 | 6525 | }, |
| ... | ... | @@ -7345,14 +7331,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 7345 | 7331 | .Buffer = @constCast(sub_path_w.ptr), |
| 7346 | 7332 | }; |
| 7347 | 7333 | const attr: windows.OBJECT_ATTRIBUTES = .{ |
| 7348 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 7349 | 7334 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 7350 | .Attributes = .{ | |
| 7351 | .INHERIT = false, | |
| 7352 | }, | |
| 7353 | 7335 | .ObjectName = &nt_name, |
| 7354 | .SecurityDescriptor = null, | |
| 7355 | .SecurityQualityOfService = null, | |
| 7356 | 7336 | }; |
| 7357 | 7337 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 7358 | 7338 | var result_handle: windows.HANDLE = undefined; |
| ... | ... | @@ -7909,24 +7889,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 7909 | 7889 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7910 | 7890 | _ = t; |
| 7911 | 7891 | |
| 7892 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 7912 | 7893 | const syscall: Syscall = try .start(); |
| 7913 | 7894 | while (true) { |
| 7914 | if (windows.kernel32.FlushFileBuffers(file.handle) != 0) { | |
| 7915 | return syscall.finish(); | |
| 7916 | } | |
| 7917 | switch (windows.GetLastError()) { | |
| 7918 | .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero | |
| 7919 | .INVALID_HANDLE => unreachable, | |
| 7920 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time | |
| 7921 | .UNEXP_NET_ERR => return syscall.fail(error.InputOutput), | |
| 7922 | .OPERATION_ABORTED => { | |
| 7895 | switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) { | |
| 7896 | .SUCCESS => break syscall.finish(), | |
| 7897 | .CANCELLED => { | |
| 7923 | 7898 | try syscall.checkCancel(); |
| 7924 | 7899 | continue; |
| 7925 | 7900 | }, |
| 7926 | else => |err| { | |
| 7927 | syscall.finish(); | |
| 7928 | return windows.unexpectedError(err); | |
| 7929 | }, | |
| 7901 | .INVALID_HANDLE => unreachable, | |
| 7902 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time | |
| 7903 | .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput), | |
| 7904 | else => |status| return syscall.unexpectedNtstatus(status), | |
| 7930 | 7905 | } |
| 7931 | 7906 | } |
| 7932 | 7907 | } |
| ... | ... | @@ -14446,7 +14421,10 @@ const WindowsEnvironStrings = struct { |
| 14446 | 14421 | PATHEXT: ?[:0]const u16 = null, |
| 14447 | 14422 | |
| 14448 | 14423 | fn scan() WindowsEnvironStrings { |
| 14449 | const ptr = windows.peb().ProcessParameters.Environment; | |
| 14424 | const peb = windows.peb(); | |
| 14425 | assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 14426 | defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 14427 | const ptr = peb.ProcessParameters.Environment; | |
| 14450 | 14428 | |
| 14451 | 14429 | var result: WindowsEnvironStrings = .{}; |
| 14452 | 14430 | var i: usize = 0; |
| ... | ... | @@ -14472,7 +14450,7 @@ const WindowsEnvironStrings = struct { |
| 14472 | 14450 | |
| 14473 | 14451 | inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| { |
| 14474 | 14452 | const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name); |
| 14475 | if (std.os.windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w; | |
| 14453 | if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w; | |
| 14476 | 14454 | } |
| 14477 | 14455 | } |
| 14478 | 14456 | |
| ... | ... | @@ -14491,29 +14469,46 @@ fn scanEnviron(t: *Threaded) void { |
| 14491 | 14469 | // This value expires with any call that modifies the environment, |
| 14492 | 14470 | // which is outside of this Io implementation's control, so references |
| 14493 | 14471 | // must be short-lived. |
| 14494 | const ptr = windows.peb().ProcessParameters.Environment; | |
| 14472 | const peb = windows.peb(); | |
| 14473 | assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 14474 | defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 14475 | const ptr = peb.ProcessParameters.Environment; | |
| 14495 | 14476 | |
| 14496 | 14477 | var i: usize = 0; |
| 14497 | 14478 | while (ptr[i] != 0) { |
| 14498 | const key_start = i; | |
| 14499 | 14479 | |
| 14500 | 14480 | // There are some special environment variables that start with =, |
| 14501 | 14481 | // so we need a special case to not treat = as a key/value separator |
| 14502 | 14482 | // if it's the first character. |
| 14503 | 14483 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 14504 | if (ptr[key_start] == '=') i += 1; | |
| 14505 | ||
| 14484 | const key_start = i; | |
| 14485 | if (ptr[i] == '=') i += 1; | |
| 14506 | 14486 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} |
| 14507 | 14487 | const key_w = ptr[key_start..i]; |
| 14508 | if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) { | |
| 14488 | ||
| 14489 | const value_start = i + 1; | |
| 14490 | while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value | |
| 14491 | const value_w = ptr[value_start..i]; | |
| 14492 | i += 1; // skip over null byte | |
| 14493 | ||
| 14494 | if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) { | |
| 14509 | 14495 | t.environ.exist.NO_COLOR = true; |
| 14510 | } else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) { | |
| 14496 | } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) { | |
| 14511 | 14497 | t.environ.exist.CLICOLOR_FORCE = true; |
| 14498 | } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) { | |
| 14499 | t.environ.zig_progress_file = file: { | |
| 14500 | var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; | |
| 14501 | const len = std.unicode.calcWtf8Len(value_w); | |
| 14502 | if (len > value_buf.len) break :file error.UnrecognizedFormat; | |
| 14503 | assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len); | |
| 14504 | break :file .{ | |
| 14505 | .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch | |
| 14506 | break :file error.UnrecognizedFormat), | |
| 14507 | .flags = .{ .nonblocking = true }, | |
| 14508 | }; | |
| 14509 | }; | |
| 14512 | 14510 | } |
| 14513 | 14511 | comptime assert(@sizeOf(Environ.String) == 0); |
| 14514 | ||
| 14515 | while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value | |
| 14516 | i += 1; // skip over null byte | |
| 14517 | 14512 | } |
| 14518 | 14513 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 14519 | 14514 | var environ_count: usize = undefined; |
| ... | ... | @@ -14559,22 +14554,28 @@ fn scanEnviron(t: *Threaded) void { |
| 14559 | 14554 | comptime assert(@sizeOf(Environ.String) == 0); |
| 14560 | 14555 | } |
| 14561 | 14556 | } else { |
| 14562 | for (t.environ.process_environ.block) |opt_line| { | |
| 14563 | const line = opt_line.?; | |
| 14564 | var line_i: usize = 0; | |
| 14565 | while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} | |
| 14566 | const key = line[0..line_i]; | |
| 14557 | for (t.environ.process_environ.block.slice) |opt_entry| { | |
| 14558 | const entry = opt_entry.?; | |
| 14559 | var entry_i: usize = 0; | |
| 14560 | while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {} | |
| 14561 | const key = entry[0..entry_i]; | |
| 14567 | 14562 | |
| 14568 | var end_i: usize = line_i; | |
| 14569 | while (line[end_i] != 0) : (end_i += 1) {} | |
| 14570 | const value = line[line_i + 1 .. end_i :0]; | |
| 14563 | var end_i: usize = entry_i; | |
| 14564 | while (entry[end_i] != 0) : (end_i += 1) {} | |
| 14565 | const value = entry[entry_i + 1 .. end_i :0]; | |
| 14571 | 14566 | |
| 14572 | 14567 | if (std.mem.eql(u8, key, "NO_COLOR")) { |
| 14573 | 14568 | t.environ.exist.NO_COLOR = true; |
| 14574 | 14569 | } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { |
| 14575 | 14570 | t.environ.exist.CLICOLOR_FORCE = true; |
| 14576 | 14571 | } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { |
| 14577 | t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat; | |
| 14572 | t.environ.zig_progress_file = file: { | |
| 14573 | break :file .{ | |
| 14574 | .handle = std.fmt.parseInt(u31, value, 10) catch | |
| 14575 | break :file error.UnrecognizedFormat, | |
| 14576 | .flags = .{ .nonblocking = true }, | |
| 14577 | }; | |
| 14578 | }; | |
| 14578 | 14579 | } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| { |
| 14579 | 14580 | if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value; |
| 14580 | 14581 | } |
| ... | ... | @@ -14597,19 +14598,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces |
| 14597 | 14598 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); |
| 14598 | 14599 | for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; |
| 14599 | 14600 | |
| 14600 | const envp: [*:null]const ?[*:0]const u8 = m: { | |
| 14601 | const env_block = env_block: { | |
| 14601 | 14602 | const prog_fd: i32 = -1; |
| 14602 | if (options.environ_map) |environ_map| { | |
| 14603 | break :m (try environ_map.createBlockPosix(arena, .{ | |
| 14604 | .zig_progress_fd = prog_fd, | |
| 14605 | })).ptr; | |
| 14606 | } | |
| 14607 | break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{ | |
| 14603 | if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ | |
| 14608 | 14604 | .zig_progress_fd = prog_fd, |
| 14609 | })).ptr; | |
| 14605 | }); | |
| 14606 | break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ | |
| 14607 | .zig_progress_fd = prog_fd, | |
| 14608 | }); | |
| 14610 | 14609 | }; |
| 14611 | 14610 | |
| 14612 | return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); | |
| 14611 | return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); | |
| 14613 | 14612 | } |
| 14614 | 14613 | |
| 14615 | 14614 | fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { |
| ... | ... | @@ -14679,16 +14678,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14679 | 14678 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); |
| 14680 | 14679 | const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; |
| 14681 | 14680 | |
| 14682 | const prog_pipe: [2]posix.fd_t = p: { | |
| 14683 | if (options.progress_node.index == .none) { | |
| 14684 | break :p .{ -1, -1 }; | |
| 14685 | } else { | |
| 14686 | // We use CLOEXEC for the same reason as in `pipe_flags`. | |
| 14687 | break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); | |
| 14688 | } | |
| 14689 | }; | |
| 14681 | const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none) | |
| 14682 | // We use CLOEXEC for the same reason as in `pipe_flags`. | |
| 14683 | try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }) | |
| 14684 | else | |
| 14685 | .{ -1, -1 }; | |
| 14690 | 14686 | errdefer destroyPipe(prog_pipe); |
| 14691 | 14687 | |
| 14688 | if (native_os == .linux and prog_pipe[0] != -1) { | |
| 14689 | _ = posix.system.fcntl(prog_pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2)); | |
| 14690 | } | |
| 14691 | ||
| 14692 | 14692 | var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); |
| 14693 | 14693 | defer arena_allocator.deinit(); |
| 14694 | 14694 | const arena = arena_allocator.allocator(); |
| ... | ... | @@ -14708,16 +14708,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14708 | 14708 | const prog_fileno = 3; |
| 14709 | 14709 | comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); |
| 14710 | 14710 | |
| 14711 | const envp: [*:null]const ?[*:0]const u8 = m: { | |
| 14711 | const env_block = env_block: { | |
| 14712 | 14712 | const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; |
| 14713 | if (options.environ_map) |environ_map| { | |
| 14714 | break :m (try environ_map.createBlockPosix(arena, .{ | |
| 14715 | .zig_progress_fd = prog_fd, | |
| 14716 | })).ptr; | |
| 14717 | } | |
| 14718 | break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{ | |
| 14713 | if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ | |
| 14719 | 14714 | .zig_progress_fd = prog_fd, |
| 14720 | })).ptr; | |
| 14715 | }); | |
| 14716 | break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ | |
| 14717 | .zig_progress_fd = prog_fd, | |
| 14718 | }); | |
| 14721 | 14719 | }; |
| 14722 | 14720 | |
| 14723 | 14721 | // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. |
| ... | ... | @@ -14800,7 +14798,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14800 | 14798 | } |
| 14801 | 14799 | } |
| 14802 | 14800 | |
| 14803 | const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); | |
| 14801 | const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); | |
| 14804 | 14802 | forkBail(ep1, err); |
| 14805 | 14803 | } |
| 14806 | 14804 | |
| ... | ... | @@ -14814,8 +14812,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14814 | 14812 | if (options.stderr == .pipe) posix.close(stderr_pipe[1]); |
| 14815 | 14813 | |
| 14816 | 14814 | if (prog_pipe[1] != -1) posix.close(prog_pipe[1]); |
| 14817 | ||
| 14818 | options.progress_node.setIpcFd(prog_pipe[0]); | |
| 14815 | options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); | |
| 14819 | 14816 | |
| 14820 | 14817 | return .{ |
| 14821 | 14818 | .pid = pid, |
| ... | ... | @@ -14938,42 +14935,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT |
| 14938 | 14935 | // some rare edge cases where our process handle no longer has the |
| 14939 | 14936 | // PROCESS_TERMINATE access right, so let's do another check to make |
| 14940 | 14937 | // sure the process is really no longer running: |
| 14941 | windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied; | |
| 14942 | return error.AlreadyTerminated; | |
| 14938 | const minimal_timeout: windows.LARGE_INTEGER = -1; | |
| 14939 | switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) { | |
| 14940 | .SUCCESS => return error.AlreadyTerminated, | |
| 14941 | else => return error.AccessDenied, | |
| 14942 | } | |
| 14943 | 14943 | }, |
| 14944 | 14944 | else => |err| return windows.unexpectedError(err), |
| 14945 | 14945 | } |
| 14946 | 14946 | } |
| 14947 | _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE); | |
| 14947 | const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); | |
| 14948 | _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout); | |
| 14948 | 14949 | childCleanupWindows(child); |
| 14949 | 14950 | } |
| 14950 | 14951 | |
| 14951 | 14952 | fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 14952 | 14953 | const handle = child.id.?; |
| 14953 | 14954 | |
| 14954 | const syscall: Syscall = try .start(); | |
| 14955 | while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) { | |
| 14956 | windows.WAIT_OBJECT_0 => break syscall.finish(), | |
| 14957 | windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => { | |
| 14958 | try syscall.checkCancel(); | |
| 14955 | const alertable_syscall: AlertableSyscall = try .start(); | |
| 14956 | const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); | |
| 14957 | while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) { | |
| 14958 | windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(), | |
| 14959 | .USER_APC, .ALERTED, .TIMEOUT => { | |
| 14960 | try alertable_syscall.checkCancel(); | |
| 14959 | 14961 | continue; |
| 14960 | 14962 | }, |
| 14961 | windows.WAIT_FAILED => { | |
| 14962 | syscall.finish(); | |
| 14963 | switch (windows.GetLastError()) { | |
| 14964 | else => |err| return windows.unexpectedError(err), | |
| 14965 | } | |
| 14966 | }, | |
| 14967 | else => return syscall.fail(error.Unexpected), | |
| 14963 | else => |status| return alertable_syscall.unexpectedNtstatus(status), | |
| 14968 | 14964 | }; |
| 14969 | 14965 | |
| 14970 | const term: process.Child.Term = x: { | |
| 14971 | var exit_code: windows.DWORD = undefined; | |
| 14972 | if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) { | |
| 14973 | break :x .{ .unknown = 0 }; | |
| 14974 | } else { | |
| 14975 | break :x .{ .exited = @as(u8, @truncate(exit_code)) }; | |
| 14976 | } | |
| 14966 | var info: windows.PROCESS_BASIC_INFORMATION = undefined; | |
| 14967 | const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess( | |
| 14968 | handle, | |
| 14969 | .BasicInformation, | |
| 14970 | &info, | |
| 14971 | @sizeOf(windows.PROCESS_BASIC_INFORMATION), | |
| 14972 | null, | |
| 14973 | )) { | |
| 14974 | .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) }, | |
| 14975 | else => .{ .unknown = 0 }, | |
| 14977 | 14976 | }; |
| 14978 | 14977 | |
| 14979 | 14978 | childCleanupWindows(child); |
| ... | ... | @@ -15236,88 +15235,71 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32 |
| 15236 | 15235 | fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 15237 | 15236 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 15238 | 15237 | |
| 15239 | var saAttr: windows.SECURITY_ATTRIBUTES = .{ | |
| 15240 | .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), | |
| 15241 | .bInheritHandle = windows.TRUE, | |
| 15242 | .lpSecurityDescriptor = null, | |
| 15243 | }; | |
| 15244 | ||
| 15245 | 15238 | const any_ignore = |
| 15246 | 15239 | options.stdin == .ignore or |
| 15247 | 15240 | options.stdout == .ignore or |
| 15248 | 15241 | options.stderr == .ignore; |
| 15249 | ||
| 15250 | const nul_handle = if (any_ignore) try getNulHandle(t) else undefined; | |
| 15251 | ||
| 15252 | var g_hChildStd_IN_Rd: ?windows.HANDLE = null; | |
| 15253 | var g_hChildStd_IN_Wr: ?windows.HANDLE = null; | |
| 15254 | switch (options.stdin) { | |
| 15255 | .pipe => { | |
| 15256 | try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr); | |
| 15257 | }, | |
| 15258 | .ignore => { | |
| 15259 | g_hChildStd_IN_Rd = nul_handle; | |
| 15260 | }, | |
| 15261 | .inherit => { | |
| 15262 | g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null; | |
| 15263 | }, | |
| 15264 | .close => { | |
| 15265 | g_hChildStd_IN_Rd = null; | |
| 15266 | }, | |
| 15267 | .file => @panic("TODO implement passing file stdio in processSpawnWindows"), | |
| 15268 | } | |
| 15269 | errdefer if (options.stdin == .pipe) { | |
| 15270 | windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); | |
| 15271 | }; | |
| 15272 | ||
| 15273 | var g_hChildStd_OUT_Rd: ?windows.HANDLE = null; | |
| 15274 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; | |
| 15275 | switch (options.stdout) { | |
| 15276 | .pipe => { | |
| 15277 | try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); | |
| 15278 | }, | |
| 15279 | .ignore => { | |
| 15280 | g_hChildStd_OUT_Wr = nul_handle; | |
| 15281 | }, | |
| 15282 | .inherit => { | |
| 15283 | g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null; | |
| 15284 | }, | |
| 15285 | .close => { | |
| 15286 | g_hChildStd_OUT_Wr = null; | |
| 15287 | }, | |
| 15288 | .file => @panic("TODO implement passing file stdio in processSpawnWindows"), | |
| 15289 | } | |
| 15290 | errdefer if (options.stdout == .pipe) { | |
| 15291 | windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); | |
| 15292 | }; | |
| 15293 | ||
| 15294 | var g_hChildStd_ERR_Rd: ?windows.HANDLE = null; | |
| 15295 | var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; | |
| 15296 | switch (options.stderr) { | |
| 15297 | .pipe => { | |
| 15298 | try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); | |
| 15299 | }, | |
| 15300 | .ignore => { | |
| 15301 | g_hChildStd_ERR_Wr = nul_handle; | |
| 15302 | }, | |
| 15303 | .inherit => { | |
| 15304 | g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null; | |
| 15305 | }, | |
| 15306 | .close => { | |
| 15307 | g_hChildStd_ERR_Wr = null; | |
| 15308 | }, | |
| 15309 | .file => @panic("TODO implement passing file stdio in processSpawnWindows"), | |
| 15310 | } | |
| 15311 | errdefer if (options.stderr == .pipe) { | |
| 15312 | windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); | |
| 15313 | }; | |
| 15242 | const nul_handle = if (any_ignore) try getNulDevice(t) else undefined; | |
| 15243 | ||
| 15244 | const any_inherit = | |
| 15245 | options.stdin == .inherit or | |
| 15246 | options.stdout == .inherit or | |
| 15247 | options.stderr == .inherit; | |
| 15248 | const peb = if (any_inherit) windows.peb() else undefined; | |
| 15249 | ||
| 15250 | const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{ | |
| 15251 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, | |
| 15252 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, | |
| 15253 | .outbound = true, | |
| 15254 | }) else undefined; | |
| 15255 | errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle); | |
| 15256 | ||
| 15257 | const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{ | |
| 15258 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, | |
| 15259 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, | |
| 15260 | .inbound = true, | |
| 15261 | }) else undefined; | |
| 15262 | errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle); | |
| 15263 | ||
| 15264 | const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{ | |
| 15265 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, | |
| 15266 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, | |
| 15267 | .inbound = true, | |
| 15268 | }) else undefined; | |
| 15269 | errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle); | |
| 15270 | ||
| 15271 | const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{ | |
| 15272 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, | |
| 15273 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } }, | |
| 15274 | .inbound = true, | |
| 15275 | .quota = std.Progress.max_packet_len * 2, | |
| 15276 | }) else undefined; | |
| 15277 | errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle); | |
| 15314 | 15278 | |
| 15315 | 15279 | var siStartInfo: windows.STARTUPINFOW = .{ |
| 15316 | 15280 | .cb = @sizeOf(windows.STARTUPINFOW), |
| 15317 | .hStdError = g_hChildStd_ERR_Wr, | |
| 15318 | .hStdOutput = g_hChildStd_OUT_Wr, | |
| 15319 | .hStdInput = g_hChildStd_IN_Rd, | |
| 15320 | 15281 | .dwFlags = windows.STARTF_USESTDHANDLES, |
| 15282 | .hStdInput = switch (options.stdin) { | |
| 15283 | .inherit => peb.ProcessParameters.hStdInput, | |
| 15284 | .file => |file| file.handle, | |
| 15285 | .ignore => nul_handle, | |
| 15286 | .pipe => stdin_pipe[1], | |
| 15287 | .close => null, | |
| 15288 | }, | |
| 15289 | .hStdOutput = switch (options.stdout) { | |
| 15290 | .inherit => peb.ProcessParameters.hStdOutput, | |
| 15291 | .file => |file| file.handle, | |
| 15292 | .ignore => nul_handle, | |
| 15293 | .pipe => stdout_pipe[1], | |
| 15294 | .close => null, | |
| 15295 | }, | |
| 15296 | .hStdError = switch (options.stderr) { | |
| 15297 | .inherit => peb.ProcessParameters.hStdError, | |
| 15298 | .file => |file| file.handle, | |
| 15299 | .ignore => nul_handle, | |
| 15300 | .pipe => stderr_pipe[1], | |
| 15301 | .close => null, | |
| 15302 | }, | |
| 15321 | 15303 | |
| 15322 | 15304 | .lpReserved = null, |
| 15323 | 15305 | .lpDesktop = null, |
| ... | ... | @@ -15363,8 +15345,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15363 | 15345 | }; |
| 15364 | 15346 | const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; |
| 15365 | 15347 | |
| 15366 | const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null; | |
| 15367 | const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; | |
| 15348 | const env_block = env_block: { | |
| 15349 | const prog_handle = if (options.progress_node.index != .none) | |
| 15350 | prog_pipe[1] | |
| 15351 | else | |
| 15352 | windows.INVALID_HANDLE_VALUE; | |
| 15353 | if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{ | |
| 15354 | .zig_progress_handle = prog_handle, | |
| 15355 | }); | |
| 15356 | break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{ | |
| 15357 | .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE, | |
| 15358 | }); | |
| 15359 | }; | |
| 15368 | 15360 | |
| 15369 | 15361 | const app_name_wtf8 = options.argv[0]; |
| 15370 | 15362 | const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); |
| ... | ... | @@ -15439,7 +15431,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15439 | 15431 | &app_buf, |
| 15440 | 15432 | PATHEXT, |
| 15441 | 15433 | &cmd_line_cache, |
| 15442 | envp_ptr, | |
| 15434 | env_block, | |
| 15443 | 15435 | cwd_w_ptr, |
| 15444 | 15436 | flags, |
| 15445 | 15437 | &siStartInfo, |
| ... | ... | @@ -15474,7 +15466,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15474 | 15466 | &app_buf, |
| 15475 | 15467 | PATHEXT, |
| 15476 | 15468 | &cmd_line_cache, |
| 15477 | envp_ptr, | |
| 15469 | env_block, | |
| 15478 | 15470 | cwd_w_ptr, |
| 15479 | 15471 | flags, |
| 15480 | 15472 | &siStartInfo, |
| ... | ... | @@ -15494,21 +15486,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15494 | 15486 | }; |
| 15495 | 15487 | } |
| 15496 | 15488 | |
| 15497 | if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?); | |
| 15498 | if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?); | |
| 15499 | if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?); | |
| 15489 | if (options.progress_node.index != .none) { | |
| 15490 | windows.CloseHandle(prog_pipe[1]); | |
| 15491 | options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); | |
| 15492 | } | |
| 15500 | 15493 | |
| 15501 | 15494 | return .{ |
| 15502 | 15495 | .id = piProcInfo.hProcess, |
| 15503 | 15496 | .thread_handle = piProcInfo.hThread, |
| 15504 | .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null, | |
| 15505 | .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, | |
| 15506 | .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, | |
| 15497 | .stdin = stdin: switch (options.stdin) { | |
| 15498 | .pipe => { | |
| 15499 | windows.CloseHandle(stdin_pipe[1]); | |
| 15500 | break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } }; | |
| 15501 | }, | |
| 15502 | else => null, | |
| 15503 | }, | |
| 15504 | .stdout = stdout: switch (options.stdout) { | |
| 15505 | .pipe => { | |
| 15506 | windows.CloseHandle(stdout_pipe[1]); | |
| 15507 | break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } }; | |
| 15508 | }, | |
| 15509 | else => null, | |
| 15510 | }, | |
| 15511 | .stderr = stderr: switch (options.stderr) { | |
| 15512 | .pipe => { | |
| 15513 | windows.CloseHandle(stderr_pipe[1]); | |
| 15514 | break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } }; | |
| 15515 | }, | |
| 15516 | else => null, | |
| 15517 | }, | |
| 15507 | 15518 | .request_resource_usage_statistics = options.request_resource_usage_statistics, |
| 15508 | 15519 | }; |
| 15509 | 15520 | } |
| 15510 | 15521 | |
| 15511 | fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { | |
| 15522 | fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { | |
| 15512 | 15523 | { |
| 15513 | 15524 | mutexLock(&t.mutex); |
| 15514 | 15525 | defer mutexUnlock(&t.mutex); |
| ... | ... | @@ -15516,12 +15527,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15516 | 15527 | } |
| 15517 | 15528 | |
| 15518 | 15529 | const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }; |
| 15519 | ||
| 15520 | var nt_name: windows.UNICODE_STRING = .{ | |
| 15521 | .Length = device_path.len * 2, | |
| 15522 | .MaximumLength = 0, | |
| 15523 | .Buffer = @constCast(&device_path), | |
| 15524 | }; | |
| 15525 | 15530 | var fresh_handle: windows.HANDLE = undefined; |
| 15526 | 15531 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 15527 | 15532 | var syscall: Syscall = try .start(); |
| ... | ... | @@ -15532,12 +15537,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15532 | 15537 | .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, |
| 15533 | 15538 | }, |
| 15534 | 15539 | &.{ |
| 15535 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 15536 | .RootDirectory = null, | |
| 15537 | .ObjectName = &nt_name, | |
| 15538 | .Attributes = .{}, | |
| 15539 | .SecurityDescriptor = null, | |
| 15540 | .SecurityQualityOfService = null, | |
| 15540 | .ObjectName = @constCast(&windows.UNICODE_STRING{ | |
| 15541 | .Length = @sizeOf(@TypeOf(device_path)), | |
| 15542 | .MaximumLength = 0, | |
| 15543 | .Buffer = @constCast(&device_path), | |
| 15544 | }), | |
| 15541 | 15545 | }, |
| 15542 | 15546 | &io_status_block, |
| 15543 | 15547 | .VALID_FLAGS, |
| ... | ... | @@ -15564,7 +15568,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15564 | 15568 | }; |
| 15565 | 15569 | } |
| 15566 | 15570 | |
| 15567 | fn getNulHandle(t: *Threaded) !windows.HANDLE { | |
| 15571 | fn getNulDevice(t: *Threaded) !windows.HANDLE { | |
| 15568 | 15572 | { |
| 15569 | 15573 | mutexLock(&t.mutex); |
| 15570 | 15574 | defer mutexUnlock(&t.mutex); |
| ... | ... | @@ -15572,44 +15576,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15572 | 15576 | } |
| 15573 | 15577 | |
| 15574 | 15578 | const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; |
| 15575 | var nt_name: windows.UNICODE_STRING = .{ | |
| 15576 | .Length = device_path.len * 2, | |
| 15577 | .MaximumLength = 0, | |
| 15578 | .Buffer = @constCast(&device_path), | |
| 15579 | }; | |
| 15580 | const attr: windows.OBJECT_ATTRIBUTES = .{ | |
| 15581 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | |
| 15582 | .RootDirectory = null, | |
| 15583 | .Attributes = .{ | |
| 15584 | .INHERIT = true, | |
| 15585 | }, | |
| 15586 | .ObjectName = &nt_name, | |
| 15587 | .SecurityDescriptor = null, | |
| 15588 | .SecurityQualityOfService = null, | |
| 15589 | }; | |
| 15590 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 15591 | 15579 | var fresh_handle: windows.HANDLE = undefined; |
| 15580 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 15592 | 15581 | var syscall: Syscall = try .start(); |
| 15593 | while (true) switch (windows.ntdll.NtCreateFile( | |
| 15582 | while (true) switch (windows.ntdll.NtOpenFile( | |
| 15594 | 15583 | &fresh_handle, |
| 15595 | 15584 | .{ |
| 15596 | 15585 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 15597 | .GENERIC = .{ .WRITE = true, .READ = true }, | |
| 15586 | .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } }, | |
| 15587 | }, | |
| 15588 | &.{ | |
| 15589 | .Attributes = .{ .INHERIT = true }, | |
| 15590 | .ObjectName = @constCast(&windows.UNICODE_STRING{ | |
| 15591 | .Length = @sizeOf(@TypeOf(device_path)), | |
| 15592 | .MaximumLength = 0, | |
| 15593 | .Buffer = @constCast(&device_path), | |
| 15594 | }), | |
| 15598 | 15595 | }, |
| 15599 | &attr, | |
| 15600 | 15596 | &io_status_block, |
| 15601 | null, | |
| 15602 | .{ .NORMAL = true }, | |
| 15603 | 15597 | .VALID_FLAGS, |
| 15604 | .OPEN, | |
| 15605 | .{ | |
| 15606 | .DIRECTORY_FILE = false, | |
| 15607 | .NON_DIRECTORY_FILE = true, | |
| 15608 | .IO = .SYNCHRONOUS_NONALERT, | |
| 15609 | .OPEN_REPARSE_POINT = false, | |
| 15610 | }, | |
| 15611 | null, | |
| 15612 | 0, | |
| 15598 | .{ .IO = .SYNCHRONOUS_NONALERT }, | |
| 15613 | 15599 | )) { |
| 15614 | 15600 | .SUCCESS => { |
| 15615 | 15601 | syscall.finish(); |
| ... | ... | @@ -15623,6 +15609,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15623 | 15609 | return fresh_handle; |
| 15624 | 15610 | } |
| 15625 | 15611 | }, |
| 15612 | .CANCELLED => { | |
| 15613 | try syscall.checkCancel(); | |
| 15614 | continue; | |
| 15615 | }, | |
| 15616 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), | |
| 15617 | .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), | |
| 15618 | .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), | |
| 15619 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), | |
| 15620 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), | |
| 15621 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), | |
| 15622 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), | |
| 15623 | .SHARING_VIOLATION => return syscall.fail(error.AccessDenied), | |
| 15624 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), | |
| 15625 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), | |
| 15626 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), | |
| 15627 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), | |
| 15628 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), | |
| 15629 | else => |status| return syscall.unexpectedNtstatus(status), | |
| 15630 | }; | |
| 15631 | } | |
| 15632 | ||
| 15633 | fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { | |
| 15634 | { | |
| 15635 | mutexLock(&t.mutex); | |
| 15636 | defer mutexUnlock(&t.mutex); | |
| 15637 | if (t.pipe_file.handle) |handle| return handle; | |
| 15638 | } | |
| 15639 | ||
| 15640 | const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' }; | |
| 15641 | var fresh_handle: windows.HANDLE = undefined; | |
| 15642 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 15643 | var syscall: Syscall = try .start(); | |
| 15644 | while (true) switch (windows.ntdll.NtOpenFile( | |
| 15645 | &fresh_handle, | |
| 15646 | .{ .STANDARD = .{ .SYNCHRONIZE = true } }, | |
| 15647 | &.{ | |
| 15648 | .ObjectName = @constCast(&windows.UNICODE_STRING{ | |
| 15649 | .Length = @sizeOf(@TypeOf(device_path)), | |
| 15650 | .MaximumLength = 0, | |
| 15651 | .Buffer = @constCast(&device_path), | |
| 15652 | }), | |
| 15653 | }, | |
| 15654 | &io_status_block, | |
| 15655 | .VALID_FLAGS, | |
| 15656 | .{ .IO = .SYNCHRONOUS_NONALERT }, | |
| 15657 | )) { | |
| 15658 | .SUCCESS => { | |
| 15659 | syscall.finish(); | |
| 15660 | mutexLock(&t.mutex); // Another thread might have won the race. | |
| 15661 | defer mutexUnlock(&t.mutex); | |
| 15662 | if (t.pipe_file.handle) |prev_handle| { | |
| 15663 | windows.CloseHandle(fresh_handle); | |
| 15664 | return prev_handle; | |
| 15665 | } else { | |
| 15666 | t.pipe_file.handle = fresh_handle; | |
| 15667 | return fresh_handle; | |
| 15668 | } | |
| 15669 | }, | |
| 15626 | 15670 | .DELETE_PENDING => { |
| 15627 | 15671 | // This error means that there *was* a file in this location on |
| 15628 | 15672 | // the file system, but it was deleted. However, the OS is not |
| ... | ... | @@ -15669,7 +15713,7 @@ fn windowsCreateProcessPathExt( |
| 15669 | 15713 | app_buf: *std.ArrayList(u16), |
| 15670 | 15714 | pathext: [:0]const u16, |
| 15671 | 15715 | cmd_line_cache: *WindowsCommandLineCache, |
| 15672 | envp_ptr: ?[*:0]const u16, | |
| 15716 | env_block: ?process.Environ.WindowsBlock, | |
| 15673 | 15717 | cwd_ptr: ?[*:0]u16, |
| 15674 | 15718 | flags: windows.CreateProcessFlags, |
| 15675 | 15719 | lpStartupInfo: *windows.STARTUPINFOW, |
| ... | ... | @@ -15846,7 +15890,7 @@ fn windowsCreateProcessPathExt( |
| 15846 | 15890 | if (windowsCreateProcess( |
| 15847 | 15891 | app_name_w.ptr, |
| 15848 | 15892 | cmd_line_w.ptr, |
| 15849 | envp_ptr, | |
| 15893 | env_block, | |
| 15850 | 15894 | cwd_ptr, |
| 15851 | 15895 | flags, |
| 15852 | 15896 | lpStartupInfo, |
| ... | ... | @@ -15906,7 +15950,7 @@ fn windowsCreateProcessPathExt( |
| 15906 | 15950 | else |
| 15907 | 15951 | full_app_name; |
| 15908 | 15952 | |
| 15909 | if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { | |
| 15953 | if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { | |
| 15910 | 15954 | return; |
| 15911 | 15955 | } else |err| switch (err) { |
| 15912 | 15956 | error.FileNotFound => continue, |
| ... | ... | @@ -15930,7 +15974,7 @@ fn windowsCreateProcessPathExt( |
| 15930 | 15974 | fn windowsCreateProcess( |
| 15931 | 15975 | app_name: [*:0]u16, |
| 15932 | 15976 | cmd_line: [*:0]u16, |
| 15933 | env_ptr: ?[*:0]const u16, | |
| 15977 | env_block: ?process.Environ.WindowsBlock, | |
| 15934 | 15978 | cwd_ptr: ?[*:0]u16, |
| 15935 | 15979 | flags: windows.CreateProcessFlags, |
| 15936 | 15980 | lpStartupInfo: *windows.STARTUPINFOW, |
| ... | ... | @@ -15945,7 +15989,7 @@ fn windowsCreateProcess( |
| 15945 | 15989 | null, |
| 15946 | 15990 | windows.TRUE, |
| 15947 | 15991 | flags, |
| 15948 | env_ptr, | |
| 15992 | if (env_block) |block| block.slice.ptr else null, | |
| 15949 | 15993 | cwd_ptr, |
| 15950 | 15994 | lpStartupInfo, |
| 15951 | 15995 | lpProcessInformation, |
| ... | ... | @@ -16466,11 +16510,11 @@ fn posixExecv( |
| 16466 | 16510 | arg0_expand: process.ArgExpansion, |
| 16467 | 16511 | file: [*:0]const u8, |
| 16468 | 16512 | child_argv: [*:null]?[*:0]const u8, |
| 16469 | envp: [*:null]const ?[*:0]const u8, | |
| 16513 | env_block: process.Environ.PosixBlock, | |
| 16470 | 16514 | PATH: []const u8, |
| 16471 | 16515 | ) process.ReplaceError { |
| 16472 | 16516 | const file_slice = std.mem.sliceTo(file, 0); |
| 16473 | if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp); | |
| 16517 | if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block); | |
| 16474 | 16518 | |
| 16475 | 16519 | // Use of PATH_MAX here is valid as the path_buf will be passed |
| 16476 | 16520 | // directly to the operating system in posixExecvPath. |
| ... | ... | @@ -16498,7 +16542,7 @@ fn posixExecv( |
| 16498 | 16542 | .expand => child_argv[0] = full_path, |
| 16499 | 16543 | .no_expand => {}, |
| 16500 | 16544 | } |
| 16501 | err = posixExecvPath(full_path, child_argv, envp); | |
| 16545 | err = posixExecvPath(full_path, child_argv, env_block); | |
| 16502 | 16546 | switch (err) { |
| 16503 | 16547 | error.AccessDenied => seen_eacces = true, |
| 16504 | 16548 | error.FileNotFound, error.NotDir => {}, |
| ... | ... | @@ -16513,10 +16557,10 @@ fn posixExecv( |
| 16513 | 16557 | pub fn posixExecvPath( |
| 16514 | 16558 | path: [*:0]const u8, |
| 16515 | 16559 | child_argv: [*:null]const ?[*:0]const u8, |
| 16516 | envp: [*:null]const ?[*:0]const u8, | |
| 16560 | env_block: process.Environ.PosixBlock, | |
| 16517 | 16561 | ) process.ReplaceError { |
| 16518 | 16562 | try Thread.checkCancel(); |
| 16519 | switch (posix.errno(posix.system.execve(path, child_argv, envp))) { | |
| 16563 | switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) { | |
| 16520 | 16564 | .FAULT => |err| return errnoBug(err), // Bad pointer parameter. |
| 16521 | 16565 | .@"2BIG" => return error.SystemResources, |
| 16522 | 16566 | .MFILE => return error.ProcessFdQuotaExceeded, |
| ... | ... | @@ -16548,100 +16592,105 @@ pub fn posixExecvPath( |
| 16548 | 16592 | } |
| 16549 | 16593 | } |
| 16550 | 16594 | |
| 16551 | fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { | |
| 16552 | var rd_h: windows.HANDLE = undefined; | |
| 16553 | var wr_h: windows.HANDLE = undefined; | |
| 16554 | try windows.CreatePipe(&rd_h, &wr_h, sattr); | |
| 16555 | errdefer windowsDestroyPipe(rd_h, wr_h); | |
| 16556 | try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0); | |
| 16557 | rd.* = rd_h; | |
| 16558 | wr.* = wr_h; | |
| 16559 | } | |
| 16560 | ||
| 16561 | fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { | |
| 16562 | if (rd) |h| posix.close(h); | |
| 16563 | if (wr) |h| posix.close(h); | |
| 16564 | } | |
| 16565 | ||
| 16566 | fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { | |
| 16567 | var tmp_bufw: [128]u16 = undefined; | |
| 16595 | pub const CreatePipeOptions = struct { | |
| 16596 | server: End, | |
| 16597 | client: End, | |
| 16598 | inbound: bool = false, | |
| 16599 | outbound: bool = false, | |
| 16600 | maximum_instances: u32 = 1, | |
| 16601 | quota: u32 = 4096, | |
| 16602 | default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100, | |
| 16568 | 16603 | |
| 16569 | // Anonymous pipes are built upon Named pipes. | |
| 16570 | // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe | |
| 16571 | // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. | |
| 16572 | // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations | |
| 16573 | const pipe_path = blk: { | |
| 16574 | var tmp_buf: [128]u8 = undefined; | |
| 16575 | // Forge a random path for the pipe. | |
| 16576 | const pipe_path = std.fmt.bufPrintSentinel( | |
| 16577 | &tmp_buf, | |
| 16578 | "\\\\.\\pipe\\zig-childprocess-{d}-{d}", | |
| 16579 | .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) }, | |
| 16580 | 0, | |
| 16581 | ) catch unreachable; | |
| 16582 | const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable; | |
| 16583 | tmp_bufw[len] = 0; | |
| 16584 | break :blk tmp_bufw[0..len :0]; | |
| 16604 | pub const End = struct { | |
| 16605 | attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{}, | |
| 16606 | mode: windows.FILE.MODE, | |
| 16585 | 16607 | }; |
| 16586 | ||
| 16587 | // Create the read handle that can be used with overlapped IO ops. | |
| 16588 | const read_handle = windows.kernel32.CreateNamedPipeW( | |
| 16589 | pipe_path.ptr, | |
| 16590 | windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, | |
| 16591 | windows.PIPE_TYPE_BYTE, | |
| 16592 | 1, | |
| 16593 | 4096, | |
| 16594 | 4096, | |
| 16595 | 0, | |
| 16596 | sattr, | |
| 16597 | ); | |
| 16598 | if (read_handle == windows.INVALID_HANDLE_VALUE) { | |
| 16599 | switch (windows.GetLastError()) { | |
| 16600 | else => |err| return windows.unexpectedError(err), | |
| 16601 | } | |
| 16602 | } | |
| 16603 | errdefer posix.close(read_handle); | |
| 16604 | ||
| 16605 | var sattr_copy = sattr.*; | |
| 16606 | const write_handle = windows.kernel32.CreateFileW( | |
| 16607 | pipe_path.ptr, | |
| 16608 | .{ .GENERIC = .{ .WRITE = true } }, | |
| 16609 | 0, | |
| 16610 | &sattr_copy, | |
| 16611 | windows.OPEN_EXISTING, | |
| 16612 | @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }), | |
| 16613 | null, | |
| 16614 | ); | |
| 16615 | if (write_handle == windows.INVALID_HANDLE_VALUE) { | |
| 16616 | switch (windows.GetLastError()) { | |
| 16617 | else => |err| return windows.unexpectedError(err), | |
| 16618 | } | |
| 16619 | } | |
| 16620 | errdefer posix.close(write_handle); | |
| 16621 | ||
| 16622 | try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); | |
| 16623 | ||
| 16624 | rd.* = read_handle; | |
| 16625 | wr.* = write_handle; | |
| 16608 | }; | |
| 16609 | pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE { | |
| 16610 | const named_pipe_device = try t.getNamedPipeDevice(); | |
| 16611 | const server_handle = server_handle: { | |
| 16612 | var handle: windows.HANDLE = undefined; | |
| 16613 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 16614 | const syscall: Syscall = try .start(); | |
| 16615 | while (true) switch (windows.ntdll.NtCreateNamedPipeFile( | |
| 16616 | &handle, | |
| 16617 | .{ | |
| 16618 | .SPECIFIC = .{ .FILE_PIPE = .{ | |
| 16619 | .READ_DATA = options.inbound, | |
| 16620 | .WRITE_DATA = options.outbound, | |
| 16621 | .WRITE_ATTRIBUTES = true, | |
| 16622 | } }, | |
| 16623 | .STANDARD = .{ .SYNCHRONIZE = true }, | |
| 16624 | }, | |
| 16625 | &.{ | |
| 16626 | .RootDirectory = named_pipe_device, | |
| 16627 | .Attributes = options.server.attributes, | |
| 16628 | }, | |
| 16629 | &io_status_block, | |
| 16630 | .{ .READ = true, .WRITE = true }, | |
| 16631 | .CREATE, | |
| 16632 | options.server.mode, | |
| 16633 | .{ .TYPE = .BYTE_STREAM }, | |
| 16634 | .{ .MODE = .BYTE_STREAM }, | |
| 16635 | .{ .OPERATION = .QUEUE }, | |
| 16636 | options.maximum_instances, | |
| 16637 | if (options.inbound) options.quota else 0, | |
| 16638 | if (options.outbound) options.quota else 0, | |
| 16639 | &options.default_timeout, | |
| 16640 | )) { | |
| 16641 | .SUCCESS => break syscall.finish(), | |
| 16642 | .CANCELLED => { | |
| 16643 | try syscall.checkCancel(); | |
| 16644 | continue; | |
| 16645 | }, | |
| 16646 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), | |
| 16647 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), | |
| 16648 | else => |status| return syscall.unexpectedNtstatus(status), | |
| 16649 | }; | |
| 16650 | break :server_handle handle; | |
| 16651 | }; | |
| 16652 | errdefer windows.CloseHandle(server_handle); | |
| 16653 | const client_handle = client_handle: { | |
| 16654 | var handle: windows.HANDLE = undefined; | |
| 16655 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 16656 | const syscall: Syscall = try .start(); | |
| 16657 | while (true) switch (windows.ntdll.NtOpenFile( | |
| 16658 | &handle, | |
| 16659 | .{ | |
| 16660 | .SPECIFIC = .{ .FILE_PIPE = .{ | |
| 16661 | .READ_DATA = options.outbound, | |
| 16662 | .WRITE_DATA = options.inbound, | |
| 16663 | .WRITE_ATTRIBUTES = true, | |
| 16664 | } }, | |
| 16665 | .STANDARD = .{ .SYNCHRONIZE = true }, | |
| 16666 | }, | |
| 16667 | &.{ | |
| 16668 | .RootDirectory = server_handle, | |
| 16669 | .Attributes = options.client.attributes, | |
| 16670 | }, | |
| 16671 | &io_status_block, | |
| 16672 | .{ .READ = true, .WRITE = true }, | |
| 16673 | options.client.mode, | |
| 16674 | )) { | |
| 16675 | .SUCCESS => break syscall.finish(), | |
| 16676 | .CANCELLED => { | |
| 16677 | try syscall.checkCancel(); | |
| 16678 | continue; | |
| 16679 | }, | |
| 16680 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), | |
| 16681 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), | |
| 16682 | else => |status| return syscall.unexpectedNtstatus(status), | |
| 16683 | }; | |
| 16684 | break :client_handle handle; | |
| 16685 | }; | |
| 16686 | errdefer windows.CloseHandle(client_handle); | |
| 16687 | return .{ server_handle, client_handle }; | |
| 16626 | 16688 | } |
| 16627 | 16689 | |
| 16628 | var pipe_name_counter = std.atomic.Value(u32).init(1); | |
| 16629 | ||
| 16630 | 16690 | fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { |
| 16631 | 16691 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 16632 | ||
| 16633 | 16692 | t.scanEnviron(); |
| 16634 | ||
| 16635 | const int = try t.environ.zig_progress_handle; | |
| 16636 | ||
| 16637 | return .{ | |
| 16638 | .handle = switch (@typeInfo(Io.File.Handle)) { | |
| 16639 | .int => int, | |
| 16640 | .pointer => @ptrFromInt(int), | |
| 16641 | else => return error.UnsupportedOperation, | |
| 16642 | }, | |
| 16643 | .flags = .{ .nonblocking = false }, | |
| 16644 | }; | |
| 16693 | return t.environ.zig_progress_file; | |
| 16645 | 16694 | } |
| 16646 | 16695 | |
| 16647 | 16696 | pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { |
| ... | ... | @@ -16737,7 +16786,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { |
| 16737 | 16786 | // despite the function being documented to always return TRUE |
| 16738 | 16787 | // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG |
| 16739 | 16788 | // Therefore, that function is avoided in favor of using the device directly. |
| 16740 | const cng_device = try getCngHandle(t); | |
| 16789 | const cng_device = try getCngDevice(t); | |
| 16741 | 16790 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 16742 | 16791 | var i: usize = 0; |
| 16743 | 16792 | const syscall: Syscall = try .start(); |
lib/std/Io/Threaded/test.zig+10-6| ... | ... | @@ -181,13 +181,17 @@ test "cancel blocked read from pipe" { |
| 181 | 181 | var write_end: Io.File = undefined; |
| 182 | 182 | switch (builtin.target.os.tag) { |
| 183 | 183 | .wasi => return error.SkipZigTest, |
| 184 | .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{ | |
| 185 | .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES), | |
| 186 | .lpSecurityDescriptor = null, | |
| 187 | .bInheritHandle = std.os.windows.FALSE, | |
| 188 | }), | |
| 184 | .windows => { | |
| 185 | const pipe = try threaded.windowsCreatePipe(.{ | |
| 186 | .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, | |
| 187 | .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, | |
| 188 | .inbound = true, | |
| 189 | }); | |
| 190 | read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; | |
| 191 | write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; | |
| 192 | }, | |
| 189 | 193 | else => { |
| 190 | const pipe = try std.Io.Threaded.pipe2(.{}); | |
| 194 | const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true }); | |
| 191 | 195 | read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; |
| 192 | 196 | write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; |
| 193 | 197 | }, |
lib/std/Progress.zig+473-472| ... | ... | @@ -11,7 +11,7 @@ const windows = std.os.windows; |
| 11 | 11 | const testing = std.testing; |
| 12 | 12 | const assert = std.debug.assert; |
| 13 | 13 | const posix = std.posix; |
| 14 | const Writer = std.Io.Writer; | |
| 14 | const Writer = Io.Writer; | |
| 15 | 15 | |
| 16 | 16 | /// Currently this API only supports this value being set to stderr, which |
| 17 | 17 | /// happens automatically inside `start`. |
| ... | ... | @@ -21,13 +21,10 @@ io: Io, |
| 21 | 21 | |
| 22 | 22 | terminal_mode: TerminalMode, |
| 23 | 23 | |
| 24 | update_worker: ?Io.Future(void), | |
| 24 | update_worker: ?Io.Future(WorkerError!void), | |
| 25 | 25 | |
| 26 | 26 | /// Atomically set by SIGWINCH as well as the root done() function. |
| 27 | 27 | redraw_event: Io.Event, |
| 28 | /// Indicates a request to shut down and reset global state. | |
| 29 | /// Accessed atomically. | |
| 30 | done: bool, | |
| 31 | 28 | need_clear: bool, |
| 32 | 29 | status: Status, |
| 33 | 30 | |
| ... | ... | @@ -43,15 +40,19 @@ draw_buffer: []u8, |
| 43 | 40 | /// This is in a separate array from `node_storage` but with the same length so |
| 44 | 41 | /// that it can be iterated over efficiently without trashing too much of the |
| 45 | 42 | /// CPU cache. |
| 46 | node_parents: []Node.Parent, | |
| 47 | node_storage: []Node.Storage, | |
| 48 | node_freelist_next: []Node.OptionalIndex, | |
| 43 | node_parents: [node_storage_buffer_len]Node.Parent, | |
| 44 | node_storage: [node_storage_buffer_len]Node.Storage, | |
| 45 | node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex, | |
| 49 | 46 | node_freelist: Freelist, |
| 50 | 47 | /// This is the number of elements in node arrays which have been used so far. Nodes before this |
| 51 | 48 | /// index are either active, or on the freelist. The remaining nodes are implicitly free. This |
| 52 | 49 | /// value may at times temporarily exceed the node count. |
| 53 | 50 | node_end_index: u32, |
| 54 | 51 | |
| 52 | ipc_next: Ipc.SlotAtomic, | |
| 53 | ipc: [ipc_storage_buffer_len]Ipc, | |
| 54 | ipc_files: [ipc_storage_buffer_len]Io.File, | |
| 55 | ||
| 55 | 56 | start_failure: StartFailure, |
| 56 | 57 | |
| 57 | 58 | pub const Status = enum { |
| ... | ... | @@ -77,6 +78,80 @@ const Freelist = packed struct(u32) { |
| 77 | 78 | generation: u24, |
| 78 | 79 | }; |
| 79 | 80 | |
| 81 | pub const Ipc = packed struct(u32) { | |
| 82 | /// mutex protecting `file` use, only locked by `serializeIpc` | |
| 83 | locked: bool, | |
| 84 | /// when unlocked: whether `file` is defined | |
| 85 | /// when locked: whether `file` does not need to be closed | |
| 86 | valid: bool, | |
| 87 | unused: @Int(.unsigned, 32 - 2 - @bitSizeOf(Generation)) = 0, | |
| 88 | generation: Generation, | |
| 89 | ||
| 90 | pub const Slot = std.math.IntFittingRange(0, ipc_storage_buffer_len - 1); | |
| 91 | pub const Generation = @Int(.unsigned, 32 - @bitSizeOf(Slot)); | |
| 92 | ||
| 93 | const SlotAtomic = @Int(.unsigned, std.math.ceilPowerOfTwoAssert(usize, @min(@bitSizeOf(Slot), 8))); | |
| 94 | ||
| 95 | pub const Index = packed struct(u32) { | |
| 96 | slot: Slot, | |
| 97 | generation: Generation, | |
| 98 | }; | |
| 99 | ||
| 100 | const Data = struct { | |
| 101 | state: State, | |
| 102 | bytes_read: u16, | |
| 103 | main_index: u8, | |
| 104 | start_index: u8, | |
| 105 | nodes_len: u8, | |
| 106 | ||
| 107 | const State = enum { unused, pending, ready }; | |
| 108 | ||
| 109 | /// No operations have been started on this file. | |
| 110 | const unused: Data = .{ | |
| 111 | .state = .unused, | |
| 112 | .bytes_read = 0, | |
| 113 | .main_index = 0, | |
| 114 | .start_index = 0, | |
| 115 | .nodes_len = 0, | |
| 116 | }; | |
| 117 | ||
| 118 | fn findLastPacket(data: *const Data, buffer: *const [max_packet_len]u8) struct { u16, u16 } { | |
| 119 | assert(data.state == .ready); | |
| 120 | var packet_start: u16 = 0; | |
| 121 | var packet_end: u16 = 0; | |
| 122 | const bytes_read = data.bytes_read; | |
| 123 | while (bytes_read - packet_end >= 1) { | |
| 124 | const nodes_len: u16 = buffer[packet_end]; | |
| 125 | const packet_len = 1 + nodes_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent)); | |
| 126 | if (packet_end + packet_len > bytes_read) break; | |
| 127 | packet_start = packet_end; | |
| 128 | packet_end += packet_len; | |
| 129 | } | |
| 130 | return .{ packet_start, packet_end }; | |
| 131 | } | |
| 132 | ||
| 133 | fn rebase( | |
| 134 | data: *Data, | |
| 135 | buffer: *[max_packet_len]u8, | |
| 136 | vec: *[1][]u8, | |
| 137 | batch: *std.Io.Batch, | |
| 138 | slot: Slot, | |
| 139 | packet_end: u16, | |
| 140 | ) void { | |
| 141 | assert(data.state == .ready); | |
| 142 | const remaining = buffer[packet_end..data.bytes_read]; | |
| 143 | @memmove(buffer[0..remaining.len], remaining); | |
| 144 | vec.* = .{buffer[remaining.len..]}; | |
| 145 | batch.addAt(slot, .{ .file_read_streaming = .{ | |
| 146 | .file = global_progress.ipc_files[slot], | |
| 147 | .data = vec, | |
| 148 | } }); | |
| 149 | data.state = .pending; | |
| 150 | data.bytes_read = @intCast(remaining.len); | |
| 151 | } | |
| 152 | }; | |
| 153 | }; | |
| 154 | ||
| 80 | 155 | pub const TerminalMode = union(enum) { |
| 81 | 156 | off, |
| 82 | 157 | ansi_escape_codes, |
| ... | ... | @@ -116,7 +191,7 @@ pub const Node = struct { |
| 116 | 191 | |
| 117 | 192 | pub const none: Node = .{ .index = .none }; |
| 118 | 193 | |
| 119 | pub const max_name_len = 40; | |
| 194 | pub const max_name_len = 120; | |
| 120 | 195 | |
| 121 | 196 | const Storage = extern struct { |
| 122 | 197 | /// Little endian. |
| ... | ... | @@ -127,25 +202,16 @@ pub const Node = struct { |
| 127 | 202 | name: [max_name_len]u8 align(@alignOf(usize)), |
| 128 | 203 | |
| 129 | 204 | /// Not thread-safe. |
| 130 | fn getIpcFd(s: Storage) ?Io.File.Handle { | |
| 131 | return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) { | |
| 132 | .int => @bitCast(s.completed_count), | |
| 133 | .pointer => @ptrFromInt(s.completed_count), | |
| 134 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | |
| 135 | } else null; | |
| 205 | fn getIpcIndex(s: Storage) ?Ipc.Index { | |
| 206 | return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null; | |
| 136 | 207 | } |
| 137 | 208 | |
| 138 | 209 | /// Thread-safe. |
| 139 | fn setIpcFd(s: *Storage, fd: Io.File.Handle) void { | |
| 140 | const integer: u32 = switch (@typeInfo(Io.File.Handle)) { | |
| 141 | .int => @bitCast(fd), | |
| 142 | .pointer => @intFromPtr(fd), | |
| 143 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | |
| 144 | }; | |
| 210 | fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) void { | |
| 145 | 211 | // `estimated_total_count` max int indicates the special state that |
| 146 | 212 | // causes `completed_count` to be treated as a file descriptor, so |
| 147 | 213 | // the order here matters. |
| 148 | @atomicStore(u32, &s.completed_count, integer, .monotonic); | |
| 214 | @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic); | |
| 149 | 215 | @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize` |
| 150 | 216 | } |
| 151 | 217 | |
| ... | ... | @@ -155,6 +221,14 @@ pub const Node = struct { |
| 155 | 221 | s.estimated_total_count = @byteSwap(s.estimated_total_count); |
| 156 | 222 | } |
| 157 | 223 | |
| 224 | fn copyRoot(dest: *Node.Storage, src: *align(1) const Node.Storage) void { | |
| 225 | dest.* = .{ | |
| 226 | .completed_count = src.completed_count, | |
| 227 | .estimated_total_count = src.estimated_total_count, | |
| 228 | .name = if (src.name[0] == 0) dest.name else src.name, | |
| 229 | }; | |
| 230 | } | |
| 231 | ||
| 158 | 232 | comptime { |
| 159 | 233 | assert((@sizeOf(Storage) % 4) == 0); |
| 160 | 234 | } |
| ... | ... | @@ -242,7 +316,7 @@ pub const Node = struct { |
| 242 | 316 | } |
| 243 | 317 | |
| 244 | 318 | const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic); |
| 245 | if (free_index >= global_progress.node_storage.len) { | |
| 319 | if (free_index >= node_storage_buffer_len) { | |
| 246 | 320 | // Ran out of node storage memory. Progress for this node will not be tracked. |
| 247 | 321 | _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic); |
| 248 | 322 | return Node.none; |
| ... | ... | @@ -292,15 +366,17 @@ pub const Node = struct { |
| 292 | 366 | const index = n.index.unwrap() orelse return; |
| 293 | 367 | const storage = storageByIndex(index); |
| 294 | 368 | // Avoid u32 max int which is used to indicate a special state. |
| 295 | const saturated = @min(std.math.maxInt(u32) - 1, count); | |
| 296 | @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic); | |
| 369 | const saturated_total_count = @min(std.math.maxInt(u32) - 1, count); | |
| 370 | @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic); | |
| 297 | 371 | } |
| 298 | 372 | |
| 299 | 373 | /// Thread-safe. |
| 300 | 374 | pub fn increaseEstimatedTotalItems(n: Node, count: usize) void { |
| 301 | 375 | const index = n.index.unwrap() orelse return; |
| 302 | 376 | const storage = storageByIndex(index); |
| 303 | _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, std.math.lossyCast(u32, count), .monotonic); | |
| 377 | // Avoid u32 max int which is used to indicate a special state. | |
| 378 | const saturated_total_count = @min(std.math.maxInt(u32) - 1, count); | |
| 379 | _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, saturated_total_count, .monotonic); | |
| 304 | 380 | } |
| 305 | 381 | |
| 306 | 382 | /// Finish a started `Node`. Thread-safe. |
| ... | ... | @@ -310,11 +386,25 @@ pub const Node = struct { |
| 310 | 386 | return; |
| 311 | 387 | } |
| 312 | 388 | const index = n.index.unwrap() orelse return; |
| 389 | const io = global_progress.io; | |
| 313 | 390 | const parent_ptr = parentByIndex(index); |
| 314 | 391 | if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| { |
| 315 | 392 | _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic); |
| 316 | 393 | @atomicStore(Node.Parent, parent_ptr, .unused, .monotonic); |
| 317 | 394 | |
| 395 | if (storageByIndex(index).getIpcIndex()) |ipc_index| { | |
| 396 | const file = global_progress.ipc_files[ipc_index.slot]; | |
| 397 | const ipc = @atomicRmw( | |
| 398 | Ipc, | |
| 399 | &global_progress.ipc[ipc_index.slot], | |
| 400 | .And, | |
| 401 | .{ .locked = true, .valid = false, .generation = std.math.maxInt(Ipc.Generation) }, | |
| 402 | .release, | |
| 403 | ); | |
| 404 | assert(ipc.valid and ipc.generation == ipc_index.generation); | |
| 405 | if (!ipc.locked) file.close(io); | |
| 406 | } | |
| 407 | ||
| 318 | 408 | const freelist = &global_progress.node_freelist; |
| 319 | 409 | var old_freelist = @atomicLoad(Freelist, freelist, .monotonic); |
| 320 | 410 | while (true) { |
| ... | ... | @@ -332,34 +422,52 @@ pub const Node = struct { |
| 332 | 422 | }; |
| 333 | 423 | } |
| 334 | 424 | } else { |
| 335 | @atomicStore(bool, &global_progress.done, true, .monotonic); | |
| 336 | const io = global_progress.io; | |
| 337 | global_progress.redraw_event.set(io); | |
| 338 | if (global_progress.update_worker) |*worker| worker.await(io); | |
| 425 | if (global_progress.update_worker) |*worker| worker.cancel(io) catch {}; | |
| 426 | for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| { | |
| 427 | assert(!ipc.locked or !ipc.valid); // missing call to end() | |
| 428 | if (ipc.locked or ipc.valid) ipc_file.close(io); | |
| 429 | } | |
| 339 | 430 | } |
| 340 | 431 | } |
| 341 | 432 | |
| 342 | /// Posix-only. Used by `std.process.Child`. Thread-safe. | |
| 343 | pub fn setIpcFd(node: Node, fd: Io.File.Handle) void { | |
| 433 | /// Used by `std.process.Child`. Thread-safe. | |
| 434 | pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void { | |
| 344 | 435 | const index = node.index.unwrap() orelse return; |
| 345 | assert(fd >= 0); | |
| 346 | assert(fd != posix.STDOUT_FILENO); | |
| 347 | assert(fd != posix.STDIN_FILENO); | |
| 348 | assert(fd != posix.STDERR_FILENO); | |
| 349 | storageByIndex(index).setIpcFd(fd); | |
| 436 | const io = global_progress.io; | |
| 437 | assert(io.userdata == expected_io_userdata); | |
| 438 | for (0..ipc_storage_buffer_len) |_| { | |
| 439 | const slot: Ipc.Slot = @truncate( | |
| 440 | @atomicRmw(Ipc.SlotAtomic, &global_progress.ipc_next, .Add, 1, .monotonic), | |
| 441 | ); | |
| 442 | if (slot >= ipc_storage_buffer_len) continue; | |
| 443 | const ipc_ptr = &global_progress.ipc[slot]; | |
| 444 | const ipc = @atomicLoad(Ipc, ipc_ptr, .monotonic); | |
| 445 | if (ipc.locked or ipc.valid) continue; | |
| 446 | const generation = ipc.generation +% 1; | |
| 447 | if (@cmpxchgWeak( | |
| 448 | Ipc, | |
| 449 | ipc_ptr, | |
| 450 | ipc, | |
| 451 | .{ .locked = false, .valid = true, .generation = generation }, | |
| 452 | .acquire, | |
| 453 | .monotonic, | |
| 454 | )) |_| continue; | |
| 455 | global_progress.ipc_files[slot] = file; | |
| 456 | storageByIndex(index).setIpcIndex(.{ .slot = slot, .generation = generation }); | |
| 457 | break; | |
| 458 | } else file.close(io); | |
| 350 | 459 | } |
| 351 | 460 | |
| 352 | /// Posix-only. Thread-safe. Assumes the node is storing an IPC file | |
| 353 | /// descriptor. | |
| 354 | pub fn getIpcFd(node: Node) ?Io.File.Handle { | |
| 355 | const index = node.index.unwrap() orelse return null; | |
| 356 | const storage = storageByIndex(index); | |
| 357 | const int = @atomicLoad(u32, &storage.completed_count, .monotonic); | |
| 358 | return switch (@typeInfo(Io.File.Handle)) { | |
| 359 | .int => @bitCast(int), | |
| 360 | .pointer => @ptrFromInt(int), | |
| 361 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | |
| 362 | }; | |
| 461 | pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void { | |
| 462 | storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index); | |
| 463 | } | |
| 464 | ||
| 465 | /// Not thread-safe. | |
| 466 | pub fn takeIpcIndex(node: Node) ?Ipc.Index { | |
| 467 | const storage = storageByIndex(node.index.unwrap() orelse return null); | |
| 468 | assert(storage.estimated_total_count == std.math.maxInt(u32)); | |
| 469 | @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic); | |
| 470 | return @bitCast(storage.completed_count); | |
| 363 | 471 | } |
| 364 | 472 | |
| 365 | 473 | fn storageByIndex(index: Node.Index) *Node.Storage { |
| ... | ... | @@ -379,7 +487,9 @@ pub const Node = struct { |
| 379 | 487 | |
| 380 | 488 | const storage = storageByIndex(free_index); |
| 381 | 489 | @atomicStore(u32, &storage.completed_count, 0, .monotonic); |
| 382 | @atomicStore(u32, &storage.estimated_total_count, std.math.lossyCast(u32, estimated_total_items), .monotonic); | |
| 490 | // Avoid u32 max int which is used to indicate a special state. | |
| 491 | const saturated_total_count = @min(std.math.maxInt(u32) - 1, estimated_total_items); | |
| 492 | @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic); | |
| 383 | 493 | const name_len = @min(max_name_len, name.len); |
| 384 | 494 | copyAtomicStore(storage.name[0..name_len], name[0..name_len]); |
| 385 | 495 | if (name_len < storage.name.len) |
| ... | ... | @@ -406,16 +516,20 @@ var global_progress: Progress = .{ |
| 406 | 516 | .rows = 0, |
| 407 | 517 | .cols = 0, |
| 408 | 518 | .draw_buffer = undefined, |
| 409 | .done = false, | |
| 410 | 519 | .need_clear = false, |
| 411 | 520 | .status = .working, |
| 412 | .start_failure = .unstarted, | |
| 413 | 521 | |
| 414 | .node_parents = &node_parents_buffer, | |
| 415 | .node_storage = &node_storage_buffer, | |
| 416 | .node_freelist_next = &node_freelist_next_buffer, | |
| 522 | .node_parents = undefined, | |
| 523 | .node_storage = undefined, | |
| 524 | .node_freelist_next = undefined, | |
| 417 | 525 | .node_freelist = .{ .head = .none, .generation = 0 }, |
| 418 | 526 | .node_end_index = 0, |
| 527 | ||
| 528 | .ipc_next = 0, | |
| 529 | .ipc = undefined, | |
| 530 | .ipc_files = undefined, | |
| 531 | ||
| 532 | .start_failure = .unstarted, | |
| 419 | 533 | }; |
| 420 | 534 | |
| 421 | 535 | pub const StartFailure = union(enum) { |
| ... | ... | @@ -425,17 +539,23 @@ pub const StartFailure = union(enum) { |
| 425 | 539 | parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat }, |
| 426 | 540 | }; |
| 427 | 541 | |
| 428 | const node_storage_buffer_len = 83; | |
| 429 | var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined; | |
| 430 | var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined; | |
| 431 | var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined; | |
| 542 | /// One less than a power of two ensures `max_packet_len` is already a power of two. | |
| 543 | const node_storage_buffer_len = ipc_storage_buffer_len - 1; | |
| 544 | ||
| 545 | /// Power of two to avoid wasted `ipc_next` increments. | |
| 546 | const ipc_storage_buffer_len = 128; | |
| 547 | ||
| 548 | pub const max_packet_len = std.math.ceilPowerOfTwoAssert( | |
| 549 | usize, | |
| 550 | 1 + node_storage_buffer_len * (@sizeOf(Node.Storage) + @sizeOf(Node.OptionalIndex)), | |
| 551 | ); | |
| 432 | 552 | |
| 433 | 553 | var default_draw_buffer: [4096]u8 = undefined; |
| 434 | 554 | |
| 435 | 555 | var debug_start_trace = std.debug.Trace.init; |
| 436 | 556 | |
| 437 | 557 | pub const have_ipc = switch (builtin.os.tag) { |
| 438 | .wasi, .freestanding, .windows => false, | |
| 558 | .wasi, .freestanding => false, | |
| 439 | 559 | else => true, |
| 440 | 560 | }; |
| 441 | 561 | |
| ... | ... | @@ -467,9 +587,9 @@ pub fn start(io: Io, options: Options) Node { |
| 467 | 587 | } |
| 468 | 588 | debug_start_trace.add("first initialized here"); |
| 469 | 589 | |
| 470 | @memset(global_progress.node_parents, .unused); | |
| 590 | @memset(&global_progress.node_parents, .unused); | |
| 591 | @memset(&global_progress.ipc, .{ .locked = false, .valid = false, .generation = 0 }); | |
| 471 | 592 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); |
| 472 | global_progress.done = false; | |
| 473 | 593 | global_progress.node_end_index = 1; |
| 474 | 594 | |
| 475 | 595 | assert(options.draw_buffer.len >= 200); |
| ... | ... | @@ -477,21 +597,18 @@ pub fn start(io: Io, options: Options) Node { |
| 477 | 597 | global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds()); |
| 478 | 598 | global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds()); |
| 479 | 599 | |
| 480 | if (noop_impl) | |
| 481 | return Node.none; | |
| 600 | if (noop_impl) return .none; | |
| 482 | 601 | |
| 483 | 602 | global_progress.io = io; |
| 484 | 603 | |
| 485 | 604 | if (io.vtable.progressParentFile(io.userdata)) |ipc_file| { |
| 486 | 605 | global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| { |
| 487 | 606 | global_progress.start_failure = .{ .spawn_ipc_worker = err }; |
| 488 | return Node.none; | |
| 607 | return .none; | |
| 489 | 608 | }; |
| 490 | 609 | } else |env_err| switch (env_err) { |
| 491 | 610 | error.EnvironmentVariableMissing => { |
| 492 | if (options.disable_printing) { | |
| 493 | return Node.none; | |
| 494 | } | |
| 611 | if (options.disable_printing) return .none; | |
| 495 | 612 | const stderr: Io.File = .stderr(); |
| 496 | 613 | global_progress.terminal = stderr; |
| 497 | 614 | if (stderr.enableAnsiEscapeCodes(io)) |_| { |
| ... | ... | @@ -504,14 +621,12 @@ pub fn start(io: Io, options: Options) Node { |
| 504 | 621 | } else |err| switch (err) { |
| 505 | 622 | error.Canceled => { |
| 506 | 623 | io.recancel(); |
| 507 | return Node.none; | |
| 624 | return .none; | |
| 508 | 625 | }, |
| 509 | 626 | } |
| 510 | 627 | } |
| 511 | 628 | |
| 512 | if (global_progress.terminal_mode == .off) { | |
| 513 | return Node.none; | |
| 514 | } | |
| 629 | if (global_progress.terminal_mode == .off) return .none; | |
| 515 | 630 | |
| 516 | 631 | if (have_sigwinch) { |
| 517 | 632 | const act: posix.Sigaction = .{ |
| ... | ... | @@ -530,12 +645,12 @@ pub fn start(io: Io, options: Options) Node { |
| 530 | 645 | global_progress.update_worker = future; |
| 531 | 646 | } else |err| { |
| 532 | 647 | global_progress.start_failure = .{ .spawn_update_worker = err }; |
| 533 | return Node.none; | |
| 648 | return .none; | |
| 534 | 649 | } |
| 535 | 650 | }, |
| 536 | 651 | else => |e| { |
| 537 | 652 | global_progress.start_failure = .{ .parent_ipc = e }; |
| 538 | return Node.none; | |
| 653 | return .none; | |
| 539 | 654 | }, |
| 540 | 655 | } |
| 541 | 656 | |
| ... | ... | @@ -548,58 +663,55 @@ pub fn setStatus(new_status: Status) void { |
| 548 | 663 | } |
| 549 | 664 | |
| 550 | 665 | /// Returns whether a resize is needed to learn the terminal size. |
| 551 | fn wait(io: Io, timeout_ns: u64) bool { | |
| 666 | fn wait(io: Io, timeout_ns: u64) Io.Cancelable!bool { | |
| 552 | 667 | const timeout: Io.Timeout = .{ .duration = .{ |
| 553 | 668 | .clock = .awake, |
| 554 | 669 | .raw = .fromNanoseconds(timeout_ns), |
| 555 | 670 | } }; |
| 556 | 671 | const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) { |
| 557 | error.Timeout, error.Canceled => false, | |
| 672 | error.Timeout => false, | |
| 673 | error.Canceled => |e| return e, | |
| 558 | 674 | }; |
| 559 | 675 | global_progress.redraw_event.reset(); |
| 560 | 676 | return resize_flag or (global_progress.cols == 0); |
| 561 | 677 | } |
| 562 | 678 | |
| 563 | fn updateTask(io: Io) void { | |
| 679 | const WorkerError = error{WindowTooSmall} || Io.ConcurrentError || Io.Cancelable || | |
| 680 | Io.File.Writer.Error || Io.Operation.FileReadStreaming.Error; | |
| 681 | ||
| 682 | fn updateTask(io: Io) WorkerError!void { | |
| 564 | 683 | // Store this data in the thread so that it does not need to be part of the |
| 565 | 684 | // linker data of the main executable. |
| 566 | 685 | var serialized_buffer: Serialized.Buffer = undefined; |
| 686 | serialized_buffer.init(); | |
| 687 | defer serialized_buffer.batch.cancel(io); | |
| 567 | 688 | |
| 568 | 689 | // In this function we bypass the wrapper code inside `Io.lockStderr` / |
| 569 | 690 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. |
| 570 | 691 | // We still want to go through the `Io` instance however in case it uses a |
| 571 | 692 | // task-switching mutex. |
| 572 | 693 | |
| 573 | { | |
| 574 | const resize_flag = wait(io, global_progress.initial_delay_ns); | |
| 575 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; | |
| 576 | maybeUpdateSize(io, resize_flag) catch return; | |
| 577 | ||
| 578 | const buffer, _ = computeRedraw(&serialized_buffer); | |
| 579 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | |
| 580 | defer io.unlockStderr(); | |
| 581 | global_progress.need_clear = true; | |
| 582 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 583 | } | |
| 694 | try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); | |
| 695 | errdefer { | |
| 696 | const cancel_protection = io.swapCancelProtection(.blocked); | |
| 697 | defer _ = io.swapCancelProtection(cancel_protection); | |
| 698 | const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { | |
| 699 | error.Canceled => unreachable, // blocked | |
| 700 | }; | |
| 701 | defer io.unlockStderr(); | |
| 702 | clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; | |
| 584 | 703 | } |
| 585 | ||
| 586 | 704 | while (true) { |
| 587 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | |
| 588 | ||
| 589 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { | |
| 590 | const stderr = io.vtable.lockStderr(io.userdata, null) catch return; | |
| 591 | defer io.unlockStderr(); | |
| 592 | return clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; | |
| 593 | } | |
| 594 | ||
| 595 | maybeUpdateSize(io, resize_flag) catch return; | |
| 596 | ||
| 597 | const buffer, _ = computeRedraw(&serialized_buffer); | |
| 598 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | |
| 705 | const buffer, _ = try computeRedraw(io, &serialized_buffer); | |
| 706 | if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| { | |
| 599 | 707 | defer io.unlockStderr(); |
| 600 | 708 | global_progress.need_clear = true; |
| 601 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 709 | locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { | |
| 710 | error.WriteFailed => return locked_stderr.file_writer.err.?, | |
| 711 | }; | |
| 602 | 712 | } |
| 713 | ||
| 714 | try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); | |
| 603 | 715 | } |
| 604 | 716 | } |
| 605 | 717 | |
| ... | ... | @@ -611,79 +723,60 @@ fn windowsApiWriteMarker() void { |
| 611 | 723 | _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); |
| 612 | 724 | } |
| 613 | 725 | |
| 614 | fn windowsApiUpdateTask(io: Io) void { | |
| 726 | fn windowsApiUpdateTask(io: Io) WorkerError!void { | |
| 727 | // Store this data in the thread so that it does not need to be part of the | |
| 728 | // linker data of the main executable. | |
| 615 | 729 | var serialized_buffer: Serialized.Buffer = undefined; |
| 730 | serialized_buffer.init(); | |
| 731 | defer serialized_buffer.batch.cancel(io); | |
| 616 | 732 | |
| 617 | 733 | // In this function we bypass the wrapper code inside `Io.lockStderr` / |
| 618 | 734 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. |
| 619 | 735 | // We still want to go through the `Io` instance however in case it uses a |
| 620 | 736 | // task-switching mutex. |
| 621 | 737 | |
| 622 | { | |
| 623 | const resize_flag = wait(io, global_progress.initial_delay_ns); | |
| 624 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; | |
| 625 | maybeUpdateSize(io, resize_flag) catch return; | |
| 626 | ||
| 627 | const buffer, const nl_n = computeRedraw(&serialized_buffer); | |
| 628 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | |
| 629 | defer io.unlockStderr(); | |
| 630 | windowsApiWriteMarker(); | |
| 631 | global_progress.need_clear = true; | |
| 632 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 633 | windowsApiMoveToMarker(nl_n) catch return; | |
| 634 | } | |
| 738 | try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); | |
| 739 | errdefer { | |
| 740 | const cancel_protection = io.swapCancelProtection(.blocked); | |
| 741 | defer _ = io.swapCancelProtection(cancel_protection); | |
| 742 | _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { | |
| 743 | error.Canceled => unreachable, // blocked | |
| 744 | }; | |
| 745 | defer io.unlockStderr(); | |
| 746 | clearWrittenWindowsApi() catch {}; | |
| 635 | 747 | } |
| 636 | ||
| 637 | 748 | while (true) { |
| 638 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | |
| 639 | ||
| 640 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { | |
| 641 | _ = io.vtable.lockStderr(io.userdata, null) catch return; | |
| 642 | defer io.unlockStderr(); | |
| 643 | return clearWrittenWindowsApi() catch {}; | |
| 644 | } | |
| 645 | ||
| 646 | maybeUpdateSize(io, resize_flag) catch return; | |
| 647 | ||
| 648 | const buffer, const nl_n = computeRedraw(&serialized_buffer); | |
| 749 | const buffer, const nl_n = try computeRedraw(io, &serialized_buffer); | |
| 649 | 750 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { |
| 650 | 751 | defer io.unlockStderr(); |
| 651 | clearWrittenWindowsApi() catch return; | |
| 752 | try clearWrittenWindowsApi(); | |
| 652 | 753 | windowsApiWriteMarker(); |
| 653 | 754 | global_progress.need_clear = true; |
| 654 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 755 | locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { | |
| 756 | error.WriteFailed => return locked_stderr.file_writer.err.?, | |
| 757 | }; | |
| 655 | 758 | windowsApiMoveToMarker(nl_n) catch return; |
| 656 | 759 | } |
| 760 | ||
| 761 | try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); | |
| 657 | 762 | } |
| 658 | 763 | } |
| 659 | 764 | |
| 660 | fn ipcThreadRun(io: Io, file: Io.File) void { | |
| 765 | fn ipcThreadRun(io: Io, file: Io.File) WorkerError!void { | |
| 661 | 766 | // Store this data in the thread so that it does not need to be part of the |
| 662 | 767 | // linker data of the main executable. |
| 663 | 768 | var serialized_buffer: Serialized.Buffer = undefined; |
| 769 | serialized_buffer.init(); | |
| 770 | defer serialized_buffer.batch.cancel(io); | |
| 771 | var fw = file.writerStreaming(io, &.{}); | |
| 664 | 772 | |
| 665 | { | |
| 666 | _ = wait(io, global_progress.initial_delay_ns); | |
| 667 | ||
| 668 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) | |
| 669 | return; | |
| 670 | ||
| 671 | const serialized = serialize(&serialized_buffer); | |
| 672 | writeIpc(io, file, serialized) catch |err| switch (err) { | |
| 673 | error.BrokenPipe => return, | |
| 674 | }; | |
| 675 | } | |
| 676 | ||
| 773 | _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake); | |
| 677 | 774 | while (true) { |
| 678 | _ = wait(io, global_progress.refresh_rate_ns); | |
| 679 | ||
| 680 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) | |
| 681 | return; | |
| 682 | ||
| 683 | const serialized = serialize(&serialized_buffer); | |
| 684 | writeIpc(io, file, serialized) catch |err| switch (err) { | |
| 685 | error.BrokenPipe => return, | |
| 775 | writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) { | |
| 776 | error.WriteFailed => return fw.err.?, | |
| 686 | 777 | }; |
| 778 | ||
| 779 | _ = try io.sleep(.fromNanoseconds(global_progress.refresh_rate_ns), .awake); | |
| 687 | 780 | } |
| 688 | 781 | } |
| 689 | 782 | |
| ... | ... | @@ -862,31 +955,49 @@ const Serialized = struct { |
| 862 | 955 | const Buffer = struct { |
| 863 | 956 | parents: [node_storage_buffer_len]Node.Parent, |
| 864 | 957 | storage: [node_storage_buffer_len]Node.Storage, |
| 865 | map: [node_storage_buffer_len]Node.OptionalIndex, | |
| 866 | 958 | |
| 867 | parents_copy: [node_storage_buffer_len]Node.Parent, | |
| 868 | storage_copy: [node_storage_buffer_len]Node.Storage, | |
| 869 | ipc_metadata_fds_copy: [node_storage_buffer_len]Fd, | |
| 870 | ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata, | |
| 871 | ||
| 872 | ipc_metadata_fds: [node_storage_buffer_len]Fd, | |
| 873 | ipc_metadata: [node_storage_buffer_len]SavedMetadata, | |
| 959 | ipc_start: u8, | |
| 960 | ipc_end: u8, | |
| 961 | ipc_data: [ipc_storage_buffer_len]Ipc.Data, | |
| 962 | ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8, | |
| 963 | ipc_vecs: [ipc_storage_buffer_len][1][]u8, | |
| 964 | batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage, | |
| 965 | batch: Io.Batch, | |
| 966 | ||
| 967 | fn init(buffer: *Buffer) void { | |
| 968 | buffer.ipc_start = 0; | |
| 969 | buffer.ipc_end = 0; | |
| 970 | @memset(&buffer.ipc_data, .unused); | |
| 971 | buffer.batch = .init(&buffer.batch_storage); | |
| 972 | } | |
| 874 | 973 | }; |
| 875 | 974 | }; |
| 876 | 975 | |
| 877 | fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { | |
| 878 | var serialized_len: usize = 0; | |
| 879 | var any_ipc = false; | |
| 976 | fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized { | |
| 977 | var prev_parents: [node_storage_buffer_len]Node.Parent = undefined; | |
| 978 | var prev_storage: [node_storage_buffer_len]Node.Storage = undefined; | |
| 979 | { | |
| 980 | const ipc_start = serialized_buffer.ipc_start; | |
| 981 | const ipc_end = serialized_buffer.ipc_end; | |
| 982 | @memcpy(prev_parents[ipc_start..ipc_end], serialized_buffer.parents[ipc_start..ipc_end]); | |
| 983 | @memcpy(prev_storage[ipc_start..ipc_end], serialized_buffer.storage[ipc_start..ipc_end]); | |
| 984 | } | |
| 880 | 985 | |
| 881 | 986 | // Iterate all of the nodes and construct a serializable copy of the state that can be examined |
| 882 | 987 | // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the |
| 883 | 988 | // node count sometimes. |
| 884 | const end_index = @min(@atomicLoad(u32, &global_progress.node_end_index, .monotonic), global_progress.node_storage.len); | |
| 989 | const end_index = @min( | |
| 990 | @atomicLoad(u32, &global_progress.node_end_index, .monotonic), | |
| 991 | node_storage_buffer_len, | |
| 992 | ); | |
| 993 | var map: [node_storage_buffer_len]Node.OptionalIndex = undefined; | |
| 994 | var serialized_len: u8 = 0; | |
| 995 | var maybe_ipc_start: ?u8 = null; | |
| 885 | 996 | for ( |
| 886 | 997 | global_progress.node_parents[0..end_index], |
| 887 | 998 | global_progress.node_storage[0..end_index], |
| 888 | serialized_buffer.map[0..end_index], | |
| 889 | ) |*parent_ptr, *storage_ptr, *map| { | |
| 999 | map[0..end_index], | |
| 1000 | ) |*parent_ptr, *storage_ptr, *map_entry| { | |
| 890 | 1001 | const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic); |
| 891 | 1002 | if (parent == .unused) { |
| 892 | 1003 | // We might read "mixed" node data in this loop, due to weird atomic things |
| ... | ... | @@ -900,17 +1011,17 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { |
| 900 | 1011 | // parent, it will just not be printed at all. The general idea here is that performance |
| 901 | 1012 | // is more important than 100% correct output every frame, given that this API is likely |
| 902 | 1013 | // to be used in hot paths! |
| 903 | map.* = .none; | |
| 1014 | map_entry.* = .none; | |
| 904 | 1015 | continue; |
| 905 | 1016 | } |
| 906 | 1017 | const dest_storage = &serialized_buffer.storage[serialized_len]; |
| 907 | 1018 | copyAtomicLoad(&dest_storage.name, &storage_ptr.name); |
| 908 | dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcFd` | |
| 1019 | dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcIndex` | |
| 909 | 1020 | dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic); |
| 910 | 1021 | |
| 911 | any_ipc = any_ipc or (dest_storage.getIpcFd() != null); | |
| 912 | 1022 | serialized_buffer.parents[serialized_len] = parent; |
| 913 | map.* = @enumFromInt(serialized_len); | |
| 1023 | map_entry.* = @enumFromInt(serialized_len); | |
| 1024 | if (maybe_ipc_start == null and dest_storage.getIpcIndex() != null) maybe_ipc_start = serialized_len; | |
| 914 | 1025 | serialized_len += 1; |
| 915 | 1026 | } |
| 916 | 1027 | |
| ... | ... | @@ -919,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { |
| 919 | 1030 | parent.* = switch (parent.*) { |
| 920 | 1031 | .unused => unreachable, |
| 921 | 1032 | .none => .none, |
| 922 | _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(), | |
| 1033 | _ => |p| map[@intFromEnum(p)].toParent(), | |
| 923 | 1034 | }; |
| 924 | 1035 | } |
| 925 | 1036 | |
| 926 | // Find nodes which correspond to child processes. | |
| 927 | if (any_ipc) | |
| 928 | serialized_len = serializeIpc(serialized_len, serialized_buffer); | |
| 929 | ||
| 930 | return .{ | |
| 931 | .parents = serialized_buffer.parents[0..serialized_len], | |
| 932 | .storage = serialized_buffer.storage[0..serialized_len], | |
| 1037 | // Fill pipe buffers. | |
| 1038 | const batch = &serialized_buffer.batch; | |
| 1039 | batch.awaitConcurrent(io, .{ | |
| 1040 | .duration = .{ .raw = .zero, .clock = .awake }, | |
| 1041 | }) catch |err| switch (err) { | |
| 1042 | error.Timeout => {}, | |
| 1043 | else => |e| return e, | |
| 1044 | }; | |
| 1045 | var ready_len: u8 = 0; | |
| 1046 | while (batch.next()) |operation| switch (operation.index) { | |
| 1047 | 0...ipc_storage_buffer_len - 1 => { | |
| 1048 | const ipc_data = &serialized_buffer.ipc_data[operation.index]; | |
| 1049 | ipc_data.bytes_read += @intCast( | |
| 1050 | operation.result.file_read_streaming catch |err| switch (err) { | |
| 1051 | error.EndOfStream => { | |
| 1052 | const file = global_progress.ipc_files[operation.index]; | |
| 1053 | const ipc = @atomicRmw( | |
| 1054 | Ipc, | |
| 1055 | &global_progress.ipc[operation.index], | |
| 1056 | .And, | |
| 1057 | .{ | |
| 1058 | .locked = false, | |
| 1059 | .valid = true, | |
| 1060 | .generation = std.math.maxInt(Ipc.Generation), | |
| 1061 | }, | |
| 1062 | .release, | |
| 1063 | ); | |
| 1064 | assert(ipc.locked); | |
| 1065 | if (!ipc.valid) file.close(io); | |
| 1066 | ipc_data.* = .unused; | |
| 1067 | continue; | |
| 1068 | }, | |
| 1069 | else => |e| return e, | |
| 1070 | }, | |
| 1071 | ); | |
| 1072 | assert(ipc_data.state == .pending); | |
| 1073 | ipc_data.state = .ready; | |
| 1074 | ready_len += 1; | |
| 1075 | }, | |
| 1076 | else => unreachable, | |
| 933 | 1077 | }; |
| 934 | } | |
| 935 | ||
| 936 | const SavedMetadata = struct { | |
| 937 | remaining_read_trash_bytes: u16, | |
| 938 | main_index: u8, | |
| 939 | start_index: u8, | |
| 940 | nodes_len: u8, | |
| 941 | }; | |
| 942 | ||
| 943 | const Fd = enum(i32) { | |
| 944 | _, | |
| 945 | ||
| 946 | fn init(fd: Io.File.Handle) Fd { | |
| 947 | return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd); | |
| 948 | } | |
| 949 | ||
| 950 | fn get(fd: Fd) Io.File.Handle { | |
| 951 | return if (is_windows) | |
| 952 | @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd))))) | |
| 953 | else | |
| 954 | @intFromEnum(fd); | |
| 955 | } | |
| 956 | }; | |
| 957 | ||
| 958 | var ipc_metadata_len: u8 = 0; | |
| 959 | ||
| 960 | fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize { | |
| 961 | const io = global_progress.io; | |
| 962 | const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy; | |
| 963 | const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy; | |
| 964 | const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; | |
| 965 | const ipc_metadata = &serialized_buffer.ipc_metadata; | |
| 966 | ||
| 967 | var serialized_len = start_serialized_len; | |
| 968 | var pipe_buf: [2 * 4096]u8 = undefined; | |
| 969 | ||
| 970 | const old_ipc_metadata_fds = ipc_metadata_fds_copy[0..ipc_metadata_len]; | |
| 971 | const old_ipc_metadata = ipc_metadata_copy[0..ipc_metadata_len]; | |
| 972 | ipc_metadata_len = 0; | |
| 973 | 1078 | |
| 974 | main_loop: for ( | |
| 975 | serialized_buffer.parents[0..serialized_len], | |
| 976 | serialized_buffer.storage[0..serialized_len], | |
| 977 | 0.., | |
| 1079 | // Find nodes which correspond to child processes. | |
| 1080 | const ipc_start = maybe_ipc_start orelse serialized_len; | |
| 1081 | serialized_buffer.ipc_start = ipc_start; | |
| 1082 | for ( | |
| 1083 | serialized_buffer.parents[ipc_start..serialized_len], | |
| 1084 | serialized_buffer.storage[ipc_start..serialized_len], | |
| 1085 | ipc_start.., | |
| 978 | 1086 | ) |main_parent, *main_storage, main_index| { |
| 979 | 1087 | if (main_parent == .unused) continue; |
| 980 | const file: Io.File = .{ | |
| 981 | .handle = main_storage.getIpcFd() orelse continue, | |
| 982 | .flags = .{ .nonblocking = true }, | |
| 983 | }; | |
| 984 | const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); | |
| 985 | var bytes_read: usize = 0; | |
| 986 | while (true) { | |
| 987 | const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) { | |
| 988 | error.WouldBlock, error.EndOfStream => break, | |
| 989 | else => |e| { | |
| 990 | std.log.debug("failed to read child progress data: {t}", .{e}); | |
| 991 | main_storage.completed_count = 0; | |
| 992 | main_storage.estimated_total_count = 0; | |
| 993 | continue :main_loop; | |
| 994 | }, | |
| 995 | }; | |
| 996 | if (opt_saved_metadata) |m| { | |
| 997 | if (m.remaining_read_trash_bytes > 0) { | |
| 998 | assert(bytes_read == 0); | |
| 999 | if (m.remaining_read_trash_bytes >= n) { | |
| 1000 | m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n); | |
| 1001 | continue; | |
| 1002 | } | |
| 1003 | const src = pipe_buf[m.remaining_read_trash_bytes..n]; | |
| 1004 | @memmove(pipe_buf[0..src.len], src); | |
| 1005 | m.remaining_read_trash_bytes = 0; | |
| 1006 | bytes_read = src.len; | |
| 1007 | continue; | |
| 1008 | } | |
| 1009 | } | |
| 1010 | bytes_read += n; | |
| 1011 | } | |
| 1012 | // Ignore all but the last message on the pipe. | |
| 1013 | var input: []u8 = pipe_buf[0..bytes_read]; | |
| 1014 | if (input.len == 0) { | |
| 1015 | serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle); | |
| 1016 | continue; | |
| 1017 | } | |
| 1018 | ||
| 1019 | const storage, const parents = while (true) { | |
| 1020 | const subtree_len: usize = input[0]; | |
| 1021 | const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent)); | |
| 1022 | if (input.len < expected_bytes) { | |
| 1023 | // Ignore short reads. We'll handle the next full message when it comes instead. | |
| 1024 | const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len); | |
| 1025 | serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle); | |
| 1026 | continue :main_loop; | |
| 1027 | } | |
| 1028 | if (input.len > expected_bytes) { | |
| 1029 | input = input[expected_bytes..]; | |
| 1030 | continue; | |
| 1031 | } | |
| 1032 | const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)]; | |
| 1033 | const parents_bytes = input[1 + storage_bytes.len ..][0 .. subtree_len * @sizeOf(Node.Parent)]; | |
| 1034 | break .{ | |
| 1035 | std.mem.bytesAsSlice(Node.Storage, storage_bytes), | |
| 1036 | std.mem.bytesAsSlice(Node.Parent, parents_bytes), | |
| 1037 | }; | |
| 1038 | }; | |
| 1039 | ||
| 1040 | const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len)); | |
| 1088 | const ipc_index = main_storage.getIpcIndex() orelse continue; | |
| 1089 | const ipc = &global_progress.ipc[ipc_index.slot]; | |
| 1090 | const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot]; | |
| 1091 | state: switch (ipc_data.state) { | |
| 1092 | .unused => { | |
| 1093 | if (@cmpxchgWeak( | |
| 1094 | Ipc, | |
| 1095 | ipc, | |
| 1096 | .{ .locked = false, .valid = true, .generation = ipc_index.generation }, | |
| 1097 | .{ .locked = true, .valid = true, .generation = ipc_index.generation }, | |
| 1098 | .acquire, | |
| 1099 | .monotonic, | |
| 1100 | )) |_| continue; | |
| 1101 | ||
| 1102 | const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot]; | |
| 1103 | ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]}; | |
| 1104 | batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{ | |
| 1105 | .file = global_progress.ipc_files[ipc_index.slot], | |
| 1106 | .data = ipc_vec, | |
| 1107 | } }); | |
| 1108 | ||
| 1109 | ipc_data.* = .{ | |
| 1110 | .state = .pending, | |
| 1111 | .bytes_read = 0, | |
| 1112 | .main_index = @intCast(main_index), | |
| 1113 | .start_index = serialized_len, | |
| 1114 | .nodes_len = 0, | |
| 1115 | }; | |
| 1116 | main_storage.completed_count = 0; | |
| 1117 | main_storage.estimated_total_count = 0; | |
| 1118 | }, | |
| 1119 | .pending => { | |
| 1120 | const start_index = ipc_data.start_index; | |
| 1121 | const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len); | |
| 1122 | ||
| 1123 | main_storage.copyRoot(&prev_storage[ipc_data.main_index]); | |
| 1124 | @memcpy( | |
| 1125 | serialized_buffer.storage[serialized_len..][0..nodes_len], | |
| 1126 | prev_storage[start_index..][0..nodes_len], | |
| 1127 | ); | |
| 1128 | for ( | |
| 1129 | serialized_buffer.parents[serialized_len..][0..nodes_len], | |
| 1130 | prev_parents[serialized_len..][0..nodes_len], | |
| 1131 | ) |*parent, prev_parent| parent.* = switch (prev_parent) { | |
| 1132 | .none, .unused => .none, | |
| 1133 | _ => if (@intFromEnum(prev_parent) == ipc_data.main_index) | |
| 1134 | @enumFromInt(main_index) | |
| 1135 | else if (@intFromEnum(prev_parent) >= start_index and | |
| 1136 | @intFromEnum(prev_parent) < start_index + nodes_len) | |
| 1137 | @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len) | |
| 1138 | else | |
| 1139 | .none, | |
| 1140 | }; | |
| 1041 | 1141 | |
| 1042 | // Remember in case the pipe is empty on next update. | |
| 1043 | ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle); | |
| 1044 | ipc_metadata[ipc_metadata_len] = .{ | |
| 1045 | .remaining_read_trash_bytes = 0, | |
| 1046 | .start_index = @intCast(serialized_len), | |
| 1047 | .nodes_len = nodes_len, | |
| 1048 | .main_index = @intCast(main_index), | |
| 1049 | }; | |
| 1050 | ipc_metadata_len += 1; | |
| 1051 | ||
| 1052 | // Mount the root here. | |
| 1053 | copyRoot(main_storage, &storage[0]); | |
| 1054 | if (is_big_endian) main_storage.byteSwap(); | |
| 1055 | ||
| 1056 | // Copy the rest of the tree to the end. | |
| 1057 | const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len]; | |
| 1058 | @memcpy(storage_dest, storage[1..][0..nodes_len]); | |
| 1059 | ||
| 1060 | // Always little-endian over the pipe. | |
| 1061 | if (is_big_endian) for (storage_dest) |*s| s.byteSwap(); | |
| 1062 | ||
| 1063 | // Patch up parent pointers taking into account how the subtree is mounted. | |
| 1064 | for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| { | |
| 1065 | dest.* = switch (p) { | |
| 1066 | // Fix bad data so the rest of the code does not see `unused`. | |
| 1067 | .none, .unused => .none, | |
| 1068 | // Root node is being mounted here. | |
| 1069 | @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index), | |
| 1070 | // Other nodes mounted at the end. | |
| 1071 | // Don't trust child data; if the data is outside the expected range, ignore the data. | |
| 1072 | // This also handles the case when data was truncated. | |
| 1073 | _ => |off| if (@intFromEnum(off) > nodes_len) | |
| 1074 | .none | |
| 1075 | else | |
| 1076 | @enumFromInt(serialized_len + @intFromEnum(off) - 1), | |
| 1077 | }; | |
| 1142 | ipc_data.main_index = @intCast(main_index); | |
| 1143 | ipc_data.start_index = serialized_len; | |
| 1144 | ipc_data.nodes_len = nodes_len; | |
| 1145 | serialized_len += nodes_len; | |
| 1146 | }, | |
| 1147 | .ready => { | |
| 1148 | const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot]; | |
| 1149 | const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer); | |
| 1150 | const packet_is_empty = packet_end - packet_start <= 1; | |
| 1151 | if (!packet_is_empty) { | |
| 1152 | const storage, const parents, const nodes_len = packet_contents: { | |
| 1153 | var packet_index: usize = packet_start; | |
| 1154 | const nodes_len: u16 = ipc_buffer[packet_index]; | |
| 1155 | packet_index += 1; | |
| 1156 | const storage_bytes = | |
| 1157 | ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)]; | |
| 1158 | packet_index += storage_bytes.len; | |
| 1159 | const parents_bytes = | |
| 1160 | ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)]; | |
| 1161 | packet_index += parents_bytes.len; | |
| 1162 | assert(packet_index == packet_end); | |
| 1163 | const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes); | |
| 1164 | const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes); | |
| 1165 | const children_nodes_len = | |
| 1166 | @min(nodes_len - 1, node_storage_buffer_len - serialized_len); | |
| 1167 | break :packet_contents .{ storage, parents, children_nodes_len }; | |
| 1168 | }; | |
| 1169 | ||
| 1170 | // Mount the root here. | |
| 1171 | main_storage.copyRoot(&storage[0]); | |
| 1172 | if (is_big_endian) main_storage.byteSwap(); | |
| 1173 | ||
| 1174 | // Copy the rest of the tree to the end. | |
| 1175 | const serialized_storage = | |
| 1176 | serialized_buffer.storage[serialized_len..][0..nodes_len]; | |
| 1177 | @memcpy(serialized_storage, storage[1..][0..nodes_len]); | |
| 1178 | if (is_big_endian) for (serialized_storage) |*s| s.byteSwap(); | |
| 1179 | ||
| 1180 | // Patch up parent pointers taking into account how the subtree is mounted. | |
| 1181 | for ( | |
| 1182 | serialized_buffer.parents[serialized_len..][0..nodes_len], | |
| 1183 | parents[1..][0..nodes_len], | |
| 1184 | ) |*parent, prev_parent| parent.* = switch (prev_parent) { | |
| 1185 | // Fix bad data so the rest of the code does not see `unused`. | |
| 1186 | .none, .unused => .none, | |
| 1187 | // Root node is being mounted here. | |
| 1188 | @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index), | |
| 1189 | // Other nodes mounted at the end. | |
| 1190 | // Don't trust child data; if the data is outside the expected range, | |
| 1191 | // ignore the data. This also handles the case when data was truncated. | |
| 1192 | _ => if (@intFromEnum(prev_parent) <= nodes_len) | |
| 1193 | @enumFromInt(@intFromEnum(prev_parent) - 1 + serialized_len) | |
| 1194 | else | |
| 1195 | .none, | |
| 1196 | }; | |
| 1197 | ||
| 1198 | ipc_data.main_index = @intCast(main_index); | |
| 1199 | ipc_data.start_index = serialized_len; | |
| 1200 | ipc_data.nodes_len = nodes_len; | |
| 1201 | serialized_len += nodes_len; | |
| 1202 | } | |
| 1203 | const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot]; | |
| 1204 | ipc_data.rebase(ipc_buffer, ipc_vec, batch, ipc_index.slot, packet_end); | |
| 1205 | ready_len -= 1; | |
| 1206 | if (packet_is_empty) continue :state .pending; | |
| 1207 | }, | |
| 1078 | 1208 | } |
| 1079 | ||
| 1080 | serialized_len += nodes_len; | |
| 1081 | } | |
| 1082 | ||
| 1083 | // Save a copy in case any pipes are empty on the next update. | |
| 1084 | @memcpy(serialized_buffer.parents_copy[0..serialized_len], serialized_buffer.parents[0..serialized_len]); | |
| 1085 | @memcpy(serialized_buffer.storage_copy[0..serialized_len], serialized_buffer.storage[0..serialized_len]); | |
| 1086 | @memcpy(ipc_metadata_fds_copy[0..ipc_metadata_len], ipc_metadata_fds[0..ipc_metadata_len]); | |
| 1087 | @memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]); | |
| 1088 | ||
| 1089 | return serialized_len; | |
| 1090 | } | |
| 1091 | ||
| 1092 | fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void { | |
| 1093 | dest.* = .{ | |
| 1094 | .completed_count = src.completed_count, | |
| 1095 | .estimated_total_count = src.estimated_total_count, | |
| 1096 | .name = if (src.name[0] == 0) dest.name else src.name, | |
| 1097 | }; | |
| 1098 | } | |
| 1099 | ||
| 1100 | fn findOld( | |
| 1101 | ipc_fd: Io.File.Handle, | |
| 1102 | old_metadata_fds: []Fd, | |
| 1103 | old_metadata: []SavedMetadata, | |
| 1104 | ) ?*SavedMetadata { | |
| 1105 | for (old_metadata_fds, old_metadata) |fd, *m| { | |
| 1106 | if (fd.get() == ipc_fd) | |
| 1107 | return m; | |
| 1108 | 1209 | } |
| 1109 | return null; | |
| 1110 | } | |
| 1111 | ||
| 1112 | fn useSavedIpcData( | |
| 1113 | start_serialized_len: usize, | |
| 1114 | serialized_buffer: *Serialized.Buffer, | |
| 1115 | main_storage: *Node.Storage, | |
| 1116 | main_index: usize, | |
| 1117 | opt_saved_metadata: ?*SavedMetadata, | |
| 1118 | remaining_read_trash_bytes: u16, | |
| 1119 | fd: Io.File.Handle, | |
| 1120 | ) usize { | |
| 1121 | const parents_copy = &serialized_buffer.parents_copy; | |
| 1122 | const storage_copy = &serialized_buffer.storage_copy; | |
| 1123 | const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; | |
| 1124 | const ipc_metadata = &serialized_buffer.ipc_metadata; | |
| 1125 | ||
| 1126 | const saved_metadata = opt_saved_metadata orelse { | |
| 1127 | main_storage.completed_count = 0; | |
| 1128 | main_storage.estimated_total_count = 0; | |
| 1129 | if (remaining_read_trash_bytes > 0) { | |
| 1130 | ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd); | |
| 1131 | ipc_metadata[ipc_metadata_len] = .{ | |
| 1132 | .remaining_read_trash_bytes = remaining_read_trash_bytes, | |
| 1133 | .start_index = @intCast(start_serialized_len), | |
| 1134 | .nodes_len = 0, | |
| 1135 | .main_index = @intCast(main_index), | |
| 1136 | }; | |
| 1137 | ipc_metadata_len += 1; | |
| 1138 | } | |
| 1139 | return start_serialized_len; | |
| 1210 | serialized_buffer.ipc_end = serialized_len; | |
| 1211 | ||
| 1212 | // Ignore data from unused pipes. This ensures that if a child process exists we will | |
| 1213 | // eventually see `EndOfStream` and close the pipe. | |
| 1214 | if (ready_len > 0) for ( | |
| 1215 | &serialized_buffer.ipc_data, | |
| 1216 | &serialized_buffer.ipc_buffers, | |
| 1217 | &serialized_buffer.ipc_vecs, | |
| 1218 | 0.., | |
| 1219 | ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) { | |
| 1220 | .unused, .pending => {}, | |
| 1221 | .ready => { | |
| 1222 | _, const packet_end = ipc_data.findLastPacket(ipc_buffer); | |
| 1223 | ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end); | |
| 1224 | ready_len -= 1; | |
| 1225 | }, | |
| 1140 | 1226 | }; |
| 1227 | assert(ready_len == 0); | |
| 1141 | 1228 | |
| 1142 | const start_index = saved_metadata.start_index; | |
| 1143 | const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len); | |
| 1144 | const old_main_index = saved_metadata.main_index; | |
| 1145 | ||
| 1146 | ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd); | |
| 1147 | ipc_metadata[ipc_metadata_len] = .{ | |
| 1148 | .remaining_read_trash_bytes = remaining_read_trash_bytes, | |
| 1149 | .start_index = @intCast(start_serialized_len), | |
| 1150 | .nodes_len = nodes_len, | |
| 1151 | .main_index = @intCast(main_index), | |
| 1229 | return .{ | |
| 1230 | .parents = serialized_buffer.parents[0..serialized_len], | |
| 1231 | .storage = serialized_buffer.storage[0..serialized_len], | |
| 1152 | 1232 | }; |
| 1153 | ipc_metadata_len += 1; | |
| 1154 | ||
| 1155 | const parents = parents_copy[start_index..][0..nodes_len]; | |
| 1156 | const storage = storage_copy[start_index..][0..nodes_len]; | |
| 1157 | ||
| 1158 | copyRoot(main_storage, &storage_copy[old_main_index]); | |
| 1159 | ||
| 1160 | @memcpy(serialized_buffer.storage[start_serialized_len..][0..storage.len], storage); | |
| 1161 | ||
| 1162 | for (serialized_buffer.parents[start_serialized_len..][0..parents.len], parents) |*dest, p| { | |
| 1163 | dest.* = switch (p) { | |
| 1164 | .none, .unused => .none, | |
| 1165 | _ => |prev| d: { | |
| 1166 | if (@intFromEnum(prev) == old_main_index) { | |
| 1167 | break :d @enumFromInt(main_index); | |
| 1168 | } else if (@intFromEnum(prev) > nodes_len) { | |
| 1169 | break :d .none; | |
| 1170 | } else { | |
| 1171 | break :d @enumFromInt(@intFromEnum(prev) - start_index + start_serialized_len); | |
| 1172 | } | |
| 1173 | }, | |
| 1174 | }; | |
| 1175 | } | |
| 1176 | ||
| 1177 | return start_serialized_len + storage.len; | |
| 1178 | 1233 | } |
| 1179 | 1234 | |
| 1180 | fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } { | |
| 1181 | const serialized = serialize(serialized_buffer); | |
| 1235 | fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } { | |
| 1236 | if (global_progress.rows == 0 or global_progress.cols == 0) return error.WindowTooSmall; | |
| 1237 | ||
| 1238 | const serialized = try serialize(io, serialized_buffer); | |
| 1182 | 1239 | |
| 1183 | 1240 | // Now we can analyze our copy of the graph without atomics, reconstructing |
| 1184 | 1241 | // children lists which do not exist in the canonical data. These are |
| ... | ... | @@ -1413,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { |
| 1413 | 1470 | return nl_n + 2 < p.rows; |
| 1414 | 1471 | } |
| 1415 | 1472 | |
| 1416 | var remaining_write_trash_bytes: usize = 0; | |
| 1417 | ||
| 1418 | fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { | |
| 1473 | fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void { | |
| 1419 | 1474 | // Byteswap if necessary to ensure little endian over the pipe. This is |
| 1420 | 1475 | // needed because the parent or child process might be running in qemu. |
| 1421 | 1476 | if (is_big_endian) for (serialized.storage) |*s| s.byteSwap(); |
| ... | ... | @@ -1426,62 +1481,8 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi |
| 1426 | 1481 | const storage = std.mem.sliceAsBytes(serialized.storage); |
| 1427 | 1482 | const parents = std.mem.sliceAsBytes(serialized.parents); |
| 1428 | 1483 | |
| 1429 | var vecs: [3][]const u8 = .{ header, storage, parents }; | |
| 1430 | ||
| 1431 | // Ensures the packet can fit in the pipe buffer. | |
| 1432 | const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) + | |
| 1433 | node_storage_buffer_len * @sizeOf(Node.OptionalIndex); | |
| 1434 | comptime assert(upper_bound_msg_len <= 4096); | |
| 1435 | ||
| 1436 | while (remaining_write_trash_bytes > 0) { | |
| 1437 | // We do this in a separate write call to give a better chance for the | |
| 1438 | // writev below to be in a single packet. | |
| 1439 | const n = @min(parents.len, remaining_write_trash_bytes); | |
| 1440 | if (file.writeStreaming(io, &.{}, &.{parents[0..n]}, 1)) |written| { | |
| 1441 | remaining_write_trash_bytes -= written; | |
| 1442 | continue; | |
| 1443 | } else |err| switch (err) { | |
| 1444 | error.WouldBlock => return, | |
| 1445 | error.BrokenPipe => return error.BrokenPipe, | |
| 1446 | else => |e| { | |
| 1447 | std.log.debug("failed to send progress to parent process: {t}", .{e}); | |
| 1448 | return error.BrokenPipe; | |
| 1449 | }, | |
| 1450 | } | |
| 1451 | } | |
| 1452 | ||
| 1453 | // If this write would block we do not want to keep trying, but we need to | |
| 1454 | // know if a partial message was written. | |
| 1455 | if (writevNonblock(io, file, &vecs)) |written| { | |
| 1456 | const total = header.len + storage.len + parents.len; | |
| 1457 | if (written < total) { | |
| 1458 | remaining_write_trash_bytes = total - written; | |
| 1459 | } | |
| 1460 | } else |err| switch (err) { | |
| 1461 | error.WouldBlock => {}, | |
| 1462 | error.BrokenPipe => return error.BrokenPipe, | |
| 1463 | else => |e| { | |
| 1464 | std.log.debug("failed to send progress to parent process: {t}", .{e}); | |
| 1465 | return error.BrokenPipe; | |
| 1466 | }, | |
| 1467 | } | |
| 1468 | } | |
| 1469 | ||
| 1470 | fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize { | |
| 1471 | var iov_index: usize = 0; | |
| 1472 | var written: usize = 0; | |
| 1473 | var total_written: usize = 0; | |
| 1474 | while (true) { | |
| 1475 | while (if (iov_index < iov.len) | |
| 1476 | written >= iov[iov_index].len | |
| 1477 | else | |
| 1478 | return total_written) : (iov_index += 1) written -= iov[iov_index].len; | |
| 1479 | iov[iov_index].ptr += written; | |
| 1480 | iov[iov_index].len -= written; | |
| 1481 | written = try file.writeStreaming(io, &.{}, iov, 1); | |
| 1482 | if (written == 0) return total_written; | |
| 1483 | total_written += written; | |
| 1484 | } | |
| 1484 | var vec = [3][]const u8{ header, storage, parents }; | |
| 1485 | try writer.writeVecAll(&vec); | |
| 1485 | 1486 | } |
| 1486 | 1487 | |
| 1487 | 1488 | fn maybeUpdateSize(io: Io, resize_flag: bool) !void { |
lib/std/Thread.zig+5-1| ... | ... | @@ -598,7 +598,11 @@ const WindowsThreadImpl = struct { |
| 598 | 598 | } |
| 599 | 599 | |
| 600 | 600 | fn join(self: Impl) void { |
| 601 | windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable; | |
| 601 | const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); | |
| 602 | switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) { | |
| 603 | windows.NTSTATUS.WAIT_0 => {}, | |
| 604 | else => |status| windows.unexpectedStatus(status) catch unreachable, | |
| 605 | } | |
| 602 | 606 | windows.CloseHandle(self.thread.thread_handle); |
| 603 | 607 | assert(self.thread.completion.load(.seq_cst) == .completed); |
| 604 | 608 | self.thread.free(); |
lib/std/mem/Allocator.zig+13-2| ... | ... | @@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T { |
| 452 | 452 | return new_buf; |
| 453 | 453 | } |
| 454 | 454 | |
| 455 | /// Deprecated in favor of `dupeSentinel` | |
| 455 | 456 | /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. |
| 456 | 457 | pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T { |
| 458 | return allocator.dupeSentinel(T, m, 0); | |
| 459 | } | |
| 460 | ||
| 461 | /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. | |
| 462 | pub fn dupeSentinel( | |
| 463 | allocator: Allocator, | |
| 464 | comptime T: type, | |
| 465 | m: []const T, | |
| 466 | comptime sentinel: T, | |
| 467 | ) Error![:sentinel]T { | |
| 457 | 468 | const new_buf = try allocator.alloc(T, m.len + 1); |
| 458 | 469 | @memcpy(new_buf[0..m.len], m); |
| 459 | new_buf[m.len] = 0; | |
| 460 | return new_buf[0..m.len :0]; | |
| 470 | new_buf[m.len] = sentinel; | |
| 471 | return new_buf[0..m.len :sentinel]; | |
| 461 | 472 | } |
| 462 | 473 | |
| 463 | 474 | /// An allocator that always fails to allocate. |
lib/std/os/linux.zig+18| ... | ... | @@ -1848,6 +1848,24 @@ pub const F = struct { |
| 1848 | 1848 | pub const RDLCK = if (is_sparc) 1 else 0; |
| 1849 | 1849 | pub const WRLCK = if (is_sparc) 2 else 1; |
| 1850 | 1850 | pub const UNLCK = if (is_sparc) 3 else 2; |
| 1851 | ||
| 1852 | pub const LINUX_SPECIFIC_BASE = 1024; | |
| 1853 | ||
| 1854 | pub const SETLEASE = LINUX_SPECIFIC_BASE + 0; | |
| 1855 | pub const GETLEASE = LINUX_SPECIFIC_BASE + 1; | |
| 1856 | pub const NOTIFY = LINUX_SPECIFIC_BASE + 2; | |
| 1857 | pub const DUPFD_QUERY = LINUX_SPECIFIC_BASE + 3; | |
| 1858 | pub const CREATED_QUERY = LINUX_SPECIFIC_BASE + 4; | |
| 1859 | pub const CANCELLK = LINUX_SPECIFIC_BASE + 5; | |
| 1860 | pub const DUPFD_CLOEXEC = LINUX_SPECIFIC_BASE + 6; | |
| 1861 | pub const SETPIPE_SZ = LINUX_SPECIFIC_BASE + 7; | |
| 1862 | pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8; | |
| 1863 | pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9; | |
| 1864 | pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10; | |
| 1865 | pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11; | |
| 1866 | pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12; | |
| 1867 | pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13; | |
| 1868 | pub const SET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 14; | |
| 1851 | 1869 | }; |
| 1852 | 1870 | |
| 1853 | 1871 | pub const F_OWNER = enum(i32) { |
lib/std/os/windows.zig+19-250| ... | ... | @@ -521,7 +521,7 @@ pub const FILE = struct { |
| 521 | 521 | _, |
| 522 | 522 | |
| 523 | 523 | pub const VALID_FLAGS: @This() = @enumFromInt(0b11); |
| 524 | } = .ASYNCHRONOUS, | |
| 524 | }, | |
| 525 | 525 | /// The file being opened must not be a directory file or this call |
| 526 | 526 | /// fails. The file object being opened can represent a data file, a |
| 527 | 527 | /// logical, virtual, or physical device, or a volume. |
| ... | ... | @@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP { |
| 2324 | 2324 | // ref: um/winternl.h |
| 2325 | 2325 | |
| 2326 | 2326 | pub const OBJECT_ATTRIBUTES = extern struct { |
| 2327 | Length: ULONG, | |
| 2328 | RootDirectory: ?HANDLE, | |
| 2329 | ObjectName: ?*UNICODE_STRING, | |
| 2330 | Attributes: ATTRIBUTES, | |
| 2331 | SecurityDescriptor: ?*anyopaque, | |
| 2332 | SecurityQualityOfService: ?*anyopaque, | |
| 2327 | Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES), | |
| 2328 | RootDirectory: ?HANDLE = null, | |
| 2329 | ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty), | |
| 2330 | Attributes: ATTRIBUTES = .{}, | |
| 2331 | SecurityDescriptor: ?*anyopaque = null, | |
| 2332 | SecurityQualityOfService: ?*anyopaque = null, | |
| 2333 | 2333 | |
| 2334 | 2334 | // Valid values for the Attributes field |
| 2335 | 2335 | pub const ATTRIBUTES = packed struct(ULONG) { |
| ... | ... | @@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 2420 | 2420 | .Buffer = @constCast(sub_path_w.ptr), |
| 2421 | 2421 | }; |
| 2422 | 2422 | const attr: OBJECT_ATTRIBUTES = .{ |
| 2423 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | |
| 2424 | 2423 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, |
| 2425 | .Attributes = .{ | |
| 2426 | .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false, | |
| 2427 | }, | |
| 2424 | .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false }, | |
| 2428 | 2425 | .ObjectName = &nt_name, |
| 2429 | 2426 | .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, |
| 2430 | .SecurityQualityOfService = null, | |
| 2431 | 2427 | }; |
| 2432 | 2428 | var io: IO_STATUS_BLOCK = undefined; |
| 2433 | 2429 | while (true) { |
| ... | ... | @@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 2475 | 2471 | // call has failed. There is not really a sane way to handle |
| 2476 | 2472 | // this other than retrying the creation after the OS finishes |
| 2477 | 2473 | // the deletion. |
| 2478 | _ = kernel32.SleepEx(1, TRUE); | |
| 2474 | const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100); | |
| 2475 | _ = ntdll.NtDelayExecution(TRUE, &delay_one_ms); | |
| 2479 | 2476 | continue; |
| 2480 | 2477 | }, |
| 2481 | 2478 | .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference, |
| ... | ... | @@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD { |
| 2506 | 2503 | pub fn GetLastError() Win32Error { |
| 2507 | 2504 | return @enumFromInt(teb().LastErrorValue); |
| 2508 | 2505 | } |
| 2509 | ||
| 2510 | pub const CreatePipeError = error{ Unexpected, SystemResources }; | |
| 2511 | ||
| 2512 | var npfs: ?HANDLE = null; | |
| 2513 | ||
| 2514 | /// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls. | |
| 2515 | /// It implements similar behavior to `CreatePipe` and is meant to serve | |
| 2516 | /// as a direct substitute for that call. | |
| 2517 | pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void { | |
| 2518 | // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to: | |
| 2519 | // | |
| 2520 | // \??\pipe\Win32Pipes.{pid}.{count} | |
| 2521 | // | |
| 2522 | // where `pid` is the process id and count is a incrementing counter. | |
| 2523 | // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System | |
| 2524 | // and use that as the root directory for `NtCreateNamedPipeFile`. | |
| 2525 | // This object is visible under the NPFS but has no filename attached to it. | |
| 2526 | // | |
| 2527 | // This implementation replicates how `CreatePipe` works in modern Windows versions. | |
| 2528 | const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst); | |
| 2529 | const dev_handle = opt_dev_handle orelse blk: { | |
| 2530 | const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\"); | |
| 2531 | const len: u16 = @truncate(str.len * @sizeOf(u16)); | |
| 2532 | const name: UNICODE_STRING = .{ | |
| 2533 | .Length = len, | |
| 2534 | .MaximumLength = len, | |
| 2535 | .Buffer = @ptrCast(@constCast(str)), | |
| 2536 | }; | |
| 2537 | const attrs: OBJECT_ATTRIBUTES = .{ | |
| 2538 | .ObjectName = @constCast(&name), | |
| 2539 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | |
| 2540 | .RootDirectory = null, | |
| 2541 | .Attributes = .{}, | |
| 2542 | .SecurityDescriptor = null, | |
| 2543 | .SecurityQualityOfService = null, | |
| 2544 | }; | |
| 2545 | ||
| 2546 | var iosb: IO_STATUS_BLOCK = undefined; | |
| 2547 | var handle: HANDLE = undefined; | |
| 2548 | switch (ntdll.NtCreateFile( | |
| 2549 | &handle, | |
| 2550 | .{ | |
| 2551 | .STANDARD = .{ .SYNCHRONIZE = true }, | |
| 2552 | .GENERIC = .{ .READ = true }, | |
| 2553 | }, | |
| 2554 | @constCast(&attrs), | |
| 2555 | &iosb, | |
| 2556 | null, | |
| 2557 | .{}, | |
| 2558 | .VALID_FLAGS, | |
| 2559 | .OPEN, | |
| 2560 | .{ .IO = .SYNCHRONOUS_NONALERT }, | |
| 2561 | null, | |
| 2562 | 0, | |
| 2563 | )) { | |
| 2564 | .SUCCESS => {}, | |
| 2565 | // Judging from the ReactOS sources this is technically possible. | |
| 2566 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | |
| 2567 | .INVALID_PARAMETER => unreachable, | |
| 2568 | else => |e| return unexpectedStatus(e), | |
| 2569 | } | |
| 2570 | if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| { | |
| 2571 | CloseHandle(handle); | |
| 2572 | break :blk xchg.?; | |
| 2573 | } else break :blk handle; | |
| 2574 | }; | |
| 2575 | ||
| 2576 | const name: UNICODE_STRING = .{ .Buffer = null, .Length = 0, .MaximumLength = 0 }; | |
| 2577 | var attrs: OBJECT_ATTRIBUTES = .{ | |
| 2578 | .ObjectName = @constCast(&name), | |
| 2579 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | |
| 2580 | .RootDirectory = dev_handle, | |
| 2581 | .Attributes = .{ .INHERIT = sattr.bInheritHandle != FALSE }, | |
| 2582 | .SecurityDescriptor = sattr.lpSecurityDescriptor, | |
| 2583 | .SecurityQualityOfService = null, | |
| 2584 | }; | |
| 2585 | ||
| 2586 | // 120 second relative timeout in 100ns units. | |
| 2587 | const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100; | |
| 2588 | var iosb: IO_STATUS_BLOCK = undefined; | |
| 2589 | var read: HANDLE = undefined; | |
| 2590 | switch (ntdll.NtCreateNamedPipeFile( | |
| 2591 | &read, | |
| 2592 | .{ | |
| 2593 | .SPECIFIC = .{ .FILE_PIPE = .{ | |
| 2594 | .WRITE_ATTRIBUTES = true, | |
| 2595 | } }, | |
| 2596 | .STANDARD = .{ .SYNCHRONIZE = true }, | |
| 2597 | .GENERIC = .{ .READ = true }, | |
| 2598 | }, | |
| 2599 | &attrs, | |
| 2600 | &iosb, | |
| 2601 | .{ .READ = true, .WRITE = true }, | |
| 2602 | .CREATE, | |
| 2603 | .{ .IO = .SYNCHRONOUS_NONALERT }, | |
| 2604 | .{ .TYPE = .BYTE_STREAM }, | |
| 2605 | .{ .MODE = .BYTE_STREAM }, | |
| 2606 | .{ .OPERATION = .QUEUE }, | |
| 2607 | 1, | |
| 2608 | 4096, | |
| 2609 | 4096, | |
| 2610 | @constCast(&default_timeout), | |
| 2611 | )) { | |
| 2612 | .SUCCESS => {}, | |
| 2613 | .INVALID_PARAMETER => unreachable, | |
| 2614 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | |
| 2615 | else => |e| return unexpectedStatus(e), | |
| 2616 | } | |
| 2617 | errdefer CloseHandle(read); | |
| 2618 | ||
| 2619 | attrs.RootDirectory = read; | |
| 2620 | ||
| 2621 | var write: HANDLE = undefined; | |
| 2622 | switch (ntdll.NtCreateFile( | |
| 2623 | &write, | |
| 2624 | .{ | |
| 2625 | .SPECIFIC = .{ .FILE_PIPE = .{ | |
| 2626 | .READ_ATTRIBUTES = true, | |
| 2627 | } }, | |
| 2628 | .STANDARD = .{ .SYNCHRONIZE = true }, | |
| 2629 | .GENERIC = .{ .WRITE = true }, | |
| 2630 | }, | |
| 2631 | &attrs, | |
| 2632 | &iosb, | |
| 2633 | null, | |
| 2634 | .{}, | |
| 2635 | .VALID_FLAGS, | |
| 2636 | .OPEN, | |
| 2637 | .{ | |
| 2638 | .IO = .SYNCHRONOUS_NONALERT, | |
| 2639 | .NON_DIRECTORY_FILE = true, | |
| 2640 | }, | |
| 2641 | null, | |
| 2642 | 0, | |
| 2643 | )) { | |
| 2644 | .SUCCESS => {}, | |
| 2645 | .INVALID_PARAMETER => unreachable, | |
| 2646 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | |
| 2647 | else => |e| return unexpectedStatus(e), | |
| 2648 | } | |
| 2649 | ||
| 2650 | rd.* = read; | |
| 2651 | wr.* = write; | |
| 2652 | } | |
| 2653 | ||
| 2654 | 2506 | /// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. |
| 2655 | 2507 | /// It implements similar behavior to `DeviceIoControl` and is meant to serve |
| 2656 | 2508 | /// as a direct substitute for that call. |
| ... | ... | @@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR |
| 2707 | 2559 | return bytes; |
| 2708 | 2560 | } |
| 2709 | 2561 | |
| 2710 | pub const SetHandleInformationError = error{Unexpected}; | |
| 2711 | ||
| 2712 | pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformationError!void { | |
| 2713 | if (kernel32.SetHandleInformation(h, mask, flags) == 0) { | |
| 2714 | switch (GetLastError()) { | |
| 2715 | else => |err| return unexpectedError(err), | |
| 2716 | } | |
| 2717 | } | |
| 2718 | } | |
| 2719 | ||
| 2720 | pub const WaitForSingleObjectError = error{ | |
| 2721 | WaitAbandoned, | |
| 2722 | WaitTimeOut, | |
| 2723 | Unexpected, | |
| 2724 | }; | |
| 2725 | ||
| 2726 | pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void { | |
| 2727 | return WaitForSingleObjectEx(handle, milliseconds, false); | |
| 2728 | } | |
| 2729 | ||
| 2730 | pub fn WaitForSingleObjectEx(handle: HANDLE, milliseconds: DWORD, alertable: bool) WaitForSingleObjectError!void { | |
| 2731 | switch (kernel32.WaitForSingleObjectEx(handle, milliseconds, @intFromBool(alertable))) { | |
| 2732 | WAIT_ABANDONED => return error.WaitAbandoned, | |
| 2733 | WAIT_OBJECT_0 => return, | |
| 2734 | WAIT_TIMEOUT => return error.WaitTimeOut, | |
| 2735 | WAIT_FAILED => switch (GetLastError()) { | |
| 2736 | else => |err| return unexpectedError(err), | |
| 2737 | }, | |
| 2738 | else => return error.Unexpected, | |
| 2739 | } | |
| 2740 | } | |
| 2741 | ||
| 2742 | pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, milliseconds: DWORD, alertable: bool) !u32 { | |
| 2743 | assert(handles.len > 0 and handles.len <= MAXIMUM_WAIT_OBJECTS); | |
| 2744 | const nCount: DWORD = @as(DWORD, @intCast(handles.len)); | |
| 2745 | switch (kernel32.WaitForMultipleObjectsEx( | |
| 2746 | nCount, | |
| 2747 | handles.ptr, | |
| 2748 | @intFromBool(waitAll), | |
| 2749 | milliseconds, | |
| 2750 | @intFromBool(alertable), | |
| 2751 | )) { | |
| 2752 | WAIT_OBJECT_0...WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS => |n| { | |
| 2753 | const handle_index = n - WAIT_OBJECT_0; | |
| 2754 | assert(handle_index < nCount); | |
| 2755 | return handle_index; | |
| 2756 | }, | |
| 2757 | WAIT_ABANDONED_0...WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS => |n| { | |
| 2758 | const handle_index = n - WAIT_ABANDONED_0; | |
| 2759 | assert(handle_index < nCount); | |
| 2760 | return error.WaitAbandoned; | |
| 2761 | }, | |
| 2762 | WAIT_TIMEOUT => return error.WaitTimeOut, | |
| 2763 | WAIT_FAILED => switch (GetLastError()) { | |
| 2764 | else => |err| return unexpectedError(err), | |
| 2765 | }, | |
| 2766 | else => return error.Unexpected, | |
| 2767 | } | |
| 2768 | } | |
| 2769 | ||
| 2770 | 2562 | pub const CreateIoCompletionPortError = error{Unexpected}; |
| 2771 | 2563 | |
| 2772 | 2564 | pub fn CreateIoCompletionPort( |
| ... | ... | @@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void { |
| 2878 | 2670 | assert(ntdll.NtClose(hObject) == .SUCCESS); |
| 2879 | 2671 | } |
| 2880 | 2672 | |
| 2881 | pub const GetStdHandleError = error{ | |
| 2882 | NoStandardHandleAttached, | |
| 2883 | Unexpected, | |
| 2884 | }; | |
| 2885 | ||
| 2886 | pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE { | |
| 2887 | const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached; | |
| 2888 | if (handle == INVALID_HANDLE_VALUE) { | |
| 2889 | switch (GetLastError()) { | |
| 2890 | else => |err| return unexpectedError(err), | |
| 2891 | } | |
| 2892 | } | |
| 2893 | return handle; | |
| 2894 | } | |
| 2895 | ||
| 2896 | 2673 | pub const QueryObjectNameError = error{ |
| 2897 | 2674 | AccessDenied, |
| 2898 | 2675 | InvalidHandle, |
| ... | ... | @@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME { |
| 3545 | 3322 | }; |
| 3546 | 3323 | } |
| 3547 | 3324 | |
| 3325 | /// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a | |
| 3326 | /// redundant copy of the uppercase data. | |
| 3327 | pub inline fn toUpperWtf16(c: u16) u16 { | |
| 3328 | return (if (builtin.os.tag != .windows or @inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar)(c); | |
| 3329 | } | |
| 3330 | ||
| 3548 | 3331 | /// Compares two WTF16 strings using the equivalent functionality of |
| 3549 | 3332 | /// `RtlEqualUnicodeString` (with case insensitive comparison enabled). |
| 3550 | 3333 | /// This function can be called on any target. |
| ... | ... | @@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool { |
| 3598 | 3381 | var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator(); |
| 3599 | 3382 | var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator(); |
| 3600 | 3383 | |
| 3601 | // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a | |
| 3602 | // redundant copy of the uppercase data. | |
| 3603 | const upcaseImpl = switch (builtin.os.tag) { | |
| 3604 | .windows => if (@inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar, | |
| 3605 | else => nls.upcaseW, | |
| 3606 | }; | |
| 3607 | ||
| 3608 | 3384 | while (true) { |
| 3609 | 3385 | const a_cp = a_wtf8_it.nextCodepoint() orelse break; |
| 3610 | 3386 | const b_cp = b_wtf8_it.nextCodepoint() orelse return false; |
| 3611 | 3387 | |
| 3612 | 3388 | if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) { |
| 3613 | if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) { | |
| 3389 | if (a_cp != b_cp and toUpperWtf16(@intCast(a_cp)) != toUpperWtf16(@intCast(b_cp))) { | |
| 3614 | 3390 | return false; |
| 3615 | 3391 | } |
| 3616 | 3392 | } else if (a_cp != b_cp) { |
| ... | ... | @@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error; |
| 4098 | 3874 | pub const LANG = @import("windows/lang.zig"); |
| 4099 | 3875 | pub const SUBLANG = @import("windows/sublang.zig"); |
| 4100 | 3876 | |
| 4101 | /// The standard input device. Initially, this is the console input buffer, CONIN$. | |
| 4102 | pub const STD_INPUT_HANDLE = maxInt(DWORD) - 10 + 1; | |
| 4103 | ||
| 4104 | /// The standard output device. Initially, this is the active console screen buffer, CONOUT$. | |
| 4105 | pub const STD_OUTPUT_HANDLE = maxInt(DWORD) - 11 + 1; | |
| 4106 | ||
| 4107 | /// The standard error device. Initially, this is the active console screen buffer, CONOUT$. | |
| 4108 | pub const STD_ERROR_HANDLE = maxInt(DWORD) - 12 + 1; | |
| 4109 | ||
| 4110 | 3877 | pub const BOOL = c_int; |
| 4111 | 3878 | pub const BOOLEAN = BYTE; |
| 4112 | 3879 | pub const BYTE = u8; |
| ... | ... | @@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct { |
| 5244 | 5011 | Length: c_ushort, |
| 5245 | 5012 | MaximumLength: c_ushort, |
| 5246 | 5013 | Buffer: ?[*]WCHAR, |
| 5014 | ||
| 5015 | pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null }; | |
| 5247 | 5016 | }; |
| 5248 | 5017 | |
| 5249 | 5018 | pub const ACTIVATION_CONTEXT_DATA = opaque {}; |
lib/std/os/windows/kernel32.zig-108| ... | ... | @@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME; |
| 12 | 12 | const HANDLE = windows.HANDLE; |
| 13 | 13 | const HANDLER_ROUTINE = windows.HANDLER_ROUTINE; |
| 14 | 14 | const HMODULE = windows.HMODULE; |
| 15 | const INIT_ONCE = windows.INIT_ONCE; | |
| 16 | const INIT_ONCE_FN = windows.INIT_ONCE_FN; | |
| 17 | 15 | const LARGE_INTEGER = windows.LARGE_INTEGER; |
| 18 | 16 | const LPCSTR = windows.LPCSTR; |
| 19 | 17 | const LPCVOID = windows.LPCVOID; |
| ... | ... | @@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR; |
| 24 | 22 | const MODULEENTRY32 = windows.MODULEENTRY32; |
| 25 | 23 | const OVERLAPPED = windows.OVERLAPPED; |
| 26 | 24 | const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY; |
| 27 | const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION; | |
| 28 | 25 | const PROCESS_INFORMATION = windows.PROCESS_INFORMATION; |
| 29 | 26 | const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES; |
| 30 | 27 | const SIZE_T = windows.SIZE_T; |
| ... | ... | @@ -37,7 +34,6 @@ const ULONG = windows.ULONG; |
| 37 | 34 | const ULONG_PTR = windows.ULONG_PTR; |
| 38 | 35 | const va_list = windows.va_list; |
| 39 | 36 | const WCHAR = windows.WCHAR; |
| 40 | const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW; | |
| 41 | 37 | const Win32Error = windows.Win32Error; |
| 42 | 38 | const WORD = windows.WORD; |
| 43 | 39 | |
| ... | ... | @@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo( |
| 59 | 55 | hFile: HANDLE, |
| 60 | 56 | ) callconv(.winapi) BOOL; |
| 61 | 57 | |
| 62 | // TODO: Wrapper around NtCancelIoFileEx. | |
| 63 | pub extern "kernel32" fn CancelIoEx( | |
| 64 | hFile: HANDLE, | |
| 65 | lpOverlapped: ?*OVERLAPPED, | |
| 66 | ) callconv(.winapi) BOOL; | |
| 67 | ||
| 68 | pub extern "kernel32" fn CreateFileW( | |
| 69 | lpFileName: LPCWSTR, | |
| 70 | dwDesiredAccess: ACCESS_MASK, | |
| 71 | dwShareMode: DWORD, | |
| 72 | lpSecurityAttributes: ?*SECURITY_ATTRIBUTES, | |
| 73 | dwCreationDisposition: DWORD, | |
| 74 | dwFlagsAndAttributes: DWORD, | |
| 75 | hTemplateFile: ?HANDLE, | |
| 76 | ) callconv(.winapi) HANDLE; | |
| 77 | ||
| 78 | // TODO A bunch of logic around NtCreateNamedPipe | |
| 79 | pub extern "kernel32" fn CreateNamedPipeW( | |
| 80 | lpName: LPCWSTR, | |
| 81 | dwOpenMode: DWORD, | |
| 82 | dwPipeMode: DWORD, | |
| 83 | nMaxInstances: DWORD, | |
| 84 | nOutBufferSize: DWORD, | |
| 85 | nInBufferSize: DWORD, | |
| 86 | nDefaultTimeOut: DWORD, | |
| 87 | lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES, | |
| 88 | ) callconv(.winapi) HANDLE; | |
| 89 | ||
| 90 | // TODO: Matches `STD_*_HANDLE` to peb().ProcessParameters.Standard* | |
| 91 | pub extern "kernel32" fn GetStdHandle( | |
| 92 | nStdHandle: DWORD, | |
| 93 | ) callconv(.winapi) ?HANDLE; | |
| 94 | ||
| 95 | 58 | // TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`. |
| 96 | 59 | // `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END` |
| 97 | 60 | pub extern "kernel32" fn SetFilePointerEx( |
| ... | ... | @@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile( |
| 117 | 80 | in_out_lpOverlapped: ?*OVERLAPPED, |
| 118 | 81 | ) callconv(.winapi) BOOL; |
| 119 | 82 | |
| 120 | // TODO: Wrapper around GetStdHandle + NtFlushBuffersFile. | |
| 121 | pub extern "kernel32" fn FlushFileBuffers( | |
| 122 | hFile: HANDLE, | |
| 123 | ) callconv(.winapi) BOOL; | |
| 124 | ||
| 125 | 83 | // TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`. |
| 126 | 84 | pub extern "kernel32" fn SetFileCompletionNotificationModes( |
| 127 | 85 | FileHandle: HANDLE, |
| ... | ... | @@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW( |
| 143 | 101 | |
| 144 | 102 | // I/O - Kernel Objects |
| 145 | 103 | |
| 146 | // TODO: Wrapper around GetStdHandle + NtDuplicateObject. | |
| 147 | pub extern "kernel32" fn DuplicateHandle( | |
| 148 | hSourceProcessHandle: HANDLE, | |
| 149 | hSourceHandle: HANDLE, | |
| 150 | hTargetProcessHandle: HANDLE, | |
| 151 | lpTargetHandle: *HANDLE, | |
| 152 | dwDesiredAccess: ACCESS_MASK, | |
| 153 | bInheritHandle: BOOL, | |
| 154 | dwOptions: DWORD, | |
| 155 | ) callconv(.winapi) BOOL; | |
| 156 | ||
| 157 | // TODO: Wrapper around GetStdHandle + NtQueryObject + NtSetInformationObject with .ObjectHandleFlagInformation. | |
| 158 | pub extern "kernel32" fn SetHandleInformation( | |
| 159 | hObject: HANDLE, | |
| 160 | dwMask: DWORD, | |
| 161 | dwFlags: DWORD, | |
| 162 | ) callconv(.winapi) BOOL; | |
| 163 | ||
| 164 | 104 | // TODO: Wrapper around NtRemoveIoCompletion. |
| 165 | 105 | pub extern "kernel32" fn GetQueuedCompletionStatus( |
| 166 | 106 | CompletionPort: HANDLE, |
| ... | ... | @@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess( |
| 210 | 150 | uExitCode: UINT, |
| 211 | 151 | ) callconv(.winapi) BOOL; |
| 212 | 152 | |
| 213 | // TODO: WaitForSingleObjectEx with bAlertable=false. | |
| 214 | pub extern "kernel32" fn WaitForSingleObject( | |
| 215 | hHandle: HANDLE, | |
| 216 | dwMilliseconds: DWORD, | |
| 217 | ) callconv(.winapi) DWORD; | |
| 218 | ||
| 219 | // TODO: Wrapper for GetStdHandle + NtWaitForSingleObject. | |
| 220 | // Sets up an activation context before calling NtWaitForSingleObject. | |
| 221 | pub extern "kernel32" fn WaitForSingleObjectEx( | |
| 222 | hHandle: HANDLE, | |
| 223 | dwMilliseconds: DWORD, | |
| 224 | bAlertable: BOOL, | |
| 225 | ) callconv(.winapi) DWORD; | |
| 226 | ||
| 227 | // TODO: WaitForMultipleObjectsEx with alertable=false | |
| 228 | pub extern "kernel32" fn WaitForMultipleObjects( | |
| 229 | nCount: DWORD, | |
| 230 | lpHandle: [*]const HANDLE, | |
| 231 | bWaitAll: BOOL, | |
| 232 | dwMilliseconds: DWORD, | |
| 233 | ) callconv(.winapi) DWORD; | |
| 234 | ||
| 235 | // TODO: Wrapper around NtWaitForMultipleObjects. | |
| 236 | pub extern "kernel32" fn WaitForMultipleObjectsEx( | |
| 237 | nCount: DWORD, | |
| 238 | lpHandle: [*]const HANDLE, | |
| 239 | bWaitAll: BOOL, | |
| 240 | dwMilliseconds: DWORD, | |
| 241 | bAlertable: BOOL, | |
| 242 | ) callconv(.winapi) DWORD; | |
| 243 | ||
| 244 | 153 | // Process Management |
| 245 | 154 | |
| 246 | 155 | pub extern "kernel32" fn CreateProcessW( |
| ... | ... | @@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW( |
| 256 | 165 | lpProcessInformation: *PROCESS_INFORMATION, |
| 257 | 166 | ) callconv(.winapi) BOOL; |
| 258 | 167 | |
| 259 | // TODO: implement via ntdll instead | |
| 260 | pub extern "kernel32" fn SleepEx( | |
| 261 | dwMilliseconds: DWORD, | |
| 262 | bAlertable: BOOL, | |
| 263 | ) callconv(.winapi) DWORD; | |
| 264 | ||
| 265 | 168 | // TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`. |
| 266 | 169 | pub extern "kernel32" fn GetExitCodeProcess( |
| 267 | 170 | hProcess: HANDLE, |
| ... | ... | @@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW( |
| 436 | 339 | |
| 437 | 340 | // TODO: Getter for teb().LastErrorValue. |
| 438 | 341 | pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error; |
| 439 | ||
| 440 | // TODO: Wrapper around RtlSetLastWin32Error. | |
| 441 | pub extern "kernel32" fn SetLastError( | |
| 442 | dwErrCode: Win32Error, | |
| 443 | ) callconv(.winapi) void; | |
| 444 | ||
| 445 | // Everything Else | |
| 446 | ||
| 447 | pub extern "kernel32" fn GetSystemInfo( | |
| 448 | lpSystemInfo: *SYSTEM_INFO, | |
| 449 | ) callconv(.winapi) void; |
lib/std/os/windows/ntdll.zig+9-4| ... | ... | @@ -407,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile( |
| 407 | 407 | DefaultTimeout: ?*const LARGE_INTEGER, |
| 408 | 408 | ) callconv(.winapi) NTSTATUS; |
| 409 | 409 | |
| 410 | pub extern "ntdll" fn NtFlushBuffersFile( | |
| 411 | FileHandle: HANDLE, | |
| 412 | IoStatusBlock: *IO_STATUS_BLOCK, | |
| 413 | ) callconv(.winapi) NTSTATUS; | |
| 414 | ||
| 410 | 415 | pub extern "ntdll" fn NtMapViewOfSection( |
| 411 | 416 | SectionHandle: HANDLE, |
| 412 | 417 | ProcessHandle: HANDLE, |
| ... | ... | @@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread( |
| 590 | 595 | |
| 591 | 596 | pub extern "ntdll" fn NtCancelSynchronousIoFile( |
| 592 | 597 | ThreadHandle: HANDLE, |
| 593 | RequestToCancel: ?*IO_STATUS_BLOCK, | |
| 598 | IoRequestToCancel: ?*IO_STATUS_BLOCK, | |
| 594 | 599 | IoStatusBlock: *IO_STATUS_BLOCK, |
| 595 | 600 | ) callconv(.winapi) NTSTATUS; |
| 596 | 601 | |
| ... | ... | @@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution( |
| 606 | 611 | DelayInterval: *const LARGE_INTEGER, |
| 607 | 612 | ) callconv(.winapi) NTSTATUS; |
| 608 | 613 | |
| 609 | pub extern "ntdll" fn NtCancelIoFileEx( | |
| 614 | pub extern "ntdll" fn NtCancelIoFile( | |
| 610 | 615 | FileHandle: HANDLE, |
| 611 | IoRequestToCancel: *const IO_STATUS_BLOCK, | |
| 612 | 616 | IoStatusBlock: *IO_STATUS_BLOCK, |
| 613 | 617 | ) callconv(.winapi) NTSTATUS; |
| 614 | 618 | |
| 615 | pub extern "ntdll" fn NtCancelIoFile( | |
| 619 | pub extern "ntdll" fn NtCancelIoFileEx( | |
| 616 | 620 | FileHandle: HANDLE, |
| 621 | IoRequestToCancel: *const IO_STATUS_BLOCK, | |
| 617 | 622 | IoStatusBlock: *IO_STATUS_BLOCK, |
| 618 | 623 | ) callconv(.winapi) NTSTATUS; |
lib/std/process/Environ.zig+438-272| ... | ... | @@ -4,7 +4,7 @@ const builtin = @import("builtin"); |
| 4 | 4 | const native_os = builtin.os.tag; |
| 5 | 5 | |
| 6 | 6 | const std = @import("../std.zig"); |
| 7 | const Allocator = std.mem.Allocator; | |
| 7 | const Allocator = mem.Allocator; | |
| 8 | 8 | const assert = std.debug.assert; |
| 9 | 9 | const testing = std.testing; |
| 10 | 10 | const unicode = std.unicode; |
| ... | ... | @@ -14,12 +14,7 @@ const mem = std.mem; |
| 14 | 14 | /// Unmodified, unprocessed data provided by the operating system. |
| 15 | 15 | block: Block, |
| 16 | 16 | |
| 17 | pub const empty: Environ = .{ | |
| 18 | .block = switch (Block) { | |
| 19 | void => {}, | |
| 20 | else => &.{}, | |
| 21 | }, | |
| 22 | }; | |
| 17 | pub const empty: Environ = .{ .block = .empty }; | |
| 23 | 18 | |
| 24 | 19 | /// On WASI without libc, this is `void` because the environment has to be |
| 25 | 20 | /// queried and heap-allocated at runtime. |
| ... | ... | @@ -28,13 +23,65 @@ pub const empty: Environ = .{ |
| 28 | 23 | /// is modified, so a long-lived pointer cannot be used. Therefore, on this |
| 29 | 24 | /// operating system `void` is also used. |
| 30 | 25 | pub const Block = switch (native_os) { |
| 31 | .windows => void, | |
| 26 | .windows => GlobalBlock, | |
| 32 | 27 | .wasi => switch (builtin.link_libc) { |
| 33 | false => void, | |
| 34 | true => [:null]const ?[*:0]const u8, | |
| 28 | false => GlobalBlock, | |
| 29 | true => PosixBlock, | |
| 35 | 30 | }, |
| 36 | .freestanding, .other => void, | |
| 37 | else => [:null]const ?[*:0]const u8, | |
| 31 | .freestanding, .other => GlobalBlock, | |
| 32 | else => PosixBlock, | |
| 33 | }; | |
| 34 | ||
| 35 | pub const GlobalBlock = struct { | |
| 36 | use_global: bool, | |
| 37 | ||
| 38 | pub const empty: GlobalBlock = .{ .use_global = false }; | |
| 39 | pub const global: GlobalBlock = .{ .use_global = true }; | |
| 40 | ||
| 41 | pub fn deinit(_: GlobalBlock, _: Allocator) void {} | |
| 42 | }; | |
| 43 | ||
| 44 | pub const PosixBlock = struct { | |
| 45 | slice: [:null]const ?[*:0]const u8, | |
| 46 | ||
| 47 | pub const empty: PosixBlock = .{ .slice = &.{} }; | |
| 48 | ||
| 49 | pub fn deinit(block: PosixBlock, gpa: Allocator) void { | |
| 50 | for (block.slice) |entry| gpa.free(mem.span(entry.?)); | |
| 51 | gpa.free(block.slice); | |
| 52 | } | |
| 53 | ||
| 54 | pub const View = struct { | |
| 55 | slice: []const [*:0]const u8, | |
| 56 | ||
| 57 | pub fn isEmpty(v: View) bool { | |
| 58 | return v.slice.len == 0; | |
| 59 | } | |
| 60 | }; | |
| 61 | pub fn view(block: PosixBlock) View { | |
| 62 | return .{ .slice = @ptrCast(block.slice) }; | |
| 63 | } | |
| 64 | }; | |
| 65 | ||
| 66 | pub const WindowsBlock = struct { | |
| 67 | slice: [:0]const u16, | |
| 68 | ||
| 69 | pub const empty: WindowsBlock = .{ .slice = &.{0} }; | |
| 70 | ||
| 71 | pub fn deinit(block: WindowsBlock, gpa: Allocator) void { | |
| 72 | gpa.free(block.slice); | |
| 73 | } | |
| 74 | ||
| 75 | pub const View = struct { | |
| 76 | ptr: [*:0]const u16, | |
| 77 | ||
| 78 | pub fn isEmpty(v: View) bool { | |
| 79 | return v.ptr[0] == 0; | |
| 80 | } | |
| 81 | }; | |
| 82 | pub fn view(block: WindowsBlock) View { | |
| 83 | return .{ .ptr = block.slice.ptr }; | |
| 84 | } | |
| 38 | 85 | }; |
| 39 | 86 | |
| 40 | 87 | pub const Map = struct { |
| ... | ... | @@ -46,47 +93,60 @@ pub const Map = struct { |
| 46 | 93 | pub const Size = usize; |
| 47 | 94 | |
| 48 | 95 | pub const EnvNameHashContext = struct { |
| 49 | fn upcase(c: u21) u21 { | |
| 50 | if (c <= std.math.maxInt(u16)) | |
| 51 | return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); | |
| 52 | return c; | |
| 53 | } | |
| 54 | ||
| 55 | 96 | pub fn hash(self: @This(), s: []const u8) u32 { |
| 56 | 97 | _ = self; |
| 57 | if (native_os == .windows) { | |
| 58 | var h = std.hash.Wyhash.init(0); | |
| 59 | var it = unicode.Wtf8View.initUnchecked(s).iterator(); | |
| 60 | while (it.nextCodepoint()) |cp| { | |
| 61 | const cp_upper = upcase(cp); | |
| 62 | h.update(&[_]u8{ | |
| 63 | @as(u8, @intCast((cp_upper >> 16) & 0xff)), | |
| 64 | @as(u8, @intCast((cp_upper >> 8) & 0xff)), | |
| 65 | @as(u8, @intCast((cp_upper >> 0) & 0xff)), | |
| 66 | }); | |
| 67 | } | |
| 68 | return @truncate(h.final()); | |
| 98 | switch (native_os) { | |
| 99 | else => return std.array_hash_map.hashString(s), | |
| 100 | .windows => { | |
| 101 | var h = std.hash.Wyhash.init(0); | |
| 102 | var it = unicode.Wtf8View.initUnchecked(s).iterator(); | |
| 103 | while (it.nextCodepoint()) |cp| { | |
| 104 | const cp_upper = if (std.math.cast(u16, cp)) |wtf16| | |
| 105 | std.os.windows.toUpperWtf16(wtf16) | |
| 106 | else | |
| 107 | cp; | |
| 108 | h.update(&[_]u8{ | |
| 109 | @truncate(cp_upper >> 0), | |
| 110 | @truncate(cp_upper >> 8), | |
| 111 | @truncate(cp_upper >> 16), | |
| 112 | }); | |
| 113 | } | |
| 114 | return @truncate(h.final()); | |
| 115 | }, | |
| 69 | 116 | } |
| 70 | return std.array_hash_map.hashString(s); | |
| 71 | 117 | } |
| 72 | 118 | |
| 73 | 119 | pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool { |
| 74 | 120 | _ = self; |
| 75 | 121 | _ = b_index; |
| 76 | if (native_os == .windows) { | |
| 77 | var it_a = unicode.Wtf8View.initUnchecked(a).iterator(); | |
| 78 | var it_b = unicode.Wtf8View.initUnchecked(b).iterator(); | |
| 79 | while (true) { | |
| 80 | const c_a = it_a.nextCodepoint() orelse break; | |
| 81 | const c_b = it_b.nextCodepoint() orelse return false; | |
| 82 | if (upcase(c_a) != upcase(c_b)) | |
| 83 | return false; | |
| 84 | } | |
| 85 | return if (it_b.nextCodepoint()) |_| false else true; | |
| 86 | } | |
| 87 | return std.array_hash_map.eqlString(a, b); | |
| 122 | return eqlKeys(a, b); | |
| 88 | 123 | } |
| 89 | 124 | }; |
| 125 | fn eqlKeys(a: []const u8, b: []const u8) bool { | |
| 126 | return switch (native_os) { | |
| 127 | else => std.array_hash_map.eqlString(a, b), | |
| 128 | .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b), | |
| 129 | }; | |
| 130 | } | |
| 131 | ||
| 132 | pub fn validateKey(key: []const u8) bool { | |
| 133 | switch (native_os) { | |
| 134 | else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null, | |
| 135 | .windows => { | |
| 136 | if (!unicode.wtf8ValidateSlice(key)) return false; | |
| 137 | var it = unicode.Wtf8View.initUnchecked(key).iterator(); | |
| 138 | switch (it.nextCodepoint() orelse return false) { | |
| 139 | 0 => return false, | |
| 140 | else => {}, | |
| 141 | } | |
| 142 | while (it.nextCodepoint()) |cp| switch (cp) { | |
| 143 | 0, '=' => return false, | |
| 144 | else => {}, | |
| 145 | }; | |
| 146 | return true; | |
| 147 | }, | |
| 148 | } | |
| 149 | } | |
| 90 | 150 | |
| 91 | 151 | /// Create a Map backed by a specific allocator. |
| 92 | 152 | /// That allocator will be used for both backing allocations |
| ... | ... | @@ -99,30 +159,71 @@ pub const Map = struct { |
| 99 | 159 | /// of the stored keys and values. |
| 100 | 160 | pub fn deinit(self: *Map) void { |
| 101 | 161 | const gpa = self.allocator; |
| 102 | var it = self.array_hash_map.iterator(); | |
| 103 | while (it.next()) |entry| { | |
| 104 | gpa.free(entry.key_ptr.*); | |
| 105 | gpa.free(entry.value_ptr.*); | |
| 106 | } | |
| 162 | for (self.keys()) |key| gpa.free(key); | |
| 163 | for (self.values()) |value| gpa.free(value); | |
| 107 | 164 | self.array_hash_map.deinit(gpa); |
| 108 | 165 | self.* = undefined; |
| 109 | 166 | } |
| 110 | 167 | |
| 111 | pub fn keys(m: *const Map) [][]const u8 { | |
| 112 | return m.array_hash_map.keys(); | |
| 168 | pub fn keys(map: *const Map) [][]const u8 { | |
| 169 | return map.array_hash_map.keys(); | |
| 170 | } | |
| 171 | ||
| 172 | pub fn values(map: *const Map) [][]const u8 { | |
| 173 | return map.array_hash_map.values(); | |
| 174 | } | |
| 175 | ||
| 176 | pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void { | |
| 177 | for (view.slice) |entry| { | |
| 178 | var entry_i: usize = 0; | |
| 179 | while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {} | |
| 180 | const key = entry[0..entry_i]; | |
| 181 | ||
| 182 | var end_i: usize = entry_i; | |
| 183 | while (entry[end_i] != 0) : (end_i += 1) {} | |
| 184 | const value = entry[entry_i + 1 .. end_i]; | |
| 185 | ||
| 186 | try map.put(key, value); | |
| 187 | } | |
| 113 | 188 | } |
| 114 | 189 | |
| 115 | pub fn values(m: *const Map) [][]const u8 { | |
| 116 | return m.array_hash_map.values(); | |
| 190 | pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void { | |
| 191 | var i: usize = 0; | |
| 192 | while (view.ptr[i] != 0) { | |
| 193 | const key_start = i; | |
| 194 | ||
| 195 | // There are some special environment variables that start with =, | |
| 196 | // so we need a special case to not treat = as a key/value separator | |
| 197 | // if it's the first character. | |
| 198 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | |
| 199 | if (view.ptr[key_start] == '=') i += 1; | |
| 200 | ||
| 201 | while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {} | |
| 202 | const key_w = view.ptr[key_start..i]; | |
| 203 | const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w); | |
| 204 | errdefer map.allocator.free(key); | |
| 205 | ||
| 206 | if (view.ptr[i] == '=') i += 1; | |
| 207 | ||
| 208 | const value_start = i; | |
| 209 | while (view.ptr[i] != 0) : (i += 1) {} | |
| 210 | const value_w = view.ptr[value_start..i]; | |
| 211 | const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w); | |
| 212 | errdefer map.allocator.free(value); | |
| 213 | ||
| 214 | i += 1; // skip over null byte | |
| 215 | ||
| 216 | try map.putMove(key, value); | |
| 217 | } | |
| 117 | 218 | } |
| 118 | 219 | |
| 119 | 220 | /// Same as `put` but the key and value become owned by the Map rather |
| 120 | 221 | /// than being copied. |
| 121 | 222 | /// If `putMove` fails, the ownership of key and value does not transfer. |
| 122 | 223 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 123 | pub fn putMove(self: *Map, key: []u8, value: []u8) !void { | |
| 224 | pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void { | |
| 225 | assert(validateKey(key)); | |
| 124 | 226 | const gpa = self.allocator; |
| 125 | assert(unicode.wtf8ValidateSlice(key)); | |
| 126 | 227 | const get_or_put = try self.array_hash_map.getOrPut(gpa, key); |
| 127 | 228 | if (get_or_put.found_existing) { |
| 128 | 229 | gpa.free(get_or_put.key_ptr.*); |
| ... | ... | @@ -134,8 +235,8 @@ pub const Map = struct { |
| 134 | 235 | |
| 135 | 236 | /// `key` and `value` are copied into the Map. |
| 136 | 237 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 137 | pub fn put(self: *Map, key: []const u8, value: []const u8) !void { | |
| 138 | assert(unicode.wtf8ValidateSlice(key)); | |
| 238 | pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void { | |
| 239 | assert(validateKey(key)); | |
| 139 | 240 | const gpa = self.allocator; |
| 140 | 241 | const value_copy = try gpa.dupe(u8, value); |
| 141 | 242 | errdefer gpa.free(value_copy); |
| ... | ... | @@ -155,7 +256,7 @@ pub const Map = struct { |
| 155 | 256 | /// The returned pointer is invalidated if the map resizes. |
| 156 | 257 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 157 | 258 | pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 { |
| 158 | assert(unicode.wtf8ValidateSlice(key)); | |
| 259 | assert(validateKey(key)); | |
| 159 | 260 | return self.array_hash_map.getPtr(key); |
| 160 | 261 | } |
| 161 | 262 | |
| ... | ... | @@ -164,11 +265,12 @@ pub const Map = struct { |
| 164 | 265 | /// key is removed from the map. |
| 165 | 266 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 166 | 267 | pub fn get(self: Map, key: []const u8) ?[]const u8 { |
| 167 | assert(unicode.wtf8ValidateSlice(key)); | |
| 268 | assert(validateKey(key)); | |
| 168 | 269 | return self.array_hash_map.get(key); |
| 169 | 270 | } |
| 170 | 271 | |
| 171 | 272 | pub fn contains(m: *const Map, key: []const u8) bool { |
| 273 | assert(validateKey(key)); | |
| 172 | 274 | return m.array_hash_map.contains(key); |
| 173 | 275 | } |
| 174 | 276 | |
| ... | ... | @@ -181,7 +283,7 @@ pub const Map = struct { |
| 181 | 283 | /// This invalidates the value returned by get() for this key. |
| 182 | 284 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 183 | 285 | pub fn swapRemove(self: *Map, key: []const u8) bool { |
| 184 | assert(unicode.wtf8ValidateSlice(key)); | |
| 286 | assert(validateKey(key)); | |
| 185 | 287 | const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false; |
| 186 | 288 | const gpa = self.allocator; |
| 187 | 289 | gpa.free(kv.key); |
| ... | ... | @@ -198,7 +300,7 @@ pub const Map = struct { |
| 198 | 300 | /// This invalidates the value returned by get() for this key. |
| 199 | 301 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 200 | 302 | pub fn orderedRemove(self: *Map, key: []const u8) bool { |
| 201 | assert(unicode.wtf8ValidateSlice(key)); | |
| 303 | assert(validateKey(key)); | |
| 202 | 304 | const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false; |
| 203 | 305 | const gpa = self.allocator; |
| 204 | 306 | gpa.free(kv.key); |
| ... | ... | @@ -233,105 +335,120 @@ pub const Map = struct { |
| 233 | 335 | |
| 234 | 336 | /// Creates a null-delimited environment variable block in the format |
| 235 | 337 | /// expected by POSIX, from a hash map plus options. |
| 236 | pub fn createBlockPosix( | |
| 338 | pub fn createPosixBlock( | |
| 237 | 339 | map: *const Map, |
| 238 | arena: Allocator, | |
| 239 | options: CreateBlockPosixOptions, | |
| 240 | ) Allocator.Error![:null]?[*:0]u8 { | |
| 340 | gpa: Allocator, | |
| 341 | options: CreatePosixBlockOptions, | |
| 342 | ) Allocator.Error!PosixBlock { | |
| 241 | 343 | const ZigProgressAction = enum { nothing, edit, delete, add }; |
| 242 | const zig_progress_action: ZigProgressAction = a: { | |
| 243 | const fd = options.zig_progress_fd orelse break :a .nothing; | |
| 244 | const exists = map.get("ZIG_PROGRESS") != null; | |
| 344 | const zig_progress_action: ZigProgressAction = action: { | |
| 345 | const fd = options.zig_progress_fd orelse break :action .nothing; | |
| 346 | const exists = map.contains("ZIG_PROGRESS"); | |
| 245 | 347 | if (fd >= 0) { |
| 246 | break :a if (exists) .edit else .add; | |
| 348 | break :action if (exists) .edit else .add; | |
| 247 | 349 | } else { |
| 248 | if (exists) break :a .delete; | |
| 350 | if (exists) break :action .delete; | |
| 249 | 351 | } |
| 250 | break :a .nothing; | |
| 352 | break :action .nothing; | |
| 251 | 353 | }; |
| 252 | 354 | |
| 253 | const envp_count: usize = c: { | |
| 254 | var c: usize = map.count(); | |
| 355 | const envp = try gpa.allocSentinel(?[*:0]u8, len: { | |
| 356 | var len: usize = map.count(); | |
| 255 | 357 | switch (zig_progress_action) { |
| 256 | .add => c += 1, | |
| 257 | .delete => c -= 1, | |
| 358 | .add => len += 1, | |
| 359 | .delete => len -= 1, | |
| 258 | 360 | .nothing, .edit => {}, |
| 259 | 361 | } |
| 260 | break :c c; | |
| 261 | }; | |
| 262 | ||
| 263 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | |
| 264 | var i: usize = 0; | |
| 362 | break :len len; | |
| 363 | }, null); | |
| 364 | var envp_len: usize = 0; | |
| 365 | errdefer { | |
| 366 | envp[envp_len] = null; | |
| 367 | PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa); | |
| 368 | } | |
| 265 | 369 | |
| 266 | 370 | if (zig_progress_action == .add) { |
| 267 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | |
| 268 | i += 1; | |
| 371 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | |
| 372 | envp_len += 1; | |
| 269 | 373 | } |
| 270 | 374 | |
| 271 | { | |
| 272 | var it = map.iterator(); | |
| 273 | while (it.next()) |pair| { | |
| 274 | if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { | |
| 275 | .add => unreachable, | |
| 276 | .delete => continue, | |
| 277 | .edit => { | |
| 278 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{ | |
| 279 | pair.key_ptr.*, options.zig_progress_fd.?, | |
| 280 | }, 0); | |
| 281 | i += 1; | |
| 282 | continue; | |
| 283 | }, | |
| 284 | .nothing => {}, | |
| 285 | }; | |
| 286 | ||
| 287 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); | |
| 288 | i += 1; | |
| 289 | } | |
| 375 | for (map.keys(), map.values()) |key, value| { | |
| 376 | if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) { | |
| 377 | .add => unreachable, | |
| 378 | .delete => continue, | |
| 379 | .edit => { | |
| 380 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{ | |
| 381 | key, options.zig_progress_fd.?, | |
| 382 | }, 0); | |
| 383 | envp_len += 1; | |
| 384 | continue; | |
| 385 | }, | |
| 386 | .nothing => {}, | |
| 387 | }; | |
| 388 | ||
| 389 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0); | |
| 390 | envp_len += 1; | |
| 290 | 391 | } |
| 291 | 392 | |
| 292 | assert(i == envp_count); | |
| 293 | return envp_buf; | |
| 393 | assert(envp_len == envp.len); | |
| 394 | return .{ .slice = envp }; | |
| 294 | 395 | } |
| 295 | 396 | |
| 296 | 397 | /// Caller owns result. |
| 297 | pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 { | |
| 398 | pub fn createWindowsBlock( | |
| 399 | map: *const Map, | |
| 400 | gpa: Allocator, | |
| 401 | options: CreateWindowsBlockOptions, | |
| 402 | ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock { | |
| 298 | 403 | // count bytes needed |
| 299 | const max_chars_needed = x: { | |
| 300 | // Only need 2 trailing NUL code units for an empty environment | |
| 301 | var max_chars_needed: usize = if (map.count() == 0) 2 else 1; | |
| 302 | var it = map.iterator(); | |
| 303 | while (it.next()) |pair| { | |
| 304 | // +1 for '=' | |
| 305 | // +1 for null byte | |
| 306 | max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; | |
| 404 | const max_chars_needed = max_chars_needed: { | |
| 405 | var max_chars_needed: usize = "\x00".len; | |
| 406 | if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { | |
| 407 | max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)}); | |
| 408 | }; | |
| 409 | for (map.keys(), map.values()) |key, value| { | |
| 410 | if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue; | |
| 411 | max_chars_needed += key.len + "=".len + value.len + "\x00".len; | |
| 307 | 412 | } |
| 308 | break :x max_chars_needed; | |
| 413 | break :max_chars_needed @max("\x00\x00".len, max_chars_needed); | |
| 309 | 414 | }; |
| 310 | const result = try gpa.alloc(u16, max_chars_needed); | |
| 311 | errdefer gpa.free(result); | |
| 415 | const block = try gpa.alloc(u16, max_chars_needed); | |
| 416 | errdefer gpa.free(block); | |
| 312 | 417 | |
| 313 | var it = map.iterator(); | |
| 314 | 418 | var i: usize = 0; |
| 315 | while (it.next()) |pair| { | |
| 316 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); | |
| 317 | result[i] = '='; | |
| 419 | if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { | |
| 420 | @memcpy( | |
| 421 | block[i..][0.."ZIG_PROGRESS=".len], | |
| 422 | &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' }, | |
| 423 | ); | |
| 424 | i += "ZIG_PROGRESS=".len; | |
| 425 | var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; | |
| 426 | const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable; | |
| 427 | for (block[i..][0..value.len], value) |*r, v| r.* = v; | |
| 428 | i += value.len; | |
| 429 | block[i] = 0; | |
| 318 | 430 | i += 1; |
| 319 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); | |
| 320 | result[i] = 0; | |
| 431 | }; | |
| 432 | for (map.keys(), map.values()) |key, value| { | |
| 433 | if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue; | |
| 434 | i += try unicode.wtf8ToWtf16Le(block[i..], key); | |
| 435 | block[i] = '='; | |
| 436 | i += 1; | |
| 437 | i += try unicode.wtf8ToWtf16Le(block[i..], value); | |
| 438 | block[i] = 0; | |
| 321 | 439 | i += 1; |
| 322 | 440 | } |
| 323 | result[i] = 0; | |
| 324 | i += 1; | |
| 325 | 441 | // An empty environment is a special case that requires a redundant |
| 326 | 442 | // NUL terminator. CreateProcess will read the second code unit even |
| 327 | 443 | // though theoretically the first should be enough to recognize that the |
| 328 | 444 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) |
| 329 | if (map.count() == 0) { | |
| 330 | result[i] = 0; | |
| 445 | for (0..2) |_| { | |
| 446 | block[i] = 0; | |
| 331 | 447 | i += 1; |
| 332 | } | |
| 333 | const reallocated = try gpa.realloc(result, i); | |
| 334 | return reallocated[0 .. i - 1 :0]; | |
| 448 | if (i >= 2) break; | |
| 449 | } else unreachable; | |
| 450 | const reallocated = try gpa.realloc(block, i); | |
| 451 | return .{ .slice = reallocated[0 .. i - 1 :0] }; | |
| 335 | 452 | } |
| 336 | 453 | }; |
| 337 | 454 | |
| ... | ... | @@ -344,13 +461,18 @@ pub const CreateMapError = error{ |
| 344 | 461 | |
| 345 | 462 | /// Allocates a `Map` and copies environment block into it. |
| 346 | 463 | pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 347 | if (native_os == .windows) | |
| 348 | return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator); | |
| 349 | ||
| 350 | var result = Map.init(allocator); | |
| 351 | errdefer result.deinit(); | |
| 464 | var map = Map.init(allocator); | |
| 465 | errdefer map.deinit(); | |
| 466 | if (native_os == .windows) empty: { | |
| 467 | if (!env.block.use_global) break :empty; | |
| 468 | ||
| 469 | const peb = std.os.windows.peb(); | |
| 470 | assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 471 | defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 472 | try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment }); | |
| 473 | } else if (native_os == .wasi and !builtin.link_libc) empty: { | |
| 474 | if (!env.block.use_global) break :empty; | |
| 352 | 475 | |
| 353 | if (native_os == .wasi and !builtin.link_libc) { | |
| 354 | 476 | var environ_count: usize = undefined; |
| 355 | 477 | var environ_buf_size: usize = undefined; |
| 356 | 478 | |
| ... | ... | @@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 360 | 482 | } |
| 361 | 483 | |
| 362 | 484 | if (environ_count == 0) { |
| 363 | return result; | |
| 485 | return map; | |
| 364 | 486 | } |
| 365 | 487 | |
| 366 | 488 | const environ = try allocator.alloc([*:0]u8, environ_count); |
| ... | ... | @@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 373 | 495 | return posix.unexpectedErrno(environ_get_ret); |
| 374 | 496 | } |
| 375 | 497 | |
| 376 | for (environ) |line| { | |
| 377 | const pair = mem.sliceTo(line, 0); | |
| 378 | var parts = mem.splitScalar(u8, pair, '='); | |
| 379 | const key = parts.first(); | |
| 380 | const value = parts.rest(); | |
| 381 | try result.put(key, value); | |
| 382 | } | |
| 383 | return result; | |
| 384 | } else { | |
| 385 | for (env.block) |opt_line| { | |
| 386 | const line = opt_line.?; | |
| 387 | var line_i: usize = 0; | |
| 388 | while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} | |
| 389 | const key = line[0..line_i]; | |
| 390 | ||
| 391 | var end_i: usize = line_i; | |
| 392 | while (line[end_i] != 0) : (end_i += 1) {} | |
| 393 | const value = line[line_i + 1 .. end_i]; | |
| 394 | ||
| 395 | try result.put(key, value); | |
| 396 | } | |
| 397 | return result; | |
| 398 | } | |
| 399 | } | |
| 400 | ||
| 401 | pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map { | |
| 402 | var result = Map.init(gpa); | |
| 403 | errdefer result.deinit(); | |
| 404 | ||
| 405 | var i: usize = 0; | |
| 406 | while (ptr[i] != 0) { | |
| 407 | const key_start = i; | |
| 408 | ||
| 409 | // There are some special environment variables that start with =, | |
| 410 | // so we need a special case to not treat = as a key/value separator | |
| 411 | // if it's the first character. | |
| 412 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | |
| 413 | if (ptr[key_start] == '=') i += 1; | |
| 414 | ||
| 415 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} | |
| 416 | const key_w = ptr[key_start..i]; | |
| 417 | const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w); | |
| 418 | errdefer gpa.free(key); | |
| 419 | ||
| 420 | if (ptr[i] == '=') i += 1; | |
| 421 | ||
| 422 | const value_start = i; | |
| 423 | while (ptr[i] != 0) : (i += 1) {} | |
| 424 | const value_w = ptr[value_start..i]; | |
| 425 | const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w); | |
| 426 | errdefer gpa.free(value); | |
| 427 | ||
| 428 | i += 1; // skip over null byte | |
| 429 | ||
| 430 | try result.putMove(key, value); | |
| 431 | } | |
| 432 | return result; | |
| 498 | try map.putPosixBlock(.{ .slice = environ }); | |
| 499 | } else try map.putPosixBlock(env.block.view()); | |
| 500 | return map; | |
| 433 | 501 | } |
| 434 | 502 | |
| 435 | 503 | pub const ContainsError = error{ |
| ... | ... | @@ -451,6 +519,7 @@ pub const ContainsError = error{ |
| 451 | 519 | /// * `containsConstant` |
| 452 | 520 | /// * `containsUnempty` |
| 453 | 521 | pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { |
| 522 | if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8; | |
| 454 | 523 | var map = try createMap(environ, gpa); |
| 455 | 524 | defer map.deinit(); |
| 456 | 525 | return map.contains(key); |
| ... | ... | @@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError |
| 464 | 533 | /// * `containsUnemptyConstant` |
| 465 | 534 | /// * `contains` |
| 466 | 535 | pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { |
| 536 | if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8; | |
| 467 | 537 | var map = try createMap(environ, gpa); |
| 468 | 538 | defer map.deinit(); |
| 469 | 539 | const value = map.get(key) orelse return false; |
| ... | ... | @@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8 |
| 516 | 586 | /// * `createMap` |
| 517 | 587 | pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { |
| 518 | 588 | if (mem.findScalar(u8, key, '=') != null) return null; |
| 519 | for (environ.block) |opt_line| { | |
| 520 | const line = opt_line.?; | |
| 521 | var line_i: usize = 0; | |
| 522 | while (line[line_i] != 0) : (line_i += 1) { | |
| 523 | if (line_i == key.len) break; | |
| 524 | if (line[line_i] != key[line_i]) break; | |
| 589 | for (environ.block.view().slice) |entry| { | |
| 590 | var entry_i: usize = 0; | |
| 591 | while (entry[entry_i] != 0) : (entry_i += 1) { | |
| 592 | if (entry_i == key.len) break; | |
| 593 | if (entry[entry_i] != key[entry_i]) break; | |
| 525 | 594 | } |
| 526 | if ((line_i != key.len) or (line[line_i] != '=')) continue; | |
| 595 | if ((entry_i != key.len) or (entry[entry_i] != '=')) continue; | |
| 527 | 596 | |
| 528 | return mem.sliceTo(line + line_i + 1, 0); | |
| 597 | return mem.sliceTo(entry + entry_i + 1, 0); | |
| 529 | 598 | } |
| 530 | 599 | return null; |
| 531 | 600 | } |
| ... | ... | @@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { |
| 541 | 610 | /// * `containsConstant` |
| 542 | 611 | /// * `contains` |
| 543 | 612 | pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { |
| 544 | comptime assert(native_os == .windows); | |
| 545 | comptime assert(@TypeOf(environ.block) == void); | |
| 546 | ||
| 547 | 613 | // '=' anywhere but the start makes this an invalid environment variable name. |
| 548 | 614 | const key_slice = mem.sliceTo(key, 0); |
| 549 | if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null; | |
| 615 | assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null); | |
| 550 | 616 | |
| 551 | const ptr = std.os.windows.peb().ProcessParameters.Environment; | |
| 617 | if (!environ.block.use_global) return null; | |
| 618 | ||
| 619 | const peb = std.os.windows.peb(); | |
| 620 | assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 621 | defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 622 | const ptr = peb.ProcessParameters.Environment; | |
| 552 | 623 | |
| 553 | 624 | var i: usize = 0; |
| 554 | 625 | while (ptr[i] != 0) { |
| ... | ... | @@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { |
| 558 | 629 | // so we need a special case to not treat = as a key/value separator |
| 559 | 630 | // if it's the first character. |
| 560 | 631 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 561 | const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; | |
| 562 | const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { | |
| 632 | const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse { | |
| 563 | 633 | // This is enforced by CreateProcess. |
| 564 | 634 | // If violated, CreateProcess will fail with INVALID_PARAMETER. |
| 565 | 635 | unreachable; // must contain a = |
| ... | ... | @@ -598,13 +668,14 @@ pub const GetAllocError = error{ |
| 598 | 668 | /// See also: |
| 599 | 669 | /// * `createMap` |
| 600 | 670 | pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 { |
| 671 | if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8; | |
| 601 | 672 | var map = createMap(environ, gpa) catch return error.OutOfMemory; |
| 602 | 673 | defer map.deinit(); |
| 603 | 674 | const val = map.get(key) orelse return error.EnvironmentVariableMissing; |
| 604 | 675 | return gpa.dupe(u8, val); |
| 605 | 676 | } |
| 606 | 677 | |
| 607 | pub const CreateBlockPosixOptions = struct { | |
| 678 | pub const CreatePosixBlockOptions = struct { | |
| 608 | 679 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. |
| 609 | 680 | /// If non-null, negative means to remove the environment variable, and >= 0 |
| 610 | 681 | /// means to provide it with the given integer. |
| ... | ... | @@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct { |
| 613 | 684 | |
| 614 | 685 | /// Creates a null-delimited environment variable block in the format expected |
| 615 | 686 | /// by POSIX, from a different one. |
| 616 | pub fn createBlockPosix( | |
| 687 | pub fn createPosixBlock( | |
| 617 | 688 | existing: Environ, |
| 618 | arena: Allocator, | |
| 619 | options: CreateBlockPosixOptions, | |
| 620 | ) Allocator.Error![:null]?[*:0]u8 { | |
| 621 | const contains_zig_progress = for (existing.block) |opt_line| { | |
| 622 | if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; | |
| 689 | gpa: Allocator, | |
| 690 | options: CreatePosixBlockOptions, | |
| 691 | ) Allocator.Error!PosixBlock { | |
| 692 | const contains_zig_progress = for (existing.block.view().slice) |entry| { | |
| 693 | if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true; | |
| 623 | 694 | } else false; |
| 624 | 695 | |
| 625 | 696 | const ZigProgressAction = enum { nothing, edit, delete, add }; |
| 626 | const zig_progress_action: ZigProgressAction = a: { | |
| 627 | const fd = options.zig_progress_fd orelse break :a .nothing; | |
| 697 | const zig_progress_action: ZigProgressAction = action: { | |
| 698 | const fd = options.zig_progress_fd orelse break :action .nothing; | |
| 628 | 699 | if (fd >= 0) { |
| 629 | break :a if (contains_zig_progress) .edit else .add; | |
| 700 | break :action if (contains_zig_progress) .edit else .add; | |
| 630 | 701 | } else { |
| 631 | if (contains_zig_progress) break :a .delete; | |
| 702 | if (contains_zig_progress) break :action .delete; | |
| 632 | 703 | } |
| 633 | break :a .nothing; | |
| 704 | break :action .nothing; | |
| 634 | 705 | }; |
| 635 | 706 | |
| 636 | const envp_count: usize = c: { | |
| 637 | var count: usize = existing.block.len; | |
| 707 | const envp = try gpa.allocSentinel(?[*:0]u8, len: { | |
| 708 | var len: usize = existing.block.slice.len; | |
| 638 | 709 | switch (zig_progress_action) { |
| 639 | .add => count += 1, | |
| 640 | .delete => count -= 1, | |
| 710 | .add => len += 1, | |
| 711 | .delete => len -= 1, | |
| 641 | 712 | .nothing, .edit => {}, |
| 642 | 713 | } |
| 643 | break :c count; | |
| 644 | }; | |
| 645 | ||
| 646 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | |
| 647 | var i: usize = 0; | |
| 648 | var existing_index: usize = 0; | |
| 649 | ||
| 714 | break :len len; | |
| 715 | }, null); | |
| 716 | var envp_len: usize = 0; | |
| 717 | errdefer { | |
| 718 | envp[envp_len] = null; | |
| 719 | PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa); | |
| 720 | } | |
| 650 | 721 | if (zig_progress_action == .add) { |
| 651 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | |
| 652 | i += 1; | |
| 722 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | |
| 723 | envp_len += 1; | |
| 653 | 724 | } |
| 654 | 725 | |
| 655 | while (existing.block[existing_index]) |line| : (existing_index += 1) { | |
| 656 | if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { | |
| 726 | var existing_index: usize = 0; | |
| 727 | while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) { | |
| 728 | if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { | |
| 657 | 729 | .add => unreachable, |
| 658 | 730 | .delete => continue, |
| 659 | 731 | .edit => { |
| 660 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | |
| 661 | i += 1; | |
| 732 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | |
| 733 | envp_len += 1; | |
| 662 | 734 | continue; |
| 663 | 735 | }, |
| 664 | 736 | .nothing => {}, |
| 665 | 737 | }; |
| 666 | envp_buf[i] = try arena.dupeZ(u8, mem.span(line)); | |
| 667 | i += 1; | |
| 738 | envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry)); | |
| 739 | envp_len += 1; | |
| 668 | 740 | } |
| 669 | 741 | |
| 670 | assert(i == envp_count); | |
| 671 | return envp_buf; | |
| 742 | assert(envp_len == envp.len); | |
| 743 | return .{ .slice = envp }; | |
| 672 | 744 | } |
| 673 | 745 | |
| 674 | test "Map.createBlock" { | |
| 675 | const allocator = testing.allocator; | |
| 676 | var envmap = Map.init(allocator); | |
| 746 | pub const CreateWindowsBlockOptions = struct { | |
| 747 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. | |
| 748 | /// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the | |
| 749 | /// environment variable, otherwise provide it with the given handle as an integer. | |
| 750 | zig_progress_handle: ?std.os.windows.HANDLE = null, | |
| 751 | }; | |
| 752 | ||
| 753 | /// Creates a null-delimited environment variable block in the format expected | |
| 754 | /// by POSIX, from a different one. | |
| 755 | pub fn createWindowsBlock( | |
| 756 | existing: Environ, | |
| 757 | gpa: Allocator, | |
| 758 | options: CreateWindowsBlockOptions, | |
| 759 | ) Allocator.Error!WindowsBlock { | |
| 760 | if (!existing.block.use_global) return .{ | |
| 761 | .slice = try gpa.dupeSentinel(u16, WindowsBlock.empty.slice, 0), | |
| 762 | }; | |
| 763 | const peb = std.os.windows.peb(); | |
| 764 | assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 765 | defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); | |
| 766 | const existing_block = peb.ProcessParameters.Environment; | |
| 767 | var ranges: [2]struct { start: usize, end: usize } = undefined; | |
| 768 | var ranges_len: usize = 0; | |
| 769 | ranges[ranges_len].start = 0; | |
| 770 | const zig_progress_key = [_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' }; | |
| 771 | const needed_len = needed_len: { | |
| 772 | var needed_len: usize = "\x00".len; | |
| 773 | if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { | |
| 774 | needed_len += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)}); | |
| 775 | }; | |
| 776 | var i: usize = 0; | |
| 777 | while (existing_block[i] != 0) { | |
| 778 | const start = i; | |
| 779 | const entry = mem.sliceTo(existing_block[start..], 0); | |
| 780 | i += entry.len + "\x00".len; | |
| 781 | if (options.zig_progress_handle != null and entry.len >= zig_progress_key.len and | |
| 782 | std.os.windows.eqlIgnoreCaseWtf16(entry[0..zig_progress_key.len], &zig_progress_key)) | |
| 783 | { | |
| 784 | ranges[ranges_len].end = start; | |
| 785 | ranges_len += 1; | |
| 786 | ranges[ranges_len].start = i; | |
| 787 | } else needed_len += entry.len + "\x00".len; | |
| 788 | } | |
| 789 | ranges[ranges_len].end = i; | |
| 790 | ranges_len += 1; | |
| 791 | break :needed_len @max("\x00\x00".len, needed_len); | |
| 792 | }; | |
| 793 | const block = try gpa.alloc(u16, needed_len); | |
| 794 | errdefer gpa.free(block); | |
| 795 | var i: usize = 0; | |
| 796 | if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { | |
| 797 | @memcpy(block[i..][0..zig_progress_key.len], &zig_progress_key); | |
| 798 | i += zig_progress_key.len; | |
| 799 | var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; | |
| 800 | const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable; | |
| 801 | for (block[i..][0..value.len], value) |*r, v| r.* = v; | |
| 802 | i += value.len; | |
| 803 | block[i] = 0; | |
| 804 | i += 1; | |
| 805 | }; | |
| 806 | for (ranges[0..ranges_len]) |range| { | |
| 807 | const range_len = range.end - range.start; | |
| 808 | @memcpy(block[i..][0..range_len], existing_block[range.start..range.end]); | |
| 809 | i += range_len; | |
| 810 | } | |
| 811 | // An empty environment is a special case that requires a redundant | |
| 812 | // NUL terminator. CreateProcess will read the second code unit even | |
| 813 | // though theoretically the first should be enough to recognize that the | |
| 814 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) | |
| 815 | for (0..2) |_| { | |
| 816 | block[i] = 0; | |
| 817 | i += 1; | |
| 818 | if (i >= 2) break; | |
| 819 | } else unreachable; | |
| 820 | assert(i == block.len); | |
| 821 | return .{ .slice = block[0 .. i - 1 :0] }; | |
| 822 | } | |
| 823 | ||
| 824 | test "Map.createPosixBlock" { | |
| 825 | const gpa = testing.allocator; | |
| 826 | ||
| 827 | var envmap = Map.init(gpa); | |
| 677 | 828 | defer envmap.deinit(); |
| 678 | 829 | |
| 679 | 830 | try envmap.put("HOME", "/home/ifreund"); |
| ... | ... | @@ -682,29 +833,24 @@ test "Map.createBlock" { |
| 682 | 833 | try envmap.put("DEBUGINFOD_URLS", " "); |
| 683 | 834 | try envmap.put("XCURSOR_SIZE", "24"); |
| 684 | 835 | |
| 685 | var arena = std.heap.ArenaAllocator.init(allocator); | |
| 686 | defer arena.deinit(); | |
| 687 | const environ = try envmap.createBlockPosix(arena.allocator(), .{}); | |
| 836 | const block = try envmap.createPosixBlock(gpa, .{}); | |
| 837 | defer block.deinit(gpa); | |
| 688 | 838 | |
| 689 | try testing.expectEqual(@as(usize, 5), environ.len); | |
| 839 | try testing.expectEqual(@as(usize, 5), block.slice.len); | |
| 690 | 840 | |
| 691 | inline for (.{ | |
| 841 | for (&[_][]const u8{ | |
| 692 | 842 | "HOME=/home/ifreund", |
| 693 | 843 | "WAYLAND_DISPLAY=wayland-1", |
| 694 | 844 | "DISPLAY=:1", |
| 695 | 845 | "DEBUGINFOD_URLS= ", |
| 696 | 846 | "XCURSOR_SIZE=24", |
| 697 | }) |target| { | |
| 698 | for (environ) |variable| { | |
| 699 | if (mem.eql(u8, mem.span(variable orelse continue), target)) break; | |
| 700 | } else { | |
| 701 | try testing.expect(false); // Environment variable not found | |
| 702 | } | |
| 703 | } | |
| 847 | }, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?)); | |
| 704 | 848 | } |
| 705 | 849 | |
| 706 | 850 | test Map { |
| 707 | var env = Map.init(testing.allocator); | |
| 851 | const gpa = testing.allocator; | |
| 852 | ||
| 853 | var env: Map = .init(gpa); | |
| 708 | 854 | defer env.deinit(); |
| 709 | 855 | |
| 710 | 856 | try env.put("SOMETHING_NEW", "hello"); |
| ... | ... | @@ -740,6 +886,7 @@ test Map { |
| 740 | 886 | try testing.expect(env.swapRemove("SOMETHING_NEW")); |
| 741 | 887 | try testing.expect(!env.swapRemove("SOMETHING_NEW")); |
| 742 | 888 | try testing.expect(env.get("SOMETHING_NEW") == null); |
| 889 | try testing.expect(!env.contains("SOMETHING_NEW")); | |
| 743 | 890 | |
| 744 | 891 | try testing.expectEqual(@as(Map.Size, 1), env.count()); |
| 745 | 892 | |
| ... | ... | @@ -749,10 +896,10 @@ test Map { |
| 749 | 896 | try testing.expectEqualStrings("something else", env.get("кириллица").?); |
| 750 | 897 | |
| 751 | 898 | // and WTF-8 that's not valid UTF-8 |
| 752 | const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ | |
| 899 | const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(gpa, &[_]u16{ | |
| 753 | 900 | mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate |
| 754 | 901 | }); |
| 755 | defer testing.allocator.free(wtf8_with_surrogate_pair); | |
| 902 | defer gpa.free(wtf8_with_surrogate_pair); | |
| 756 | 903 | |
| 757 | 904 | try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); |
| 758 | 905 | try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); |
| ... | ... | @@ -769,13 +916,9 @@ test "convert from Environ to Map and back again" { |
| 769 | 916 | defer map.deinit(); |
| 770 | 917 | try map.put("FOO", "BAR"); |
| 771 | 918 | try map.put("A", ""); |
| 772 | try map.put("", "B"); | |
| 773 | ||
| 774 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); | |
| 775 | defer arena_allocator.deinit(); | |
| 776 | const arena = arena_allocator.allocator(); | |
| 777 | 919 | |
| 778 | const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) }; | |
| 920 | const environ: Environ = .{ .block = try map.createPosixBlock(gpa, .{}) }; | |
| 921 | defer environ.block.deinit(gpa); | |
| 779 | 922 | |
| 780 | 923 | try testing.expectEqual(true, environ.contains(gpa, "FOO")); |
| 781 | 924 | try testing.expectEqual(false, environ.contains(gpa, "BAR")); |
| ... | ... | @@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" { |
| 783 | 926 | try testing.expectEqual(true, environ.containsConstant("A")); |
| 784 | 927 | try testing.expectEqual(false, environ.containsUnempty(gpa, "A")); |
| 785 | 928 | try testing.expectEqual(false, environ.containsUnemptyConstant("A")); |
| 786 | try testing.expectEqual(true, environ.contains(gpa, "")); | |
| 787 | 929 | try testing.expectEqual(false, environ.contains(gpa, "B")); |
| 788 | 930 | |
| 789 | 931 | try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS")); |
| ... | ... | @@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" { |
| 800 | 942 | try testing.expectEqualDeep(map.values(), map2.values()); |
| 801 | 943 | } |
| 802 | 944 | |
| 803 | test createMapWide { | |
| 804 | if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO | |
| 945 | test "Map.putPosixBlock" { | |
| 946 | const gpa = testing.allocator; | |
| 947 | ||
| 948 | var map: Map = .init(gpa); | |
| 949 | defer map.deinit(); | |
| 950 | ||
| 951 | try map.put("FOO", "BAR"); | |
| 952 | try map.put("A", ""); | |
| 953 | try map.put("ZIG_PROGRESS", "unchanged"); | |
| 954 | ||
| 955 | const block = try map.createPosixBlock(gpa, .{}); | |
| 956 | defer block.deinit(gpa); | |
| 957 | ||
| 958 | var map2: Map = .init(gpa); | |
| 959 | defer map2.deinit(); | |
| 960 | try map2.putPosixBlock(block.view()); | |
| 961 | ||
| 962 | try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "ZIG_PROGRESS" }, map2.keys()); | |
| 963 | try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "unchanged" }, map2.values()); | |
| 964 | } | |
| 965 | ||
| 966 | test "Map.putWindowsBlock" { | |
| 967 | if (native_os != .windows) return; | |
| 805 | 968 | |
| 806 | 969 | const gpa = testing.allocator; |
| 807 | 970 | |
| 808 | 971 | var map: Map = .init(gpa); |
| 809 | 972 | defer map.deinit(); |
| 973 | ||
| 810 | 974 | try map.put("FOO", "BAR"); |
| 811 | 975 | try map.put("A", ""); |
| 812 | try map.put("", "B"); | |
| 976 | try map.put("=B", ""); | |
| 977 | try map.put("ZIG_PROGRESS", "unchanged"); | |
| 813 | 978 | |
| 814 | const environ: [:0]u16 = try map.createBlockWindows(gpa); | |
| 815 | defer gpa.free(environ); | |
| 979 | const block = try map.createWindowsBlock(gpa, .{}); | |
| 980 | defer block.deinit(gpa); | |
| 816 | 981 | |
| 817 | var map2 = try createMapWide(environ, gpa); | |
| 982 | var map2: Map = .init(gpa); | |
| 818 | 983 | defer map2.deinit(); |
| 984 | try map2.putWindowsBlock(block.view()); | |
| 819 | 985 | |
| 820 | try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys()); | |
| 821 | try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values()); | |
| 986 | try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys()); | |
| 987 | try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "", "unchanged" }, map2.values()); | |
| 822 | 988 | } |
lib/std/start.zig+9-8| ... | ... | @@ -90,15 +90,15 @@ fn _DllMainCRTStartup( |
| 90 | 90 | fn wasm_freestanding_start() callconv(.c) void { |
| 91 | 91 | // This is marked inline because for some reason LLVM in |
| 92 | 92 | // release mode fails to inline it, and we want fewer call frames in stack traces. |
| 93 | _ = @call(.always_inline, callMain, .{ {}, {} }); | |
| 93 | _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }); | |
| 94 | 94 | } |
| 95 | 95 | |
| 96 | 96 | fn startWasi() callconv(.c) void { |
| 97 | 97 | // The function call is marked inline because for some reason LLVM in |
| 98 | 98 | // release mode fails to inline it, and we want fewer call frames in stack traces. |
| 99 | 99 | switch (builtin.wasi_exec_model) { |
| 100 | .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }), | |
| 101 | .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })), | |
| 100 | .reactor => _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }), | |
| 101 | .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })), | |
| 102 | 102 | } |
| 103 | 103 | } |
| 104 | 104 | |
| ... | ... | @@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| 476 | 476 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; |
| 477 | 477 | const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; |
| 478 | 478 | |
| 479 | std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {})); | |
| 479 | std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, .global)); | |
| 480 | 480 | } |
| 481 | 481 | |
| 482 | 482 | fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| ... | ... | @@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void { |
| 620 | 620 | } |
| 621 | 621 | |
| 622 | 622 | inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 { |
| 623 | const env_block: std.process.Environ.Block = .{ .slice = envp }; | |
| 623 | 624 | if (std.Options.debug_threaded_io) |t| { |
| 624 | 625 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; |
| 625 | t.environ = .{ .process_environ = .{ .block = envp } }; | |
| 626 | t.environ = .{ .process_environ = .{ .block = env_block } }; | |
| 626 | 627 | } |
| 627 | 628 | std.Thread.maybeAttachSignalStack(); |
| 628 | 629 | std.debug.maybeEnableSegfaultHandler(); |
| 629 | return callMain(argv[0..argc], envp); | |
| 630 | return callMain(argv[0..argc], env_block); | |
| 630 | 631 | } |
| 631 | 632 | |
| 632 | 633 | fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { |
| ... | ... | @@ -648,7 +649,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal |
| 648 | 649 | std.debug.maybeEnableSegfaultHandler(); |
| 649 | 650 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; |
| 650 | 651 | const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; |
| 651 | return callMain(cmd_line_w, {}); | |
| 652 | return callMain(cmd_line_w, .global); | |
| 652 | 653 | }, |
| 653 | 654 | else => {}, |
| 654 | 655 | } |
| ... | ... | @@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { |
| 661 | 662 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) { |
| 662 | 663 | if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0]; |
| 663 | 664 | } |
| 664 | return callMain(argv, &.{}); | |
| 665 | return callMain(argv, .empty); | |
| 665 | 666 | } |
| 666 | 667 | |
| 667 | 668 | /// General error message for a malformed return type |
test/standalone/env_vars/main.zig-23| ... | ... | @@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void { |
| 12 | 12 | // containsUnempty |
| 13 | 13 | { |
| 14 | 14 | try std.testing.expect(try environ.containsUnempty(allocator, "FOO")); |
| 15 | try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO="))); | |
| 16 | try std.testing.expect(!(try environ.containsUnempty(allocator, "FO"))); | |
| 17 | try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO"))); | |
| 18 | 15 | if (builtin.os.tag == .windows) { |
| 19 | 16 | try std.testing.expect(try environ.containsUnempty(allocator, "foo")); |
| 20 | 17 | } |
| 21 | 18 | try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS")); |
| 22 | try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC"))); | |
| 23 | 19 | try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА")); |
| 24 | 20 | if (builtin.os.tag == .windows) { |
| 25 | 21 | try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица")); |
| ... | ... | @@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void { |
| 35 | 31 | // containsUnemptyConstant |
| 36 | 32 | { |
| 37 | 33 | try std.testing.expect(environ.containsUnemptyConstant("FOO")); |
| 38 | try std.testing.expect(!environ.containsUnemptyConstant("FOO=")); | |
| 39 | try std.testing.expect(!environ.containsUnemptyConstant("FO")); | |
| 40 | try std.testing.expect(!environ.containsUnemptyConstant("FOOO")); | |
| 41 | 34 | if (builtin.os.tag == .windows) { |
| 42 | 35 | try std.testing.expect(environ.containsUnemptyConstant("foo")); |
| 43 | 36 | } |
| 44 | 37 | try std.testing.expect(environ.containsUnemptyConstant("EQUALS")); |
| 45 | try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC")); | |
| 46 | 38 | try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА")); |
| 47 | 39 | if (builtin.os.tag == .windows) { |
| 48 | 40 | try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица")); |
| ... | ... | @@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void { |
| 58 | 50 | // contains |
| 59 | 51 | { |
| 60 | 52 | try std.testing.expect(try environ.contains(allocator, "FOO")); |
| 61 | try std.testing.expect(!(try environ.contains(allocator, "FOO="))); | |
| 62 | try std.testing.expect(!(try environ.contains(allocator, "FO"))); | |
| 63 | try std.testing.expect(!(try environ.contains(allocator, "FOOO"))); | |
| 64 | 53 | if (builtin.os.tag == .windows) { |
| 65 | 54 | try std.testing.expect(try environ.contains(allocator, "foo")); |
| 66 | 55 | } |
| 67 | 56 | try std.testing.expect(try environ.contains(allocator, "EQUALS")); |
| 68 | try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC"))); | |
| 69 | 57 | try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА")); |
| 70 | 58 | if (builtin.os.tag == .windows) { |
| 71 | 59 | try std.testing.expect(try environ.contains(allocator, "кирИЛЛица")); |
| ... | ... | @@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void { |
| 81 | 69 | // containsConstant |
| 82 | 70 | { |
| 83 | 71 | try std.testing.expect(environ.containsConstant("FOO")); |
| 84 | try std.testing.expect(!environ.containsConstant("FOO=")); | |
| 85 | try std.testing.expect(!environ.containsConstant("FO")); | |
| 86 | try std.testing.expect(!environ.containsConstant("FOOO")); | |
| 87 | 72 | if (builtin.os.tag == .windows) { |
| 88 | 73 | try std.testing.expect(environ.containsConstant("foo")); |
| 89 | 74 | } |
| 90 | 75 | try std.testing.expect(environ.containsConstant("EQUALS")); |
| 91 | try std.testing.expect(!environ.containsConstant("EQUALS=ABC")); | |
| 92 | 76 | try std.testing.expect(environ.containsConstant("КИРиллИЦА")); |
| 93 | 77 | if (builtin.os.tag == .windows) { |
| 94 | 78 | try std.testing.expect(environ.containsConstant("кирИЛЛица")); |
| ... | ... | @@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void { |
| 104 | 88 | // getAlloc |
| 105 | 89 | { |
| 106 | 90 | try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO")); |
| 107 | try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO=")); | |
| 108 | try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO")); | |
| 109 | try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO")); | |
| 110 | 91 | if (builtin.os.tag == .windows) { |
| 111 | 92 | try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo")); |
| 112 | 93 | } |
| 113 | 94 | try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS")); |
| 114 | try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC")); | |
| 115 | 95 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА")); |
| 116 | 96 | if (builtin.os.tag == .windows) { |
| 117 | 97 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица")); |
| ... | ... | @@ -130,13 +110,10 @@ pub fn main(init: std.process.Init) !void { |
| 130 | 110 | defer environ_map.deinit(); |
| 131 | 111 | |
| 132 | 112 | try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?); |
| 133 | try std.testing.expectEqual(null, environ_map.get("FO")); | |
| 134 | try std.testing.expectEqual(null, environ_map.get("FOOO")); | |
| 135 | 113 | if (builtin.os.tag == .windows) { |
| 136 | 114 | try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?); |
| 137 | 115 | } |
| 138 | 116 | try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?); |
| 139 | try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC")); | |
| 140 | 117 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?); |
| 141 | 118 | if (builtin.os.tag == .windows) { |
| 142 | 119 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?); |
test/standalone/windows_argv/fuzz.zig+7-2| ... | ... | @@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO |
| 125 | 125 | .lpReserved2 = null, |
| 126 | 126 | .hStdInput = null, |
| 127 | 127 | .hStdOutput = null, |
| 128 | .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null, | |
| 128 | .hStdError = windows.peb().ProcessParameters.hStdError, | |
| 129 | 129 | }; |
| 130 | 130 | var proc_info: windows.PROCESS_INFORMATION = undefined; |
| 131 | 131 | |
| ... | ... | @@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO |
| 149 | 149 | break :spawn proc_info.hProcess; |
| 150 | 150 | }; |
| 151 | 151 | defer windows.CloseHandle(child_proc); |
| 152 | try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false); | |
| 152 | const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); | |
| 153 | switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) { | |
| 154 | windows.NTSTATUS.WAIT_0 => {}, | |
| 155 | .TIMEOUT => return error.WaitTimeOut, | |
| 156 | else => |status| return windows.unexpectedStatus(status), | |
| 157 | } | |
| 153 | 158 | |
| 154 | 159 | var exit_code: windows.DWORD = undefined; |
| 155 | 160 | if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) { |
test/standalone/windows_spawn/main.zig+4-3| ... | ... | @@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr |
| 233 | 233 | } |
| 234 | 234 | |
| 235 | 235 | fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void { |
| 236 | var attempt: u5 = 0; | |
| 236 | var attempt: u5 = 10; | |
| 237 | 237 | while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) { |
| 238 | 238 | error.AccessDenied => { |
| 239 | if (attempt == 13) return error.AccessDenied; | |
| 239 | if (attempt == 26) return error.AccessDenied; | |
| 240 | 240 | // give the kernel a chance to finish closing the executable handle |
| 241 | _ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE); | |
| 241 | const interval = @as(std.os.windows.LARGE_INTEGER, -1) << attempt; | |
| 242 | _ = std.os.windows.ntdll.NtDelayExecution(std.os.windows.FALSE, &interval); | |
| 242 | 243 | attempt += 1; |
| 243 | 244 | continue; |
| 244 | 245 | }, |