| 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,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1498 | defer dir.close(io); | 1498 | defer dir.close(io); |
| 1499 | 1499 | ||
| 1500 | var wf = b.addWriteFiles(); | 1500 | var wf = b.addWriteFiles(); |
| 1501 | b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step); | ||
| 1501 | 1502 | ||
| 1502 | var it = dir.iterateAssumeFirstIteration(); | 1503 | var it = dir.iterateAssumeFirstIteration(); |
| 1503 | while (it.next(io) catch @panic("failed to read dir")) |entry| { | 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,10 +386,14 @@ pub const ZigProcess = struct { |
| 386 | child: std.process.Child, | 386 | child: std.process.Child, |
| 387 | multi_reader_buffer: Io.File.MultiReader.Buffer(2), | 387 | multi_reader_buffer: Io.File.MultiReader.Buffer(2), |
| 388 | multi_reader: Io.File.MultiReader, | 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 | pub const StreamEnum = enum { stdout, stderr }; | 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 | pub fn deinit(zp: *ZigProcess, io: Io) void { | 397 | pub fn deinit(zp: *ZigProcess, io: Io) void { |
| 394 | zp.child.kill(io); | 398 | zp.child.kill(io); |
| 395 | zp.multi_reader.deinit(); | 399 | zp.multi_reader.deinit(); |
| ... | @@ -417,7 +421,14 @@ pub fn evalZigProcess( | ... | @@ -417,7 +421,14 @@ pub fn evalZigProcess( |
| 417 | 421 | ||
| 418 | if (s.getZigProcess()) |zp| update: { | 422 | if (s.getZigProcess()) |zp| update: { |
| 419 | assert(watch); | 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 | const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { | 432 | const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { |
| 422 | error.BrokenPipe, error.EndOfStream => |reason| { | 433 | error.BrokenPipe, error.EndOfStream => |reason| { |
| 423 | std.log.info("{s} restart required: {t}", .{ argv[0], reason }); | 434 | std.log.info("{s} restart required: {t}", .{ argv[0], reason }); |
| ... | @@ -426,7 +437,7 @@ pub fn evalZigProcess( | ... | @@ -426,7 +437,7 @@ pub fn evalZigProcess( |
| 426 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); | 437 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); |
| 427 | }; | 438 | }; |
| 428 | _ = term; | 439 | _ = term; |
| 429 | s.clearZigProcess(gpa); | 440 | exited = true; |
| 430 | break :update; | 441 | break :update; |
| 431 | }, | 442 | }, |
| 432 | else => |e| return e, | 443 | else => |e| return e, |
| ... | @@ -442,7 +453,7 @@ pub fn evalZigProcess( | ... | @@ -442,7 +453,7 @@ pub fn evalZigProcess( |
| 442 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); | 453 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); |
| 443 | }; | 454 | }; |
| 444 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; | 455 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; |
| 445 | s.clearZigProcess(gpa); | 456 | exited = true; |
| 446 | try handleChildProcessTerm(s, term); | 457 | try handleChildProcessTerm(s, term); |
| 447 | return error.MakeFailed; | 458 | return error.MakeFailed; |
| 448 | } | 459 | } |
| ... | @@ -467,19 +478,16 @@ pub fn evalZigProcess( | ... | @@ -467,19 +478,16 @@ pub fn evalZigProcess( |
| 467 | .progress_node = prog_node, | 478 | .progress_node = prog_node, |
| 468 | }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); | 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 | zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{ | 481 | zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{ |
| 477 | zp.child.stdout.?, zp.child.stderr.?, | 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 | defer if (!watch) zp.deinit(io); | 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 | if (!watch) { | 492 | if (!watch) { |
| 485 | // Send EOF to stdin. | 493 | // Send EOF to stdin. |
| ... | @@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess { | ... | @@ -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 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | 681 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { |
| 694 | const header: std.zig.Client.Message.Header = .{ | 682 | const header: std.zig.Client.Message.Header = .{ |
| 695 | .tag = tag, | 683 | .tag = tag, |
lib/std/Build/Watch.zig+7-11| ... | @@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) { | ... | @@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) { |
| 366 | .MaximumLength = @intCast(path_len_bytes), | 366 | .MaximumLength = @intCast(path_len_bytes), |
| 367 | .Buffer = @constCast(sub_path_w.span().ptr), | 367 | .Buffer = @constCast(sub_path_w.span().ptr), |
| 368 | }; | 368 | }; |
| 369 | var attr = windows.OBJECT_ATTRIBUTES{ | 369 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 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; | ||
| 378 | 370 | ||
| 379 | switch (windows.ntdll.NtCreateFile( | 371 | switch (windows.ntdll.NtCreateFile( |
| 380 | &dir_handle, | 372 | &dir_handle, |
| ... | @@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) { | ... | @@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) { |
| 385 | .STANDARD = .{ .SYNCHRONIZE = true }, | 377 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 386 | .GENERIC = .{ .READ = true }, | 378 | .GENERIC = .{ .READ = true }, |
| 387 | }, | 379 | }, |
| 388 | &attr, | 380 | &.{ |
| 389 | &io, | 381 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, |
| 382 | .ObjectName = &nt_name, | ||
| 383 | }, | ||
| 384 | &iosb, | ||
| 390 | null, | 385 | null, |
| 391 | .{}, | 386 | .{}, |
| 392 | .VALID_FLAGS, | 387 | .VALID_FLAGS, |
| 393 | .OPEN, | 388 | .OPEN, |
| 394 | .{ | 389 | .{ |
| 395 | .DIRECTORY_FILE = true, | 390 | .DIRECTORY_FILE = true, |
| 391 | .IO = .ASYNCHRONOUS, | ||
| 396 | .OPEN_FOR_BACKUP_INTENT = true, | 392 | .OPEN_FOR_BACKUP_INTENT = true, |
| 397 | }, | 393 | }, |
| 398 | null, | 394 | null, |
lib/std/Io/Threaded.zig+416-367| ... | @@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment; | ... | @@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment; |
| 19 | const assert = std.debug.assert; | 19 | const assert = std.debug.assert; |
| 20 | const posix = std.posix; | 20 | const posix = std.posix; |
| 21 | const windows = std.os.windows; | 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 | /// Thread-safe. | 24 | /// Thread-safe. |
| 25 | /// | 25 | /// |
| ... | @@ -76,6 +76,7 @@ environ: Environ, | ... | @@ -76,6 +76,7 @@ environ: Environ, |
| 76 | 76 | ||
| 77 | null_file: NullFile = .{}, | 77 | null_file: NullFile = .{}, |
| 78 | random_file: RandomFile = .{}, | 78 | random_file: RandomFile = .{}, |
| 79 | pipe_file: PipeFile = .{}, | ||
| 79 | 80 | ||
| 80 | csprng: Csprng = .{}, | 81 | csprng: Csprng = .{}, |
| 81 | 82 | ||
| ... | @@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) { | ... | @@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) { |
| 121 | 122 | ||
| 122 | const Environ = struct { | 123 | const Environ = struct { |
| 123 | /// Unmodified data directly from the OS. | 124 | /// Unmodified data directly from the OS. |
| 124 | process_environ: process.Environ = .empty, | 125 | process_environ: process.Environ, |
| 125 | /// Protected by `mutex`. Determines whether the other fields have been | 126 | /// Protected by `mutex`. Determines whether the other fields have been |
| 126 | /// memoized based on `process_environ`. | 127 | /// memoized based on `process_environ`. |
| 127 | initialized: bool = false, | 128 | initialized: bool = false, |
| ... | @@ -131,13 +132,15 @@ const Environ = struct { | ... | @@ -131,13 +132,15 @@ const Environ = struct { |
| 131 | /// Protected by `mutex`. Memoized based on `process_environ`. | 132 | /// Protected by `mutex`. Memoized based on `process_environ`. |
| 132 | string: String = .{}, | 133 | string: String = .{}, |
| 133 | /// ZIG_PROGRESS | 134 | /// ZIG_PROGRESS |
| 134 | zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing, | 135 | zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing, |
| 135 | /// Protected by `mutex`. Tracks the problem, if any, that occurred when | 136 | /// Protected by `mutex`. Tracks the problem, if any, that occurred when |
| 136 | /// trying to scan environment variables. | 137 | /// trying to scan environment variables. |
| 137 | /// | 138 | /// |
| 138 | /// Errors are only possible on WASI. | 139 | /// Errors are only possible on WASI. |
| 139 | err: ?Error = null, | 140 | err: ?Error = null, |
| 140 | 141 | ||
| 142 | pub const empty: Environ = .{ .process_environ = .empty }; | ||
| 143 | |||
| 141 | pub const Error = Allocator.Error || Io.UnexpectedError; | 144 | pub const Error = Allocator.Error || Io.UnexpectedError; |
| 142 | 145 | ||
| 143 | pub const Exist = struct { | 146 | pub const Exist = struct { |
| ... | @@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) { | ... | @@ -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 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { | 217 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 197 | unknown = 0, | 218 | unknown = 0, |
| 198 | _, | 219 | _, |
| ... | @@ -1498,7 +1519,9 @@ pub const init_single_threaded: Threaded = .{ | ... | @@ -1498,7 +1519,9 @@ pub const init_single_threaded: Threaded = .{ |
| 1498 | .old_sig_pipe = undefined, | 1519 | .old_sig_pipe = undefined, |
| 1499 | .have_signal_handler = false, | 1520 | .have_signal_handler = false, |
| 1500 | .argv0 = .empty, | 1521 | .argv0 = .empty, |
| 1501 | .environ = .{}, | 1522 | .environ = .{ .process_environ = .{ |
| 1523 | .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty, | ||
| 1524 | } }, | ||
| 1502 | .worker_threads = .init(null), | 1525 | .worker_threads = .init(null), |
| 1503 | .disable_memory_mapping = false, | 1526 | .disable_memory_mapping = false, |
| 1504 | }; | 1527 | }; |
| ... | @@ -1533,6 +1556,7 @@ pub fn deinit(t: *Threaded) void { | ... | @@ -1533,6 +1556,7 @@ pub fn deinit(t: *Threaded) void { |
| 1533 | } | 1556 | } |
| 1534 | t.null_file.deinit(); | 1557 | t.null_file.deinit(); |
| 1535 | t.random_file.deinit(); | 1558 | t.random_file.deinit(); |
| 1559 | t.pipe_file.deinit(); | ||
| 1536 | t.* = undefined; | 1560 | t.* = undefined; |
| 1537 | } | 1561 | } |
| 1538 | 1562 | ||
| ... | @@ -1576,14 +1600,7 @@ fn worker(t: *Threaded) void { | ... | @@ -1576,14 +1600,7 @@ fn worker(t: *Threaded) void { |
| 1576 | }, | 1600 | }, |
| 1577 | }, | 1601 | }, |
| 1578 | }, | 1602 | }, |
| 1579 | &.{ | 1603 | &.{ .ObjectName = null }, |
| 1580 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | ||
| 1581 | .RootDirectory = null, | ||
| 1582 | .ObjectName = null, | ||
| 1583 | .Attributes = .{}, | ||
| 1584 | .SecurityDescriptor = null, | ||
| 1585 | .SecurityQualityOfService = null, | ||
| 1586 | }, | ||
| 1587 | &windows.teb().ClientId, | 1604 | &windows.teb().ClientId, |
| 1588 | ) == .SUCCESS); | 1605 | ) == .SUCCESS); |
| 1589 | } | 1606 | } |
| ... | @@ -2595,8 +2612,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { | ... | @@ -2595,8 +2612,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { |
| 2595 | // opportunity to find additional ready operations. | 2612 | // opportunity to find additional ready operations. |
| 2596 | break :t 0; | 2613 | break :t 0; |
| 2597 | } | 2614 | } |
| 2598 | const max_poll_ms = std.math.maxInt(i32); | 2615 | break :t std.math.maxInt(i32); |
| 2599 | break :t max_poll_ms; | ||
| 2600 | }; | 2616 | }; |
| 2601 | const syscall = try Syscall.start(); | 2617 | const syscall = try Syscall.start(); |
| 2602 | const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); | 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,6 +2732,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout |
| 2716 | break :allocation allocation; | 2732 | break :allocation allocation; |
| 2717 | }; | 2733 | }; |
| 2718 | @memcpy(slice[0..poll_buffer_len], storage.slice); | 2734 | @memcpy(slice[0..poll_buffer_len], storage.slice); |
| 2735 | storage.slice = slice; | ||
| 2719 | } | 2736 | } |
| 2720 | storage.slice[len] = .{ | 2737 | storage.slice[len] = .{ |
| 2721 | .fd = file.handle, | 2738 | .fd = file.handle, |
| ... | @@ -2769,9 +2786,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout | ... | @@ -2769,9 +2786,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout |
| 2769 | } | 2786 | } |
| 2770 | const d = deadline orelse break :t -1; | 2787 | const d = deadline orelse break :t -1; |
| 2771 | const duration = d.durationFromNow(t_io); | 2788 | const duration = d.durationFromNow(t_io); |
| 2772 | if (duration.raw.nanoseconds <= 0) return error.Timeout; | 2789 | break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32)); |
| 2773 | const max_poll_ms = std.math.maxInt(i32); | ||
| 2774 | break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); | ||
| 2775 | }; | 2790 | }; |
| 2776 | const syscall = try Syscall.start(); | 2791 | const syscall = try Syscall.start(); |
| 2777 | const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms); | 2792 | const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms); |
| ... | @@ -3379,12 +3394,8 @@ fn dirCreateDirPathOpenWindows( | ... | @@ -3379,12 +3394,8 @@ fn dirCreateDirPathOpenWindows( |
| 3379 | }, | 3394 | }, |
| 3380 | }, | 3395 | }, |
| 3381 | &.{ | 3396 | &.{ |
| 3382 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | ||
| 3383 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, | 3397 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 3384 | .Attributes = .{}, | ||
| 3385 | .ObjectName = &nt_name, | 3398 | .ObjectName = &nt_name, |
| 3386 | .SecurityDescriptor = null, | ||
| 3387 | .SecurityQualityOfService = null, | ||
| 3388 | }, | 3399 | }, |
| 3389 | &io_status_block, | 3400 | &io_status_block, |
| 3390 | null, | 3401 | null, |
| ... | @@ -4066,13 +4077,9 @@ fn dirAccessWindows( | ... | @@ -4066,13 +4077,9 @@ fn dirAccessWindows( |
| 4066 | .MaximumLength = path_len_bytes, | 4077 | .MaximumLength = path_len_bytes, |
| 4067 | .Buffer = @constCast(sub_path_w.ptr), | 4078 | .Buffer = @constCast(sub_path_w.ptr), |
| 4068 | }; | 4079 | }; |
| 4069 | var attr: windows.OBJECT_ATTRIBUTES = .{ | 4080 | const attr: windows.OBJECT_ATTRIBUTES = .{ |
| 4070 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | ||
| 4071 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, | 4081 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 4072 | .Attributes = .{}, | ||
| 4073 | .ObjectName = &nt_name, | 4082 | .ObjectName = &nt_name, |
| 4074 | .SecurityDescriptor = null, | ||
| 4075 | .SecurityQualityOfService = null, | ||
| 4076 | }; | 4083 | }; |
| 4077 | var basic_info: windows.FILE.BASIC_INFORMATION = undefined; | 4084 | var basic_info: windows.FILE.BASIC_INFORMATION = undefined; |
| 4078 | const syscall: Syscall = try .start(); | 4085 | const syscall: Syscall = try .start(); |
| ... | @@ -4288,14 +4295,8 @@ fn dirCreateFileWindows( | ... | @@ -4288,14 +4295,8 @@ fn dirCreateFileWindows( |
| 4288 | .Buffer = @constCast(sub_path_w.ptr), | 4295 | .Buffer = @constCast(sub_path_w.ptr), |
| 4289 | }; | 4296 | }; |
| 4290 | const attr: windows.OBJECT_ATTRIBUTES = .{ | 4297 | const attr: windows.OBJECT_ATTRIBUTES = .{ |
| 4291 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | ||
| 4292 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, | 4298 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 4293 | .Attributes = .{ | ||
| 4294 | .INHERIT = false, | ||
| 4295 | }, | ||
| 4296 | .ObjectName = &nt_name, | 4299 | .ObjectName = &nt_name, |
| 4297 | .SecurityDescriptor = null, | ||
| 4298 | .SecurityQualityOfService = null, | ||
| 4299 | }; | 4300 | }; |
| 4300 | const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) | 4301 | const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) |
| 4301 | .CREATE | 4302 | .CREATE |
| ... | @@ -4908,17 +4909,6 @@ pub fn dirOpenFileWtf16( | ... | @@ -4908,17 +4909,6 @@ pub fn dirOpenFileWtf16( |
| 4908 | .MaximumLength = path_len_bytes, | 4909 | .MaximumLength = path_len_bytes, |
| 4909 | .Buffer = @constCast(sub_path_w.ptr), | 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 | var io_status_block: w.IO_STATUS_BLOCK = undefined; | 4912 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 4923 | 4913 | ||
| 4924 | // There are multiple kernel bugs being worked around with retries. | 4914 | // There are multiple kernel bugs being worked around with retries. |
| ... | @@ -4937,7 +4927,10 @@ pub fn dirOpenFileWtf16( | ... | @@ -4937,7 +4927,10 @@ pub fn dirOpenFileWtf16( |
| 4937 | .WRITE = flags.isWrite(), | 4927 | .WRITE = flags.isWrite(), |
| 4938 | }, | 4928 | }, |
| 4939 | }, | 4929 | }, |
| 4940 | &attr, | 4930 | &.{ |
| 4931 | .RootDirectory = dir_handle, | ||
| 4932 | .ObjectName = &nt_name, | ||
| 4933 | }, | ||
| 4941 | &io_status_block, | 4934 | &io_status_block, |
| 4942 | null, | 4935 | null, |
| 4943 | .{ .NORMAL = true }, | 4936 | .{ .NORMAL = true }, |
| ... | @@ -5305,12 +5298,8 @@ pub fn dirOpenDirWindows( | ... | @@ -5305,12 +5298,8 @@ pub fn dirOpenDirWindows( |
| 5305 | }, | 5298 | }, |
| 5306 | }, | 5299 | }, |
| 5307 | &.{ | 5300 | &.{ |
| 5308 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | ||
| 5309 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, | 5301 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 5310 | .Attributes = .{}, | ||
| 5311 | .ObjectName = &nt_name, | 5302 | .ObjectName = &nt_name, |
| 5312 | .SecurityDescriptor = null, | ||
| 5313 | .SecurityQualityOfService = null, | ||
| 5314 | }, | 5303 | }, |
| 5315 | &io_status_block, | 5304 | &io_status_block, |
| 5316 | null, | 5305 | null, |
| ... | @@ -6520,12 +6509,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov | ... | @@ -6520,12 +6509,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 6520 | .SYNCHRONIZE = true, | 6509 | .SYNCHRONIZE = true, |
| 6521 | } }, | 6510 | } }, |
| 6522 | &.{ | 6511 | &.{ |
| 6523 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | ||
| 6524 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, | 6512 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 6525 | .Attributes = .{}, | ||
| 6526 | .ObjectName = &nt_name, | 6513 | .ObjectName = &nt_name, |
| 6527 | .SecurityDescriptor = null, | ||
| 6528 | .SecurityQualityOfService = null, | ||
| 6529 | }, | 6514 | }, |
| 6530 | &io_status_block, | 6515 | &io_status_block, |
| 6531 | null, | 6516 | null, |
| ... | @@ -6534,6 +6519,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov | ... | @@ -6534,6 +6519,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov |
| 6534 | .OPEN, | 6519 | .OPEN, |
| 6535 | .{ | 6520 | .{ |
| 6536 | .DIRECTORY_FILE = remove_dir, | 6521 | .DIRECTORY_FILE = remove_dir, |
| 6522 | .IO = .SYNCHRONOUS_NONALERT, | ||
| 6537 | .NON_DIRECTORY_FILE = !remove_dir, | 6523 | .NON_DIRECTORY_FILE = !remove_dir, |
| 6538 | .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? | 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,14 +7331,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink |
| 7345 | .Buffer = @constCast(sub_path_w.ptr), | 7331 | .Buffer = @constCast(sub_path_w.ptr), |
| 7346 | }; | 7332 | }; |
| 7347 | const attr: windows.OBJECT_ATTRIBUTES = .{ | 7333 | const attr: windows.OBJECT_ATTRIBUTES = .{ |
| 7348 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | ||
| 7349 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, | 7334 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 7350 | .Attributes = .{ | ||
| 7351 | .INHERIT = false, | ||
| 7352 | }, | ||
| 7353 | .ObjectName = &nt_name, | 7335 | .ObjectName = &nt_name, |
| 7354 | .SecurityDescriptor = null, | ||
| 7355 | .SecurityQualityOfService = null, | ||
| 7356 | }; | 7336 | }; |
| 7357 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 7337 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 7358 | var result_handle: windows.HANDLE = undefined; | 7338 | var result_handle: windows.HANDLE = undefined; |
| ... | @@ -7909,24 +7889,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { | ... | @@ -7909,24 +7889,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 7909 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 7889 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7910 | _ = t; | 7890 | _ = t; |
| 7911 | 7891 | ||
| 7892 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | ||
| 7912 | const syscall: Syscall = try .start(); | 7893 | const syscall: Syscall = try .start(); |
| 7913 | while (true) { | 7894 | while (true) { |
| 7914 | if (windows.kernel32.FlushFileBuffers(file.handle) != 0) { | 7895 | switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) { |
| 7915 | return syscall.finish(); | 7896 | .SUCCESS => break syscall.finish(), |
| 7916 | } | 7897 | .CANCELLED => { |
| 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 => { | ||
| 7923 | try syscall.checkCancel(); | 7898 | try syscall.checkCancel(); |
| 7924 | continue; | 7899 | continue; |
| 7925 | }, | 7900 | }, |
| 7926 | else => |err| { | 7901 | .INVALID_HANDLE => unreachable, |
| 7927 | syscall.finish(); | 7902 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time |
| 7928 | return windows.unexpectedError(err); | 7903 | .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput), |
| 7929 | }, | 7904 | else => |status| return syscall.unexpectedNtstatus(status), |
| 7930 | } | 7905 | } |
| 7931 | } | 7906 | } |
| 7932 | } | 7907 | } |
| ... | @@ -14446,7 +14421,10 @@ const WindowsEnvironStrings = struct { | ... | @@ -14446,7 +14421,10 @@ const WindowsEnvironStrings = struct { |
| 14446 | PATHEXT: ?[:0]const u16 = null, | 14421 | PATHEXT: ?[:0]const u16 = null, |
| 14447 | 14422 | ||
| 14448 | fn scan() WindowsEnvironStrings { | 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 | var result: WindowsEnvironStrings = .{}; | 14429 | var result: WindowsEnvironStrings = .{}; |
| 14452 | var i: usize = 0; | 14430 | var i: usize = 0; |
| ... | @@ -14472,7 +14450,7 @@ const WindowsEnvironStrings = struct { | ... | @@ -14472,7 +14450,7 @@ const WindowsEnvironStrings = struct { |
| 14472 | 14450 | ||
| 14473 | inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| { | 14451 | inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| { |
| 14474 | const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name); | 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,29 +14469,46 @@ fn scanEnviron(t: *Threaded) void { |
| 14491 | // This value expires with any call that modifies the environment, | 14469 | // This value expires with any call that modifies the environment, |
| 14492 | // which is outside of this Io implementation's control, so references | 14470 | // which is outside of this Io implementation's control, so references |
| 14493 | // must be short-lived. | 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 | var i: usize = 0; | 14477 | var i: usize = 0; |
| 14497 | while (ptr[i] != 0) { | 14478 | while (ptr[i] != 0) { |
| 14498 | const key_start = i; | ||
| 14499 | 14479 | ||
| 14500 | // There are some special environment variables that start with =, | 14480 | // There are some special environment variables that start with =, |
| 14501 | // so we need a special case to not treat = as a key/value separator | 14481 | // so we need a special case to not treat = as a key/value separator |
| 14502 | // if it's the first character. | 14482 | // if it's the first character. |
| 14503 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | 14483 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 14504 | if (ptr[key_start] == '=') i += 1; | 14484 | const key_start = i; |
| 14505 | 14485 | if (ptr[i] == '=') i += 1; | |
| 14506 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} | 14486 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} |
| 14507 | const key_w = ptr[key_start..i]; | 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 | t.environ.exist.NO_COLOR = true; | 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 | t.environ.exist.CLICOLOR_FORCE = true; | 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 | comptime assert(@sizeOf(Environ.String) == 0); | 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 | } else if (native_os == .wasi and !builtin.link_libc) { | 14513 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 14519 | var environ_count: usize = undefined; | 14514 | var environ_count: usize = undefined; |
| ... | @@ -14559,22 +14554,28 @@ fn scanEnviron(t: *Threaded) void { | ... | @@ -14559,22 +14554,28 @@ fn scanEnviron(t: *Threaded) void { |
| 14559 | comptime assert(@sizeOf(Environ.String) == 0); | 14554 | comptime assert(@sizeOf(Environ.String) == 0); |
| 14560 | } | 14555 | } |
| 14561 | } else { | 14556 | } else { |
| 14562 | for (t.environ.process_environ.block) |opt_line| { | 14557 | for (t.environ.process_environ.block.slice) |opt_entry| { |
| 14563 | const line = opt_line.?; | 14558 | const entry = opt_entry.?; |
| 14564 | var line_i: usize = 0; | 14559 | var entry_i: usize = 0; |
| 14565 | while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} | 14560 | while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {} |
| 14566 | const key = line[0..line_i]; | 14561 | const key = entry[0..entry_i]; |
| 14567 | 14562 | ||
| 14568 | var end_i: usize = line_i; | 14563 | var end_i: usize = entry_i; |
| 14569 | while (line[end_i] != 0) : (end_i += 1) {} | 14564 | while (entry[end_i] != 0) : (end_i += 1) {} |
| 14570 | const value = line[line_i + 1 .. end_i :0]; | 14565 | const value = entry[entry_i + 1 .. end_i :0]; |
| 14571 | 14566 | ||
| 14572 | if (std.mem.eql(u8, key, "NO_COLOR")) { | 14567 | if (std.mem.eql(u8, key, "NO_COLOR")) { |
| 14573 | t.environ.exist.NO_COLOR = true; | 14568 | t.environ.exist.NO_COLOR = true; |
| 14574 | } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { | 14569 | } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { |
| 14575 | t.environ.exist.CLICOLOR_FORCE = true; | 14570 | t.environ.exist.CLICOLOR_FORCE = true; |
| 14576 | } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { | 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 | } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| { | 14579 | } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| { |
| 14579 | if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value; | 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,19 +14598,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces |
| 14597 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); | 14598 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); |
| 14598 | for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; | 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 | const prog_fd: i32 = -1; | 14602 | const prog_fd: i32 = -1; |
| 14602 | if (options.environ_map) |environ_map| { | 14603 | if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ |
| 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, .{ | ||
| 14608 | .zig_progress_fd = prog_fd, | 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 | fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { | 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,16 +14678,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14679 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); | 14678 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); |
| 14680 | const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; | 14679 | const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; |
| 14681 | 14680 | ||
| 14682 | const prog_pipe: [2]posix.fd_t = p: { | 14681 | const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none) |
| 14683 | if (options.progress_node.index == .none) { | 14682 | // We use CLOEXEC for the same reason as in `pipe_flags`. |
| 14684 | break :p .{ -1, -1 }; | 14683 | try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }) |
| 14685 | } else { | 14684 | else |
| 14686 | // We use CLOEXEC for the same reason as in `pipe_flags`. | 14685 | .{ -1, -1 }; |
| 14687 | break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); | ||
| 14688 | } | ||
| 14689 | }; | ||
| 14690 | errdefer destroyPipe(prog_pipe); | 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 | var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); | 14692 | var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); |
| 14693 | defer arena_allocator.deinit(); | 14693 | defer arena_allocator.deinit(); |
| 14694 | const arena = arena_allocator.allocator(); | 14694 | const arena = arena_allocator.allocator(); |
| ... | @@ -14708,16 +14708,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp | ... | @@ -14708,16 +14708,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14708 | const prog_fileno = 3; | 14708 | const prog_fileno = 3; |
| 14709 | comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); | 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 | const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; | 14712 | const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; |
| 14713 | if (options.environ_map) |environ_map| { | 14713 | if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ |
| 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, .{ | ||
| 14719 | .zig_progress_fd = prog_fd, | 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 | // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. | 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,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 | forkBail(ep1, err); | 14802 | forkBail(ep1, err); |
| 14805 | } | 14803 | } |
| 14806 | 14804 | ||
| ... | @@ -14814,8 +14812,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp | ... | @@ -14814,8 +14812,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 14814 | if (options.stderr == .pipe) posix.close(stderr_pipe[1]); | 14812 | if (options.stderr == .pipe) posix.close(stderr_pipe[1]); |
| 14815 | 14813 | ||
| 14816 | if (prog_pipe[1] != -1) posix.close(prog_pipe[1]); | 14814 | if (prog_pipe[1] != -1) posix.close(prog_pipe[1]); |
| 14817 | 14815 | options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); | |
| 14818 | options.progress_node.setIpcFd(prog_pipe[0]); | ||
| 14819 | 14816 | ||
| 14820 | return .{ | 14817 | return .{ |
| 14821 | .pid = pid, | 14818 | .pid = pid, |
| ... | @@ -14938,42 +14935,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT | ... | @@ -14938,42 +14935,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT |
| 14938 | // some rare edge cases where our process handle no longer has the | 14935 | // some rare edge cases where our process handle no longer has the |
| 14939 | // PROCESS_TERMINATE access right, so let's do another check to make | 14936 | // PROCESS_TERMINATE access right, so let's do another check to make |
| 14940 | // sure the process is really no longer running: | 14937 | // sure the process is really no longer running: |
| 14941 | windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied; | 14938 | const minimal_timeout: windows.LARGE_INTEGER = -1; |
| 14942 | return error.AlreadyTerminated; | 14939 | switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) { |
| 14940 | .SUCCESS => return error.AlreadyTerminated, | ||
| 14941 | else => return error.AccessDenied, | ||
| 14942 | } | ||
| 14943 | }, | 14943 | }, |
| 14944 | else => |err| return windows.unexpectedError(err), | 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 | childCleanupWindows(child); | 14949 | childCleanupWindows(child); |
| 14949 | } | 14950 | } |
| 14950 | 14951 | ||
| 14951 | fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { | 14952 | fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 14952 | const handle = child.id.?; | 14953 | const handle = child.id.?; |
| 14953 | 14954 | ||
| 14954 | const syscall: Syscall = try .start(); | 14955 | const alertable_syscall: AlertableSyscall = try .start(); |
| 14955 | while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) { | 14956 | const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); |
| 14956 | windows.WAIT_OBJECT_0 => break syscall.finish(), | 14957 | while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) { |
| 14957 | windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => { | 14958 | windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(), |
| 14958 | try syscall.checkCancel(); | 14959 | .USER_APC, .ALERTED, .TIMEOUT => { |
| 14960 | try alertable_syscall.checkCancel(); | ||
| 14959 | continue; | 14961 | continue; |
| 14960 | }, | 14962 | }, |
| 14961 | windows.WAIT_FAILED => { | 14963 | else => |status| return alertable_syscall.unexpectedNtstatus(status), |
| 14962 | syscall.finish(); | ||
| 14963 | switch (windows.GetLastError()) { | ||
| 14964 | else => |err| return windows.unexpectedError(err), | ||
| 14965 | } | ||
| 14966 | }, | ||
| 14967 | else => return syscall.fail(error.Unexpected), | ||
| 14968 | }; | 14964 | }; |
| 14969 | 14965 | ||
| 14970 | const term: process.Child.Term = x: { | 14966 | var info: windows.PROCESS_BASIC_INFORMATION = undefined; |
| 14971 | var exit_code: windows.DWORD = undefined; | 14967 | const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess( |
| 14972 | if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) { | 14968 | handle, |
| 14973 | break :x .{ .unknown = 0 }; | 14969 | .BasicInformation, |
| 14974 | } else { | 14970 | &info, |
| 14975 | break :x .{ .exited = @as(u8, @truncate(exit_code)) }; | 14971 | @sizeOf(windows.PROCESS_BASIC_INFORMATION), |
| 14976 | } | 14972 | null, |
| 14973 | )) { | ||
| 14974 | .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) }, | ||
| 14975 | else => .{ .unknown = 0 }, | ||
| 14977 | }; | 14976 | }; |
| 14978 | 14977 | ||
| 14979 | childCleanupWindows(child); | 14978 | childCleanupWindows(child); |
| ... | @@ -15236,88 +15235,71 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32 | ... | @@ -15236,88 +15235,71 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32 |
| 15236 | fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { | 15235 | fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 15237 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 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 | const any_ignore = | 15238 | const any_ignore = |
| 15246 | options.stdin == .ignore or | 15239 | options.stdin == .ignore or |
| 15247 | options.stdout == .ignore or | 15240 | options.stdout == .ignore or |
| 15248 | options.stderr == .ignore; | 15241 | options.stderr == .ignore; |
| 15249 | 15242 | const nul_handle = if (any_ignore) try getNulDevice(t) else undefined; | |
| 15250 | const nul_handle = if (any_ignore) try getNulHandle(t) else undefined; | 15243 | |
| 15251 | 15244 | const any_inherit = | |
| 15252 | var g_hChildStd_IN_Rd: ?windows.HANDLE = null; | 15245 | options.stdin == .inherit or |
| 15253 | var g_hChildStd_IN_Wr: ?windows.HANDLE = null; | 15246 | options.stdout == .inherit or |
| 15254 | switch (options.stdin) { | 15247 | options.stderr == .inherit; |
| 15255 | .pipe => { | 15248 | const peb = if (any_inherit) windows.peb() else undefined; |
| 15256 | try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr); | 15249 | |
| 15257 | }, | 15250 | const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{ |
| 15258 | .ignore => { | 15251 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15259 | g_hChildStd_IN_Rd = nul_handle; | 15252 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15260 | }, | 15253 | .outbound = true, |
| 15261 | .inherit => { | 15254 | }) else undefined; |
| 15262 | g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null; | 15255 | errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle); |
| 15263 | }, | 15256 | |
| 15264 | .close => { | 15257 | const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{ |
| 15265 | g_hChildStd_IN_Rd = null; | 15258 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, |
| 15266 | }, | 15259 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15267 | .file => @panic("TODO implement passing file stdio in processSpawnWindows"), | 15260 | .inbound = true, |
| 15268 | } | 15261 | }) else undefined; |
| 15269 | errdefer if (options.stdin == .pipe) { | 15262 | errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle); |
| 15270 | windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); | 15263 | |
| 15271 | }; | 15264 | const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{ |
| 15272 | 15265 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, | |
| 15273 | var g_hChildStd_OUT_Rd: ?windows.HANDLE = null; | 15266 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15274 | var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; | 15267 | .inbound = true, |
| 15275 | switch (options.stdout) { | 15268 | }) else undefined; |
| 15276 | .pipe => { | 15269 | errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle); |
| 15277 | try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); | 15270 | |
| 15278 | }, | 15271 | const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{ |
| 15279 | .ignore => { | 15272 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, |
| 15280 | g_hChildStd_OUT_Wr = nul_handle; | 15273 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } }, |
| 15281 | }, | 15274 | .inbound = true, |
| 15282 | .inherit => { | 15275 | .quota = std.Progress.max_packet_len * 2, |
| 15283 | g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null; | 15276 | }) else undefined; |
| 15284 | }, | 15277 | errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle); |
| 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 | }; | ||
| 15314 | 15278 | ||
| 15315 | var siStartInfo: windows.STARTUPINFOW = .{ | 15279 | var siStartInfo: windows.STARTUPINFOW = .{ |
| 15316 | .cb = @sizeOf(windows.STARTUPINFOW), | 15280 | .cb = @sizeOf(windows.STARTUPINFOW), |
| 15317 | .hStdError = g_hChildStd_ERR_Wr, | ||
| 15318 | .hStdOutput = g_hChildStd_OUT_Wr, | ||
| 15319 | .hStdInput = g_hChildStd_IN_Rd, | ||
| 15320 | .dwFlags = windows.STARTF_USESTDHANDLES, | 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 | .lpReserved = null, | 15304 | .lpReserved = null, |
| 15323 | .lpDesktop = null, | 15305 | .lpDesktop = null, |
| ... | @@ -15363,8 +15345,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro | ... | @@ -15363,8 +15345,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15363 | }; | 15345 | }; |
| 15364 | const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; | 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; | 15348 | const env_block = env_block: { |
| 15367 | const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; | 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 | const app_name_wtf8 = options.argv[0]; | 15361 | const app_name_wtf8 = options.argv[0]; |
| 15370 | const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); | 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,7 +15431,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15439 | &app_buf, | 15431 | &app_buf, |
| 15440 | PATHEXT, | 15432 | PATHEXT, |
| 15441 | &cmd_line_cache, | 15433 | &cmd_line_cache, |
| 15442 | envp_ptr, | 15434 | env_block, |
| 15443 | cwd_w_ptr, | 15435 | cwd_w_ptr, |
| 15444 | flags, | 15436 | flags, |
| 15445 | &siStartInfo, | 15437 | &siStartInfo, |
| ... | @@ -15474,7 +15466,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro | ... | @@ -15474,7 +15466,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 15474 | &app_buf, | 15466 | &app_buf, |
| 15475 | PATHEXT, | 15467 | PATHEXT, |
| 15476 | &cmd_line_cache, | 15468 | &cmd_line_cache, |
| 15477 | envp_ptr, | 15469 | env_block, |
| 15478 | cwd_w_ptr, | 15470 | cwd_w_ptr, |
| 15479 | flags, | 15471 | flags, |
| 15480 | &siStartInfo, | 15472 | &siStartInfo, |
| ... | @@ -15494,21 +15486,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro | ... | @@ -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.?); | 15489 | if (options.progress_node.index != .none) { |
| 15498 | if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?); | 15490 | windows.CloseHandle(prog_pipe[1]); |
| 15499 | if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?); | 15491 | options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); |
| 15492 | } | ||
| 15500 | 15493 | ||
| 15501 | return .{ | 15494 | return .{ |
| 15502 | .id = piProcInfo.hProcess, | 15495 | .id = piProcInfo.hProcess, |
| 15503 | .thread_handle = piProcInfo.hThread, | 15496 | .thread_handle = piProcInfo.hThread, |
| 15504 | .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null, | 15497 | .stdin = stdin: switch (options.stdin) { |
| 15505 | .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, | 15498 | .pipe => { |
| 15506 | .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, | 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 | .request_resource_usage_statistics = options.request_resource_usage_statistics, | 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 | mutexLock(&t.mutex); | 15524 | mutexLock(&t.mutex); |
| 15514 | defer mutexUnlock(&t.mutex); | 15525 | defer mutexUnlock(&t.mutex); |
| ... | @@ -15516,12 +15527,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { | ... | @@ -15516,12 +15527,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15516 | } | 15527 | } |
| 15517 | 15528 | ||
| 15518 | const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }; | 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 | var fresh_handle: windows.HANDLE = undefined; | 15530 | var fresh_handle: windows.HANDLE = undefined; |
| 15526 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 15531 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 15527 | var syscall: Syscall = try .start(); | 15532 | var syscall: Syscall = try .start(); |
| ... | @@ -15532,12 +15537,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { | ... | @@ -15532,12 +15537,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 15532 | .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, | 15537 | .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, |
| 15533 | }, | 15538 | }, |
| 15534 | &.{ | 15539 | &.{ |
| 15535 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | 15540 | .ObjectName = @constCast(&windows.UNICODE_STRING{ |
| 15536 | .RootDirectory = null, | 15541 | .Length = @sizeOf(@TypeOf(device_path)), |
| 15537 | .ObjectName = &nt_name, | 15542 | .MaximumLength = 0, |
| 15538 | .Attributes = .{}, | 15543 | .Buffer = @constCast(&device_path), |
| 15539 | .SecurityDescriptor = null, | 15544 | }), |
| 15540 | .SecurityQualityOfService = null, | ||
| 15541 | }, | 15545 | }, |
| 15542 | &io_status_block, | 15546 | &io_status_block, |
| 15543 | .VALID_FLAGS, | 15547 | .VALID_FLAGS, |
| ... | @@ -15564,7 +15568,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { | ... | @@ -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 | mutexLock(&t.mutex); | 15573 | mutexLock(&t.mutex); |
| 15570 | defer mutexUnlock(&t.mutex); | 15574 | defer mutexUnlock(&t.mutex); |
| ... | @@ -15572,44 +15576,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { | ... | @@ -15572,44 +15576,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15572 | } | 15576 | } |
| 15573 | 15577 | ||
| 15574 | const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; | 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 | var fresh_handle: windows.HANDLE = undefined; | 15579 | var fresh_handle: windows.HANDLE = undefined; |
| 15580 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | ||
| 15592 | var syscall: Syscall = try .start(); | 15581 | var syscall: Syscall = try .start(); |
| 15593 | while (true) switch (windows.ntdll.NtCreateFile( | 15582 | while (true) switch (windows.ntdll.NtOpenFile( |
| 15594 | &fresh_handle, | 15583 | &fresh_handle, |
| 15595 | .{ | 15584 | .{ |
| 15596 | .STANDARD = .{ .SYNCHRONIZE = true }, | 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 | &io_status_block, | 15596 | &io_status_block, |
| 15601 | null, | ||
| 15602 | .{ .NORMAL = true }, | ||
| 15603 | .VALID_FLAGS, | 15597 | .VALID_FLAGS, |
| 15604 | .OPEN, | 15598 | .{ .IO = .SYNCHRONOUS_NONALERT }, |
| 15605 | .{ | ||
| 15606 | .DIRECTORY_FILE = false, | ||
| 15607 | .NON_DIRECTORY_FILE = true, | ||
| 15608 | .IO = .SYNCHRONOUS_NONALERT, | ||
| 15609 | .OPEN_REPARSE_POINT = false, | ||
| 15610 | }, | ||
| 15611 | null, | ||
| 15612 | 0, | ||
| 15613 | )) { | 15599 | )) { |
| 15614 | .SUCCESS => { | 15600 | .SUCCESS => { |
| 15615 | syscall.finish(); | 15601 | syscall.finish(); |
| ... | @@ -15623,6 +15609,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { | ... | @@ -15623,6 +15609,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 15623 | return fresh_handle; | 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 | .DELETE_PENDING => { | 15670 | .DELETE_PENDING => { |
| 15627 | // This error means that there *was* a file in this location on | 15671 | // This error means that there *was* a file in this location on |
| 15628 | // the file system, but it was deleted. However, the OS is not | 15672 | // the file system, but it was deleted. However, the OS is not |
| ... | @@ -15669,7 +15713,7 @@ fn windowsCreateProcessPathExt( | ... | @@ -15669,7 +15713,7 @@ fn windowsCreateProcessPathExt( |
| 15669 | app_buf: *std.ArrayList(u16), | 15713 | app_buf: *std.ArrayList(u16), |
| 15670 | pathext: [:0]const u16, | 15714 | pathext: [:0]const u16, |
| 15671 | cmd_line_cache: *WindowsCommandLineCache, | 15715 | cmd_line_cache: *WindowsCommandLineCache, |
| 15672 | envp_ptr: ?[*:0]const u16, | 15716 | env_block: ?process.Environ.WindowsBlock, |
| 15673 | cwd_ptr: ?[*:0]u16, | 15717 | cwd_ptr: ?[*:0]u16, |
| 15674 | flags: windows.CreateProcessFlags, | 15718 | flags: windows.CreateProcessFlags, |
| 15675 | lpStartupInfo: *windows.STARTUPINFOW, | 15719 | lpStartupInfo: *windows.STARTUPINFOW, |
| ... | @@ -15846,7 +15890,7 @@ fn windowsCreateProcessPathExt( | ... | @@ -15846,7 +15890,7 @@ fn windowsCreateProcessPathExt( |
| 15846 | if (windowsCreateProcess( | 15890 | if (windowsCreateProcess( |
| 15847 | app_name_w.ptr, | 15891 | app_name_w.ptr, |
| 15848 | cmd_line_w.ptr, | 15892 | cmd_line_w.ptr, |
| 15849 | envp_ptr, | 15893 | env_block, |
| 15850 | cwd_ptr, | 15894 | cwd_ptr, |
| 15851 | flags, | 15895 | flags, |
| 15852 | lpStartupInfo, | 15896 | lpStartupInfo, |
| ... | @@ -15906,7 +15950,7 @@ fn windowsCreateProcessPathExt( | ... | @@ -15906,7 +15950,7 @@ fn windowsCreateProcessPathExt( |
| 15906 | else | 15950 | else |
| 15907 | full_app_name; | 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 | return; | 15954 | return; |
| 15911 | } else |err| switch (err) { | 15955 | } else |err| switch (err) { |
| 15912 | error.FileNotFound => continue, | 15956 | error.FileNotFound => continue, |
| ... | @@ -15930,7 +15974,7 @@ fn windowsCreateProcessPathExt( | ... | @@ -15930,7 +15974,7 @@ fn windowsCreateProcessPathExt( |
| 15930 | fn windowsCreateProcess( | 15974 | fn windowsCreateProcess( |
| 15931 | app_name: [*:0]u16, | 15975 | app_name: [*:0]u16, |
| 15932 | cmd_line: [*:0]u16, | 15976 | cmd_line: [*:0]u16, |
| 15933 | env_ptr: ?[*:0]const u16, | 15977 | env_block: ?process.Environ.WindowsBlock, |
| 15934 | cwd_ptr: ?[*:0]u16, | 15978 | cwd_ptr: ?[*:0]u16, |
| 15935 | flags: windows.CreateProcessFlags, | 15979 | flags: windows.CreateProcessFlags, |
| 15936 | lpStartupInfo: *windows.STARTUPINFOW, | 15980 | lpStartupInfo: *windows.STARTUPINFOW, |
| ... | @@ -15945,7 +15989,7 @@ fn windowsCreateProcess( | ... | @@ -15945,7 +15989,7 @@ fn windowsCreateProcess( |
| 15945 | null, | 15989 | null, |
| 15946 | windows.TRUE, | 15990 | windows.TRUE, |
| 15947 | flags, | 15991 | flags, |
| 15948 | env_ptr, | 15992 | if (env_block) |block| block.slice.ptr else null, |
| 15949 | cwd_ptr, | 15993 | cwd_ptr, |
| 15950 | lpStartupInfo, | 15994 | lpStartupInfo, |
| 15951 | lpProcessInformation, | 15995 | lpProcessInformation, |
| ... | @@ -16466,11 +16510,11 @@ fn posixExecv( | ... | @@ -16466,11 +16510,11 @@ fn posixExecv( |
| 16466 | arg0_expand: process.ArgExpansion, | 16510 | arg0_expand: process.ArgExpansion, |
| 16467 | file: [*:0]const u8, | 16511 | file: [*:0]const u8, |
| 16468 | child_argv: [*:null]?[*:0]const u8, | 16512 | child_argv: [*:null]?[*:0]const u8, |
| 16469 | envp: [*:null]const ?[*:0]const u8, | 16513 | env_block: process.Environ.PosixBlock, |
| 16470 | PATH: []const u8, | 16514 | PATH: []const u8, |
| 16471 | ) process.ReplaceError { | 16515 | ) process.ReplaceError { |
| 16472 | const file_slice = std.mem.sliceTo(file, 0); | 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 | // Use of PATH_MAX here is valid as the path_buf will be passed | 16519 | // Use of PATH_MAX here is valid as the path_buf will be passed |
| 16476 | // directly to the operating system in posixExecvPath. | 16520 | // directly to the operating system in posixExecvPath. |
| ... | @@ -16498,7 +16542,7 @@ fn posixExecv( | ... | @@ -16498,7 +16542,7 @@ fn posixExecv( |
| 16498 | .expand => child_argv[0] = full_path, | 16542 | .expand => child_argv[0] = full_path, |
| 16499 | .no_expand => {}, | 16543 | .no_expand => {}, |
| 16500 | } | 16544 | } |
| 16501 | err = posixExecvPath(full_path, child_argv, envp); | 16545 | err = posixExecvPath(full_path, child_argv, env_block); |
| 16502 | switch (err) { | 16546 | switch (err) { |
| 16503 | error.AccessDenied => seen_eacces = true, | 16547 | error.AccessDenied => seen_eacces = true, |
| 16504 | error.FileNotFound, error.NotDir => {}, | 16548 | error.FileNotFound, error.NotDir => {}, |
| ... | @@ -16513,10 +16557,10 @@ fn posixExecv( | ... | @@ -16513,10 +16557,10 @@ fn posixExecv( |
| 16513 | pub fn posixExecvPath( | 16557 | pub fn posixExecvPath( |
| 16514 | path: [*:0]const u8, | 16558 | path: [*:0]const u8, |
| 16515 | child_argv: [*:null]const ?[*:0]const u8, | 16559 | child_argv: [*:null]const ?[*:0]const u8, |
| 16516 | envp: [*:null]const ?[*:0]const u8, | 16560 | env_block: process.Environ.PosixBlock, |
| 16517 | ) process.ReplaceError { | 16561 | ) process.ReplaceError { |
| 16518 | try Thread.checkCancel(); | 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 | .FAULT => |err| return errnoBug(err), // Bad pointer parameter. | 16564 | .FAULT => |err| return errnoBug(err), // Bad pointer parameter. |
| 16521 | .@"2BIG" => return error.SystemResources, | 16565 | .@"2BIG" => return error.SystemResources, |
| 16522 | .MFILE => return error.ProcessFdQuotaExceeded, | 16566 | .MFILE => return error.ProcessFdQuotaExceeded, |
| ... | @@ -16548,100 +16592,105 @@ pub fn posixExecvPath( | ... | @@ -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 { | 16595 | pub const CreatePipeOptions = struct { |
| 16552 | var rd_h: windows.HANDLE = undefined; | 16596 | server: End, |
| 16553 | var wr_h: windows.HANDLE = undefined; | 16597 | client: End, |
| 16554 | try windows.CreatePipe(&rd_h, &wr_h, sattr); | 16598 | inbound: bool = false, |
| 16555 | errdefer windowsDestroyPipe(rd_h, wr_h); | 16599 | outbound: bool = false, |
| 16556 | try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0); | 16600 | maximum_instances: u32 = 1, |
| 16557 | rd.* = rd_h; | 16601 | quota: u32 = 4096, |
| 16558 | wr.* = wr_h; | 16602 | default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100, |
| 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; | ||
| 16568 | 16603 | ||
| 16569 | // Anonymous pipes are built upon Named pipes. | 16604 | pub const End = struct { |
| 16570 | // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe | 16605 | attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{}, |
| 16571 | // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. | 16606 | mode: windows.FILE.MODE, |
| 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]; | ||
| 16585 | }; | 16607 | }; |
| 16586 | 16608 | }; | |
| 16587 | // Create the read handle that can be used with overlapped IO ops. | 16609 | pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE { |
| 16588 | const read_handle = windows.kernel32.CreateNamedPipeW( | 16610 | const named_pipe_device = try t.getNamedPipeDevice(); |
| 16589 | pipe_path.ptr, | 16611 | const server_handle = server_handle: { |
| 16590 | windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, | 16612 | var handle: windows.HANDLE = undefined; |
| 16591 | windows.PIPE_TYPE_BYTE, | 16613 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 16592 | 1, | 16614 | const syscall: Syscall = try .start(); |
| 16593 | 4096, | 16615 | while (true) switch (windows.ntdll.NtCreateNamedPipeFile( |
| 16594 | 4096, | 16616 | &handle, |
| 16595 | 0, | 16617 | .{ |
| 16596 | sattr, | 16618 | .SPECIFIC = .{ .FILE_PIPE = .{ |
| 16597 | ); | 16619 | .READ_DATA = options.inbound, |
| 16598 | if (read_handle == windows.INVALID_HANDLE_VALUE) { | 16620 | .WRITE_DATA = options.outbound, |
| 16599 | switch (windows.GetLastError()) { | 16621 | .WRITE_ATTRIBUTES = true, |
| 16600 | else => |err| return windows.unexpectedError(err), | 16622 | } }, |
| 16601 | } | 16623 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 16602 | } | 16624 | }, |
| 16603 | errdefer posix.close(read_handle); | 16625 | &.{ |
| 16604 | 16626 | .RootDirectory = named_pipe_device, | |
| 16605 | var sattr_copy = sattr.*; | 16627 | .Attributes = options.server.attributes, |
| 16606 | const write_handle = windows.kernel32.CreateFileW( | 16628 | }, |
| 16607 | pipe_path.ptr, | 16629 | &io_status_block, |
| 16608 | .{ .GENERIC = .{ .WRITE = true } }, | 16630 | .{ .READ = true, .WRITE = true }, |
| 16609 | 0, | 16631 | .CREATE, |
| 16610 | &sattr_copy, | 16632 | options.server.mode, |
| 16611 | windows.OPEN_EXISTING, | 16633 | .{ .TYPE = .BYTE_STREAM }, |
| 16612 | @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }), | 16634 | .{ .MODE = .BYTE_STREAM }, |
| 16613 | null, | 16635 | .{ .OPERATION = .QUEUE }, |
| 16614 | ); | 16636 | options.maximum_instances, |
| 16615 | if (write_handle == windows.INVALID_HANDLE_VALUE) { | 16637 | if (options.inbound) options.quota else 0, |
| 16616 | switch (windows.GetLastError()) { | 16638 | if (options.outbound) options.quota else 0, |
| 16617 | else => |err| return windows.unexpectedError(err), | 16639 | &options.default_timeout, |
| 16618 | } | 16640 | )) { |
| 16619 | } | 16641 | .SUCCESS => break syscall.finish(), |
| 16620 | errdefer posix.close(write_handle); | 16642 | .CANCELLED => { |
| 16621 | 16643 | try syscall.checkCancel(); | |
| 16622 | try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); | 16644 | continue; |
| 16623 | 16645 | }, | |
| 16624 | rd.* = read_handle; | 16646 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 16625 | wr.* = write_handle; | 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 | fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { | 16690 | fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { |
| 16631 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 16691 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 16632 | |||
| 16633 | t.scanEnviron(); | 16692 | t.scanEnviron(); |
| 16634 | 16693 | return t.environ.zig_progress_file; | |
| 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 | }; | ||
| 16645 | } | 16694 | } |
| 16646 | 16695 | ||
| 16647 | pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { | 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,7 +16786,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { |
| 16737 | // despite the function being documented to always return TRUE | 16786 | // despite the function being documented to always return TRUE |
| 16738 | // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG | 16787 | // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG |
| 16739 | // Therefore, that function is avoided in favor of using the device directly. | 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 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | 16790 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 16742 | var i: usize = 0; | 16791 | var i: usize = 0; |
| 16743 | const syscall: Syscall = try .start(); | 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,13 +181,17 @@ test "cancel blocked read from pipe" { |
| 181 | var write_end: Io.File = undefined; | 181 | var write_end: Io.File = undefined; |
| 182 | switch (builtin.target.os.tag) { | 182 | switch (builtin.target.os.tag) { |
| 183 | .wasi => return error.SkipZigTest, | 183 | .wasi => return error.SkipZigTest, |
| 184 | .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{ | 184 | .windows => { |
| 185 | .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES), | 185 | const pipe = try threaded.windowsCreatePipe(.{ |
| 186 | .lpSecurityDescriptor = null, | 186 | .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 187 | .bInheritHandle = std.os.windows.FALSE, | 187 | .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 188 | }), | 188 | .inbound = true, |
| 189 | }); | ||
| 190 | read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; | ||
| 191 | write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; | ||
| 192 | }, | ||
| 189 | else => { | 193 | else => { |
| 190 | const pipe = try std.Io.Threaded.pipe2(.{}); | 194 | const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true }); |
| 191 | read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; | 195 | read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; |
| 192 | write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; | 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,7 +11,7 @@ const windows = std.os.windows; |
| 11 | const testing = std.testing; | 11 | const testing = std.testing; |
| 12 | const assert = std.debug.assert; | 12 | const assert = std.debug.assert; |
| 13 | const posix = std.posix; | 13 | const posix = std.posix; |
| 14 | const Writer = std.Io.Writer; | 14 | const Writer = Io.Writer; |
| 15 | 15 | ||
| 16 | /// Currently this API only supports this value being set to stderr, which | 16 | /// Currently this API only supports this value being set to stderr, which |
| 17 | /// happens automatically inside `start`. | 17 | /// happens automatically inside `start`. |
| ... | @@ -21,13 +21,10 @@ io: Io, | ... | @@ -21,13 +21,10 @@ io: Io, |
| 21 | 21 | ||
| 22 | terminal_mode: TerminalMode, | 22 | terminal_mode: TerminalMode, |
| 23 | 23 | ||
| 24 | update_worker: ?Io.Future(void), | 24 | update_worker: ?Io.Future(WorkerError!void), |
| 25 | 25 | ||
| 26 | /// Atomically set by SIGWINCH as well as the root done() function. | 26 | /// Atomically set by SIGWINCH as well as the root done() function. |
| 27 | redraw_event: Io.Event, | 27 | redraw_event: Io.Event, |
| 28 | /// Indicates a request to shut down and reset global state. | ||
| 29 | /// Accessed atomically. | ||
| 30 | done: bool, | ||
| 31 | need_clear: bool, | 28 | need_clear: bool, |
| 32 | status: Status, | 29 | status: Status, |
| 33 | 30 | ||
| ... | @@ -43,15 +40,19 @@ draw_buffer: []u8, | ... | @@ -43,15 +40,19 @@ draw_buffer: []u8, |
| 43 | /// This is in a separate array from `node_storage` but with the same length so | 40 | /// This is in a separate array from `node_storage` but with the same length so |
| 44 | /// that it can be iterated over efficiently without trashing too much of the | 41 | /// that it can be iterated over efficiently without trashing too much of the |
| 45 | /// CPU cache. | 42 | /// CPU cache. |
| 46 | node_parents: []Node.Parent, | 43 | node_parents: [node_storage_buffer_len]Node.Parent, |
| 47 | node_storage: []Node.Storage, | 44 | node_storage: [node_storage_buffer_len]Node.Storage, |
| 48 | node_freelist_next: []Node.OptionalIndex, | 45 | node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex, |
| 49 | node_freelist: Freelist, | 46 | node_freelist: Freelist, |
| 50 | /// This is the number of elements in node arrays which have been used so far. Nodes before this | 47 | /// This is the number of elements in node arrays which have been used so far. Nodes before this |
| 51 | /// index are either active, or on the freelist. The remaining nodes are implicitly free. This | 48 | /// index are either active, or on the freelist. The remaining nodes are implicitly free. This |
| 52 | /// value may at times temporarily exceed the node count. | 49 | /// value may at times temporarily exceed the node count. |
| 53 | node_end_index: u32, | 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 | start_failure: StartFailure, | 56 | start_failure: StartFailure, |
| 56 | 57 | ||
| 57 | pub const Status = enum { | 58 | pub const Status = enum { |
| ... | @@ -77,6 +78,80 @@ const Freelist = packed struct(u32) { | ... | @@ -77,6 +78,80 @@ const Freelist = packed struct(u32) { |
| 77 | generation: u24, | 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 | pub const TerminalMode = union(enum) { | 155 | pub const TerminalMode = union(enum) { |
| 81 | off, | 156 | off, |
| 82 | ansi_escape_codes, | 157 | ansi_escape_codes, |
| ... | @@ -116,7 +191,7 @@ pub const Node = struct { | ... | @@ -116,7 +191,7 @@ pub const Node = struct { |
| 116 | 191 | ||
| 117 | pub const none: Node = .{ .index = .none }; | 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 | const Storage = extern struct { | 196 | const Storage = extern struct { |
| 122 | /// Little endian. | 197 | /// Little endian. |
| ... | @@ -127,25 +202,16 @@ pub const Node = struct { | ... | @@ -127,25 +202,16 @@ pub const Node = struct { |
| 127 | name: [max_name_len]u8 align(@alignOf(usize)), | 202 | name: [max_name_len]u8 align(@alignOf(usize)), |
| 128 | 203 | ||
| 129 | /// Not thread-safe. | 204 | /// Not thread-safe. |
| 130 | fn getIpcFd(s: Storage) ?Io.File.Handle { | 205 | fn getIpcIndex(s: Storage) ?Ipc.Index { |
| 131 | return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) { | 206 | return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null; |
| 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; | ||
| 136 | } | 207 | } |
| 137 | 208 | ||
| 138 | /// Thread-safe. | 209 | /// Thread-safe. |
| 139 | fn setIpcFd(s: *Storage, fd: Io.File.Handle) void { | 210 | fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) 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 | }; | ||
| 145 | // `estimated_total_count` max int indicates the special state that | 211 | // `estimated_total_count` max int indicates the special state that |
| 146 | // causes `completed_count` to be treated as a file descriptor, so | 212 | // causes `completed_count` to be treated as a file descriptor, so |
| 147 | // the order here matters. | 213 | // the order here matters. |
| 148 | @atomicStore(u32, &s.completed_count, integer, .monotonic); | 214 | @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic); |
| 149 | @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize` | 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,6 +221,14 @@ pub const Node = struct { |
| 155 | s.estimated_total_count = @byteSwap(s.estimated_total_count); | 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 | comptime { | 232 | comptime { |
| 159 | assert((@sizeOf(Storage) % 4) == 0); | 233 | assert((@sizeOf(Storage) % 4) == 0); |
| 160 | } | 234 | } |
| ... | @@ -242,7 +316,7 @@ pub const Node = struct { | ... | @@ -242,7 +316,7 @@ pub const Node = struct { |
| 242 | } | 316 | } |
| 243 | 317 | ||
| 244 | const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic); | 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 | // Ran out of node storage memory. Progress for this node will not be tracked. | 320 | // Ran out of node storage memory. Progress for this node will not be tracked. |
| 247 | _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic); | 321 | _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic); |
| 248 | return Node.none; | 322 | return Node.none; |
| ... | @@ -292,15 +366,17 @@ pub const Node = struct { | ... | @@ -292,15 +366,17 @@ pub const Node = struct { |
| 292 | const index = n.index.unwrap() orelse return; | 366 | const index = n.index.unwrap() orelse return; |
| 293 | const storage = storageByIndex(index); | 367 | const storage = storageByIndex(index); |
| 294 | // Avoid u32 max int which is used to indicate a special state. | 368 | // Avoid u32 max int which is used to indicate a special state. |
| 295 | const saturated = @min(std.math.maxInt(u32) - 1, count); | 369 | const saturated_total_count = @min(std.math.maxInt(u32) - 1, count); |
| 296 | @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic); | 370 | @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic); |
| 297 | } | 371 | } |
| 298 | 372 | ||
| 299 | /// Thread-safe. | 373 | /// Thread-safe. |
| 300 | pub fn increaseEstimatedTotalItems(n: Node, count: usize) void { | 374 | pub fn increaseEstimatedTotalItems(n: Node, count: usize) void { |
| 301 | const index = n.index.unwrap() orelse return; | 375 | const index = n.index.unwrap() orelse return; |
| 302 | const storage = storageByIndex(index); | 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 | /// Finish a started `Node`. Thread-safe. | 382 | /// Finish a started `Node`. Thread-safe. |
| ... | @@ -310,11 +386,25 @@ pub const Node = struct { | ... | @@ -310,11 +386,25 @@ pub const Node = struct { |
| 310 | return; | 386 | return; |
| 311 | } | 387 | } |
| 312 | const index = n.index.unwrap() orelse return; | 388 | const index = n.index.unwrap() orelse return; |
| 389 | const io = global_progress.io; | ||
| 313 | const parent_ptr = parentByIndex(index); | 390 | const parent_ptr = parentByIndex(index); |
| 314 | if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| { | 391 | if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| { |
| 315 | _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic); | 392 | _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic); |
| 316 | @atomicStore(Node.Parent, parent_ptr, .unused, .monotonic); | 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 | const freelist = &global_progress.node_freelist; | 408 | const freelist = &global_progress.node_freelist; |
| 319 | var old_freelist = @atomicLoad(Freelist, freelist, .monotonic); | 409 | var old_freelist = @atomicLoad(Freelist, freelist, .monotonic); |
| 320 | while (true) { | 410 | while (true) { |
| ... | @@ -332,34 +422,52 @@ pub const Node = struct { | ... | @@ -332,34 +422,52 @@ pub const Node = struct { |
| 332 | }; | 422 | }; |
| 333 | } | 423 | } |
| 334 | } else { | 424 | } else { |
| 335 | @atomicStore(bool, &global_progress.done, true, .monotonic); | 425 | if (global_progress.update_worker) |*worker| worker.cancel(io) catch {}; |
| 336 | const io = global_progress.io; | 426 | for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| { |
| 337 | global_progress.redraw_event.set(io); | 427 | assert(!ipc.locked or !ipc.valid); // missing call to end() |
| 338 | if (global_progress.update_worker) |*worker| worker.await(io); | 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. | 433 | /// Used by `std.process.Child`. Thread-safe. |
| 343 | pub fn setIpcFd(node: Node, fd: Io.File.Handle) void { | 434 | pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void { |
| 344 | const index = node.index.unwrap() orelse return; | 435 | const index = node.index.unwrap() orelse return; |
| 345 | assert(fd >= 0); | 436 | const io = global_progress.io; |
| 346 | assert(fd != posix.STDOUT_FILENO); | 437 | assert(io.userdata == expected_io_userdata); |
| 347 | assert(fd != posix.STDIN_FILENO); | 438 | for (0..ipc_storage_buffer_len) |_| { |
| 348 | assert(fd != posix.STDERR_FILENO); | 439 | const slot: Ipc.Slot = @truncate( |
| 349 | storageByIndex(index).setIpcFd(fd); | 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 | 461 | pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void { |
| 353 | /// descriptor. | 462 | storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index); |
| 354 | pub fn getIpcFd(node: Node) ?Io.File.Handle { | 463 | } |
| 355 | const index = node.index.unwrap() orelse return null; | 464 | |
| 356 | const storage = storageByIndex(index); | 465 | /// Not thread-safe. |
| 357 | const int = @atomicLoad(u32, &storage.completed_count, .monotonic); | 466 | pub fn takeIpcIndex(node: Node) ?Ipc.Index { |
| 358 | return switch (@typeInfo(Io.File.Handle)) { | 467 | const storage = storageByIndex(node.index.unwrap() orelse return null); |
| 359 | .int => @bitCast(int), | 468 | assert(storage.estimated_total_count == std.math.maxInt(u32)); |
| 360 | .pointer => @ptrFromInt(int), | 469 | @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic); |
| 361 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | 470 | return @bitCast(storage.completed_count); |
| 362 | }; | ||
| 363 | } | 471 | } |
| 364 | 472 | ||
| 365 | fn storageByIndex(index: Node.Index) *Node.Storage { | 473 | fn storageByIndex(index: Node.Index) *Node.Storage { |
| ... | @@ -379,7 +487,9 @@ pub const Node = struct { | ... | @@ -379,7 +487,9 @@ pub const Node = struct { |
| 379 | 487 | ||
| 380 | const storage = storageByIndex(free_index); | 488 | const storage = storageByIndex(free_index); |
| 381 | @atomicStore(u32, &storage.completed_count, 0, .monotonic); | 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 | const name_len = @min(max_name_len, name.len); | 493 | const name_len = @min(max_name_len, name.len); |
| 384 | copyAtomicStore(storage.name[0..name_len], name[0..name_len]); | 494 | copyAtomicStore(storage.name[0..name_len], name[0..name_len]); |
| 385 | if (name_len < storage.name.len) | 495 | if (name_len < storage.name.len) |
| ... | @@ -406,16 +516,20 @@ var global_progress: Progress = .{ | ... | @@ -406,16 +516,20 @@ var global_progress: Progress = .{ |
| 406 | .rows = 0, | 516 | .rows = 0, |
| 407 | .cols = 0, | 517 | .cols = 0, |
| 408 | .draw_buffer = undefined, | 518 | .draw_buffer = undefined, |
| 409 | .done = false, | ||
| 410 | .need_clear = false, | 519 | .need_clear = false, |
| 411 | .status = .working, | 520 | .status = .working, |
| 412 | .start_failure = .unstarted, | ||
| 413 | 521 | ||
| 414 | .node_parents = &node_parents_buffer, | 522 | .node_parents = undefined, |
| 415 | .node_storage = &node_storage_buffer, | 523 | .node_storage = undefined, |
| 416 | .node_freelist_next = &node_freelist_next_buffer, | 524 | .node_freelist_next = undefined, |
| 417 | .node_freelist = .{ .head = .none, .generation = 0 }, | 525 | .node_freelist = .{ .head = .none, .generation = 0 }, |
| 418 | .node_end_index = 0, | 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 | pub const StartFailure = union(enum) { | 535 | pub const StartFailure = union(enum) { |
| ... | @@ -425,17 +539,23 @@ pub const StartFailure = union(enum) { | ... | @@ -425,17 +539,23 @@ pub const StartFailure = union(enum) { |
| 425 | parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat }, | 539 | parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat }, |
| 426 | }; | 540 | }; |
| 427 | 541 | ||
| 428 | const node_storage_buffer_len = 83; | 542 | /// One less than a power of two ensures `max_packet_len` is already a power of two. |
| 429 | var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined; | 543 | const node_storage_buffer_len = ipc_storage_buffer_len - 1; |
| 430 | var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined; | 544 | |
| 431 | var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined; | 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 | var default_draw_buffer: [4096]u8 = undefined; | 553 | var default_draw_buffer: [4096]u8 = undefined; |
| 434 | 554 | ||
| 435 | var debug_start_trace = std.debug.Trace.init; | 555 | var debug_start_trace = std.debug.Trace.init; |
| 436 | 556 | ||
| 437 | pub const have_ipc = switch (builtin.os.tag) { | 557 | pub const have_ipc = switch (builtin.os.tag) { |
| 438 | .wasi, .freestanding, .windows => false, | 558 | .wasi, .freestanding => false, |
| 439 | else => true, | 559 | else => true, |
| 440 | }; | 560 | }; |
| 441 | 561 | ||
| ... | @@ -467,9 +587,9 @@ pub fn start(io: Io, options: Options) Node { | ... | @@ -467,9 +587,9 @@ pub fn start(io: Io, options: Options) Node { |
| 467 | } | 587 | } |
| 468 | debug_start_trace.add("first initialized here"); | 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 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); | 592 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); |
| 472 | global_progress.done = false; | ||
| 473 | global_progress.node_end_index = 1; | 593 | global_progress.node_end_index = 1; |
| 474 | 594 | ||
| 475 | assert(options.draw_buffer.len >= 200); | 595 | assert(options.draw_buffer.len >= 200); |
| ... | @@ -477,21 +597,18 @@ pub fn start(io: Io, options: Options) Node { | ... | @@ -477,21 +597,18 @@ pub fn start(io: Io, options: Options) Node { |
| 477 | global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds()); | 597 | global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds()); |
| 478 | global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds()); | 598 | global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds()); |
| 479 | 599 | ||
| 480 | if (noop_impl) | 600 | if (noop_impl) return .none; |
| 481 | return Node.none; | ||
| 482 | 601 | ||
| 483 | global_progress.io = io; | 602 | global_progress.io = io; |
| 484 | 603 | ||
| 485 | if (io.vtable.progressParentFile(io.userdata)) |ipc_file| { | 604 | if (io.vtable.progressParentFile(io.userdata)) |ipc_file| { |
| 486 | global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| { | 605 | global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| { |
| 487 | global_progress.start_failure = .{ .spawn_ipc_worker = err }; | 606 | global_progress.start_failure = .{ .spawn_ipc_worker = err }; |
| 488 | return Node.none; | 607 | return .none; |
| 489 | }; | 608 | }; |
| 490 | } else |env_err| switch (env_err) { | 609 | } else |env_err| switch (env_err) { |
| 491 | error.EnvironmentVariableMissing => { | 610 | error.EnvironmentVariableMissing => { |
| 492 | if (options.disable_printing) { | 611 | if (options.disable_printing) return .none; |
| 493 | return Node.none; | ||
| 494 | } | ||
| 495 | const stderr: Io.File = .stderr(); | 612 | const stderr: Io.File = .stderr(); |
| 496 | global_progress.terminal = stderr; | 613 | global_progress.terminal = stderr; |
| 497 | if (stderr.enableAnsiEscapeCodes(io)) |_| { | 614 | if (stderr.enableAnsiEscapeCodes(io)) |_| { |
| ... | @@ -504,14 +621,12 @@ pub fn start(io: Io, options: Options) Node { | ... | @@ -504,14 +621,12 @@ pub fn start(io: Io, options: Options) Node { |
| 504 | } else |err| switch (err) { | 621 | } else |err| switch (err) { |
| 505 | error.Canceled => { | 622 | error.Canceled => { |
| 506 | io.recancel(); | 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) { | 629 | if (global_progress.terminal_mode == .off) return .none; |
| 513 | return Node.none; | ||
| 514 | } | ||
| 515 | 630 | ||
| 516 | if (have_sigwinch) { | 631 | if (have_sigwinch) { |
| 517 | const act: posix.Sigaction = .{ | 632 | const act: posix.Sigaction = .{ |
| ... | @@ -530,12 +645,12 @@ pub fn start(io: Io, options: Options) Node { | ... | @@ -530,12 +645,12 @@ pub fn start(io: Io, options: Options) Node { |
| 530 | global_progress.update_worker = future; | 645 | global_progress.update_worker = future; |
| 531 | } else |err| { | 646 | } else |err| { |
| 532 | global_progress.start_failure = .{ .spawn_update_worker = err }; | 647 | global_progress.start_failure = .{ .spawn_update_worker = err }; |
| 533 | return Node.none; | 648 | return .none; |
| 534 | } | 649 | } |
| 535 | }, | 650 | }, |
| 536 | else => |e| { | 651 | else => |e| { |
| 537 | global_progress.start_failure = .{ .parent_ipc = e }; | 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,58 +663,55 @@ pub fn setStatus(new_status: Status) void { |
| 548 | } | 663 | } |
| 549 | 664 | ||
| 550 | /// Returns whether a resize is needed to learn the terminal size. | 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 | const timeout: Io.Timeout = .{ .duration = .{ | 667 | const timeout: Io.Timeout = .{ .duration = .{ |
| 553 | .clock = .awake, | 668 | .clock = .awake, |
| 554 | .raw = .fromNanoseconds(timeout_ns), | 669 | .raw = .fromNanoseconds(timeout_ns), |
| 555 | } }; | 670 | } }; |
| 556 | const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) { | 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 | global_progress.redraw_event.reset(); | 675 | global_progress.redraw_event.reset(); |
| 560 | return resize_flag or (global_progress.cols == 0); | 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 | // Store this data in the thread so that it does not need to be part of the | 683 | // Store this data in the thread so that it does not need to be part of the |
| 565 | // linker data of the main executable. | 684 | // linker data of the main executable. |
| 566 | var serialized_buffer: Serialized.Buffer = undefined; | 685 | var serialized_buffer: Serialized.Buffer = undefined; |
| 686 | serialized_buffer.init(); | ||
| 687 | defer serialized_buffer.batch.cancel(io); | ||
| 567 | 688 | ||
| 568 | // In this function we bypass the wrapper code inside `Io.lockStderr` / | 689 | // In this function we bypass the wrapper code inside `Io.lockStderr` / |
| 569 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. | 690 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. |
| 570 | // We still want to go through the `Io` instance however in case it uses a | 691 | // We still want to go through the `Io` instance however in case it uses a |
| 571 | // task-switching mutex. | 692 | // task-switching mutex. |
| 572 | 693 | ||
| 573 | { | 694 | try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); |
| 574 | const resize_flag = wait(io, global_progress.initial_delay_ns); | 695 | errdefer { |
| 575 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; | 696 | const cancel_protection = io.swapCancelProtection(.blocked); |
| 576 | maybeUpdateSize(io, resize_flag) catch return; | 697 | defer _ = io.swapCancelProtection(cancel_protection); |
| 577 | 698 | const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { | |
| 578 | const buffer, _ = computeRedraw(&serialized_buffer); | 699 | error.Canceled => unreachable, // blocked |
| 579 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | 700 | }; |
| 580 | defer io.unlockStderr(); | 701 | defer io.unlockStderr(); |
| 581 | global_progress.need_clear = true; | 702 | clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; |
| 582 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | ||
| 583 | } | ||
| 584 | } | 703 | } |
| 585 | |||
| 586 | while (true) { | 704 | while (true) { |
| 587 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | 705 | const buffer, _ = try computeRedraw(io, &serialized_buffer); |
| 588 | 706 | if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| { | |
| 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| { | ||
| 599 | defer io.unlockStderr(); | 707 | defer io.unlockStderr(); |
| 600 | global_progress.need_clear = true; | 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,79 +723,60 @@ fn windowsApiWriteMarker() void { |
| 611 | _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); | 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 | var serialized_buffer: Serialized.Buffer = undefined; | 729 | var serialized_buffer: Serialized.Buffer = undefined; |
| 730 | serialized_buffer.init(); | ||
| 731 | defer serialized_buffer.batch.cancel(io); | ||
| 616 | 732 | ||
| 617 | // In this function we bypass the wrapper code inside `Io.lockStderr` / | 733 | // In this function we bypass the wrapper code inside `Io.lockStderr` / |
| 618 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. | 734 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. |
| 619 | // We still want to go through the `Io` instance however in case it uses a | 735 | // We still want to go through the `Io` instance however in case it uses a |
| 620 | // task-switching mutex. | 736 | // task-switching mutex. |
| 621 | 737 | ||
| 622 | { | 738 | try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); |
| 623 | const resize_flag = wait(io, global_progress.initial_delay_ns); | 739 | errdefer { |
| 624 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; | 740 | const cancel_protection = io.swapCancelProtection(.blocked); |
| 625 | maybeUpdateSize(io, resize_flag) catch return; | 741 | defer _ = io.swapCancelProtection(cancel_protection); |
| 626 | 742 | _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { | |
| 627 | const buffer, const nl_n = computeRedraw(&serialized_buffer); | 743 | error.Canceled => unreachable, // blocked |
| 628 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | 744 | }; |
| 629 | defer io.unlockStderr(); | 745 | defer io.unlockStderr(); |
| 630 | windowsApiWriteMarker(); | 746 | clearWrittenWindowsApi() catch {}; |
| 631 | global_progress.need_clear = true; | ||
| 632 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | ||
| 633 | windowsApiMoveToMarker(nl_n) catch return; | ||
| 634 | } | ||
| 635 | } | 747 | } |
| 636 | |||
| 637 | while (true) { | 748 | while (true) { |
| 638 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | 749 | const buffer, const nl_n = try computeRedraw(io, &serialized_buffer); |
| 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); | ||
| 649 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | 750 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { |
| 650 | defer io.unlockStderr(); | 751 | defer io.unlockStderr(); |
| 651 | clearWrittenWindowsApi() catch return; | 752 | try clearWrittenWindowsApi(); |
| 652 | windowsApiWriteMarker(); | 753 | windowsApiWriteMarker(); |
| 653 | global_progress.need_clear = true; | 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 | windowsApiMoveToMarker(nl_n) catch return; | 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 | // Store this data in the thread so that it does not need to be part of the | 766 | // Store this data in the thread so that it does not need to be part of the |
| 662 | // linker data of the main executable. | 767 | // linker data of the main executable. |
| 663 | var serialized_buffer: Serialized.Buffer = undefined; | 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 | { | 773 | _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake); |
| 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 | |||
| 677 | while (true) { | 774 | while (true) { |
| 678 | _ = wait(io, global_progress.refresh_rate_ns); | 775 | writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) { |
| 679 | 776 | error.WriteFailed => return fw.err.?, | |
| 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, | ||
| 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,31 +955,49 @@ const Serialized = struct { |
| 862 | const Buffer = struct { | 955 | const Buffer = struct { |
| 863 | parents: [node_storage_buffer_len]Node.Parent, | 956 | parents: [node_storage_buffer_len]Node.Parent, |
| 864 | storage: [node_storage_buffer_len]Node.Storage, | 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, | 959 | ipc_start: u8, |
| 868 | storage_copy: [node_storage_buffer_len]Node.Storage, | 960 | ipc_end: u8, |
| 869 | ipc_metadata_fds_copy: [node_storage_buffer_len]Fd, | 961 | ipc_data: [ipc_storage_buffer_len]Ipc.Data, |
| 870 | ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata, | 962 | ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8, |
| 871 | 963 | ipc_vecs: [ipc_storage_buffer_len][1][]u8, | |
| 872 | ipc_metadata_fds: [node_storage_buffer_len]Fd, | 964 | batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage, |
| 873 | ipc_metadata: [node_storage_buffer_len]SavedMetadata, | 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 { | 976 | fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized { |
| 878 | var serialized_len: usize = 0; | 977 | var prev_parents: [node_storage_buffer_len]Node.Parent = undefined; |
| 879 | var any_ipc = false; | 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 | // Iterate all of the nodes and construct a serializable copy of the state that can be examined | 986 | // Iterate all of the nodes and construct a serializable copy of the state that can be examined |
| 882 | // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the | 987 | // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the |
| 883 | // node count sometimes. | 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 | for ( | 996 | for ( |
| 886 | global_progress.node_parents[0..end_index], | 997 | global_progress.node_parents[0..end_index], |
| 887 | global_progress.node_storage[0..end_index], | 998 | global_progress.node_storage[0..end_index], |
| 888 | serialized_buffer.map[0..end_index], | 999 | map[0..end_index], |
| 889 | ) |*parent_ptr, *storage_ptr, *map| { | 1000 | ) |*parent_ptr, *storage_ptr, *map_entry| { |
| 890 | const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic); | 1001 | const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic); |
| 891 | if (parent == .unused) { | 1002 | if (parent == .unused) { |
| 892 | // We might read "mixed" node data in this loop, due to weird atomic things | 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,17 +1011,17 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { |
| 900 | // parent, it will just not be printed at all. The general idea here is that performance | 1011 | // parent, it will just not be printed at all. The general idea here is that performance |
| 901 | // is more important than 100% correct output every frame, given that this API is likely | 1012 | // is more important than 100% correct output every frame, given that this API is likely |
| 902 | // to be used in hot paths! | 1013 | // to be used in hot paths! |
| 903 | map.* = .none; | 1014 | map_entry.* = .none; |
| 904 | continue; | 1015 | continue; |
| 905 | } | 1016 | } |
| 906 | const dest_storage = &serialized_buffer.storage[serialized_len]; | 1017 | const dest_storage = &serialized_buffer.storage[serialized_len]; |
| 907 | copyAtomicLoad(&dest_storage.name, &storage_ptr.name); | 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 | dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic); | 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 | serialized_buffer.parents[serialized_len] = parent; | 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 | serialized_len += 1; | 1025 | serialized_len += 1; |
| 915 | } | 1026 | } |
| 916 | 1027 | ||
| ... | @@ -919,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { | ... | @@ -919,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { |
| 919 | parent.* = switch (parent.*) { | 1030 | parent.* = switch (parent.*) { |
| 920 | .unused => unreachable, | 1031 | .unused => unreachable, |
| 921 | .none => .none, | 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. | 1037 | // Fill pipe buffers. |
| 927 | if (any_ipc) | 1038 | const batch = &serialized_buffer.batch; |
| 928 | serialized_len = serializeIpc(serialized_len, serialized_buffer); | 1039 | batch.awaitConcurrent(io, .{ |
| 929 | 1040 | .duration = .{ .raw = .zero, .clock = .awake }, | |
| 930 | return .{ | 1041 | }) catch |err| switch (err) { |
| 931 | .parents = serialized_buffer.parents[0..serialized_len], | 1042 | error.Timeout => {}, |
| 932 | .storage = serialized_buffer.storage[0..serialized_len], | 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 ( | 1079 | // Find nodes which correspond to child processes. |
| 975 | serialized_buffer.parents[0..serialized_len], | 1080 | const ipc_start = maybe_ipc_start orelse serialized_len; |
| 976 | serialized_buffer.storage[0..serialized_len], | 1081 | serialized_buffer.ipc_start = ipc_start; |
| 977 | 0.., | 1082 | for ( |
| 1083 | serialized_buffer.parents[ipc_start..serialized_len], | ||
| 1084 | serialized_buffer.storage[ipc_start..serialized_len], | ||
| 1085 | ipc_start.., | ||
| 978 | ) |main_parent, *main_storage, main_index| { | 1086 | ) |main_parent, *main_storage, main_index| { |
| 979 | if (main_parent == .unused) continue; | 1087 | if (main_parent == .unused) continue; |
| 980 | const file: Io.File = .{ | 1088 | const ipc_index = main_storage.getIpcIndex() orelse continue; |
| 981 | .handle = main_storage.getIpcFd() orelse continue, | 1089 | const ipc = &global_progress.ipc[ipc_index.slot]; |
| 982 | .flags = .{ .nonblocking = true }, | 1090 | const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot]; |
| 983 | }; | 1091 | state: switch (ipc_data.state) { |
| 984 | const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); | 1092 | .unused => { |
| 985 | var bytes_read: usize = 0; | 1093 | if (@cmpxchgWeak( |
| 986 | while (true) { | 1094 | Ipc, |
| 987 | const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) { | 1095 | ipc, |
| 988 | error.WouldBlock, error.EndOfStream => break, | 1096 | .{ .locked = false, .valid = true, .generation = ipc_index.generation }, |
| 989 | else => |e| { | 1097 | .{ .locked = true, .valid = true, .generation = ipc_index.generation }, |
| 990 | std.log.debug("failed to read child progress data: {t}", .{e}); | 1098 | .acquire, |
| 991 | main_storage.completed_count = 0; | 1099 | .monotonic, |
| 992 | main_storage.estimated_total_count = 0; | 1100 | )) |_| continue; |
| 993 | continue :main_loop; | 1101 | |
| 994 | }, | 1102 | const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot]; |
| 995 | }; | 1103 | ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]}; |
| 996 | if (opt_saved_metadata) |m| { | 1104 | batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{ |
| 997 | if (m.remaining_read_trash_bytes > 0) { | 1105 | .file = global_progress.ipc_files[ipc_index.slot], |
| 998 | assert(bytes_read == 0); | 1106 | .data = ipc_vec, |
| 999 | if (m.remaining_read_trash_bytes >= n) { | 1107 | } }); |
| 1000 | m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n); | 1108 | |
| 1001 | continue; | 1109 | ipc_data.* = .{ |
| 1002 | } | 1110 | .state = .pending, |
| 1003 | const src = pipe_buf[m.remaining_read_trash_bytes..n]; | 1111 | .bytes_read = 0, |
| 1004 | @memmove(pipe_buf[0..src.len], src); | 1112 | .main_index = @intCast(main_index), |
| 1005 | m.remaining_read_trash_bytes = 0; | 1113 | .start_index = serialized_len, |
| 1006 | bytes_read = src.len; | 1114 | .nodes_len = 0, |
| 1007 | continue; | 1115 | }; |
| 1008 | } | 1116 | main_storage.completed_count = 0; |
| 1009 | } | 1117 | main_storage.estimated_total_count = 0; |
| 1010 | bytes_read += n; | 1118 | }, |
| 1011 | } | 1119 | .pending => { |
| 1012 | // Ignore all but the last message on the pipe. | 1120 | const start_index = ipc_data.start_index; |
| 1013 | var input: []u8 = pipe_buf[0..bytes_read]; | 1121 | const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len); |
| 1014 | if (input.len == 0) { | 1122 | |
| 1015 | serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle); | 1123 | main_storage.copyRoot(&prev_storage[ipc_data.main_index]); |
| 1016 | continue; | 1124 | @memcpy( |
| 1017 | } | 1125 | serialized_buffer.storage[serialized_len..][0..nodes_len], |
| 1018 | 1126 | prev_storage[start_index..][0..nodes_len], | |
| 1019 | const storage, const parents = while (true) { | 1127 | ); |
| 1020 | const subtree_len: usize = input[0]; | 1128 | for ( |
| 1021 | const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent)); | 1129 | serialized_buffer.parents[serialized_len..][0..nodes_len], |
| 1022 | if (input.len < expected_bytes) { | 1130 | prev_parents[serialized_len..][0..nodes_len], |
| 1023 | // Ignore short reads. We'll handle the next full message when it comes instead. | 1131 | ) |*parent, prev_parent| parent.* = switch (prev_parent) { |
| 1024 | const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len); | 1132 | .none, .unused => .none, |
| 1025 | serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle); | 1133 | _ => if (@intFromEnum(prev_parent) == ipc_data.main_index) |
| 1026 | continue :main_loop; | 1134 | @enumFromInt(main_index) |
| 1027 | } | 1135 | else if (@intFromEnum(prev_parent) >= start_index and |
| 1028 | if (input.len > expected_bytes) { | 1136 | @intFromEnum(prev_parent) < start_index + nodes_len) |
| 1029 | input = input[expected_bytes..]; | 1137 | @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len) |
| 1030 | continue; | 1138 | else |
| 1031 | } | 1139 | .none, |
| 1032 | const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)]; | 1140 | }; |
| 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)); | ||
| 1041 | 1141 | ||
| 1042 | // Remember in case the pipe is empty on next update. | 1142 | ipc_data.main_index = @intCast(main_index); |
| 1043 | ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle); | 1143 | ipc_data.start_index = serialized_len; |
| 1044 | ipc_metadata[ipc_metadata_len] = .{ | 1144 | ipc_data.nodes_len = nodes_len; |
| 1045 | .remaining_read_trash_bytes = 0, | 1145 | serialized_len += nodes_len; |
| 1046 | .start_index = @intCast(serialized_len), | 1146 | }, |
| 1047 | .nodes_len = nodes_len, | 1147 | .ready => { |
| 1048 | .main_index = @intCast(main_index), | 1148 | const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot]; |
| 1049 | }; | 1149 | const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer); |
| 1050 | ipc_metadata_len += 1; | 1150 | const packet_is_empty = packet_end - packet_start <= 1; |
| 1051 | 1151 | if (!packet_is_empty) { | |
| 1052 | // Mount the root here. | 1152 | const storage, const parents, const nodes_len = packet_contents: { |
| 1053 | copyRoot(main_storage, &storage[0]); | 1153 | var packet_index: usize = packet_start; |
| 1054 | if (is_big_endian) main_storage.byteSwap(); | 1154 | const nodes_len: u16 = ipc_buffer[packet_index]; |
| 1055 | 1155 | packet_index += 1; | |
| 1056 | // Copy the rest of the tree to the end. | 1156 | const storage_bytes = |
| 1057 | const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len]; | 1157 | ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)]; |
| 1058 | @memcpy(storage_dest, storage[1..][0..nodes_len]); | 1158 | packet_index += storage_bytes.len; |
| 1059 | 1159 | const parents_bytes = | |
| 1060 | // Always little-endian over the pipe. | 1160 | ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)]; |
| 1061 | if (is_big_endian) for (storage_dest) |*s| s.byteSwap(); | 1161 | packet_index += parents_bytes.len; |
| 1062 | 1162 | assert(packet_index == packet_end); | |
| 1063 | // Patch up parent pointers taking into account how the subtree is mounted. | 1163 | const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes); |
| 1064 | for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| { | 1164 | const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes); |
| 1065 | dest.* = switch (p) { | 1165 | const children_nodes_len = |
| 1066 | // Fix bad data so the rest of the code does not see `unused`. | 1166 | @min(nodes_len - 1, node_storage_buffer_len - serialized_len); |
| 1067 | .none, .unused => .none, | 1167 | break :packet_contents .{ storage, parents, children_nodes_len }; |
| 1068 | // Root node is being mounted here. | 1168 | }; |
| 1069 | @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index), | 1169 | |
| 1070 | // Other nodes mounted at the end. | 1170 | // Mount the root here. |
| 1071 | // Don't trust child data; if the data is outside the expected range, ignore the data. | 1171 | main_storage.copyRoot(&storage[0]); |
| 1072 | // This also handles the case when data was truncated. | 1172 | if (is_big_endian) main_storage.byteSwap(); |
| 1073 | _ => |off| if (@intFromEnum(off) > nodes_len) | 1173 | |
| 1074 | .none | 1174 | // Copy the rest of the tree to the end. |
| 1075 | else | 1175 | const serialized_storage = |
| 1076 | @enumFromInt(serialized_len + @intFromEnum(off) - 1), | 1176 | serialized_buffer.storage[serialized_len..][0..nodes_len]; |
| 1077 | }; | 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; | 1210 | serialized_buffer.ipc_end = serialized_len; |
| 1110 | } | 1211 | |
| 1111 | 1212 | // Ignore data from unused pipes. This ensures that if a child process exists we will | |
| 1112 | fn useSavedIpcData( | 1213 | // eventually see `EndOfStream` and close the pipe. |
| 1113 | start_serialized_len: usize, | 1214 | if (ready_len > 0) for ( |
| 1114 | serialized_buffer: *Serialized.Buffer, | 1215 | &serialized_buffer.ipc_data, |
| 1115 | main_storage: *Node.Storage, | 1216 | &serialized_buffer.ipc_buffers, |
| 1116 | main_index: usize, | 1217 | &serialized_buffer.ipc_vecs, |
| 1117 | opt_saved_metadata: ?*SavedMetadata, | 1218 | 0.., |
| 1118 | remaining_read_trash_bytes: u16, | 1219 | ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) { |
| 1119 | fd: Io.File.Handle, | 1220 | .unused, .pending => {}, |
| 1120 | ) usize { | 1221 | .ready => { |
| 1121 | const parents_copy = &serialized_buffer.parents_copy; | 1222 | _, const packet_end = ipc_data.findLastPacket(ipc_buffer); |
| 1122 | const storage_copy = &serialized_buffer.storage_copy; | 1223 | ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end); |
| 1123 | const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; | 1224 | ready_len -= 1; |
| 1124 | const ipc_metadata = &serialized_buffer.ipc_metadata; | 1225 | }, |
| 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; | ||
| 1140 | }; | 1226 | }; |
| 1227 | assert(ready_len == 0); | ||
| 1141 | 1228 | ||
| 1142 | const start_index = saved_metadata.start_index; | 1229 | return .{ |
| 1143 | const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len); | 1230 | .parents = serialized_buffer.parents[0..serialized_len], |
| 1144 | const old_main_index = saved_metadata.main_index; | 1231 | .storage = serialized_buffer.storage[0..serialized_len], |
| 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), | ||
| 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 } { | 1235 | fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } { |
| 1181 | const serialized = serialize(serialized_buffer); | 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 | // Now we can analyze our copy of the graph without atomics, reconstructing | 1240 | // Now we can analyze our copy of the graph without atomics, reconstructing |
| 1184 | // children lists which do not exist in the canonical data. These are | 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,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { |
| 1413 | return nl_n + 2 < p.rows; | 1470 | return nl_n + 2 < p.rows; |
| 1414 | } | 1471 | } |
| 1415 | 1472 | ||
| 1416 | var remaining_write_trash_bytes: usize = 0; | 1473 | fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void { |
| 1417 | |||
| 1418 | fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { | ||
| 1419 | // Byteswap if necessary to ensure little endian over the pipe. This is | 1474 | // Byteswap if necessary to ensure little endian over the pipe. This is |
| 1420 | // needed because the parent or child process might be running in qemu. | 1475 | // needed because the parent or child process might be running in qemu. |
| 1421 | if (is_big_endian) for (serialized.storage) |*s| s.byteSwap(); | 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,62 +1481,8 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi |
| 1426 | const storage = std.mem.sliceAsBytes(serialized.storage); | 1481 | const storage = std.mem.sliceAsBytes(serialized.storage); |
| 1427 | const parents = std.mem.sliceAsBytes(serialized.parents); | 1482 | const parents = std.mem.sliceAsBytes(serialized.parents); |
| 1428 | 1483 | ||
| 1429 | var vecs: [3][]const u8 = .{ header, storage, parents }; | 1484 | var vec = [3][]const u8{ header, storage, parents }; |
| 1430 | 1485 | try writer.writeVecAll(&vec); | |
| 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 | } | ||
| 1485 | } | 1486 | } |
| 1486 | 1487 | ||
| 1487 | fn maybeUpdateSize(io: Io, resize_flag: bool) !void { | 1488 | fn maybeUpdateSize(io: Io, resize_flag: bool) !void { |
lib/std/Thread.zig+5-1| ... | @@ -598,7 +598,11 @@ const WindowsThreadImpl = struct { | ... | @@ -598,7 +598,11 @@ const WindowsThreadImpl = struct { |
| 598 | } | 598 | } |
| 599 | 599 | ||
| 600 | fn join(self: Impl) void { | 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 | windows.CloseHandle(self.thread.thread_handle); | 606 | windows.CloseHandle(self.thread.thread_handle); |
| 603 | assert(self.thread.completion.load(.seq_cst) == .completed); | 607 | assert(self.thread.completion.load(.seq_cst) == .completed); |
| 604 | self.thread.free(); | 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,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T { |
| 452 | return new_buf; | 452 | return new_buf; |
| 453 | } | 453 | } |
| 454 | 454 | ||
| 455 | /// Deprecated in favor of `dupeSentinel` | ||
| 455 | /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. | 456 | /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. |
| 456 | pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T { | 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 | const new_buf = try allocator.alloc(T, m.len + 1); | 468 | const new_buf = try allocator.alloc(T, m.len + 1); |
| 458 | @memcpy(new_buf[0..m.len], m); | 469 | @memcpy(new_buf[0..m.len], m); |
| 459 | new_buf[m.len] = 0; | 470 | new_buf[m.len] = sentinel; |
| 460 | return new_buf[0..m.len :0]; | 471 | return new_buf[0..m.len :sentinel]; |
| 461 | } | 472 | } |
| 462 | 473 | ||
| 463 | /// An allocator that always fails to allocate. | 474 | /// An allocator that always fails to allocate. |
lib/std/os/linux.zig+18| ... | @@ -1848,6 +1848,24 @@ pub const F = struct { | ... | @@ -1848,6 +1848,24 @@ pub const F = struct { |
| 1848 | pub const RDLCK = if (is_sparc) 1 else 0; | 1848 | pub const RDLCK = if (is_sparc) 1 else 0; |
| 1849 | pub const WRLCK = if (is_sparc) 2 else 1; | 1849 | pub const WRLCK = if (is_sparc) 2 else 1; |
| 1850 | pub const UNLCK = if (is_sparc) 3 else 2; | 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 | pub const F_OWNER = enum(i32) { | 1871 | pub const F_OWNER = enum(i32) { |
lib/std/os/windows.zig+19-250| ... | @@ -521,7 +521,7 @@ pub const FILE = struct { | ... | @@ -521,7 +521,7 @@ pub const FILE = struct { |
| 521 | _, | 521 | _, |
| 522 | 522 | ||
| 523 | pub const VALID_FLAGS: @This() = @enumFromInt(0b11); | 523 | pub const VALID_FLAGS: @This() = @enumFromInt(0b11); |
| 524 | } = .ASYNCHRONOUS, | 524 | }, |
| 525 | /// The file being opened must not be a directory file or this call | 525 | /// The file being opened must not be a directory file or this call |
| 526 | /// fails. The file object being opened can represent a data file, a | 526 | /// fails. The file object being opened can represent a data file, a |
| 527 | /// logical, virtual, or physical device, or a volume. | 527 | /// logical, virtual, or physical device, or a volume. |
| ... | @@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP { | ... | @@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP { |
| 2324 | // ref: um/winternl.h | 2324 | // ref: um/winternl.h |
| 2325 | 2325 | ||
| 2326 | pub const OBJECT_ATTRIBUTES = extern struct { | 2326 | pub const OBJECT_ATTRIBUTES = extern struct { |
| 2327 | Length: ULONG, | 2327 | Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES), |
| 2328 | RootDirectory: ?HANDLE, | 2328 | RootDirectory: ?HANDLE = null, |
| 2329 | ObjectName: ?*UNICODE_STRING, | 2329 | ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty), |
| 2330 | Attributes: ATTRIBUTES, | 2330 | Attributes: ATTRIBUTES = .{}, |
| 2331 | SecurityDescriptor: ?*anyopaque, | 2331 | SecurityDescriptor: ?*anyopaque = null, |
| 2332 | SecurityQualityOfService: ?*anyopaque, | 2332 | SecurityQualityOfService: ?*anyopaque = null, |
| 2333 | 2333 | ||
| 2334 | // Valid values for the Attributes field | 2334 | // Valid values for the Attributes field |
| 2335 | pub const ATTRIBUTES = packed struct(ULONG) { | 2335 | pub const ATTRIBUTES = packed struct(ULONG) { |
| ... | @@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 2420 | .Buffer = @constCast(sub_path_w.ptr), | 2420 | .Buffer = @constCast(sub_path_w.ptr), |
| 2421 | }; | 2421 | }; |
| 2422 | const attr: OBJECT_ATTRIBUTES = .{ | 2422 | const attr: OBJECT_ATTRIBUTES = .{ |
| 2423 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | ||
| 2424 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, | 2423 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, |
| 2425 | .Attributes = .{ | 2424 | .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false }, |
| 2426 | .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false, | ||
| 2427 | }, | ||
| 2428 | .ObjectName = &nt_name, | 2425 | .ObjectName = &nt_name, |
| 2429 | .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, | 2426 | .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, |
| 2430 | .SecurityQualityOfService = null, | ||
| 2431 | }; | 2427 | }; |
| 2432 | var io: IO_STATUS_BLOCK = undefined; | 2428 | var io: IO_STATUS_BLOCK = undefined; |
| 2433 | while (true) { | 2429 | while (true) { |
| ... | @@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 2475 | // call has failed. There is not really a sane way to handle | 2471 | // call has failed. There is not really a sane way to handle |
| 2476 | // this other than retrying the creation after the OS finishes | 2472 | // this other than retrying the creation after the OS finishes |
| 2477 | // the deletion. | 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 | continue; | 2476 | continue; |
| 2480 | }, | 2477 | }, |
| 2481 | .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference, | 2478 | .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference, |
| ... | @@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD { | ... | @@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD { |
| 2506 | pub fn GetLastError() Win32Error { | 2503 | pub fn GetLastError() Win32Error { |
| 2507 | return @enumFromInt(teb().LastErrorValue); | 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 | /// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. | 2506 | /// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. |
| 2655 | /// It implements similar behavior to `DeviceIoControl` and is meant to serve | 2507 | /// It implements similar behavior to `DeviceIoControl` and is meant to serve |
| 2656 | /// as a direct substitute for that call. | 2508 | /// as a direct substitute for that call. |
| ... | @@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR | ... | @@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR |
| 2707 | return bytes; | 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 | pub const CreateIoCompletionPortError = error{Unexpected}; | 2562 | pub const CreateIoCompletionPortError = error{Unexpected}; |
| 2771 | 2563 | ||
| 2772 | pub fn CreateIoCompletionPort( | 2564 | pub fn CreateIoCompletionPort( |
| ... | @@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void { | ... | @@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void { |
| 2878 | assert(ntdll.NtClose(hObject) == .SUCCESS); | 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 | pub const QueryObjectNameError = error{ | 2673 | pub const QueryObjectNameError = error{ |
| 2897 | AccessDenied, | 2674 | AccessDenied, |
| 2898 | InvalidHandle, | 2675 | InvalidHandle, |
| ... | @@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME { | ... | @@ -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 | /// Compares two WTF16 strings using the equivalent functionality of | 3331 | /// Compares two WTF16 strings using the equivalent functionality of |
| 3549 | /// `RtlEqualUnicodeString` (with case insensitive comparison enabled). | 3332 | /// `RtlEqualUnicodeString` (with case insensitive comparison enabled). |
| 3550 | /// This function can be called on any target. | 3333 | /// This function can be called on any target. |
| ... | @@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool { | ... | @@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool { |
| 3598 | var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator(); | 3381 | var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator(); |
| 3599 | var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator(); | 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 | while (true) { | 3384 | while (true) { |
| 3609 | const a_cp = a_wtf8_it.nextCodepoint() orelse break; | 3385 | const a_cp = a_wtf8_it.nextCodepoint() orelse break; |
| 3610 | const b_cp = b_wtf8_it.nextCodepoint() orelse return false; | 3386 | const b_cp = b_wtf8_it.nextCodepoint() orelse return false; |
| 3611 | 3387 | ||
| 3612 | if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) { | 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 | return false; | 3390 | return false; |
| 3615 | } | 3391 | } |
| 3616 | } else if (a_cp != b_cp) { | 3392 | } else if (a_cp != b_cp) { |
| ... | @@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error; | ... | @@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error; |
| 4098 | pub const LANG = @import("windows/lang.zig"); | 3874 | pub const LANG = @import("windows/lang.zig"); |
| 4099 | pub const SUBLANG = @import("windows/sublang.zig"); | 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 | pub const BOOL = c_int; | 3877 | pub const BOOL = c_int; |
| 4111 | pub const BOOLEAN = BYTE; | 3878 | pub const BOOLEAN = BYTE; |
| 4112 | pub const BYTE = u8; | 3879 | pub const BYTE = u8; |
| ... | @@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct { | ... | @@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct { |
| 5244 | Length: c_ushort, | 5011 | Length: c_ushort, |
| 5245 | MaximumLength: c_ushort, | 5012 | MaximumLength: c_ushort, |
| 5246 | Buffer: ?[*]WCHAR, | 5013 | Buffer: ?[*]WCHAR, |
| 5014 | |||
| 5015 | pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null }; | ||
| 5247 | }; | 5016 | }; |
| 5248 | 5017 | ||
| 5249 | pub const ACTIVATION_CONTEXT_DATA = opaque {}; | 5018 | pub const ACTIVATION_CONTEXT_DATA = opaque {}; |
lib/std/os/windows/kernel32.zig-108| ... | @@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME; | ... | @@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME; |
| 12 | const HANDLE = windows.HANDLE; | 12 | const HANDLE = windows.HANDLE; |
| 13 | const HANDLER_ROUTINE = windows.HANDLER_ROUTINE; | 13 | const HANDLER_ROUTINE = windows.HANDLER_ROUTINE; |
| 14 | const HMODULE = windows.HMODULE; | 14 | const HMODULE = windows.HMODULE; |
| 15 | const INIT_ONCE = windows.INIT_ONCE; | ||
| 16 | const INIT_ONCE_FN = windows.INIT_ONCE_FN; | ||
| 17 | const LARGE_INTEGER = windows.LARGE_INTEGER; | 15 | const LARGE_INTEGER = windows.LARGE_INTEGER; |
| 18 | const LPCSTR = windows.LPCSTR; | 16 | const LPCSTR = windows.LPCSTR; |
| 19 | const LPCVOID = windows.LPCVOID; | 17 | const LPCVOID = windows.LPCVOID; |
| ... | @@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR; | ... | @@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR; |
| 24 | const MODULEENTRY32 = windows.MODULEENTRY32; | 22 | const MODULEENTRY32 = windows.MODULEENTRY32; |
| 25 | const OVERLAPPED = windows.OVERLAPPED; | 23 | const OVERLAPPED = windows.OVERLAPPED; |
| 26 | const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY; | 24 | const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY; |
| 27 | const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION; | ||
| 28 | const PROCESS_INFORMATION = windows.PROCESS_INFORMATION; | 25 | const PROCESS_INFORMATION = windows.PROCESS_INFORMATION; |
| 29 | const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES; | 26 | const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES; |
| 30 | const SIZE_T = windows.SIZE_T; | 27 | const SIZE_T = windows.SIZE_T; |
| ... | @@ -37,7 +34,6 @@ const ULONG = windows.ULONG; | ... | @@ -37,7 +34,6 @@ const ULONG = windows.ULONG; |
| 37 | const ULONG_PTR = windows.ULONG_PTR; | 34 | const ULONG_PTR = windows.ULONG_PTR; |
| 38 | const va_list = windows.va_list; | 35 | const va_list = windows.va_list; |
| 39 | const WCHAR = windows.WCHAR; | 36 | const WCHAR = windows.WCHAR; |
| 40 | const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW; | ||
| 41 | const Win32Error = windows.Win32Error; | 37 | const Win32Error = windows.Win32Error; |
| 42 | const WORD = windows.WORD; | 38 | const WORD = windows.WORD; |
| 43 | 39 | ||
| ... | @@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo( | ... | @@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo( |
| 59 | hFile: HANDLE, | 55 | hFile: HANDLE, |
| 60 | ) callconv(.winapi) BOOL; | 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 | // TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`. | 58 | // TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`. |
| 96 | // `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END` | 59 | // `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END` |
| 97 | pub extern "kernel32" fn SetFilePointerEx( | 60 | pub extern "kernel32" fn SetFilePointerEx( |
| ... | @@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile( | ... | @@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile( |
| 117 | in_out_lpOverlapped: ?*OVERLAPPED, | 80 | in_out_lpOverlapped: ?*OVERLAPPED, |
| 118 | ) callconv(.winapi) BOOL; | 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 | // TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`. | 83 | // TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`. |
| 126 | pub extern "kernel32" fn SetFileCompletionNotificationModes( | 84 | pub extern "kernel32" fn SetFileCompletionNotificationModes( |
| 127 | FileHandle: HANDLE, | 85 | FileHandle: HANDLE, |
| ... | @@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW( | ... | @@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW( |
| 143 | 101 | ||
| 144 | // I/O - Kernel Objects | 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 | // TODO: Wrapper around NtRemoveIoCompletion. | 104 | // TODO: Wrapper around NtRemoveIoCompletion. |
| 165 | pub extern "kernel32" fn GetQueuedCompletionStatus( | 105 | pub extern "kernel32" fn GetQueuedCompletionStatus( |
| 166 | CompletionPort: HANDLE, | 106 | CompletionPort: HANDLE, |
| ... | @@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess( | ... | @@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess( |
| 210 | uExitCode: UINT, | 150 | uExitCode: UINT, |
| 211 | ) callconv(.winapi) BOOL; | 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 | // Process Management | 153 | // Process Management |
| 245 | 154 | ||
| 246 | pub extern "kernel32" fn CreateProcessW( | 155 | pub extern "kernel32" fn CreateProcessW( |
| ... | @@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW( | ... | @@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW( |
| 256 | lpProcessInformation: *PROCESS_INFORMATION, | 165 | lpProcessInformation: *PROCESS_INFORMATION, |
| 257 | ) callconv(.winapi) BOOL; | 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 | // TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`. | 168 | // TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`. |
| 266 | pub extern "kernel32" fn GetExitCodeProcess( | 169 | pub extern "kernel32" fn GetExitCodeProcess( |
| 267 | hProcess: HANDLE, | 170 | hProcess: HANDLE, |
| ... | @@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW( | ... | @@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW( |
| 436 | 339 | ||
| 437 | // TODO: Getter for teb().LastErrorValue. | 340 | // TODO: Getter for teb().LastErrorValue. |
| 438 | pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error; | 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,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile( |
| 407 | DefaultTimeout: ?*const LARGE_INTEGER, | 407 | DefaultTimeout: ?*const LARGE_INTEGER, |
| 408 | ) callconv(.winapi) NTSTATUS; | 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 | pub extern "ntdll" fn NtMapViewOfSection( | 415 | pub extern "ntdll" fn NtMapViewOfSection( |
| 411 | SectionHandle: HANDLE, | 416 | SectionHandle: HANDLE, |
| 412 | ProcessHandle: HANDLE, | 417 | ProcessHandle: HANDLE, |
| ... | @@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread( | ... | @@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread( |
| 590 | 595 | ||
| 591 | pub extern "ntdll" fn NtCancelSynchronousIoFile( | 596 | pub extern "ntdll" fn NtCancelSynchronousIoFile( |
| 592 | ThreadHandle: HANDLE, | 597 | ThreadHandle: HANDLE, |
| 593 | RequestToCancel: ?*IO_STATUS_BLOCK, | 598 | IoRequestToCancel: ?*IO_STATUS_BLOCK, |
| 594 | IoStatusBlock: *IO_STATUS_BLOCK, | 599 | IoStatusBlock: *IO_STATUS_BLOCK, |
| 595 | ) callconv(.winapi) NTSTATUS; | 600 | ) callconv(.winapi) NTSTATUS; |
| 596 | 601 | ||
| ... | @@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution( | ... | @@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution( |
| 606 | DelayInterval: *const LARGE_INTEGER, | 611 | DelayInterval: *const LARGE_INTEGER, |
| 607 | ) callconv(.winapi) NTSTATUS; | 612 | ) callconv(.winapi) NTSTATUS; |
| 608 | 613 | ||
| 609 | pub extern "ntdll" fn NtCancelIoFileEx( | 614 | pub extern "ntdll" fn NtCancelIoFile( |
| 610 | FileHandle: HANDLE, | 615 | FileHandle: HANDLE, |
| 611 | IoRequestToCancel: *const IO_STATUS_BLOCK, | ||
| 612 | IoStatusBlock: *IO_STATUS_BLOCK, | 616 | IoStatusBlock: *IO_STATUS_BLOCK, |
| 613 | ) callconv(.winapi) NTSTATUS; | 617 | ) callconv(.winapi) NTSTATUS; |
| 614 | 618 | ||
| 615 | pub extern "ntdll" fn NtCancelIoFile( | 619 | pub extern "ntdll" fn NtCancelIoFileEx( |
| 616 | FileHandle: HANDLE, | 620 | FileHandle: HANDLE, |
| 621 | IoRequestToCancel: *const IO_STATUS_BLOCK, | ||
| 617 | IoStatusBlock: *IO_STATUS_BLOCK, | 622 | IoStatusBlock: *IO_STATUS_BLOCK, |
| 618 | ) callconv(.winapi) NTSTATUS; | 623 | ) callconv(.winapi) NTSTATUS; |
lib/std/process/Environ.zig+438-272| ... | @@ -4,7 +4,7 @@ const builtin = @import("builtin"); | ... | @@ -4,7 +4,7 @@ const builtin = @import("builtin"); |
| 4 | const native_os = builtin.os.tag; | 4 | const native_os = builtin.os.tag; |
| 5 | 5 | ||
| 6 | const std = @import("../std.zig"); | 6 | const std = @import("../std.zig"); |
| 7 | const Allocator = std.mem.Allocator; | 7 | const Allocator = mem.Allocator; |
| 8 | const assert = std.debug.assert; | 8 | const assert = std.debug.assert; |
| 9 | const testing = std.testing; | 9 | const testing = std.testing; |
| 10 | const unicode = std.unicode; | 10 | const unicode = std.unicode; |
| ... | @@ -14,12 +14,7 @@ const mem = std.mem; | ... | @@ -14,12 +14,7 @@ const mem = std.mem; |
| 14 | /// Unmodified, unprocessed data provided by the operating system. | 14 | /// Unmodified, unprocessed data provided by the operating system. |
| 15 | block: Block, | 15 | block: Block, |
| 16 | 16 | ||
| 17 | pub const empty: Environ = .{ | 17 | pub const empty: Environ = .{ .block = .empty }; |
| 18 | .block = switch (Block) { | ||
| 19 | void => {}, | ||
| 20 | else => &.{}, | ||
| 21 | }, | ||
| 22 | }; | ||
| 23 | 18 | ||
| 24 | /// On WASI without libc, this is `void` because the environment has to be | 19 | /// On WASI without libc, this is `void` because the environment has to be |
| 25 | /// queried and heap-allocated at runtime. | 20 | /// queried and heap-allocated at runtime. |
| ... | @@ -28,13 +23,65 @@ pub const empty: Environ = .{ | ... | @@ -28,13 +23,65 @@ pub const empty: Environ = .{ |
| 28 | /// is modified, so a long-lived pointer cannot be used. Therefore, on this | 23 | /// is modified, so a long-lived pointer cannot be used. Therefore, on this |
| 29 | /// operating system `void` is also used. | 24 | /// operating system `void` is also used. |
| 30 | pub const Block = switch (native_os) { | 25 | pub const Block = switch (native_os) { |
| 31 | .windows => void, | 26 | .windows => GlobalBlock, |
| 32 | .wasi => switch (builtin.link_libc) { | 27 | .wasi => switch (builtin.link_libc) { |
| 33 | false => void, | 28 | false => GlobalBlock, |
| 34 | true => [:null]const ?[*:0]const u8, | 29 | true => PosixBlock, |
| 35 | }, | 30 | }, |
| 36 | .freestanding, .other => void, | 31 | .freestanding, .other => GlobalBlock, |
| 37 | else => [:null]const ?[*:0]const u8, | 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 | pub const Map = struct { | 87 | pub const Map = struct { |
| ... | @@ -46,47 +93,60 @@ pub const Map = struct { | ... | @@ -46,47 +93,60 @@ pub const Map = struct { |
| 46 | pub const Size = usize; | 93 | pub const Size = usize; |
| 47 | 94 | ||
| 48 | pub const EnvNameHashContext = struct { | 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 | pub fn hash(self: @This(), s: []const u8) u32 { | 96 | pub fn hash(self: @This(), s: []const u8) u32 { |
| 56 | _ = self; | 97 | _ = self; |
| 57 | if (native_os == .windows) { | 98 | switch (native_os) { |
| 58 | var h = std.hash.Wyhash.init(0); | 99 | else => return std.array_hash_map.hashString(s), |
| 59 | var it = unicode.Wtf8View.initUnchecked(s).iterator(); | 100 | .windows => { |
| 60 | while (it.nextCodepoint()) |cp| { | 101 | var h = std.hash.Wyhash.init(0); |
| 61 | const cp_upper = upcase(cp); | 102 | var it = unicode.Wtf8View.initUnchecked(s).iterator(); |
| 62 | h.update(&[_]u8{ | 103 | while (it.nextCodepoint()) |cp| { |
| 63 | @as(u8, @intCast((cp_upper >> 16) & 0xff)), | 104 | const cp_upper = if (std.math.cast(u16, cp)) |wtf16| |
| 64 | @as(u8, @intCast((cp_upper >> 8) & 0xff)), | 105 | std.os.windows.toUpperWtf16(wtf16) |
| 65 | @as(u8, @intCast((cp_upper >> 0) & 0xff)), | 106 | else |
| 66 | }); | 107 | cp; |
| 67 | } | 108 | h.update(&[_]u8{ |
| 68 | return @truncate(h.final()); | 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 | pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool { | 119 | pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool { |
| 74 | _ = self; | 120 | _ = self; |
| 75 | _ = b_index; | 121 | _ = b_index; |
| 76 | if (native_os == .windows) { | 122 | return eqlKeys(a, b); |
| 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); | ||
| 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 | /// Create a Map backed by a specific allocator. | 151 | /// Create a Map backed by a specific allocator. |
| 92 | /// That allocator will be used for both backing allocations | 152 | /// That allocator will be used for both backing allocations |
| ... | @@ -99,30 +159,71 @@ pub const Map = struct { | ... | @@ -99,30 +159,71 @@ pub const Map = struct { |
| 99 | /// of the stored keys and values. | 159 | /// of the stored keys and values. |
| 100 | pub fn deinit(self: *Map) void { | 160 | pub fn deinit(self: *Map) void { |
| 101 | const gpa = self.allocator; | 161 | const gpa = self.allocator; |
| 102 | var it = self.array_hash_map.iterator(); | 162 | for (self.keys()) |key| gpa.free(key); |
| 103 | while (it.next()) |entry| { | 163 | for (self.values()) |value| gpa.free(value); |
| 104 | gpa.free(entry.key_ptr.*); | ||
| 105 | gpa.free(entry.value_ptr.*); | ||
| 106 | } | ||
| 107 | self.array_hash_map.deinit(gpa); | 164 | self.array_hash_map.deinit(gpa); |
| 108 | self.* = undefined; | 165 | self.* = undefined; |
| 109 | } | 166 | } |
| 110 | 167 | ||
| 111 | pub fn keys(m: *const Map) [][]const u8 { | 168 | pub fn keys(map: *const Map) [][]const u8 { |
| 112 | return m.array_hash_map.keys(); | 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 { | 190 | pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void { |
| 116 | return m.array_hash_map.values(); | 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 | /// Same as `put` but the key and value become owned by the Map rather | 220 | /// Same as `put` but the key and value become owned by the Map rather |
| 120 | /// than being copied. | 221 | /// than being copied. |
| 121 | /// If `putMove` fails, the ownership of key and value does not transfer. | 222 | /// If `putMove` fails, the ownership of key and value does not transfer. |
| 122 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | 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 | const gpa = self.allocator; | 226 | const gpa = self.allocator; |
| 125 | assert(unicode.wtf8ValidateSlice(key)); | ||
| 126 | const get_or_put = try self.array_hash_map.getOrPut(gpa, key); | 227 | const get_or_put = try self.array_hash_map.getOrPut(gpa, key); |
| 127 | if (get_or_put.found_existing) { | 228 | if (get_or_put.found_existing) { |
| 128 | gpa.free(get_or_put.key_ptr.*); | 229 | gpa.free(get_or_put.key_ptr.*); |
| ... | @@ -134,8 +235,8 @@ pub const Map = struct { | ... | @@ -134,8 +235,8 @@ pub const Map = struct { |
| 134 | 235 | ||
| 135 | /// `key` and `value` are copied into the Map. | 236 | /// `key` and `value` are copied into the Map. |
| 136 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | 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 { | 238 | pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void { |
| 138 | assert(unicode.wtf8ValidateSlice(key)); | 239 | assert(validateKey(key)); |
| 139 | const gpa = self.allocator; | 240 | const gpa = self.allocator; |
| 140 | const value_copy = try gpa.dupe(u8, value); | 241 | const value_copy = try gpa.dupe(u8, value); |
| 141 | errdefer gpa.free(value_copy); | 242 | errdefer gpa.free(value_copy); |
| ... | @@ -155,7 +256,7 @@ pub const Map = struct { | ... | @@ -155,7 +256,7 @@ pub const Map = struct { |
| 155 | /// The returned pointer is invalidated if the map resizes. | 256 | /// The returned pointer is invalidated if the map resizes. |
| 156 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | 257 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 157 | pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 { | 258 | pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 { |
| 158 | assert(unicode.wtf8ValidateSlice(key)); | 259 | assert(validateKey(key)); |
| 159 | return self.array_hash_map.getPtr(key); | 260 | return self.array_hash_map.getPtr(key); |
| 160 | } | 261 | } |
| 161 | 262 | ||
| ... | @@ -164,11 +265,12 @@ pub const Map = struct { | ... | @@ -164,11 +265,12 @@ pub const Map = struct { |
| 164 | /// key is removed from the map. | 265 | /// key is removed from the map. |
| 165 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | 266 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 166 | pub fn get(self: Map, key: []const u8) ?[]const u8 { | 267 | pub fn get(self: Map, key: []const u8) ?[]const u8 { |
| 167 | assert(unicode.wtf8ValidateSlice(key)); | 268 | assert(validateKey(key)); |
| 168 | return self.array_hash_map.get(key); | 269 | return self.array_hash_map.get(key); |
| 169 | } | 270 | } |
| 170 | 271 | ||
| 171 | pub fn contains(m: *const Map, key: []const u8) bool { | 272 | pub fn contains(m: *const Map, key: []const u8) bool { |
| 273 | assert(validateKey(key)); | ||
| 172 | return m.array_hash_map.contains(key); | 274 | return m.array_hash_map.contains(key); |
| 173 | } | 275 | } |
| 174 | 276 | ||
| ... | @@ -181,7 +283,7 @@ pub const Map = struct { | ... | @@ -181,7 +283,7 @@ pub const Map = struct { |
| 181 | /// This invalidates the value returned by get() for this key. | 283 | /// This invalidates the value returned by get() for this key. |
| 182 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | 284 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 183 | pub fn swapRemove(self: *Map, key: []const u8) bool { | 285 | pub fn swapRemove(self: *Map, key: []const u8) bool { |
| 184 | assert(unicode.wtf8ValidateSlice(key)); | 286 | assert(validateKey(key)); |
| 185 | const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false; | 287 | const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false; |
| 186 | const gpa = self.allocator; | 288 | const gpa = self.allocator; |
| 187 | gpa.free(kv.key); | 289 | gpa.free(kv.key); |
| ... | @@ -198,7 +300,7 @@ pub const Map = struct { | ... | @@ -198,7 +300,7 @@ pub const Map = struct { |
| 198 | /// This invalidates the value returned by get() for this key. | 300 | /// This invalidates the value returned by get() for this key. |
| 199 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. | 301 | /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. |
| 200 | pub fn orderedRemove(self: *Map, key: []const u8) bool { | 302 | pub fn orderedRemove(self: *Map, key: []const u8) bool { |
| 201 | assert(unicode.wtf8ValidateSlice(key)); | 303 | assert(validateKey(key)); |
| 202 | const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false; | 304 | const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false; |
| 203 | const gpa = self.allocator; | 305 | const gpa = self.allocator; |
| 204 | gpa.free(kv.key); | 306 | gpa.free(kv.key); |
| ... | @@ -233,105 +335,120 @@ pub const Map = struct { | ... | @@ -233,105 +335,120 @@ pub const Map = struct { |
| 233 | 335 | ||
| 234 | /// Creates a null-delimited environment variable block in the format | 336 | /// Creates a null-delimited environment variable block in the format |
| 235 | /// expected by POSIX, from a hash map plus options. | 337 | /// expected by POSIX, from a hash map plus options. |
| 236 | pub fn createBlockPosix( | 338 | pub fn createPosixBlock( |
| 237 | map: *const Map, | 339 | map: *const Map, |
| 238 | arena: Allocator, | 340 | gpa: Allocator, |
| 239 | options: CreateBlockPosixOptions, | 341 | options: CreatePosixBlockOptions, |
| 240 | ) Allocator.Error![:null]?[*:0]u8 { | 342 | ) Allocator.Error!PosixBlock { |
| 241 | const ZigProgressAction = enum { nothing, edit, delete, add }; | 343 | const ZigProgressAction = enum { nothing, edit, delete, add }; |
| 242 | const zig_progress_action: ZigProgressAction = a: { | 344 | const zig_progress_action: ZigProgressAction = action: { |
| 243 | const fd = options.zig_progress_fd orelse break :a .nothing; | 345 | const fd = options.zig_progress_fd orelse break :action .nothing; |
| 244 | const exists = map.get("ZIG_PROGRESS") != null; | 346 | const exists = map.contains("ZIG_PROGRESS"); |
| 245 | if (fd >= 0) { | 347 | if (fd >= 0) { |
| 246 | break :a if (exists) .edit else .add; | 348 | break :action if (exists) .edit else .add; |
| 247 | } else { | 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: { | 355 | const envp = try gpa.allocSentinel(?[*:0]u8, len: { |
| 254 | var c: usize = map.count(); | 356 | var len: usize = map.count(); |
| 255 | switch (zig_progress_action) { | 357 | switch (zig_progress_action) { |
| 256 | .add => c += 1, | 358 | .add => len += 1, |
| 257 | .delete => c -= 1, | 359 | .delete => len -= 1, |
| 258 | .nothing, .edit => {}, | 360 | .nothing, .edit => {}, |
| 259 | } | 361 | } |
| 260 | break :c c; | 362 | break :len len; |
| 261 | }; | 363 | }, null); |
| 262 | 364 | var envp_len: usize = 0; | |
| 263 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | 365 | errdefer { |
| 264 | var i: usize = 0; | 366 | envp[envp_len] = null; |
| 367 | PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa); | ||
| 368 | } | ||
| 265 | 369 | ||
| 266 | if (zig_progress_action == .add) { | 370 | if (zig_progress_action == .add) { |
| 267 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | 371 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); |
| 268 | i += 1; | 372 | envp_len += 1; |
| 269 | } | 373 | } |
| 270 | 374 | ||
| 271 | { | 375 | for (map.keys(), map.values()) |key, value| { |
| 272 | var it = map.iterator(); | 376 | if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) { |
| 273 | while (it.next()) |pair| { | 377 | .add => unreachable, |
| 274 | if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { | 378 | .delete => continue, |
| 275 | .add => unreachable, | 379 | .edit => { |
| 276 | .delete => continue, | 380 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{ |
| 277 | .edit => { | 381 | key, options.zig_progress_fd.?, |
| 278 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{ | 382 | }, 0); |
| 279 | pair.key_ptr.*, options.zig_progress_fd.?, | 383 | envp_len += 1; |
| 280 | }, 0); | 384 | continue; |
| 281 | i += 1; | 385 | }, |
| 282 | continue; | 386 | .nothing => {}, |
| 283 | }, | 387 | }; |
| 284 | .nothing => {}, | 388 | |
| 285 | }; | 389 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0); |
| 286 | 390 | envp_len += 1; | |
| 287 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); | ||
| 288 | i += 1; | ||
| 289 | } | ||
| 290 | } | 391 | } |
| 291 | 392 | ||
| 292 | assert(i == envp_count); | 393 | assert(envp_len == envp.len); |
| 293 | return envp_buf; | 394 | return .{ .slice = envp }; |
| 294 | } | 395 | } |
| 295 | 396 | ||
| 296 | /// Caller owns result. | 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 | // count bytes needed | 403 | // count bytes needed |
| 299 | const max_chars_needed = x: { | 404 | const max_chars_needed = max_chars_needed: { |
| 300 | // Only need 2 trailing NUL code units for an empty environment | 405 | var max_chars_needed: usize = "\x00".len; |
| 301 | var max_chars_needed: usize = if (map.count() == 0) 2 else 1; | 406 | if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { |
| 302 | var it = map.iterator(); | 407 | max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)}); |
| 303 | while (it.next()) |pair| { | 408 | }; |
| 304 | // +1 for '=' | 409 | for (map.keys(), map.values()) |key, value| { |
| 305 | // +1 for null byte | 410 | if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue; |
| 306 | max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; | 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); | 415 | const block = try gpa.alloc(u16, max_chars_needed); |
| 311 | errdefer gpa.free(result); | 416 | errdefer gpa.free(block); |
| 312 | 417 | ||
| 313 | var it = map.iterator(); | ||
| 314 | var i: usize = 0; | 418 | var i: usize = 0; |
| 315 | while (it.next()) |pair| { | 419 | if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { |
| 316 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); | 420 | @memcpy( |
| 317 | result[i] = '='; | 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 | i += 1; | 430 | i += 1; |
| 319 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); | 431 | }; |
| 320 | result[i] = 0; | 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 | i += 1; | 439 | i += 1; |
| 322 | } | 440 | } |
| 323 | result[i] = 0; | ||
| 324 | i += 1; | ||
| 325 | // An empty environment is a special case that requires a redundant | 441 | // An empty environment is a special case that requires a redundant |
| 326 | // NUL terminator. CreateProcess will read the second code unit even | 442 | // NUL terminator. CreateProcess will read the second code unit even |
| 327 | // though theoretically the first should be enough to recognize that the | 443 | // though theoretically the first should be enough to recognize that the |
| 328 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) | 444 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) |
| 329 | if (map.count() == 0) { | 445 | for (0..2) |_| { |
| 330 | result[i] = 0; | 446 | block[i] = 0; |
| 331 | i += 1; | 447 | i += 1; |
| 332 | } | 448 | if (i >= 2) break; |
| 333 | const reallocated = try gpa.realloc(result, i); | 449 | } else unreachable; |
| 334 | return reallocated[0 .. i - 1 :0]; | 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,13 +461,18 @@ pub const CreateMapError = error{ |
| 344 | 461 | ||
| 345 | /// Allocates a `Map` and copies environment block into it. | 462 | /// Allocates a `Map` and copies environment block into it. |
| 346 | pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { | 463 | pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 347 | if (native_os == .windows) | 464 | var map = Map.init(allocator); |
| 348 | return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator); | 465 | errdefer map.deinit(); |
| 349 | 466 | if (native_os == .windows) empty: { | |
| 350 | var result = Map.init(allocator); | 467 | if (!env.block.use_global) break :empty; |
| 351 | errdefer result.deinit(); | 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 | var environ_count: usize = undefined; | 476 | var environ_count: usize = undefined; |
| 355 | var environ_buf_size: usize = undefined; | 477 | var environ_buf_size: usize = undefined; |
| 356 | 478 | ||
| ... | @@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { | ... | @@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 360 | } | 482 | } |
| 361 | 483 | ||
| 362 | if (environ_count == 0) { | 484 | if (environ_count == 0) { |
| 363 | return result; | 485 | return map; |
| 364 | } | 486 | } |
| 365 | 487 | ||
| 366 | const environ = try allocator.alloc([*:0]u8, environ_count); | 488 | const environ = try allocator.alloc([*:0]u8, environ_count); |
| ... | @@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { | ... | @@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 373 | return posix.unexpectedErrno(environ_get_ret); | 495 | return posix.unexpectedErrno(environ_get_ret); |
| 374 | } | 496 | } |
| 375 | 497 | ||
| 376 | for (environ) |line| { | 498 | try map.putPosixBlock(.{ .slice = environ }); |
| 377 | const pair = mem.sliceTo(line, 0); | 499 | } else try map.putPosixBlock(env.block.view()); |
| 378 | var parts = mem.splitScalar(u8, pair, '='); | 500 | return map; |
| 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; | ||
| 433 | } | 501 | } |
| 434 | 502 | ||
| 435 | pub const ContainsError = error{ | 503 | pub const ContainsError = error{ |
| ... | @@ -451,6 +519,7 @@ pub const ContainsError = error{ | ... | @@ -451,6 +519,7 @@ pub const ContainsError = error{ |
| 451 | /// * `containsConstant` | 519 | /// * `containsConstant` |
| 452 | /// * `containsUnempty` | 520 | /// * `containsUnempty` |
| 453 | pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { | 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 | var map = try createMap(environ, gpa); | 523 | var map = try createMap(environ, gpa); |
| 455 | defer map.deinit(); | 524 | defer map.deinit(); |
| 456 | return map.contains(key); | 525 | return map.contains(key); |
| ... | @@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError | ... | @@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError |
| 464 | /// * `containsUnemptyConstant` | 533 | /// * `containsUnemptyConstant` |
| 465 | /// * `contains` | 534 | /// * `contains` |
| 466 | pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { | 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 | var map = try createMap(environ, gpa); | 537 | var map = try createMap(environ, gpa); |
| 468 | defer map.deinit(); | 538 | defer map.deinit(); |
| 469 | const value = map.get(key) orelse return false; | 539 | const value = map.get(key) orelse return false; |
| ... | @@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8 | ... | @@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8 |
| 516 | /// * `createMap` | 586 | /// * `createMap` |
| 517 | pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { | 587 | pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { |
| 518 | if (mem.findScalar(u8, key, '=') != null) return null; | 588 | if (mem.findScalar(u8, key, '=') != null) return null; |
| 519 | for (environ.block) |opt_line| { | 589 | for (environ.block.view().slice) |entry| { |
| 520 | const line = opt_line.?; | 590 | var entry_i: usize = 0; |
| 521 | var line_i: usize = 0; | 591 | while (entry[entry_i] != 0) : (entry_i += 1) { |
| 522 | while (line[line_i] != 0) : (line_i += 1) { | 592 | if (entry_i == key.len) break; |
| 523 | if (line_i == key.len) break; | 593 | if (entry[entry_i] != key[entry_i]) break; |
| 524 | if (line[line_i] != key[line_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 | return null; | 599 | return null; |
| 531 | } | 600 | } |
| ... | @@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { | ... | @@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { |
| 541 | /// * `containsConstant` | 610 | /// * `containsConstant` |
| 542 | /// * `contains` | 611 | /// * `contains` |
| 543 | pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { | 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 | // '=' anywhere but the start makes this an invalid environment variable name. | 613 | // '=' anywhere but the start makes this an invalid environment variable name. |
| 548 | const key_slice = mem.sliceTo(key, 0); | 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 | var i: usize = 0; | 624 | var i: usize = 0; |
| 554 | while (ptr[i] != 0) { | 625 | while (ptr[i] != 0) { |
| ... | @@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { | ... | @@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { |
| 558 | // so we need a special case to not treat = as a key/value separator | 629 | // so we need a special case to not treat = as a key/value separator |
| 559 | // if it's the first character. | 630 | // if it's the first character. |
| 560 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | 631 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 561 | const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; | 632 | const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse { |
| 562 | const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { | ||
| 563 | // This is enforced by CreateProcess. | 633 | // This is enforced by CreateProcess. |
| 564 | // If violated, CreateProcess will fail with INVALID_PARAMETER. | 634 | // If violated, CreateProcess will fail with INVALID_PARAMETER. |
| 565 | unreachable; // must contain a = | 635 | unreachable; // must contain a = |
| ... | @@ -598,13 +668,14 @@ pub const GetAllocError = error{ | ... | @@ -598,13 +668,14 @@ pub const GetAllocError = error{ |
| 598 | /// See also: | 668 | /// See also: |
| 599 | /// * `createMap` | 669 | /// * `createMap` |
| 600 | pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 { | 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 | var map = createMap(environ, gpa) catch return error.OutOfMemory; | 672 | var map = createMap(environ, gpa) catch return error.OutOfMemory; |
| 602 | defer map.deinit(); | 673 | defer map.deinit(); |
| 603 | const val = map.get(key) orelse return error.EnvironmentVariableMissing; | 674 | const val = map.get(key) orelse return error.EnvironmentVariableMissing; |
| 604 | return gpa.dupe(u8, val); | 675 | return gpa.dupe(u8, val); |
| 605 | } | 676 | } |
| 606 | 677 | ||
| 607 | pub const CreateBlockPosixOptions = struct { | 678 | pub const CreatePosixBlockOptions = struct { |
| 608 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. | 679 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. |
| 609 | /// If non-null, negative means to remove the environment variable, and >= 0 | 680 | /// If non-null, negative means to remove the environment variable, and >= 0 |
| 610 | /// means to provide it with the given integer. | 681 | /// means to provide it with the given integer. |
| ... | @@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct { | ... | @@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct { |
| 613 | 684 | ||
| 614 | /// Creates a null-delimited environment variable block in the format expected | 685 | /// Creates a null-delimited environment variable block in the format expected |
| 615 | /// by POSIX, from a different one. | 686 | /// by POSIX, from a different one. |
| 616 | pub fn createBlockPosix( | 687 | pub fn createPosixBlock( |
| 617 | existing: Environ, | 688 | existing: Environ, |
| 618 | arena: Allocator, | 689 | gpa: Allocator, |
| 619 | options: CreateBlockPosixOptions, | 690 | options: CreatePosixBlockOptions, |
| 620 | ) Allocator.Error![:null]?[*:0]u8 { | 691 | ) Allocator.Error!PosixBlock { |
| 621 | const contains_zig_progress = for (existing.block) |opt_line| { | 692 | const contains_zig_progress = for (existing.block.view().slice) |entry| { |
| 622 | if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; | 693 | if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true; |
| 623 | } else false; | 694 | } else false; |
| 624 | 695 | ||
| 625 | const ZigProgressAction = enum { nothing, edit, delete, add }; | 696 | const ZigProgressAction = enum { nothing, edit, delete, add }; |
| 626 | const zig_progress_action: ZigProgressAction = a: { | 697 | const zig_progress_action: ZigProgressAction = action: { |
| 627 | const fd = options.zig_progress_fd orelse break :a .nothing; | 698 | const fd = options.zig_progress_fd orelse break :action .nothing; |
| 628 | if (fd >= 0) { | 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 | } else { | 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: { | 707 | const envp = try gpa.allocSentinel(?[*:0]u8, len: { |
| 637 | var count: usize = existing.block.len; | 708 | var len: usize = existing.block.slice.len; |
| 638 | switch (zig_progress_action) { | 709 | switch (zig_progress_action) { |
| 639 | .add => count += 1, | 710 | .add => len += 1, |
| 640 | .delete => count -= 1, | 711 | .delete => len -= 1, |
| 641 | .nothing, .edit => {}, | 712 | .nothing, .edit => {}, |
| 642 | } | 713 | } |
| 643 | break :c count; | 714 | break :len len; |
| 644 | }; | 715 | }, null); |
| 645 | 716 | var envp_len: usize = 0; | |
| 646 | const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); | 717 | errdefer { |
| 647 | var i: usize = 0; | 718 | envp[envp_len] = null; |
| 648 | var existing_index: usize = 0; | 719 | PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa); |
| 649 | 720 | } | |
| 650 | if (zig_progress_action == .add) { | 721 | if (zig_progress_action == .add) { |
| 651 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | 722 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); |
| 652 | i += 1; | 723 | envp_len += 1; |
| 653 | } | 724 | } |
| 654 | 725 | ||
| 655 | while (existing.block[existing_index]) |line| : (existing_index += 1) { | 726 | var existing_index: usize = 0; |
| 656 | if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { | 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 | .add => unreachable, | 729 | .add => unreachable, |
| 658 | .delete => continue, | 730 | .delete => continue, |
| 659 | .edit => { | 731 | .edit => { |
| 660 | envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); | 732 | envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); |
| 661 | i += 1; | 733 | envp_len += 1; |
| 662 | continue; | 734 | continue; |
| 663 | }, | 735 | }, |
| 664 | .nothing => {}, | 736 | .nothing => {}, |
| 665 | }; | 737 | }; |
| 666 | envp_buf[i] = try arena.dupeZ(u8, mem.span(line)); | 738 | envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry)); |
| 667 | i += 1; | 739 | envp_len += 1; |
| 668 | } | 740 | } |
| 669 | 741 | ||
| 670 | assert(i == envp_count); | 742 | assert(envp_len == envp.len); |
| 671 | return envp_buf; | 743 | return .{ .slice = envp }; |
| 672 | } | 744 | } |
| 673 | 745 | ||
| 674 | test "Map.createBlock" { | 746 | pub const CreateWindowsBlockOptions = struct { |
| 675 | const allocator = testing.allocator; | 747 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. |
| 676 | var envmap = Map.init(allocator); | 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 | defer envmap.deinit(); | 828 | defer envmap.deinit(); |
| 678 | 829 | ||
| 679 | try envmap.put("HOME", "/home/ifreund"); | 830 | try envmap.put("HOME", "/home/ifreund"); |
| ... | @@ -682,29 +833,24 @@ test "Map.createBlock" { | ... | @@ -682,29 +833,24 @@ test "Map.createBlock" { |
| 682 | try envmap.put("DEBUGINFOD_URLS", " "); | 833 | try envmap.put("DEBUGINFOD_URLS", " "); |
| 683 | try envmap.put("XCURSOR_SIZE", "24"); | 834 | try envmap.put("XCURSOR_SIZE", "24"); |
| 684 | 835 | ||
| 685 | var arena = std.heap.ArenaAllocator.init(allocator); | 836 | const block = try envmap.createPosixBlock(gpa, .{}); |
| 686 | defer arena.deinit(); | 837 | defer block.deinit(gpa); |
| 687 | const environ = try envmap.createBlockPosix(arena.allocator(), .{}); | ||
| 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 | "HOME=/home/ifreund", | 842 | "HOME=/home/ifreund", |
| 693 | "WAYLAND_DISPLAY=wayland-1", | 843 | "WAYLAND_DISPLAY=wayland-1", |
| 694 | "DISPLAY=:1", | 844 | "DISPLAY=:1", |
| 695 | "DEBUGINFOD_URLS= ", | 845 | "DEBUGINFOD_URLS= ", |
| 696 | "XCURSOR_SIZE=24", | 846 | "XCURSOR_SIZE=24", |
| 697 | }) |target| { | 847 | }, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?)); |
| 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 | } | ||
| 704 | } | 848 | } |
| 705 | 849 | ||
| 706 | test Map { | 850 | test Map { |
| 707 | var env = Map.init(testing.allocator); | 851 | const gpa = testing.allocator; |
| 852 | |||
| 853 | var env: Map = .init(gpa); | ||
| 708 | defer env.deinit(); | 854 | defer env.deinit(); |
| 709 | 855 | ||
| 710 | try env.put("SOMETHING_NEW", "hello"); | 856 | try env.put("SOMETHING_NEW", "hello"); |
| ... | @@ -740,6 +886,7 @@ test Map { | ... | @@ -740,6 +886,7 @@ test Map { |
| 740 | try testing.expect(env.swapRemove("SOMETHING_NEW")); | 886 | try testing.expect(env.swapRemove("SOMETHING_NEW")); |
| 741 | try testing.expect(!env.swapRemove("SOMETHING_NEW")); | 887 | try testing.expect(!env.swapRemove("SOMETHING_NEW")); |
| 742 | try testing.expect(env.get("SOMETHING_NEW") == null); | 888 | try testing.expect(env.get("SOMETHING_NEW") == null); |
| 889 | try testing.expect(!env.contains("SOMETHING_NEW")); | ||
| 743 | 890 | ||
| 744 | try testing.expectEqual(@as(Map.Size, 1), env.count()); | 891 | try testing.expectEqual(@as(Map.Size, 1), env.count()); |
| 745 | 892 | ||
| ... | @@ -749,10 +896,10 @@ test Map { | ... | @@ -749,10 +896,10 @@ test Map { |
| 749 | try testing.expectEqualStrings("something else", env.get("кириллица").?); | 896 | try testing.expectEqualStrings("something else", env.get("кириллица").?); |
| 750 | 897 | ||
| 751 | // and WTF-8 that's not valid UTF-8 | 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 | mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate | 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 | try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); | 904 | try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); |
| 758 | try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); | 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,13 +916,9 @@ test "convert from Environ to Map and back again" { |
| 769 | defer map.deinit(); | 916 | defer map.deinit(); |
| 770 | try map.put("FOO", "BAR"); | 917 | try map.put("FOO", "BAR"); |
| 771 | try map.put("A", ""); | 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 | try testing.expectEqual(true, environ.contains(gpa, "FOO")); | 923 | try testing.expectEqual(true, environ.contains(gpa, "FOO")); |
| 781 | try testing.expectEqual(false, environ.contains(gpa, "BAR")); | 924 | try testing.expectEqual(false, environ.contains(gpa, "BAR")); |
| ... | @@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" { | ... | @@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" { |
| 783 | try testing.expectEqual(true, environ.containsConstant("A")); | 926 | try testing.expectEqual(true, environ.containsConstant("A")); |
| 784 | try testing.expectEqual(false, environ.containsUnempty(gpa, "A")); | 927 | try testing.expectEqual(false, environ.containsUnempty(gpa, "A")); |
| 785 | try testing.expectEqual(false, environ.containsUnemptyConstant("A")); | 928 | try testing.expectEqual(false, environ.containsUnemptyConstant("A")); |
| 786 | try testing.expectEqual(true, environ.contains(gpa, "")); | ||
| 787 | try testing.expectEqual(false, environ.contains(gpa, "B")); | 929 | try testing.expectEqual(false, environ.contains(gpa, "B")); |
| 788 | 930 | ||
| 789 | try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS")); | 931 | try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS")); |
| ... | @@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" { | ... | @@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" { |
| 800 | try testing.expectEqualDeep(map.values(), map2.values()); | 942 | try testing.expectEqualDeep(map.values(), map2.values()); |
| 801 | } | 943 | } |
| 802 | 944 | ||
| 803 | test createMapWide { | 945 | test "Map.putPosixBlock" { |
| 804 | if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO | 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 | const gpa = testing.allocator; | 969 | const gpa = testing.allocator; |
| 807 | 970 | ||
| 808 | var map: Map = .init(gpa); | 971 | var map: Map = .init(gpa); |
| 809 | defer map.deinit(); | 972 | defer map.deinit(); |
| 973 | |||
| 810 | try map.put("FOO", "BAR"); | 974 | try map.put("FOO", "BAR"); |
| 811 | try map.put("A", ""); | 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); | 979 | const block = try map.createWindowsBlock(gpa, .{}); |
| 815 | defer gpa.free(environ); | 980 | defer block.deinit(gpa); |
| 816 | 981 | ||
| 817 | var map2 = try createMapWide(environ, gpa); | 982 | var map2: Map = .init(gpa); |
| 818 | defer map2.deinit(); | 983 | defer map2.deinit(); |
| 984 | try map2.putWindowsBlock(block.view()); | ||
| 819 | 985 | ||
| 820 | try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys()); | 986 | try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys()); |
| 821 | try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values()); | 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,15 +90,15 @@ fn _DllMainCRTStartup( |
| 90 | fn wasm_freestanding_start() callconv(.c) void { | 90 | fn wasm_freestanding_start() callconv(.c) void { |
| 91 | // This is marked inline because for some reason LLVM in | 91 | // This is marked inline because for some reason LLVM in |
| 92 | // release mode fails to inline it, and we want fewer call frames in stack traces. | 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 | fn startWasi() callconv(.c) void { | 96 | fn startWasi() callconv(.c) void { |
| 97 | // The function call is marked inline because for some reason LLVM in | 97 | // The function call is marked inline because for some reason LLVM in |
| 98 | // release mode fails to inline it, and we want fewer call frames in stack traces. | 98 | // release mode fails to inline it, and we want fewer call frames in stack traces. |
| 99 | switch (builtin.wasi_exec_model) { | 99 | switch (builtin.wasi_exec_model) { |
| 100 | .reactor => _ = @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, .{ {}, {} })), | 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,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| 476 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; | 476 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; |
| 477 | const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; | 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 | fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { | 482 | fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| ... | @@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void { | ... | @@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void { |
| 620 | } | 620 | } |
| 621 | 621 | ||
| 622 | inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 { | 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 | if (std.Options.debug_threaded_io) |t| { | 624 | if (std.Options.debug_threaded_io) |t| { |
| 624 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; | 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 | std.Thread.maybeAttachSignalStack(); | 628 | std.Thread.maybeAttachSignalStack(); |
| 628 | std.debug.maybeEnableSegfaultHandler(); | 629 | std.debug.maybeEnableSegfaultHandler(); |
| 629 | return callMain(argv[0..argc], envp); | 630 | return callMain(argv[0..argc], env_block); |
| 630 | } | 631 | } |
| 631 | 632 | ||
| 632 | fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { | 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,7 +649,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal |
| 648 | std.debug.maybeEnableSegfaultHandler(); | 649 | std.debug.maybeEnableSegfaultHandler(); |
| 649 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; | 650 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; |
| 650 | const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; | 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 | else => {}, | 654 | else => {}, |
| 654 | } | 655 | } |
| ... | @@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { | ... | @@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { |
| 661 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) { | 662 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) { |
| 662 | if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0]; | 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 | /// General error message for a malformed return type | 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,14 +12,10 @@ pub fn main(init: std.process.Init) !void { |
| 12 | // containsUnempty | 12 | // containsUnempty |
| 13 | { | 13 | { |
| 14 | try std.testing.expect(try environ.containsUnempty(allocator, "FOO")); | 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 | if (builtin.os.tag == .windows) { | 15 | if (builtin.os.tag == .windows) { |
| 19 | try std.testing.expect(try environ.containsUnempty(allocator, "foo")); | 16 | try std.testing.expect(try environ.containsUnempty(allocator, "foo")); |
| 20 | } | 17 | } |
| 21 | try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS")); | 18 | try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS")); |
| 22 | try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC"))); | ||
| 23 | try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА")); | 19 | try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА")); |
| 24 | if (builtin.os.tag == .windows) { | 20 | if (builtin.os.tag == .windows) { |
| 25 | try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица")); | 21 | try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица")); |
| ... | @@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void { |
| 35 | // containsUnemptyConstant | 31 | // containsUnemptyConstant |
| 36 | { | 32 | { |
| 37 | try std.testing.expect(environ.containsUnemptyConstant("FOO")); | 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 | if (builtin.os.tag == .windows) { | 34 | if (builtin.os.tag == .windows) { |
| 42 | try std.testing.expect(environ.containsUnemptyConstant("foo")); | 35 | try std.testing.expect(environ.containsUnemptyConstant("foo")); |
| 43 | } | 36 | } |
| 44 | try std.testing.expect(environ.containsUnemptyConstant("EQUALS")); | 37 | try std.testing.expect(environ.containsUnemptyConstant("EQUALS")); |
| 45 | try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC")); | ||
| 46 | try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА")); | 38 | try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА")); |
| 47 | if (builtin.os.tag == .windows) { | 39 | if (builtin.os.tag == .windows) { |
| 48 | try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица")); | 40 | try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица")); |
| ... | @@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void { |
| 58 | // contains | 50 | // contains |
| 59 | { | 51 | { |
| 60 | try std.testing.expect(try environ.contains(allocator, "FOO")); | 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 | if (builtin.os.tag == .windows) { | 53 | if (builtin.os.tag == .windows) { |
| 65 | try std.testing.expect(try environ.contains(allocator, "foo")); | 54 | try std.testing.expect(try environ.contains(allocator, "foo")); |
| 66 | } | 55 | } |
| 67 | try std.testing.expect(try environ.contains(allocator, "EQUALS")); | 56 | try std.testing.expect(try environ.contains(allocator, "EQUALS")); |
| 68 | try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC"))); | ||
| 69 | try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА")); | 57 | try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА")); |
| 70 | if (builtin.os.tag == .windows) { | 58 | if (builtin.os.tag == .windows) { |
| 71 | try std.testing.expect(try environ.contains(allocator, "кирИЛЛица")); | 59 | try std.testing.expect(try environ.contains(allocator, "кирИЛЛица")); |
| ... | @@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void { |
| 81 | // containsConstant | 69 | // containsConstant |
| 82 | { | 70 | { |
| 83 | try std.testing.expect(environ.containsConstant("FOO")); | 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 | if (builtin.os.tag == .windows) { | 72 | if (builtin.os.tag == .windows) { |
| 88 | try std.testing.expect(environ.containsConstant("foo")); | 73 | try std.testing.expect(environ.containsConstant("foo")); |
| 89 | } | 74 | } |
| 90 | try std.testing.expect(environ.containsConstant("EQUALS")); | 75 | try std.testing.expect(environ.containsConstant("EQUALS")); |
| 91 | try std.testing.expect(!environ.containsConstant("EQUALS=ABC")); | ||
| 92 | try std.testing.expect(environ.containsConstant("КИРиллИЦА")); | 76 | try std.testing.expect(environ.containsConstant("КИРиллИЦА")); |
| 93 | if (builtin.os.tag == .windows) { | 77 | if (builtin.os.tag == .windows) { |
| 94 | try std.testing.expect(environ.containsConstant("кирИЛЛица")); | 78 | try std.testing.expect(environ.containsConstant("кирИЛЛица")); |
| ... | @@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void { |
| 104 | // getAlloc | 88 | // getAlloc |
| 105 | { | 89 | { |
| 106 | try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO")); | 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 | if (builtin.os.tag == .windows) { | 91 | if (builtin.os.tag == .windows) { |
| 111 | try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo")); | 92 | try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo")); |
| 112 | } | 93 | } |
| 113 | try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS")); | 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 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА")); | 95 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА")); |
| 116 | if (builtin.os.tag == .windows) { | 96 | if (builtin.os.tag == .windows) { |
| 117 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица")); | 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,13 +110,10 @@ pub fn main(init: std.process.Init) !void { |
| 130 | defer environ_map.deinit(); | 110 | defer environ_map.deinit(); |
| 131 | 111 | ||
| 132 | try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?); | 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 | if (builtin.os.tag == .windows) { | 113 | if (builtin.os.tag == .windows) { |
| 136 | try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?); | 114 | try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?); |
| 137 | } | 115 | } |
| 138 | try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?); | 116 | try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?); |
| 139 | try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC")); | ||
| 140 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?); | 117 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?); |
| 141 | if (builtin.os.tag == .windows) { | 118 | if (builtin.os.tag == .windows) { |
| 142 | try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?); | 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,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO |
| 125 | .lpReserved2 = null, | 125 | .lpReserved2 = null, |
| 126 | .hStdInput = null, | 126 | .hStdInput = null, |
| 127 | .hStdOutput = null, | 127 | .hStdOutput = null, |
| 128 | .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null, | 128 | .hStdError = windows.peb().ProcessParameters.hStdError, |
| 129 | }; | 129 | }; |
| 130 | var proc_info: windows.PROCESS_INFORMATION = undefined; | 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,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO |
| 149 | break :spawn proc_info.hProcess; | 149 | break :spawn proc_info.hProcess; |
| 150 | }; | 150 | }; |
| 151 | defer windows.CloseHandle(child_proc); | 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 | var exit_code: windows.DWORD = undefined; | 159 | var exit_code: windows.DWORD = undefined; |
| 155 | if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) { | 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,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr |
| 233 | } | 233 | } |
| 234 | 234 | ||
| 235 | fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void { | 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 | while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) { | 237 | while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) { |
| 238 | error.AccessDenied => { | 238 | error.AccessDenied => { |
| 239 | if (attempt == 13) return error.AccessDenied; | 239 | if (attempt == 26) return error.AccessDenied; |
| 240 | // give the kernel a chance to finish closing the executable handle | 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 | attempt += 1; | 243 | attempt += 1; |
| 243 | continue; | 244 | continue; |
| 244 | }, | 245 | }, |