1const Threaded = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6const is_darwin = native_os.isDarwin();
7const is_debug = builtin.mode == .debug;
8
9const std = @import("../std.zig");
10const Io = std.Io;
11const net = std.Io.net;
12const File = std.Io.File;
13const Dir = std.Io.Dir;
14const HostName = net.HostName;
15const IpAddress = net.IpAddress;
16const process = std.process;
17const Allocator = std.mem.Allocator;
18const Alignment = std.mem.Alignment;
19const assert = std.debug.assert;
20const posix = std.posix;
21const windows = std.os.windows;
22const ws2_32 = windows.ws2_32;
23
24/// Thread-safe.
25///
26/// Used for:
27/// * allocating `Io.Future` and `Io.Group` closures.
28/// * formatting spawning child processes
29/// * scanning environment variables on some targets
30/// * memory-mapping when mmap or equivalent is not available
31allocator: Allocator,
32mutex: Io.Mutex = .init,
33cond: Io.Condition = .init,
34run_queue: std.SinglyLinkedList = .{},
35join_requested: bool = false,
36stack_size: usize,
37/// All threads are spawned detached; this is how we wait until they all exit.
38wait_group: WaitGroup = .init,
39async_limit: Io.Limit,
40concurrent_limit: Io.Limit = .unlimited,
41/// Error from calling `std.Thread.getCpuCount` in `init`.
42cpu_count_error: ?std.Thread.CpuCountError,
43/// Number of threads that are unavailable to take tasks. To calculate
44/// available count, subtract this from either `async_limit` or
45/// `concurrent_limit`.
46busy_count: usize = 0,
47worker_threads: std.atomic.Value(?*Thread),
48pid: Pid = .unknown,
49
50have_signal_handler: bool,
51old_sig_io: if (have_sig_io) posix.Sigaction else void,
52old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
53
54use_sendfile: UseSendfile = .default,
55use_copy_file_range: UseCopyFileRange = .default,
56use_fcopyfile: UseFcopyfile = .default,
57use_fchmodat2: UseFchmodat2 = .default,
58disable_memory_mapping: bool,
59
60stderr_writer: File.Writer = .{
61 .io = undefined,
62 .interface = File.Writer.initInterface(&.{}),
63 .file = if (is_windows) undefined else .stderr(),
64 .mode = .streaming,
65},
66stderr_mode: Io.Terminal.Mode = .no_color,
67stderr_writer_initialized: bool = false,
68stderr_mutex: Io.Mutex = .init,
69stderr_mutex_locker: std.Thread.Id = Thread.invalid_id,
70stderr_mutex_lock_count: usize = 0,
71
72argv0: Argv0,
73/// Protected by `mutex`. Determines whether `environ` has been
74/// memoized based on `process_environ`.
75environ_initialized: bool,
76environ: Environ,
77
78dl: Dl = .init,
79
80null_file: NullFile = .{},
81random_file: RandomFile = .{},
82pipe_file: PipeFile = .{},
83
84csprng: Csprng = .uninitialized,
85
86system_basic_information: SystemBasicInformation = .{},
87
88const SystemBasicInformation = if (!is_windows) struct {} else struct {
89 buffer: windows.SYSTEM.BASIC_INFORMATION = undefined,
90 initialized: std.atomic.Value(bool) = .{ .raw = false },
91};
92
93const Dl = switch (native_os) {
94 .windows => struct {
95 iphlpapi_dll: std.atomic.Value(?*anyopaque),
96 ConvertInterfaceNameToLuidW: std.atomic.Value(?*const fn (
97 InterfaceName: [*:0]const windows.WCHAR,
98 InterfaceLuid: *windows.NET.LUID,
99 ) callconv(.winapi) windows.Win32Error),
100 ConvertInterfaceLuidToIndex: std.atomic.Value(?*const fn (
101 InterfaceLuid: *const windows.NET.LUID,
102 InterfaceIndex: *windows.NET.IFINDEX,
103 ) callconv(.winapi) windows.Win32Error),
104 ConvertInterfaceIndexToLuid: std.atomic.Value(?*const fn (
105 InterfaceIndex: std.os.windows.NET.IFINDEX,
106 InterfaceLuid: *std.os.windows.NET.LUID,
107 ) callconv(.winapi) windows.Win32Error),
108 ConvertInterfaceLuidToNameW: std.atomic.Value(?*const fn (
109 InterfaceLuid: *const std.os.windows.NET.LUID,
110 InterfaceName: std.os.windows.PWSTR,
111 Length: std.os.windows.SIZE_T,
112 ) callconv(.winapi) std.os.windows.Win32Error),
113
114 dnsapi_dll: std.atomic.Value(?*anyopaque),
115 DnsQueryEx: std.atomic.Value(?*const fn (
116 pQueryRequest: *const windows.DNS.QUERY.REQUEST,
117 pQueryResults: *windows.DNS.QUERY.RESULT,
118 pCancelHandle: ?*windows.DNS.QUERY.CANCEL,
119 ) callconv(.winapi) windows.DNS.STATUS),
120 //DnsCancelQuery: std.atomic.Value(?*const fn (
121 // pCancelHandle: *const windows.DNS.QUERY.CANCEL,
122 //) callconv(.winapi) windows.DNS.STATUS),
123 DnsFree: std.atomic.Value(?*const fn (
124 pRecordList: ?*anyopaque,
125 FreeType: windows.DNS.FREE_TYPE,
126 ) callconv(.winapi) void),
127
128 const init: Dl = .{
129 .iphlpapi_dll = .init(null),
130 .ConvertInterfaceNameToLuidW = .init(null),
131 .ConvertInterfaceLuidToIndex = .init(null),
132 .ConvertInterfaceIndexToLuid = .init(null),
133 .ConvertInterfaceLuidToNameW = .init(null),
134
135 .dnsapi_dll = .init(null),
136 .DnsQueryEx = .init(null),
137 //.DnsCancelQuery = .init(null),
138 .DnsFree = .init(null),
139 };
140 fn deinit(dl: *Dl) void {
141 if (dl.iphlpapi_dll.raw) |iphlpapi_dll| switch (windows.ntdll.LdrUnloadDll(iphlpapi_dll)) {
142 .SUCCESS => {},
143 else => |status| windows.unexpectedStatus(status) catch {},
144 };
145 dl.* = .init;
146 }
147 },
148 else => struct {
149 const init: Dl = .{};
150 fn deinit(_: Dl) void {}
151 },
152};
153
154pub const Csprng = struct {
155 rng: std.Random.DefaultCsprng,
156
157 pub const uninitialized: Csprng = .{ .rng = .{
158 .state = undefined,
159 .offset = std.math.maxInt(usize),
160 } };
161
162 pub const seed_len = std.Random.DefaultCsprng.secret_seed_length;
163
164 pub fn isInitialized(c: *const Csprng) bool {
165 return c.rng.offset != std.math.maxInt(usize);
166 }
167};
168
169pub const Argv0 = switch (native_os) {
170 .openbsd, .haiku => struct {
171 value: ?[*:0]const u8,
172
173 pub const empty: Argv0 = .{ .value = null };
174
175 pub fn init(args: process.Args) Argv0 {
176 return .{ .value = args.vector[0] };
177 }
178 },
179 else => struct {
180 pub const empty: Argv0 = .{};
181
182 pub fn init(args: process.Args) Argv0 {
183 _ = args;
184 return .{};
185 }
186 },
187};
188
189pub const Environ = struct {
190 /// Unmodified data directly from the OS.
191 process_environ: process.Environ,
192 /// Protected by `mutex`. Memoized based on `process_environ`. Tracks whether the
193 /// environment variables are present, ignoring their value.
194 exist: Exist = .{},
195 /// Protected by `mutex`. Memoized based on `process_environ`.
196 string: String = .{},
197 /// ZIG_PROGRESS
198 zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing,
199 /// Protected by `mutex`. Tracks the problem, if any, that occurred when
200 /// trying to scan environment variables.
201 ///
202 /// Errors are only possible on WASI.
203 err: ?Error = null,
204
205 pub const empty: Environ = .{ .process_environ = .empty };
206
207 pub const Error = Allocator.Error || Io.UnexpectedError;
208
209 pub const Exist = struct {
210 NO_COLOR: bool = false,
211 CLICOLOR_FORCE: bool = false,
212 };
213
214 pub const String = switch (native_os) {
215 .windows, .wasi => struct {},
216 else => struct {
217 PATH: ?[:0]const u8 = null,
218 DEBUGINFOD_CACHE_PATH: ?[:0]const u8 = null,
219 XDG_CACHE_HOME: ?[:0]const u8 = null,
220 HOME: ?[:0]const u8 = null,
221 TERM: ?[:0]const u8 = null,
222 },
223 };
224
225 pub fn scan(environ: *Environ, allocator: Allocator) void {
226 if (is_windows) {
227 // This value expires with any call that modifies the environment,
228 // which is outside of this Io implementation's control, so references
229 // must be short-lived.
230 const peb = windows.peb();
231 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
232 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
233 const ptr = peb.ProcessParameters.Environment;
234
235 var i: usize = 0;
236 while (ptr[i] != 0) {
237 // There are some special environment variables that start with =,
238 // so we need a special case to not treat = as a key/value separator
239 // if it's the first character.
240 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
241 const key_start = i;
242 if (ptr[i] == '=') i += 1;
243 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
244 const key_w = ptr[key_start..i];
245
246 const value_start = i + 1;
247 while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
248 const value_w = ptr[value_start..i];
249 i += 1; // skip over null byte
250
251 if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
252 environ.exist.NO_COLOR = true;
253 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
254 environ.exist.CLICOLOR_FORCE = true;
255 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) {
256 environ.zig_progress_file = file: {
257 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
258 const len = std.unicode.calcWtf8Len(value_w);
259 if (len > value_buf.len) break :file error.UnrecognizedFormat;
260 assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len);
261 break :file .{
262 .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch
263 break :file error.UnrecognizedFormat),
264 .flags = .{ .nonblocking = true },
265 };
266 };
267 }
268 comptime assert(@sizeOf(String) == 0);
269 }
270 } else if (native_os == .wasi and !builtin.link_libc) {
271 var environ_size: usize = undefined;
272 var environ_buf_size: usize = undefined;
273
274 switch (std.os.wasi.environ_sizes_get(&environ_size, &environ_buf_size)) {
275 .SUCCESS => {},
276 else => |err| {
277 environ.err = posix.unexpectedErrno(err);
278 return;
279 },
280 }
281 if (environ_size == 0) return;
282
283 const wasi_environ = allocator.alloc([*:0]u8, environ_size) catch |err| {
284 environ.err = err;
285 return;
286 };
287 defer allocator.free(wasi_environ);
288 const wasi_environ_buf = allocator.alloc(u8, environ_buf_size) catch |err| {
289 environ.err = err;
290 return;
291 };
292 defer allocator.free(wasi_environ_buf);
293
294 switch (std.os.wasi.environ_get(wasi_environ.ptr, wasi_environ_buf.ptr)) {
295 .SUCCESS => {},
296 else => |err| {
297 environ.err = posix.unexpectedErrno(err);
298 return;
299 },
300 }
301
302 for (wasi_environ) |env| {
303 const pair = std.mem.sliceTo(env, 0);
304 var parts = std.mem.splitScalar(u8, pair, '=');
305 const key = parts.first();
306 if (std.mem.eql(u8, key, "NO_COLOR")) {
307 environ.exist.NO_COLOR = true;
308 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
309 environ.exist.CLICOLOR_FORCE = true;
310 }
311 comptime assert(@sizeOf(String) == 0);
312 }
313 } else {
314 for (environ.process_environ.block.slice) |opt_entry| {
315 const entry = opt_entry.?;
316 var entry_i: usize = 0;
317 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
318 const key = entry[0..entry_i];
319
320 var end_i: usize = entry_i;
321 while (entry[end_i] != 0) : (end_i += 1) {}
322 const value = entry[entry_i + 1 .. end_i :0];
323
324 if (std.mem.eql(u8, key, "NO_COLOR")) {
325 environ.exist.NO_COLOR = true;
326 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
327 environ.exist.CLICOLOR_FORCE = true;
328 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
329 environ.zig_progress_file = file: {
330 break :file .{
331 .handle = std.fmt.parseInt(u31, value, 10) catch
332 break :file error.UnrecognizedFormat,
333 .flags = .{ .nonblocking = true },
334 };
335 };
336 } else inline for (@typeInfo(String).@"struct".field_names) |field_name| {
337 if (std.mem.eql(u8, key, field_name)) @field(environ.string, field_name) = value;
338 }
339 }
340 }
341 }
342};
343
344pub const NullFile = switch (native_os) {
345 .windows => struct {
346 handle: ?windows.HANDLE = null,
347
348 fn deinit(this: *@This()) void {
349 if (this.handle) |handle| {
350 windows.CloseHandle(handle);
351 this.handle = null;
352 }
353 }
354 },
355 .wasi, .ios, .tvos, .visionos, .watchos => struct {
356 fn deinit(this: @This()) void {
357 _ = this;
358 }
359 },
360 else => struct {
361 fd: posix.fd_t = -1,
362
363 fn deinit(this: *@This()) void {
364 if (this.fd >= 0) {
365 closeFd(this.fd);
366 this.fd = -1;
367 }
368 }
369 },
370};
371
372pub const RandomFile = switch (native_os) {
373 .windows => NullFile,
374 else => if (use_dev_urandom) NullFile else struct {
375 fn deinit(this: @This()) void {
376 _ = this;
377 }
378 },
379};
380
381pub const PipeFile = switch (native_os) {
382 .windows => struct {
383 handle: ?windows.HANDLE = null,
384
385 fn deinit(this: *@This()) void {
386 if (this.handle) |handle| {
387 windows.CloseHandle(handle);
388 this.handle = null;
389 }
390 }
391 },
392 else => struct {
393 fn deinit(this: @This()) void {
394 _ = this;
395 }
396 },
397};
398
399pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
400 unknown = 0,
401 _,
402} else enum(u0) { unknown = 0 };
403
404pub const UseSendfile = if (have_sendfile) enum {
405 enabled,
406 disabled,
407 pub const default: UseSendfile = .enabled;
408} else enum {
409 disabled,
410 pub const default: UseSendfile = .disabled;
411};
412
413pub const UseCopyFileRange = if (have_copy_file_range) enum {
414 enabled,
415 disabled,
416 pub const default: UseCopyFileRange = .enabled;
417} else enum {
418 disabled,
419 pub const default: UseCopyFileRange = .disabled;
420};
421
422pub const UseFcopyfile = if (have_fcopyfile) enum {
423 enabled,
424 disabled,
425 pub const default: UseFcopyfile = .enabled;
426} else enum {
427 disabled,
428 pub const default: UseFcopyfile = .disabled;
429};
430
431pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum {
432 enabled,
433 disabled,
434 pub const default: UseFchmodat2 = .enabled;
435} else enum {
436 disabled,
437 pub const default: UseFchmodat2 = .disabled;
438};
439
440pub const apc_align = @max(default_fn_align, 2);
441
442const default_fn_align = switch (builtin.mode) {
443 .debug, .safe, .fast => switch (builtin.cpu.arch) {
444 else => |arch| @compileError("Unsupported architecture: " ++ @tagName(arch)),
445 .arm, .thumb => 4,
446 .aarch64, .x86, .x86_64 => 16,
447 },
448 .small => 1,
449};
450
451const Runnable = struct {
452 node: std.SinglyLinkedList.Node,
453 startFn: *const fn (*Runnable, *Thread, *Threaded) void,
454};
455
456const Group = struct {
457 ptr: *Io.Group,
458
459 /// Returns a correctly-typed pointer to the `Io.Group.token` field.
460 ///
461 /// The status indicates how many pending tasks are in the group, whether the group has been
462 /// canceled, and whether the group has been awaited.
463 ///
464 /// Note that the zero value of `Status` intentionally represents the initial group state (empty
465 /// with no awaiters). This is a requirement of `Io.Group`.
466 fn status(g: Group) *std.atomic.Value(Status) {
467 return @ptrCast(&g.ptr.token);
468 }
469 /// Returns a correctly-typed pointer to the `Io.Group.state` field. The double-pointer here is
470 /// intentional, because the `state` field itself stores a pointer, and this function returns a
471 /// pointer to that field.
472 ///
473 /// On completion of the whole group, if `status` indicates that there is an awaiter, the last
474 /// task must increment this `u32` and do a futex wake on it to signal that awaiter.
475 fn awaiter(g: Group) **std.atomic.Value(u32) {
476 return @ptrCast(&g.ptr.state);
477 }
478
479 const Status = packed struct(usize) {
480 num_running: @Int(.unsigned, @bitSizeOf(usize) - 2),
481 have_awaiter: bool,
482 canceled: bool,
483 };
484
485 const Task = struct {
486 runnable: Runnable,
487 group: *Io.Group,
488 func: *const fn (context: *const anyopaque) void,
489 context_alignment: Alignment,
490 alloc_len: usize,
491
492 /// `Task.runnable.node` is `undefined` in the created `Task`.
493 fn create(
494 gpa: Allocator,
495 group: Group,
496 context: []const u8,
497 context_alignment: Alignment,
498 func: *const fn (context: *const anyopaque) void,
499 ) Allocator.Error!*Task {
500 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task);
501 const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment);
502 const alloc_len = worst_case_context_offset + context.len;
503
504 const task: *Task = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Task), alloc_len)));
505 errdefer comptime unreachable;
506
507 task.* = .{
508 .runnable = .{
509 .node = undefined,
510 .startFn = &start,
511 },
512 .group = group.ptr,
513 .func = func,
514 .context_alignment = context_alignment,
515 .alloc_len = alloc_len,
516 };
517 @memcpy(task.contextPointer()[0..context.len], context);
518 return task;
519 }
520
521 fn destroy(task: *Task, gpa: Allocator) void {
522 const base: [*]align(@alignOf(Task)) u8 = @ptrCast(task);
523 gpa.free(base[0..task.alloc_len]);
524 }
525
526 fn contextPointer(task: *Task) [*]u8 {
527 const base: [*]u8 = @ptrCast(task);
528 const offset = task.context_alignment.forward(@intFromPtr(base) + @sizeOf(Task)) - @intFromPtr(base);
529 return base + offset;
530 }
531
532 fn start(r: *Runnable, thread: *Thread, t: *Threaded) void {
533 const task: *Task = @fieldParentPtr("runnable", r);
534 const group: Group = .{ .ptr = task.group };
535
536 // This would be a simple store, but it's upgraded to an RMW so we can use `.acquire` to
537 // enforce the ordering between this and the `group.status().load` below. Paired with
538 // the `.release` rmw on `Thread.status` in `cancelThreads`, this creates a StoreLoad
539 // barrier which guarantees that when a group is canceled, either we see the cancelation
540 // in the group status, or the canceler sees our thread status so can directly notify us
541 // of the cancelation.
542 _ = thread.status.swap(.{
543 .cancelation = .none,
544 .awaitable = .fromGroup(group.ptr),
545 }, .acquire);
546 if (group.status().load(.monotonic).canceled) {
547 thread.status.store(.{
548 .cancelation = .canceling,
549 .awaitable = .fromGroup(group.ptr),
550 }, .monotonic);
551 }
552
553 task.func(task.contextPointer());
554
555 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
556 const old_status = group.status().fetchSub(.{
557 .num_running = 1,
558 .have_awaiter = false,
559 .canceled = false,
560 }, .acq_rel); // acquire `group.awaiter()`, release task results
561 assert(old_status.num_running > 0);
562 if (old_status.have_awaiter and old_status.num_running == 1) {
563 const to_signal = group.awaiter().*;
564 // `awaiter` should only be modified by us. For another thread to see `num_running`
565 // drop to 0 after this point would indicate that another task started up, meaning
566 // `async`/`cancel` was racing with awaited group completion.
567 group.awaiter().* = undefined;
568 _ = to_signal.fetchAdd(1, .release); // release results
569 Thread.futexWake(&to_signal.raw, 1);
570 }
571
572 // Task completed. Self-destruct sequence initiated.
573 task.destroy(t.allocator);
574 }
575 };
576
577 /// Assumes the caller has already atomically updated the group status to indicate cancelation,
578 /// and notifies any already-running threads of this cancelation.
579 fn cancelThreads(g: Group, t: *Threaded) bool {
580 var any_blocked = false;
581 var it = t.worker_threads.load(.acquire); // acquire `Thread` values
582 while (it) |thread| : (it = thread.next) {
583 // This non-mutating RMW exists for ordering reasons: see comment in `Group.Task.start` for reasons.
584 _ = thread.status.fetchOr(.{ .cancelation = @fromBackingInt(@intCast(0)), .awaitable = .null }, .release);
585 if (thread.cancelAwaitable(.fromGroup(g.ptr))) any_blocked = true;
586 }
587 return any_blocked;
588 }
589
590 /// Uses `Thread.signalCanceledSyscall` to signal any threads which are still blocked in a
591 /// syscall for this group and have not observed a cancelation request yet. Returns `true` if
592 /// more signals may be necessary, in which case the caller must call this again after a delay.
593 fn signalAllCanceledSyscalls(g: Group, t: *Threaded) bool {
594 var any_signaled = false;
595 var it = t.worker_threads.load(.acquire); // acquire `Thread` values
596 while (it) |thread| : (it = thread.next) {
597 if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr))) any_signaled = true;
598 }
599 return any_signaled;
600 }
601
602 /// The caller has canceled `g`. Inform any threads working on that group of the cancelation if
603 /// necessary, and wait for `g` to finish (indicated by `num_completed` being incremented from 0
604 /// to 1), while sending regular signals to threads if necessary for them to unblock from any
605 /// cancelable syscalls.
606 ///
607 /// `skip_signals` means it is already known that no threads are currently working on the group
608 /// so no notifications or signals are necessary.
609 fn waitForCancelWithSignaling(
610 g: Group,
611 t: *Threaded,
612 num_completed: *std.atomic.Value(u32),
613 skip_signals: bool,
614 ) void {
615 var need_signal: bool = !skip_signals and g.cancelThreads(t);
616 var timeout_ns: u64 = 1 << 10;
617 while (true) {
618 need_signal = need_signal and g.signalAllCanceledSyscalls(t);
619 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
620 switch (num_completed.load(.acquire)) { // acquire task results
621 0 => {},
622 1 => break,
623 else => unreachable,
624 }
625 timeout_ns <<|= 1;
626 }
627 }
628};
629
630/// Trailing data:
631/// 1. context
632/// 2. result
633const Future = struct {
634 runnable: Runnable,
635 func: *const fn (context: *const anyopaque, result: *anyopaque) void,
636 status: std.atomic.Value(Status),
637 /// On completion, increment this `u32` and do a futex wake on it.
638 awaiter: *std.atomic.Value(u32),
639 context_alignment: Alignment,
640 result_offset: usize,
641 alloc_len: usize,
642
643 const Status = packed struct(usize) {
644 /// The values of this enum are chosen so that await/cancel can just OR with 0b01 and 0b11
645 /// respectively. That *does* clobber `.done`, but that's actually fine, because if the tag
646 /// is `.done` then only the awaiter is referencing this `Future` anyway.
647 tag: enum(u2) {
648 /// The future is queued or running (depending on whether `thread` is set).
649 pending = 0b00,
650 /// Like `pending`, but the future is being awaited. `Future.awaiter` is populated.
651 pending_awaited = 0b01,
652 /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated.
653 pending_canceled = 0b11,
654 /// The future has already completed. `thread` is `.null`, unless the future terminated
655 /// with an acknowledged cancel request, in which case `thread` is `.all_ones`.
656 done = 0b10,
657 },
658 /// When the future begins execution, this is atomically updated from `null` to the thread running the
659 /// `Future`, so that cancelation knows which thread to cancel.
660 thread: Thread.PackedPtr,
661 };
662
663 /// `Future.runnable.node` is `undefined` in the created `Future`.
664 fn create(
665 gpa: Allocator,
666 result_len: usize,
667 result_alignment: Alignment,
668 context: []const u8,
669 context_alignment: Alignment,
670 func: *const fn (context: *const anyopaque, result: *anyopaque) void,
671 ) Allocator.Error!*Future {
672 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Future);
673 const worst_case_context_offset = context_alignment.forward(@sizeOf(Future) + max_context_misalignment);
674 const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len);
675 const alloc_len = worst_case_result_offset + result_len;
676
677 const future: *Future = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Future), alloc_len)));
678 errdefer comptime unreachable;
679
680 const actual_context_addr = context_alignment.forward(@intFromPtr(future) + @sizeOf(Future));
681 const actual_result_addr = result_alignment.forward(actual_context_addr + context.len);
682 const actual_result_offset = actual_result_addr - @intFromPtr(future);
683 future.* = .{
684 .runnable = .{
685 .node = undefined,
686 .startFn = &start,
687 },
688 .func = func,
689 .status = .init(.{
690 .tag = .pending,
691 .thread = .null,
692 }),
693 .awaiter = undefined,
694 .context_alignment = context_alignment,
695 .result_offset = actual_result_offset,
696 .alloc_len = alloc_len,
697 };
698 @memcpy(future.contextPointer()[0..context.len], context);
699 return future;
700 }
701
702 fn destroy(future: *Future, gpa: Allocator) void {
703 const base: [*]align(@alignOf(Future)) u8 = @ptrCast(future);
704 gpa.free(base[0..future.alloc_len]);
705 }
706
707 fn resultPointer(future: *Future) [*]u8 {
708 const base: [*]u8 = @ptrCast(future);
709 return base + future.result_offset;
710 }
711
712 fn contextPointer(future: *Future) [*]u8 {
713 const base: [*]u8 = @ptrCast(future);
714 const context_offset = future.context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)) - @intFromPtr(future);
715 return base + context_offset;
716 }
717
718 fn start(r: *Runnable, thread: *Thread, t: *Threaded) void {
719 _ = t;
720 const future: *Future = @fieldParentPtr("runnable", r);
721
722 thread.status.store(.{
723 .cancelation = .none,
724 .awaitable = .fromFuture(future),
725 }, .monotonic);
726 {
727 const old_status = future.status.fetchOr(.{
728 .tag = .pending,
729 .thread = .pack(thread),
730 }, .release);
731 assert(old_status.thread == .null);
732 switch (old_status.tag) {
733 .pending, .pending_awaited => {},
734 .pending_canceled => thread.status.store(.{
735 .cancelation = .canceling,
736 .awaitable = .fromFuture(future),
737 }, .monotonic),
738 .done => unreachable,
739 }
740 }
741
742 future.func(future.contextPointer(), future.resultPointer());
743
744 const had_acknowledged_cancel = switch (thread.status.load(.monotonic).cancelation) {
745 .none, .canceling => false,
746 .canceled => true,
747 .parked => unreachable,
748 .blocked => unreachable,
749 .blocked_alertable => unreachable,
750 .blocked_alertable_canceling => unreachable,
751 .blocked_canceling => unreachable,
752 };
753 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic);
754 const old_status = future.status.swap(.{
755 .tag = .done,
756 .thread = if (had_acknowledged_cancel) .all_ones else .null,
757 }, .acq_rel); // acquire `future.awaiter`, release results
758 switch (old_status.tag) {
759 .pending => {},
760 .pending_awaited, .pending_canceled => {
761 const to_signal = future.awaiter;
762 _ = to_signal.fetchAdd(1, .release); // release results
763 Thread.futexWake(&to_signal.raw, 1);
764 },
765 .done => unreachable,
766 }
767 }
768
769 /// The caller has canceled `future`. `thread` is the thread currently running that future.
770 /// Inform `thread` of the cancelation if necessary, and wait for `future` to finish (indicated
771 /// by `num_completed` being incremented from 0 to 1), while sending regular signals to `thread`
772 /// if necessary for it to unblock from a cancelable syscall.
773 fn waitForCancelWithSignaling(
774 future: *Future,
775 t: *Threaded,
776 num_completed: *std.atomic.Value(u32),
777 thread: ?*Thread,
778 ) void {
779 var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false;
780 var timeout_ns: u64 = 1 << 10;
781 while (true) {
782 need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future));
783 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
784 switch (num_completed.load(.acquire)) { // acquire task results
785 0 => {},
786 1 => break,
787 else => unreachable,
788 }
789 timeout_ns <<|= 1;
790 }
791 }
792};
793
794/// A sequence of (ptr_bit_width - 3) bits which uniquely identifies a group or future. The bits are
795/// the MSBs of the `*Io.Group` or `*Future`. These things do not necessarily have 3 zero bits at
796/// the end (they are pointer-aligned, so on 32-bit targets only have 2), but because they both have
797/// a *size* of at least 8 bytes, no two groups/futures in memory at the same time will have the
798/// same value for all of these bits. In other words, given a group/future pointer, the next group
799/// or future must be at least 8 bytes later, so its address will have a different value for one of
800/// the top (ptr_bit_width - 3) bits.
801const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) {
802 comptime {
803 assert(@sizeOf(Future) >= 8);
804 assert(@sizeOf(Io.Group) >= 8);
805 }
806 null = 0,
807 all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 3)),
808 _,
809 const Split = packed struct(usize) { low: u3, high: AwaitableId };
810 fn fromGroup(g: *Io.Group) AwaitableId {
811 const split: Split = @bitCast(@intFromPtr(g));
812 return split.high;
813 }
814 fn fromFuture(f: *Future) AwaitableId {
815 const split: Split = @bitCast(@intFromPtr(f));
816 return split.high;
817 }
818};
819
820const Thread = struct {
821 next: ?*Thread,
822
823 id: std.Thread.Id,
824 handle: Handle,
825
826 status: std.atomic.Value(Status),
827
828 cancel_protection: Io.CancelProtection,
829 /// Always released when `Status.cancelation` is set to `.parked`.
830 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
831 unpark_flag: UnparkFlag,
832 park_tid: if (ParkTid == std.Thread.Id) void else ParkTid,
833
834 csprng: Csprng,
835
836 const Handle = Handle: {
837 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
838 if (is_windows) break :Handle windows.HANDLE;
839 break :Handle void;
840 };
841
842 const Status = packed struct(usize) {
843 /// The specific values of these enum fields are chosen to simplify the implementation of
844 /// the transformations we need to apply to this state.
845 cancelation: enum(u3) {
846 /// The thread has not yet been canceled, and is not in a cancelable operation.
847 /// To request cancelation, just set the status to `.canceling`.
848 none = 0b000,
849
850 /// The thread is parked in a cancelable futex wait or sleep.
851 /// Only applicable if `use_parking_futex` or `use_parking_sleep`.
852 /// To request cancelation, set the status to `.canceling` and unpark the thread.
853 /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread.
854 parked = 0b001,
855
856 /// The thread is blocked in a cancelable system call.
857 /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes.
858 blocked = 0b011,
859
860 /// Windows-only: the thread is blocked in an alertable wait via
861 /// `NtDelayExecution`. To request cancelation, set the status to
862 /// `blocked_alertable_canceling` and repeatedly alert the thread
863 /// until the status changes.
864 blocked_alertable = 0b010,
865
866 /// The thread has an outstanding cancelation request but is not in a cancelable operation.
867 /// When it acknowledges the cancelation, it will set the status to `.canceled`.
868 canceling = 0b110,
869
870 /// The thread has received and acknowledged a cancelation request.
871 /// If `recancel` is called, the status will revert to `.canceling`, but otherwise, the status
872 /// will not change for the remainder of this task's execution.
873 canceled = 0b111,
874
875 /// The thread is blocked in a cancelable system call, and is being
876 /// canceled. The thread which triggered the cancelation will send
877 /// signals to this thread until its status changes.
878 blocked_canceling = 0b101,
879
880 /// Windows-only: the thread is blocked in an alertable wait via
881 /// `NtDelayExecution`, and is being canceled. The thread which
882 /// triggered the cancelation will send signals to this thread
883 /// until its status changes.
884 blocked_alertable_canceling = 0b100,
885 },
886
887 /// We cannot turn this value back into a pointer. Instead, it exists so that a task can be
888 /// canceled by a cmpxchg on thread status: if it is running the task we want to cancel,
889 /// then update the `cancelation` field.
890 awaitable: AwaitableId,
891 };
892
893 const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id;
894
895 threadlocal var current: ?*Thread = null;
896
897 /// A value that does not alias any other thread id.
898 const invalid_id: std.Thread.Id = std.math.maxInt(std.Thread.Id);
899
900 fn currentId() std.Thread.Id {
901 return if (current) |t| t.id else std.Thread.getCurrentId();
902 }
903
904 /// The thread is neither in a syscall nor entering one, but we want to check for cancelation
905 /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`.
906 fn checkCancel() Io.Cancelable!void {
907 const thread = Thread.current orelse return;
908 switch (thread.cancel_protection) {
909 .blocked => return,
910 .unblocked => {},
911 }
912 // Here, unlike `Syscall.checkCancel`, it's not particularly likely that we're canceled, so
913 // it seems preferable to do a cheap atomic load and, in the unlikely case, a separate store
914 // to acknowledge. Besides, the state transitions we need here can't be done with one atomic
915 // OR/AND/XOR on `Status.cancelation`, so we don't actually have any other option.
916 const status = thread.status.load(.monotonic);
917 switch (status.cancelation) {
918 .parked => unreachable,
919 .blocked => unreachable,
920 .blocked_alertable => unreachable,
921 .blocked_alertable_canceling => unreachable,
922 .blocked_canceling => unreachable,
923 .none, .canceled => {},
924 .canceling => {
925 thread.status.store(.{
926 .cancelation = .canceled,
927 .awaitable = status.awaitable,
928 }, .monotonic);
929 return error.Canceled;
930 },
931 }
932 }
933
934 fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void {
935 return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable;
936 }
937
938 fn futexWait(ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void {
939 return Thread.futexWaitInner(ptr, expect, false, timeout_ns);
940 }
941
942 fn futexWaitInner(ptr: *const u32, expect: u32, uncancelable: bool, timeout_ns: ?u64) Io.Cancelable!void {
943 @branchHint(.cold);
944
945 if (builtin.single_threaded) unreachable; // nobody would ever wake us
946
947 if (use_parking_futex) {
948 return parking_futex.wait(
949 ptr,
950 expect,
951 uncancelable,
952 if (timeout_ns) |ns| .{ .duration = .{
953 .raw = .fromNanoseconds(ns),
954 .clock = .boot,
955 } } else .none,
956 );
957 } else if (builtin.cpu.arch.isWasm()) {
958 comptime assert(builtin.cpu.has(.wasm, .atomics));
959 // TODO implement cancelation for WASM futex waits by signaling the futex
960 if (!uncancelable) try Thread.checkCancel();
961 const to: i64 = if (timeout_ns) |ns| std.math.cast(i64, ns) orelse std.math.maxInt(i64) else -1;
962 const signed_expect: i32 = @bitCast(expect);
963 const result = asm volatile (
964 \\local.get %[ptr]
965 \\local.get %[expected]
966 \\local.get %[timeout]
967 \\memory.atomic.wait32 0
968 \\local.set %[ret]
969 : [ret] "=r" (-> u32),
970 : [ptr] "r" (ptr),
971 [expected] "r" (signed_expect),
972 [timeout] "r" (to),
973 );
974 switch (result) {
975 0 => {}, // ok
976 1 => {}, // expected != loaded
977 2 => {}, // timeout
978 else => assert(!is_debug),
979 }
980 } else switch (native_os) {
981 .linux => {
982 const linux = std.os.linux;
983 var ts_buffer: linux.timespec = undefined;
984 const ts: ?*linux.timespec = if (timeout_ns) |ns| ts: {
985 ts_buffer = timestampToPosix(ns);
986 break :ts &ts_buffer;
987 } else null;
988 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
989 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts);
990 syscall.finish();
991 switch (linux.errno(rc)) {
992 .SUCCESS => {}, // notified by `wake()`
993 .INTR => {}, // caller's responsibility to retry
994 .AGAIN => {}, // ptr.* != expect
995 .INVAL => {}, // possibly timeout overflow
996 .TIMEDOUT => {},
997 .FAULT => recoverableOsBugDetected(), // ptr was invalid
998 else => recoverableOsBugDetected(),
999 }
1000 },
1001 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1002 const c = std.c;
1003 const flags: c.UL = .{
1004 .op = .COMPARE_AND_WAIT,
1005 .NO_ERRNO = true,
1006 };
1007 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
1008 const status = switch (darwin_supports_ulock_wait2) {
1009 true => c.__ulock_wait2(flags, ptr, expect, ns: {
1010 const ns = timeout_ns orelse break :ns 0;
1011 if (ns == 0) break :ns 1;
1012 break :ns ns;
1013 }, 0),
1014 false => c.__ulock_wait(flags, ptr, expect, us: {
1015 const ns = timeout_ns orelse break :us 0;
1016 const us = std.math.lossyCast(u32, ns / std.time.ns_per_us);
1017 if (us == 0) break :us 1;
1018 break :us us;
1019 }),
1020 };
1021 syscall.finish();
1022 if (status >= 0) return;
1023 switch (@as(c.E, @fromBackingInt(@intCast(-status)))) {
1024 .INTR => {}, // spurious wake
1025 // Address of the futex was paged out. This is unlikely, but possible in theory, and
1026 // pthread/libdispatch on darwin bother to handle it. In this case we'll return
1027 // without waiting, but the caller should retry anyway.
1028 .FAULT => {},
1029 .TIMEDOUT => {}, // timeout
1030 else => recoverableOsBugDetected(),
1031 }
1032 },
1033 .freebsd => {
1034 const flags = @backingInt(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
1035 var tm_size: usize = 0;
1036 var tm: std.c._umtx_time = undefined;
1037 var tm_ptr: ?*const std.c._umtx_time = null;
1038 if (timeout_ns) |ns| {
1039 tm_ptr = &tm;
1040 tm_size = @sizeOf(@TypeOf(tm));
1041 tm.flags = 0; // use relative time not UMTX_ABSTIME
1042 tm.clockid = .MONOTONIC;
1043 tm.timeout = timestampToPosix(ns);
1044 }
1045 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
1046 const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr));
1047 syscall.finish();
1048 if (is_debug) switch (posix.errno(rc)) {
1049 .SUCCESS => {},
1050 .FAULT => unreachable, // one of the args points to invalid memory
1051 .INVAL => unreachable, // arguments should be correct
1052 .TIMEDOUT => {}, // timeout
1053 .INTR => {}, // spurious wake
1054 else => unreachable,
1055 };
1056 },
1057 .openbsd => {
1058 var tm: std.c.timespec = undefined;
1059 var tm_ptr: ?*const std.c.timespec = null;
1060 if (timeout_ns) |ns| {
1061 tm_ptr = &tm;
1062 tm = timestampToPosix(ns);
1063 }
1064 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
1065 const rc = std.c.futex(
1066 ptr,
1067 std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
1068 @as(c_int, @bitCast(expect)),
1069 tm_ptr,
1070 null, // uaddr2 is ignored
1071 );
1072 syscall.finish();
1073 if (is_debug) switch (posix.errno(rc)) {
1074 .SUCCESS => {},
1075 .NOSYS => unreachable, // constant op known good value
1076 .AGAIN => {}, // contents of uaddr != val
1077 .INVAL => unreachable, // invalid timeout
1078 .TIMEDOUT => {}, // timeout
1079 .INTR => {}, // a signal arrived
1080 .CANCELED => {}, // a signal arrived and SA_RESTART was set
1081 else => unreachable,
1082 };
1083 },
1084 .dragonfly => {
1085 var timeout_us: c_int = undefined;
1086 if (timeout_ns) |ns| {
1087 timeout_us = std.math.cast(c_int, ns / std.time.ns_per_us) orelse std.math.maxInt(c_int);
1088 } else {
1089 timeout_us = 0;
1090 }
1091 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
1092 const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
1093 syscall.finish();
1094 if (is_debug) switch (std.posix.errno(rc)) {
1095 .SUCCESS => {},
1096 .BUSY => {}, // ptr != expect
1097 .AGAIN => {}, // maybe timed out, or paged out, or hit 2s kernel refresh
1098 .INTR => {}, // spurious wake
1099 .INVAL => unreachable, // invalid timeout
1100 else => unreachable,
1101 };
1102 },
1103 else => @compileError("unimplemented: futexWait"),
1104 }
1105 }
1106
1107 fn futexWake(ptr: *const u32, max_waiters: u32) void {
1108 @branchHint(.cold);
1109 assert(max_waiters != 0);
1110
1111 if (builtin.single_threaded) return; // nothing to wake up
1112
1113 if (use_parking_futex) {
1114 return parking_futex.wake(ptr, max_waiters);
1115 } else if (builtin.cpu.arch.isWasm()) {
1116 comptime assert(builtin.cpu.has(.wasm, .atomics));
1117 const woken_count = asm volatile (
1118 \\local.get %[ptr]
1119 \\local.get %[waiters]
1120 \\memory.atomic.notify 0
1121 \\local.set %[ret]
1122 : [ret] "=r" (-> u32),
1123 : [ptr] "r" (ptr),
1124 [waiters] "r" (max_waiters),
1125 );
1126 _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled
1127 } else switch (native_os) {
1128 .linux => {
1129 const linux = std.os.linux;
1130 switch (linux.errno(linux.futex_3arg(
1131 ptr,
1132 .{ .cmd = .WAKE, .private = true },
1133 @min(max_waiters, std.math.maxInt(i32)),
1134 ))) {
1135 .SUCCESS => return, // successful wake up
1136 .INVAL => return, // invalid futex_wait() on ptr done elsewhere
1137 .FAULT => return, // pointer became invalid while doing the wake
1138 else => return recoverableOsBugDetected(), // deadlock due to operating system bug
1139 }
1140 },
1141 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1142 const c = std.c;
1143 const flags: c.UL = .{
1144 .op = .COMPARE_AND_WAIT,
1145 .NO_ERRNO = true,
1146 .WAKE_ALL = max_waiters > 1,
1147 };
1148 while (true) {
1149 const status = c.__ulock_wake(flags, ptr, 0);
1150 if (status >= 0) return;
1151 switch (@as(c.E, @fromBackingInt(@intCast(-status)))) {
1152 .INTR, .CANCELED => continue, // spurious wake()
1153 .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t
1154 .NOENT => return, // nothing was woken up
1155 .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD
1156 else => unreachable, // deadlock due to operating system bug
1157 }
1158 }
1159 },
1160 .freebsd => {
1161 const rc = std.c._umtx_op(
1162 @intFromPtr(ptr),
1163 @backingInt(std.c.UMTX_OP.WAKE_PRIVATE),
1164 @as(c_ulong, @min(max_waiters, std.math.maxInt(c_int))),
1165 0, // there is no timeout struct
1166 0, // there is no timeout struct pointer
1167 );
1168 switch (posix.errno(rc)) {
1169 .SUCCESS => {},
1170 .FAULT => {}, // it's ok if the ptr doesn't point to valid memory
1171 .INVAL => unreachable, // arguments should be correct
1172 else => unreachable, // deadlock due to operating system bug
1173 }
1174 },
1175 .openbsd => {
1176 const rc = std.c.futex(
1177 ptr,
1178 std.c.FUTEX.WAKE | std.c.FUTEX.PRIVATE_FLAG,
1179 @min(max_waiters, std.math.maxInt(c_int)),
1180 null, // timeout is ignored
1181 null, // uaddr2 is ignored
1182 );
1183 assert(rc >= 0);
1184 },
1185 .dragonfly => {
1186 // will generally return 0 unless the address is bad
1187 _ = std.c.umtx_wakeup(
1188 @ptrCast(ptr),
1189 @min(max_waiters, std.math.maxInt(c_int)),
1190 );
1191 },
1192 else => @compileError("unimplemented: futexWake"),
1193 }
1194 }
1195
1196 /// Cancels `thread` if it is working on `awaitable`.
1197 ///
1198 /// It is possible that `thread` gets canceled by this function, but is blocked in a syscall. In
1199 /// that case, the thread may need to be sent a signal to interrupt the call. This function will
1200 /// return `true` to indicate this, in which case the caller must call `signalCanceledSyscall`.
1201 fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) bool {
1202 var status = thread.status.load(.monotonic);
1203 while (true) {
1204 if (status.awaitable != awaitable) return false; // thread is working on something else
1205 status = switch (status.cancelation) {
1206 .none => thread.status.cmpxchgWeak(
1207 .{ .cancelation = .none, .awaitable = awaitable },
1208 .{ .cancelation = .canceling, .awaitable = awaitable },
1209 .monotonic,
1210 .monotonic,
1211 ) orelse return false,
1212
1213 .parked => thread.status.cmpxchgWeak(
1214 .{ .cancelation = .parked, .awaitable = awaitable },
1215 .{ .cancelation = .canceling, .awaitable = awaitable },
1216 .acquire, // acquire `thread.futex_waiter`
1217 .monotonic,
1218 ) orelse {
1219 if (!use_parking_futex and !use_parking_sleep) unreachable;
1220 if (thread.futex_waiter) |futex_waiter| {
1221 parking_futex.removeCanceledWaiter(futex_waiter);
1222 }
1223 if (need_unpark_flag) setUnparkFlag(&thread.unpark_flag);
1224 unpark(&.{if (ParkTid == std.Thread.Id) thread.id else thread.park_tid}, null);
1225 return false;
1226 },
1227
1228 .blocked => thread.status.cmpxchgWeak(
1229 .{ .cancelation = .blocked, .awaitable = awaitable },
1230 .{ .cancelation = .blocked_canceling, .awaitable = awaitable },
1231 .monotonic,
1232 .monotonic,
1233 ) orelse return true,
1234
1235 .blocked_alertable => thread.status.cmpxchgWeak(
1236 .{ .cancelation = .blocked_alertable, .awaitable = awaitable },
1237 .{ .cancelation = .blocked_alertable_canceling, .awaitable = awaitable },
1238 .monotonic,
1239 .monotonic,
1240 ) orelse {
1241 if (!is_windows) unreachable;
1242 return true;
1243 },
1244
1245 .canceling, .canceled => {
1246 // This can happen when the task start raced with the cancelation, so the thread
1247 // saw the cancelation on the future/group *and* we are trying to signal the
1248 // thread here.
1249 return false;
1250 },
1251
1252 .blocked_canceling => unreachable, // `awaitable` has not been canceled before now
1253 .blocked_alertable_canceling => unreachable, // `awaitable` has not been canceled before now
1254 };
1255 }
1256 }
1257
1258 /// Sends a signal to `thread` if it is still blocked in a syscall (i.e. has not yet observed
1259 /// the cancelation request from `cancelAwaitable`).
1260 ///
1261 /// Unfortunately, the signal could arrive before the syscall actually starts, so the interrupt
1262 /// is missed. To handle this, we may need to send multiple signals. As such, if this function
1263 /// returns `true`, then it should be called again after a short delay to send another signal if
1264 /// the thread is still blocked. For the implementation, `Future.waitForCancelWithSignaling` and
1265 /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and
1266 /// doubling each call. In practice, it is rare to send more than one signal.
1267 fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool {
1268 const status = thread.status.load(.monotonic);
1269 if (status.awaitable != awaitable) {
1270 // The thread has moved on and is working on something totally different.
1271 return false;
1272 }
1273
1274 // The thread ID and/or handle can be read non-atomically because they never change and were
1275 // released by the store that made `thread` available to us.
1276
1277 switch (status.cancelation) {
1278 .blocked_canceling => if (std.Thread.use_pthreads) {
1279 return switch (std.c.pthread_kill(thread.handle, .IO)) {
1280 0 => true,
1281 else => false,
1282 };
1283 } else switch (native_os) {
1284 .linux => {
1285 const pid: posix.pid_t = pid: {
1286 const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic);
1287 if (cached_pid != .unknown) break :pid @backingInt(cached_pid);
1288 const pid = std.os.linux.getpid();
1289 @atomicStore(Pid, &t.pid, @fromBackingInt(@intCast(pid)), .monotonic);
1290 break :pid pid;
1291 };
1292 return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) {
1293 0 => true,
1294 else => false,
1295 };
1296 },
1297 .windows => {
1298 var iosb: windows.IO_STATUS_BLOCK = undefined;
1299 return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) {
1300 .NOT_FOUND => true, // this might mean the operation hasn't started yet
1301 .SUCCESS => false, // the OS confirmed that our cancelation worked
1302 else => false,
1303 };
1304 },
1305 else => return false,
1306 },
1307
1308 .blocked_alertable_canceling => {
1309 if (!is_windows) unreachable;
1310 return switch (windows.ntdll.NtAlertThread(thread.handle)) {
1311 .SUCCESS => true,
1312 else => false,
1313 };
1314 },
1315
1316 else => {
1317 // The thread is working on `awaitable`, but no longer needs signaling (they already
1318 // woke up and saw the cancelation).
1319 return false;
1320 },
1321 }
1322 }
1323
1324 /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to
1325 /// alignment) so that those two bits can be used in a `packed struct`.
1326 const PackedPtr = enum(@Int(.unsigned, @bitSizeOf(usize) - 2)) {
1327 null = 0,
1328 all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 2)),
1329 _,
1330
1331 const Split = packed struct(usize) { low: u2, high: PackedPtr };
1332 fn pack(ptr: *Thread) PackedPtr {
1333 const split: Split = @bitCast(@intFromPtr(ptr));
1334 assert(split.low == 0);
1335 return split.high;
1336 }
1337 fn unpack(ptr: PackedPtr) ?*Thread {
1338 const split: Split = .{ .low = 0, .high = ptr };
1339 return @ptrFromInt(@as(usize, @bitCast(split)));
1340 }
1341 };
1342};
1343
1344const Syscall = struct {
1345 thread: ?*Thread,
1346 /// Marks entry to a syscall region. This should be tightly scoped around the actual syscall
1347 /// to minimize races. The syscall must be marked as "finished" by `checkCancel`, `finish`,
1348 /// or one of the wrappers of `finish`.
1349 fn start() Io.Cancelable!Syscall {
1350 const thread = Thread.current orelse return .{ .thread = null };
1351 switch (thread.cancel_protection) {
1352 .blocked => return .{ .thread = null },
1353 .unblocked => {},
1354 }
1355 switch (thread.status.fetchOr(.{
1356 .cancelation = @fromBackingInt(@intCast(0b011)),
1357 .awaitable = .null,
1358 }, .monotonic).cancelation) {
1359 .parked => unreachable,
1360 .blocked => unreachable,
1361 .blocked_alertable => unreachable,
1362 .blocked_alertable_canceling => unreachable,
1363 .blocked_canceling => unreachable,
1364 .none => return .{ .thread = thread }, // new status is `.blocked`
1365 .canceling => return error.Canceled, // new status is `.canceled`
1366 .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged)
1367 }
1368 }
1369 /// Checks whether this syscall has been canceled. This should be called when a syscall is
1370 /// interrupted through a mechanism which may indicate cancelation, or may be spurious. If
1371 /// the syscall was canceled, it is finished and `error.Canceled` is returned. Otherwise,
1372 /// the syscall is not marked finished, and the caller should retry.
1373 fn checkCancel(s: Syscall) Io.Cancelable!void {
1374 const thread = s.thread orelse return;
1375 switch (thread.status.fetchOr(.{
1376 .cancelation = @fromBackingInt(@intCast(0b010)),
1377 .awaitable = .null,
1378 }, .monotonic).cancelation) {
1379 .none => unreachable,
1380 .parked => unreachable,
1381 .blocked_alertable => unreachable,
1382 .blocked_alertable_canceling => unreachable,
1383 .canceling => unreachable,
1384 .canceled => unreachable,
1385 .blocked => {}, // new status is `.blocked` (unchanged)
1386 .blocked_canceling => return error.Canceled, // new status is `.canceled`
1387 }
1388 }
1389 /// Marks this syscall as finished.
1390 fn finish(s: Syscall) void {
1391 const thread = s.thread orelse return;
1392 switch (thread.status.fetchXor(.{
1393 .cancelation = @fromBackingInt(@intCast(0b011)),
1394 .awaitable = .null,
1395 }, .monotonic).cancelation) {
1396 .none => unreachable,
1397 .parked => unreachable,
1398 .blocked_alertable => unreachable,
1399 .blocked_alertable_canceling => unreachable,
1400 .canceling => unreachable,
1401 .canceled => unreachable,
1402 .blocked => {}, // new status is `.none`
1403 .blocked_canceling => {}, // new status is `.canceling`
1404 }
1405 }
1406 /// Indicates instead of `NtCancelSynchronousIoFile` we need to use
1407 /// `NtAlertThread` to interrupt the wait.
1408 ///
1409 /// Windows only, called from blocked state only.
1410 fn toAlertable(s: Syscall) Io.Cancelable!AlertableSyscall {
1411 comptime assert(is_windows);
1412 const thread = s.thread orelse return .{ .thread = null };
1413 var prev = thread.status.load(.monotonic);
1414 while (true) prev = switch (prev.cancelation) {
1415 .none => unreachable,
1416 .parked => unreachable,
1417 .blocked_alertable => unreachable,
1418 .blocked_alertable_canceling => unreachable,
1419 .canceling => unreachable,
1420 .canceled => unreachable,
1421
1422 .blocked => thread.status.cmpxchgWeak(prev, .{
1423 .cancelation = .blocked_alertable,
1424 .awaitable = prev.awaitable,
1425 }, .monotonic, .monotonic) orelse return .{ .thread = thread },
1426
1427 .blocked_canceling => thread.status.cmpxchgWeak(prev, .{
1428 .cancelation = .canceled,
1429 .awaitable = prev.awaitable,
1430 }, .monotonic, .monotonic) orelse return error.Canceled,
1431 };
1432 }
1433 /// Convenience wrapper which calls `finish`, then returns `err`.
1434 fn fail(s: Syscall, err: anytype) @TypeOf(err) {
1435 s.finish();
1436 return err;
1437 }
1438 /// Convenience wrapper which calls `finish`, then calls `Threaded.errnoBug`.
1439 fn errnoBug(s: Syscall, err: posix.E) Io.UnexpectedError {
1440 @branchHint(.cold);
1441 s.finish();
1442 return Threaded.errnoBug(err);
1443 }
1444 /// Convenience wrapper which calls `finish`, then calls `posix.unexpectedErrno`.
1445 fn unexpectedErrno(s: Syscall, err: posix.E) Io.UnexpectedError {
1446 @branchHint(.cold);
1447 s.finish();
1448 return posix.unexpectedErrno(err);
1449 }
1450 /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`.
1451 fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
1452 @branchHint(.cold);
1453 s.finish();
1454 return windows.statusBug(status);
1455 }
1456 /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`.
1457 fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError {
1458 @branchHint(.cold);
1459 s.finish();
1460 return windows.unexpectedStatus(status);
1461 }
1462};
1463
1464const AlertableSyscall = struct {
1465 thread: ?*Thread,
1466
1467 comptime {
1468 assert(is_windows);
1469 }
1470
1471 fn start() Io.Cancelable!AlertableSyscall {
1472 const thread = Thread.current orelse return .{ .thread = null };
1473 switch (thread.cancel_protection) {
1474 .blocked => return .{ .thread = null },
1475 .unblocked => {},
1476 }
1477 const old_status = thread.status.fetchOr(.{
1478 .cancelation = @fromBackingInt(@intCast(0b010)),
1479 .awaitable = .null,
1480 }, .monotonic);
1481 switch (old_status.cancelation) {
1482 .parked => unreachable,
1483 .blocked => unreachable,
1484 .blocked_alertable => unreachable,
1485 .blocked_canceling => unreachable,
1486 .blocked_alertable_canceling => unreachable,
1487 .none => return .{ .thread = thread }, // new status is `.blocked_alertable`
1488 .canceling => {
1489 // Status is unchanged (still `.canceling`)---change to `.canceled` before return.
1490 thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic);
1491 return error.Canceled;
1492 },
1493 .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged)
1494 }
1495 }
1496
1497 fn checkCancel(s: AlertableSyscall) Io.Cancelable!void {
1498 comptime assert(is_windows);
1499 const thread = s.thread orelse return;
1500 const old_status = thread.status.fetchOr(.{
1501 .cancelation = @fromBackingInt(@intCast(0b010)),
1502 .awaitable = .null,
1503 }, .monotonic);
1504 switch (old_status.cancelation) {
1505 .none => unreachable,
1506 .parked => unreachable,
1507 .blocked => unreachable,
1508 .blocked_canceling => unreachable,
1509 .canceling => unreachable,
1510 .canceled => unreachable,
1511 .blocked_alertable => {}, // new status is `.blocked_alertable` (unchanged)
1512 .blocked_alertable_canceling => {
1513 // New status is `.canceling`---change to `.canceled` before return.
1514 thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic);
1515 return error.Canceled;
1516 },
1517 }
1518 }
1519
1520 fn finish(s: AlertableSyscall) void {
1521 comptime assert(is_windows);
1522 const thread = s.thread orelse return;
1523 switch (thread.status.fetchXor(.{
1524 .cancelation = @fromBackingInt(@intCast(0b010)),
1525 .awaitable = .null,
1526 }, .monotonic).cancelation) {
1527 .none => unreachable,
1528 .parked => unreachable,
1529 .blocked => unreachable,
1530 .blocked_canceling => unreachable,
1531 .canceling => unreachable,
1532 .canceled => unreachable,
1533 .blocked_alertable => {}, // new status is `.none`
1534 .blocked_alertable_canceling => {}, // new status is `.canceling`
1535 }
1536 }
1537
1538 fn fail(s: AlertableSyscall, err: anytype) @TypeOf(err) {
1539 s.finish();
1540 return err;
1541 }
1542
1543 fn ntstatusBug(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError {
1544 @branchHint(.cold);
1545 s.finish();
1546 return windows.statusBug(status);
1547 }
1548
1549 fn unexpectedNtstatus(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError {
1550 @branchHint(.cold);
1551 s.finish();
1552 return windows.unexpectedStatus(status);
1553 }
1554};
1555
1556pub fn waitForApcOrAlert() void {
1557 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
1558 _ = windows.ntdll.NtDelayExecution(.TRUE, &infinite_timeout);
1559}
1560
1561pub const max_iovecs_len = 8;
1562pub const splat_buffer_size = 64;
1563/// Happens to be the same number that matches maximum number of handles that
1564/// NtWaitForMultipleObjects accepts. We use this value also for poll() on
1565/// posix systems.
1566const poll_buffer_len = 64;
1567pub const default_PATH = "/usr/local/bin:/bin:/usr/bin";
1568/// There are multiple kernel bugs being worked around with retries.
1569const max_windows_kernel_bug_retries = 13;
1570
1571comptime {
1572 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
1573}
1574
1575pub const InitOptions = struct {
1576 /// Affects how many bytes are memory-mapped for threads.
1577 stack_size: usize = std.Thread.SpawnConfig.default_stack_size,
1578 /// Maximum thread pool size (excluding main thread) when dispatching async
1579 /// tasks. Until this limit, calls to `Io.async` when all threads are busy will
1580 /// cause a new thread to be spawned and permanently added to the pool. After
1581 /// this limit, calls to `Io.async` when all threads are busy run the task
1582 /// immediately.
1583 ///
1584 /// Defaults to one less than the number of logical CPU cores.
1585 ///
1586 /// Protected by `Threaded.mutex` once the I/O instance is already in use. See
1587 /// `setAsyncLimit`.
1588 async_limit: ?Io.Limit = null,
1589 /// Maximum thread pool size (excluding main thread) for dispatching concurrent
1590 /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread
1591 /// pool size.
1592 ///
1593 /// After this number, calls to `Io.concurrent` return `error.ConcurrencyUnavailable`.
1594 concurrent_limit: Io.Limit = .unlimited,
1595 /// Affects the following operations:
1596 /// * `processExecutablePath` on OpenBSD and Haiku.
1597 argv0: Argv0 = .empty,
1598 /// Affects the following operations:
1599 /// * `fileIsTty`
1600 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
1601 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
1602 environ: process.Environ = .empty,
1603 /// If set to `true`, `File.MemoryMap` APIs will always take the fallback path.
1604 disable_memory_mapping: bool = false,
1605};
1606
1607/// Related:
1608/// * `init_single_threaded`
1609pub fn init(
1610 /// Must be threadsafe. Only used for the following functions:
1611 /// * `Io.VTable.async`
1612 /// * `Io.VTable.concurrent`
1613 /// * `Io.VTable.groupAsync`
1614 /// * `Io.VTable.groupConcurrent`
1615 /// If these functions are avoided, then `Allocator.failing` may be passed
1616 /// here.
1617 gpa: Allocator,
1618 options: InitOptions,
1619) Threaded {
1620 if (builtin.single_threaded) return .{
1621 .allocator = gpa,
1622 .stack_size = options.stack_size,
1623 .async_limit = options.async_limit orelse init_single_threaded.async_limit,
1624 .cpu_count_error = init_single_threaded.cpu_count_error,
1625 .concurrent_limit = options.concurrent_limit,
1626 .old_sig_io = undefined,
1627 .old_sig_pipe = undefined,
1628 .have_signal_handler = init_single_threaded.have_signal_handler,
1629 .argv0 = options.argv0,
1630 .environ_initialized = options.environ.block.isEmpty(),
1631 .environ = .{ .process_environ = options.environ },
1632 .worker_threads = init_single_threaded.worker_threads,
1633 .disable_memory_mapping = options.disable_memory_mapping,
1634 };
1635
1636 const cpu_count = std.Thread.getCpuCount();
1637
1638 var t: Threaded = .{
1639 .allocator = gpa,
1640 .stack_size = options.stack_size,
1641 .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing,
1642 .concurrent_limit = options.concurrent_limit,
1643 .cpu_count_error = if (cpu_count) |_| null else |e| e,
1644 .old_sig_io = undefined,
1645 .old_sig_pipe = undefined,
1646 .have_signal_handler = false,
1647 .argv0 = options.argv0,
1648 .environ_initialized = options.environ.block.isEmpty(),
1649 .environ = .{ .process_environ = options.environ },
1650 .worker_threads = .init(null),
1651 .disable_memory_mapping = options.disable_memory_mapping,
1652 };
1653
1654 if (posix.Sigaction != void) {
1655 // This causes sending `posix.SIG.IO` to thread to interrupt blocking
1656 // syscalls, returning `posix.E.INTR`.
1657 const act: posix.Sigaction = .{
1658 .handler = .{ .handler = doNothingSignalHandler },
1659 .mask = posix.sigemptyset(),
1660 .flags = 0,
1661 };
1662 if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io);
1663 if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe);
1664 t.have_signal_handler = true;
1665 }
1666
1667 return t;
1668}
1669
1670/// Statically initialize such that calls to `Io.VTable.concurrent` will fail
1671/// with `error.ConcurrencyUnavailable`.
1672///
1673/// When initialized this way:
1674/// * cancel requests have no effect.
1675/// * `deinit` is safe, but unnecessary to call.
1676pub const init_single_threaded: Threaded = init: {
1677 const env_block: process.Environ.Block = if (is_windows) .global else .empty;
1678 break :init .{
1679 .allocator = .failing,
1680 .stack_size = std.Thread.SpawnConfig.default_stack_size,
1681 .async_limit = .nothing,
1682 .cpu_count_error = null,
1683 .concurrent_limit = .nothing,
1684 .old_sig_io = undefined,
1685 .old_sig_pipe = undefined,
1686 .have_signal_handler = false,
1687 .argv0 = .empty,
1688 .environ_initialized = env_block.isEmpty(),
1689 .environ = .{ .process_environ = .{ .block = env_block } },
1690 .worker_threads = .init(null),
1691 .disable_memory_mapping = false,
1692 };
1693};
1694
1695var global_single_threaded_instance: Threaded = .init_single_threaded;
1696
1697/// In general, the application is responsible for choosing the `Io`
1698/// implementation and library code should accept an `Io` parameter rather than
1699/// accessing this declaration. Most code should avoid referencing this
1700/// declaration entirely.
1701///
1702/// However, in some cases such as debugging, it is desirable to hardcode a
1703/// reference to this `Io` implementation.
1704///
1705/// This instance does not support concurrency or cancelation.
1706pub const global_single_threaded: *Threaded = &global_single_threaded_instance;
1707
1708pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
1709 mutexLock(&t.mutex);
1710 defer mutexUnlock(&t.mutex);
1711 t.async_limit = new_limit;
1712}
1713
1714pub fn deinit(t: *Threaded) void {
1715 t.join();
1716 if (posix.Sigaction != void and t.have_signal_handler) {
1717 if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null);
1718 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
1719 }
1720 t.dl.deinit();
1721 t.null_file.deinit();
1722 t.random_file.deinit();
1723 t.pipe_file.deinit();
1724 t.* = undefined;
1725}
1726
1727fn join(t: *Threaded) void {
1728 if (builtin.single_threaded) return;
1729 {
1730 mutexLock(&t.mutex);
1731 defer mutexUnlock(&t.mutex);
1732 t.join_requested = true;
1733 }
1734 condBroadcast(&t.cond);
1735 t.wait_group.wait();
1736}
1737
1738fn worker(t: *Threaded) void {
1739 var thread: Thread = .{
1740 .next = undefined,
1741 .id = std.Thread.getCurrentId(),
1742 .handle = handle: {
1743 if (std.Thread.use_pthreads) break :handle std.c.pthread_self();
1744 if (is_windows) break :handle undefined; // populated below
1745 },
1746 .status = .init(.{
1747 .cancelation = .none,
1748 .awaitable = .null,
1749 }),
1750 .cancel_protection = .unblocked,
1751 .futex_waiter = undefined,
1752 .unpark_flag = unpark_flag_init,
1753 .park_tid = if (ParkTid == std.Thread.Id) {} else getParkTid(),
1754 .csprng = .uninitialized,
1755 };
1756 Thread.current = &thread;
1757
1758 if (is_windows) {
1759 assert(windows.ntdll.NtOpenThread(
1760 &thread.handle,
1761 .{
1762 .SPECIFIC = .{
1763 .THREAD = .{
1764 .TERMINATE = true, // for `NtCancelSynchronousIoFile`
1765 .ALERT = true, // for `NtAlertThread`
1766 },
1767 },
1768 },
1769 &.{ .ObjectName = null },
1770 &windows.teb().ClientId,
1771 ) == .SUCCESS);
1772 }
1773 defer if (is_windows) {
1774 windows.CloseHandle(thread.handle);
1775 };
1776
1777 {
1778 var head = t.worker_threads.load(.monotonic);
1779 while (true) {
1780 thread.next = head;
1781 head = t.worker_threads.cmpxchgWeak(
1782 head,
1783 &thread,
1784 .release,
1785 .monotonic,
1786 ) orelse break;
1787 }
1788 }
1789
1790 defer t.wait_group.finish();
1791
1792 mutexLock(&t.mutex);
1793 defer mutexUnlock(&t.mutex);
1794
1795 while (true) {
1796 while (t.run_queue.popFirst()) |runnable_node| {
1797 mutexUnlock(&t.mutex);
1798 thread.cancel_protection = .unblocked;
1799 const runnable: *Runnable = @fieldParentPtr("node", runnable_node);
1800 runnable.startFn(runnable, &thread, t);
1801 mutexLock(&t.mutex);
1802 t.busy_count -= 1;
1803 }
1804 if (t.join_requested) break;
1805 condWait(&t.cond, &t.mutex);
1806 }
1807}
1808
1809pub fn io(t: *Threaded) Io {
1810 return .{
1811 .userdata = t,
1812 .vtable = &.{
1813 .crashHandler = crashHandler,
1814
1815 .async = async,
1816 .concurrent = concurrent,
1817 .await = await,
1818 .cancel = cancel,
1819
1820 .groupAsync = groupAsync,
1821 .groupConcurrent = groupConcurrent,
1822 .groupAwait = groupAwait,
1823 .groupCancel = groupCancel,
1824
1825 .recancel = recancel,
1826 .swapCancelProtection = swapCancelProtection,
1827 .checkCancel = checkCancel,
1828
1829 .futexWait = futexWait,
1830 .futexWaitUncancelable = futexWaitUncancelable,
1831 .futexWake = futexWake,
1832
1833 .operate = operate,
1834 .batchAwaitAsync = batchAwaitAsync,
1835 .batchAwaitConcurrent = batchAwaitConcurrent,
1836 .batchCancel = batchCancel,
1837
1838 .dirCreateDir = dirCreateDir,
1839 .dirCreateDirPath = dirCreateDirPath,
1840 .dirCreateDirPathOpen = dirCreateDirPathOpen,
1841 .dirStat = dirStat,
1842 .dirStatFile = dirStatFile,
1843 .dirAccess = dirAccess,
1844 .dirCreateFile = dirCreateFile,
1845 .dirCreateFileAtomic = dirCreateFileAtomic,
1846 .dirOpenFile = dirOpenFile,
1847 .dirOpenDir = dirOpenDir,
1848 .dirClose = dirClose,
1849 .dirRead = dirRead,
1850 .dirRealPath = dirRealPath,
1851 .dirRealPathFile = dirRealPathFile,
1852 .dirDeleteFile = dirDeleteFile,
1853 .dirDeleteDir = dirDeleteDir,
1854 .dirRename = dirRename,
1855 .dirRenamePreserve = dirRenamePreserve,
1856 .dirSymLink = dirSymLink,
1857 .dirReadLink = dirReadLink,
1858 .dirSetOwner = dirSetOwner,
1859 .dirSetFileOwner = dirSetFileOwner,
1860 .dirSetPermissions = dirSetPermissions,
1861 .dirSetFilePermissions = dirSetFilePermissions,
1862 .dirSetTimestamps = dirSetTimestamps,
1863 .dirHardLink = dirHardLink,
1864
1865 .fileStat = fileStat,
1866 .fileLength = fileLength,
1867 .fileClose = fileClose,
1868 .fileWritePositional = fileWritePositional,
1869 .fileWriteFileStreaming = fileWriteFileStreaming,
1870 .fileWriteFilePositional = fileWriteFilePositional,
1871 .fileReadPositional = fileReadPositional,
1872 .fileSeekBy = fileSeekBy,
1873 .fileSeekTo = fileSeekTo,
1874 .fileSync = fileSync,
1875 .fileIsTty = fileIsTty,
1876 .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes,
1877 .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes,
1878 .fileSetLength = fileSetLength,
1879 .fileSetOwner = fileSetOwner,
1880 .fileSetPermissions = fileSetPermissions,
1881 .fileSetTimestamps = fileSetTimestamps,
1882 .fileLock = fileLock,
1883 .fileTryLock = fileTryLock,
1884 .fileUnlock = fileUnlock,
1885 .fileDowngradeLock = fileDowngradeLock,
1886 .fileRealPath = fileRealPath,
1887 .fileHardLink = fileHardLink,
1888
1889 .fileMemoryMapCreate = fileMemoryMapCreate,
1890 .fileMemoryMapDestroy = fileMemoryMapDestroy,
1891 .fileMemoryMapSetLength = fileMemoryMapSetLength,
1892 .fileMemoryMapRead = fileMemoryMapRead,
1893 .fileMemoryMapWrite = fileMemoryMapWrite,
1894
1895 .processExecutableOpen = processExecutableOpen,
1896 .processExecutablePath = processExecutablePath,
1897 .lockStderr = lockStderr,
1898 .tryLockStderr = tryLockStderr,
1899 .unlockStderr = unlockStderr,
1900 .processCurrentPath = processCurrentPath,
1901 .processSetCurrentDir = processSetCurrentDir,
1902 .processSetCurrentPath = processSetCurrentPath,
1903 .processReplace = processReplace,
1904 .processReplacePath = processReplacePath,
1905 .processSpawn = processSpawn,
1906 .processSpawnPath = processSpawnPath,
1907 .childWait = childWait,
1908 .childKill = childKill,
1909
1910 .progressParentFile = progressParentFile,
1911
1912 .now = now,
1913 .clockResolution = clockResolution,
1914 .sleep = sleep,
1915
1916 .random = random,
1917 .randomSecure = randomSecure,
1918
1919 .netListenIp = switch (native_os) {
1920 .windows => netListenIpWindows,
1921 else => netListenIpPosix,
1922 },
1923 .netListenUnix = switch (native_os) {
1924 .windows => netListenUnixWindows,
1925 else => netListenUnixPosix,
1926 },
1927 .netAccept = switch (native_os) {
1928 .windows => netAcceptWindows,
1929 else => netAcceptPosix,
1930 },
1931 .netBindIp = switch (native_os) {
1932 .windows => netBindIpWindows,
1933 else => netBindIpPosix,
1934 },
1935 .netConnectIp = switch (native_os) {
1936 .windows => netConnectIpWindows,
1937 else => netConnectIpPosix,
1938 },
1939 .netConnectUnix = switch (native_os) {
1940 .windows => netConnectUnixWindows,
1941 else => netConnectUnixPosix,
1942 },
1943 .netSocketCreatePair = netSocketCreatePair,
1944 .netClose = netClose,
1945 .netShutdown = switch (native_os) {
1946 .windows => netShutdownWindows,
1947 else => netShutdownPosix,
1948 },
1949 .netWriteFile = netWriteFile,
1950 .netInterfaceNameResolve = netInterfaceNameResolve,
1951 .netInterfaceName = netInterfaceName,
1952 .netLookup = netLookup,
1953 },
1954 };
1955}
1956
1957pub const socket_flags_unsupported = is_darwin or native_os == .haiku;
1958const have_accept4 = !socket_flags_unsupported;
1959const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
1960const have_networking = std.options.networking and native_os != .wasi;
1961const have_flock = @TypeOf(posix.system.flock) != void;
1962const have_sendmmsg = native_os == .linux;
1963const have_futex = switch (builtin.cpu.arch) {
1964 .wasm32, .wasm64 => builtin.cpu.has(.wasm, .atomics),
1965 else => true,
1966};
1967const have_preadv = switch (native_os) {
1968 .windows, .haiku => false,
1969 else => true,
1970};
1971const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");
1972const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");
1973const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux;
1974const have_copy_file_range = switch (native_os) {
1975 .linux, .freebsd => true,
1976 else => false,
1977};
1978const have_fcopyfile = is_darwin;
1979const have_fchmodat2 = native_os == .linux and
1980 (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and
1981 (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }));
1982const have_fchmodat_flags = native_os != .linux or
1983 (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }));
1984
1985const have_fchown = switch (native_os) {
1986 .wasi, .windows => false,
1987 else => true,
1988};
1989
1990const have_fchmod = switch (native_os) {
1991 .windows => false,
1992 .wasi => builtin.link_libc,
1993 else => true,
1994};
1995
1996const have_waitid = switch (native_os) {
1997 .linux => @hasField(std.os.linux.SYS, "waitid"),
1998 else => false,
1999};
2000
2001const have_wait4 = switch (native_os) {
2002 .linux => @hasField(std.os.linux.SYS, "wait4"),
2003 .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true,
2004 else => false,
2005};
2006
2007const have_mmap = switch (native_os) {
2008 .wasi, .windows => false,
2009 else => true,
2010};
2011const have_poll = switch (native_os) {
2012 .wasi, .windows => false,
2013 else => true,
2014};
2015
2016const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open;
2017const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
2018const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
2019const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
2020const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
2021const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
2022const pread_sym = if (posix.lfs64_abi) posix.system.pread64 else posix.system.pread;
2023const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;
2024const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev;
2025const pwrite_sym = if (posix.lfs64_abi) posix.system.pwrite64 else posix.system.pwrite;
2026const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile;
2027const mmap_sym = if (posix.lfs64_abi) posix.system.mmap64 else posix.system.mmap;
2028
2029const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
2030 .major = 34,
2031 .minor = 0,
2032 .patch = 0,
2033} else .{
2034 .major = 2,
2035 .minor = 27,
2036 .patch = 0,
2037});
2038const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
2039
2040const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
2041 .{ .major = 30, .minor = 0, .patch = 0 }
2042else
2043 .{ .major = 2, .minor = 28, .patch = 0 });
2044
2045const use_libc_getrandom = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
2046 .major = 28,
2047 .minor = 0,
2048 .patch = 0,
2049} else .{
2050 .major = 2,
2051 .minor = 25,
2052 .patch = 0,
2053});
2054
2055const use_dev_urandom = @TypeOf(posix.system.getrandom) == void and native_os == .linux;
2056
2057fn crashHandler(userdata: ?*anyopaque) void {
2058 const t: *Threaded = @ptrCast(@alignCast(userdata));
2059 _ = t;
2060 const thread = Thread.current orelse return;
2061 thread.status.store(.{ .cancelation = .canceled, .awaitable = .null }, .monotonic);
2062 thread.cancel_protection = .blocked;
2063}
2064
2065fn async(
2066 userdata: ?*anyopaque,
2067 result: []u8,
2068 result_alignment: Alignment,
2069 context: []const u8,
2070 context_alignment: Alignment,
2071 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
2072) ?*Io.AnyFuture {
2073 const t: *Threaded = @ptrCast(@alignCast(userdata));
2074 if (builtin.single_threaded) {
2075 start(context.ptr, result.ptr);
2076 return null;
2077 }
2078
2079 const gpa = t.allocator;
2080 const future = Future.create(gpa, result.len, result_alignment, context, context_alignment, start) catch |err| switch (err) {
2081 error.OutOfMemory => {
2082 start(context.ptr, result.ptr);
2083 return null;
2084 },
2085 };
2086
2087 mutexLock(&t.mutex);
2088
2089 const busy_count = t.busy_count;
2090
2091 if (busy_count >= @backingInt(t.async_limit)) {
2092 mutexUnlock(&t.mutex);
2093 future.destroy(gpa);
2094 start(context.ptr, result.ptr);
2095 return null;
2096 }
2097
2098 t.busy_count = busy_count + 1;
2099
2100 const pool_size = t.wait_group.value();
2101 if (pool_size - busy_count == 0) {
2102 t.wait_group.start();
2103 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch {
2104 t.wait_group.finish();
2105 t.busy_count = busy_count;
2106 mutexUnlock(&t.mutex);
2107 future.destroy(gpa);
2108 start(context.ptr, result.ptr);
2109 return null;
2110 };
2111 thread.detach();
2112 }
2113
2114 t.run_queue.prepend(&future.runnable.node);
2115
2116 mutexUnlock(&t.mutex);
2117 condSignal(&t.cond);
2118 return @ptrCast(future);
2119}
2120
2121fn concurrent(
2122 userdata: ?*anyopaque,
2123 result_len: usize,
2124 result_alignment: Alignment,
2125 context: []const u8,
2126 context_alignment: Alignment,
2127 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
2128) Io.ConcurrentError!*Io.AnyFuture {
2129 if (builtin.single_threaded) return error.ConcurrencyUnavailable;
2130
2131 const t: *Threaded = @ptrCast(@alignCast(userdata));
2132
2133 const gpa = t.allocator;
2134 const future = Future.create(gpa, result_len, result_alignment, context, context_alignment, start) catch |err| switch (err) {
2135 error.OutOfMemory => return error.ConcurrencyUnavailable,
2136 };
2137 errdefer future.destroy(gpa);
2138
2139 mutexLock(&t.mutex);
2140 defer mutexUnlock(&t.mutex);
2141
2142 const busy_count = t.busy_count;
2143
2144 if (busy_count >= @backingInt(t.concurrent_limit))
2145 return error.ConcurrencyUnavailable;
2146
2147 t.busy_count = busy_count + 1;
2148 errdefer t.busy_count = busy_count;
2149
2150 const pool_size = t.wait_group.value();
2151 if (pool_size - busy_count == 0) {
2152 t.wait_group.start();
2153 errdefer t.wait_group.finish();
2154
2155 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch
2156 return error.ConcurrencyUnavailable;
2157
2158 thread.detach();
2159 }
2160
2161 t.run_queue.prepend(&future.runnable.node);
2162
2163 condSignal(&t.cond);
2164 return @ptrCast(future);
2165}
2166
2167fn groupAsync(
2168 userdata: ?*anyopaque,
2169 type_erased: *Io.Group,
2170 context: []const u8,
2171 context_alignment: Alignment,
2172 start: *const fn (context: *const anyopaque) void,
2173) void {
2174 const t: *Threaded = @ptrCast(@alignCast(userdata));
2175 const g: Group = .{ .ptr = type_erased };
2176
2177 if (builtin.single_threaded) return groupAsyncEager(start, context.ptr);
2178
2179 const gpa = t.allocator;
2180 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {
2181 error.OutOfMemory => return groupAsyncEager(start, context.ptr),
2182 };
2183
2184 mutexLock(&t.mutex);
2185
2186 const busy_count = t.busy_count;
2187
2188 if (busy_count >= @backingInt(t.async_limit)) {
2189 mutexUnlock(&t.mutex);
2190 task.destroy(gpa);
2191 return groupAsyncEager(start, context.ptr);
2192 }
2193
2194 t.busy_count = busy_count + 1;
2195
2196 const pool_size = t.wait_group.value();
2197 if (pool_size - busy_count == 0) {
2198 t.wait_group.start();
2199 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch {
2200 t.wait_group.finish();
2201 t.busy_count = busy_count;
2202 mutexUnlock(&t.mutex);
2203 task.destroy(gpa);
2204 return groupAsyncEager(start, context.ptr);
2205 };
2206 thread.detach();
2207 }
2208
2209 // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue
2210 // prepend so that the task doesn't finish without observing this and try to decrement the count
2211 // below zero.
2212 _ = g.status().fetchAdd(.{
2213 .num_running = 1,
2214 .have_awaiter = false,
2215 .canceled = false,
2216 }, .monotonic);
2217 t.run_queue.prepend(&task.runnable.node);
2218
2219 mutexUnlock(&t.mutex);
2220 condSignal(&t.cond);
2221}
2222fn groupAsyncEager(
2223 start: *const fn (context: *const anyopaque) void,
2224 context: *const anyopaque,
2225) void {
2226 start(context);
2227}
2228
2229fn groupConcurrent(
2230 userdata: ?*anyopaque,
2231 type_erased: *Io.Group,
2232 context: []const u8,
2233 context_alignment: Alignment,
2234 start: *const fn (context: *const anyopaque) void,
2235) Io.ConcurrentError!void {
2236 if (builtin.single_threaded) return error.ConcurrencyUnavailable;
2237
2238 const t: *Threaded = @ptrCast(@alignCast(userdata));
2239 const g: Group = .{ .ptr = type_erased };
2240
2241 const gpa = t.allocator;
2242 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) {
2243 error.OutOfMemory => return error.ConcurrencyUnavailable,
2244 };
2245 errdefer task.destroy(gpa);
2246
2247 mutexLock(&t.mutex);
2248 defer mutexUnlock(&t.mutex);
2249
2250 const busy_count = t.busy_count;
2251
2252 if (busy_count >= @backingInt(t.concurrent_limit))
2253 return error.ConcurrencyUnavailable;
2254
2255 t.busy_count = busy_count + 1;
2256 errdefer t.busy_count = busy_count;
2257
2258 const pool_size = t.wait_group.value();
2259 if (pool_size - busy_count == 0) {
2260 t.wait_group.start();
2261 errdefer t.wait_group.finish();
2262
2263 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch
2264 return error.ConcurrencyUnavailable;
2265
2266 thread.detach();
2267 }
2268
2269 // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue
2270 // prepend so that the task doesn't finish without observing this and try to decrement the count
2271 // below zero.
2272 _ = g.status().fetchAdd(.{
2273 .num_running = 1,
2274 .have_awaiter = false,
2275 .canceled = false,
2276 }, .monotonic);
2277 t.run_queue.prepend(&task.runnable.node);
2278
2279 condSignal(&t.cond);
2280}
2281
2282fn groupAwait(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void {
2283 _ = initial_token; // we need to load `token` *after* the group finishes
2284 if (builtin.single_threaded) unreachable; // nothing to await
2285 const t: *Threaded = @ptrCast(@alignCast(userdata));
2286 const g: Group = .{ .ptr = type_erased };
2287
2288 var num_completed: std.atomic.Value(u32) = .init(0);
2289 g.awaiter().* = &num_completed;
2290
2291 const pre_await_status = g.status().fetchOr(.{
2292 .num_running = 0,
2293 .have_awaiter = true,
2294 .canceled = false,
2295 }, .acq_rel); // acquire results if complete; release `g.awaiter()`
2296
2297 assert(!pre_await_status.have_awaiter);
2298 assert(!pre_await_status.canceled);
2299 if (pre_await_status.num_running == 0) {
2300 // Already done. Since the group is finished, it's illegal to spawn more tasks in it
2301 // until we return, so we can access `g.status()` non-atomically.
2302 g.status().raw.have_awaiter = false;
2303 return;
2304 }
2305
2306 while (Thread.futexWait(&num_completed.raw, 0, null)) {
2307 switch (num_completed.load(.acquire)) { // acquire task results
2308 0 => continue,
2309 1 => break,
2310 else => unreachable, // group was reused before `await` returned
2311 }
2312 } else |err| switch (err) {
2313 error.Canceled => {
2314 const pre_cancel_status = g.status().fetchOr(.{
2315 .num_running = 0,
2316 .have_awaiter = false,
2317 .canceled = true,
2318 }, .acq_rel); // acquire results if complete; release `g.awaiter()`
2319 assert(pre_cancel_status.have_awaiter);
2320 assert(!pre_cancel_status.canceled);
2321
2322 // Even if `pre_cancel_status.num_running == 0`, we still need to wait for the signal,
2323 // because in that case the last member of the group is already trying to modify it.
2324 // However, if we know everything is done, we *can* skip signaling blocked threads.
2325 const skip_signals = pre_cancel_status.num_running == 0;
2326 g.waitForCancelWithSignaling(t, &num_completed, skip_signals);
2327
2328 // The group is finished, so it's illegal to spawn more tasks in it until we return, so
2329 // we can access `g.status()` non-atomically.
2330 g.status().raw.canceled = false;
2331 g.status().raw.have_awaiter = false;
2332 return error.Canceled;
2333 },
2334 }
2335
2336 // The group is finished, so it's illegal to spawn more tasks in it until we return, so
2337 // we can access `g.status()` non-atomically.
2338 g.status().raw.have_awaiter = false;
2339}
2340
2341fn groupCancel(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) void {
2342 _ = initial_token;
2343 if (builtin.single_threaded) unreachable; // nothing to cancel
2344 const t: *Threaded = @ptrCast(@alignCast(userdata));
2345 const g: Group = .{ .ptr = type_erased };
2346
2347 var num_completed: std.atomic.Value(u32) = .init(0);
2348 g.awaiter().* = &num_completed;
2349
2350 const pre_cancel_status = g.status().fetchOr(.{
2351 .num_running = 0,
2352 .have_awaiter = true,
2353 .canceled = true,
2354 }, .acq_rel); // acquire results if complete; release `g.awaiter()`
2355
2356 assert(!pre_cancel_status.have_awaiter);
2357 assert(!pre_cancel_status.canceled);
2358 if (pre_cancel_status.num_running == 0) {
2359 // Already done. Since the group is finished, it's illegal to spawn more tasks in it
2360 // until we return, so we can access `g.status()` non-atomically.
2361 g.status().raw.have_awaiter = false;
2362 g.status().raw.canceled = false;
2363 return;
2364 }
2365
2366 g.waitForCancelWithSignaling(t, &num_completed, false);
2367
2368 g.status().raw = .{ .num_running = 0, .have_awaiter = false, .canceled = false };
2369}
2370
2371fn recancel(userdata: ?*anyopaque) void {
2372 const t: *Threaded = @ptrCast(@alignCast(userdata));
2373 _ = t;
2374 recancelInner();
2375}
2376fn recancelInner() void {
2377 const thread = Thread.current.?; // called `recancel` but was not canceled
2378 switch (thread.status.fetchXor(.{
2379 .cancelation = @fromBackingInt(@intCast(0b001)),
2380 .awaitable = .null,
2381 }, .monotonic).cancelation) {
2382 .canceled => {},
2383 .none => unreachable, // called `recancel` but was not canceled
2384 .canceling => unreachable, // called `recancel` but cancelation was already pending
2385 .parked => unreachable,
2386 .blocked => unreachable,
2387 .blocked_alertable => unreachable,
2388 .blocked_alertable_canceling => unreachable,
2389 .blocked_canceling => unreachable,
2390 }
2391}
2392
2393fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection {
2394 const t: *Threaded = @ptrCast(@alignCast(userdata));
2395 _ = t;
2396 const thread = Thread.current orelse return .unblocked;
2397 const old = thread.cancel_protection;
2398 thread.cancel_protection = new;
2399 return old;
2400}
2401
2402fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {
2403 const t: *Threaded = @ptrCast(@alignCast(userdata));
2404 _ = t;
2405 return Thread.checkCancel();
2406}
2407
2408fn await(
2409 userdata: ?*anyopaque,
2410 any_future: *Io.AnyFuture,
2411 result: []u8,
2412 result_alignment: Alignment,
2413) void {
2414 _ = result_alignment;
2415 if (builtin.single_threaded) unreachable; // nothing to await
2416 const t: *Threaded = @ptrCast(@alignCast(userdata));
2417 const future: *Future = @ptrCast(@alignCast(any_future));
2418
2419 var num_completed: std.atomic.Value(u32) = .init(0);
2420 future.awaiter = &num_completed;
2421
2422 const pre_await_status = future.status.fetchOr(.{
2423 .tag = .pending_awaited,
2424 .thread = .null,
2425 }, .acq_rel); // acquire results if complete; release `future.awaiter`
2426 switch (pre_await_status.tag) {
2427 .pending => while (Thread.futexWait(&num_completed.raw, 0, null)) {
2428 switch (num_completed.load(.acquire)) { // acquire task results
2429 0 => continue,
2430 1 => break,
2431 else => unreachable, // group was reused before `await` returned
2432 }
2433 } else |err| switch (err) {
2434 error.Canceled => {
2435 const pre_cancel_status = future.status.fetchOr(.{
2436 .tag = .pending_canceled,
2437 .thread = .null,
2438 }, .acq_rel); // acquire results if complete; release `future.awaiter`
2439 const done_status = switch (pre_cancel_status.tag) {
2440 .pending => unreachable, // invalid state: we already awaited
2441 .pending_awaited => done_status: {
2442 const working_thread = pre_cancel_status.thread.unpack();
2443 future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread));
2444 break :done_status future.status.load(.monotonic);
2445 },
2446 .pending_canceled => unreachable, // `await` raced with `cancel`
2447 .done => done_status: {
2448 // The task just finished, but we still need to wait for the signal, because the
2449 // task thread already figured out that they need to update `future.awaiter`.
2450 future.waitForCancelWithSignaling(t, &num_completed, null);
2451 // Also, we have clobbered `future.status.tag` to `.pending_canceled`, but that's
2452 // not actually a problem for the logic below.
2453 break :done_status pre_cancel_status;
2454 },
2455 };
2456 // If the future did not acknowledge the cancelation, we need to mark it outstanding
2457 // for us. Because `done_status.tag == .done`, the information about whether there
2458 // was an acknowledged cancelation is encoded in `done_status.thread`.
2459 assert(done_status.tag == .done);
2460 switch (done_status.thread) {
2461 .null => recancelInner(), // cancelation was not acknowledged, so it's ours
2462 .all_ones => {}, // cancelation was acknowledged, so it was this task's job to propagate it
2463 _ => unreachable,
2464 }
2465 },
2466 },
2467 .pending_awaited => unreachable, // `await` raced with `await`
2468 .pending_canceled => unreachable, // `await` raced with `cancel`
2469 .done => {},
2470 }
2471 @memcpy(result, future.resultPointer());
2472 future.destroy(t.allocator);
2473}
2474
2475fn cancel(
2476 userdata: ?*anyopaque,
2477 any_future: *Io.AnyFuture,
2478 result: []u8,
2479 result_alignment: Alignment,
2480) void {
2481 _ = result_alignment;
2482 if (builtin.single_threaded) unreachable; // nothing to cancel
2483 const t: *Threaded = @ptrCast(@alignCast(userdata));
2484 const future: *Future = @ptrCast(@alignCast(any_future));
2485
2486 var num_completed: std.atomic.Value(u32) = .init(0);
2487 future.awaiter = &num_completed;
2488
2489 const pre_cancel_status = future.status.fetchOr(.{
2490 .tag = .pending_canceled,
2491 .thread = .null,
2492 }, .acq_rel); // acquire results if complete; release `future.awaiter`
2493 switch (pre_cancel_status.tag) {
2494 .pending => {
2495 const working_thread = pre_cancel_status.thread.unpack();
2496 future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread));
2497 },
2498 .pending_awaited => unreachable, // `await` raced with `await`
2499 .pending_canceled => unreachable, // `await` raced with `cancel`
2500 .done => {},
2501 }
2502 @memcpy(result, future.resultPointer());
2503 future.destroy(t.allocator);
2504}
2505
2506fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void {
2507 if (builtin.single_threaded) {
2508 assert(timeout != .none); // Deadlock.
2509 return;
2510 }
2511 const t: *Threaded = @ptrCast(@alignCast(userdata));
2512 const t_io = io(t);
2513 const timeout_ns: ?u64 = ns: {
2514 const d = timeout.toDurationFromNow(t_io) orelse break :ns null;
2515 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());
2516 };
2517 return Thread.futexWait(ptr, expected, timeout_ns);
2518}
2519
2520fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
2521 if (builtin.single_threaded) unreachable; // Deadlock.
2522 const t: *Threaded = @ptrCast(@alignCast(userdata));
2523 _ = t;
2524 Thread.futexWaitUncancelable(ptr, expected, null);
2525}
2526
2527fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
2528 if (builtin.single_threaded) return; // Nothing to wake up.
2529 const t: *Threaded = @ptrCast(@alignCast(userdata));
2530 _ = t;
2531 Thread.futexWake(ptr, max_waiters);
2532}
2533
2534fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result {
2535 const t: *Threaded = @ptrCast(@alignCast(userdata));
2536 switch (operation) {
2537 .file_read_streaming => |o| return .{
2538 .file_read_streaming = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) {
2539 error.Canceled => |e| return e,
2540 else => |e| e,
2541 },
2542 },
2543 .file_write_streaming => |o| return .{
2544 .file_write_streaming = fileWriteStreaming(t, o.file, o.header, o.data, o.splat) catch |err| switch (err) {
2545 error.Canceled => |e| return e,
2546 else => |e| e,
2547 },
2548 },
2549 .device_io_control => |*o| return .{ .device_io_control = try deviceIoControl(o) },
2550 .net_receive => |*o| return .{ .net_receive = o: {
2551 if (!have_networking) break :o .{ error.NetworkDown, 0 };
2552 if (is_windows) break :o netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags);
2553 netReceivePosix(o.socket_handle, &o.message_buffer[0], o.data_buffer, o.flags, false) catch |err| switch (err) {
2554 error.Canceled => |e| return e,
2555 error.WouldBlock => unreachable,
2556 else => |e| break :o .{ e, 0 },
2557 };
2558 break :o .{ null, 1 };
2559 } },
2560 .net_send => |*o| return .{
2561 .net_send = o: {
2562 if (!have_networking) break :o .{ error.NetworkDown, 0 };
2563 if (is_windows) break :o netSendWindows(t, o.socket_handle, o.messages, o.flags);
2564 const send_err, const sent = netSendPosix(t, o.socket_handle, o.messages, o.flags, false);
2565 if (send_err) |err| switch (err) {
2566 error.Canceled => |e| if (sent == 0) {
2567 return e;
2568 } else {
2569 // Leave the `error.Canceled` for later, but don't try to send any more messages.
2570 recancelInner();
2571 break :o .{ null, sent };
2572 },
2573 error.WouldBlock => unreachable,
2574 else => |e| break :o .{ e, sent },
2575 };
2576 break :o .{ null, sent };
2577 },
2578 },
2579 .net_read => |o| return .{
2580 .net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) {
2581 error.Canceled => |e| return e,
2582 else => |e| e,
2583 },
2584 },
2585 .net_write => |o| return .{
2586 .net_write = (if (is_windows)
2587 netWriteWindows(o.socket_handle, o.header, o.data, o.splat)
2588 else
2589 netWritePosix(o.socket_handle, o.header, o.data, o.splat)) catch |err| switch (err) {
2590 error.Canceled => |e| return e,
2591 else => |e| e,
2592 },
2593 },
2594 }
2595}
2596
2597fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
2598 const t: *Threaded = @ptrCast(@alignCast(userdata));
2599 if (is_windows) {
2600 batchDrainSubmittedWindows(t, b, false) catch |err| switch (err) {
2601 error.ConcurrencyUnavailable => unreachable, // passed concurrency=false
2602 else => |e| return e,
2603 };
2604 const alertable_syscall = try AlertableSyscall.start();
2605 while (b.pending.head != .none and b.completed.head == .none) waitForApcOrAlert();
2606 alertable_syscall.finish();
2607 return;
2608 }
2609 if (have_poll) {
2610 var poll_buffer: [poll_buffer_len]posix.pollfd = undefined;
2611 var poll_len: u32 = 0;
2612 {
2613 var index = b.submitted.head;
2614 while (index != .none and poll_len < poll_buffer_len) {
2615 const submission = &b.storage[index.toIndex()].submission;
2616 switch (submission.operation) {
2617 .file_read_streaming => |o| {
2618 poll_buffer[poll_len] = .{
2619 .fd = o.file.handle,
2620 .events = posix.POLL.IN | posix.POLL.ERR,
2621 };
2622 poll_len += 1;
2623 },
2624 .file_write_streaming => |o| {
2625 poll_buffer[poll_len] = .{
2626 .fd = o.file.handle,
2627 .events = posix.POLL.OUT | posix.POLL.ERR,
2628 };
2629 poll_len += 1;
2630 },
2631 .device_io_control => |o| {
2632 poll_buffer[poll_len] = .{
2633 .fd = o.file.handle,
2634 .events = posix.POLL.OUT | posix.POLL.IN | posix.POLL.ERR,
2635 };
2636 poll_len += 1;
2637 },
2638 .net_receive => |*o| {
2639 poll_buffer[poll_len] = .{
2640 .fd = o.socket_handle,
2641 .events = posix.POLL.IN | posix.POLL.ERR,
2642 };
2643 poll_len += 1;
2644 },
2645 .net_send => |*o| {
2646 poll_buffer[poll_len] = .{
2647 .fd = o.socket_handle,
2648 .events = posix.POLL.OUT | posix.POLL.ERR,
2649 };
2650 poll_len += 1;
2651 },
2652 .net_read => |o| {
2653 poll_buffer[poll_len] = .{
2654 .fd = o.socket_handle,
2655 .events = posix.POLL.IN | posix.POLL.ERR,
2656 };
2657 poll_len += 1;
2658 },
2659 .net_write => |o| {
2660 poll_buffer[poll_len] = .{
2661 .fd = o.socket_handle,
2662 .events = posix.POLL.OUT | posix.POLL.ERR,
2663 };
2664 poll_len += 1;
2665 },
2666 }
2667 index = submission.node.next;
2668 }
2669 }
2670 switch (poll_len) {
2671 0 => return,
2672 1 => {},
2673 else => while (true) {
2674 const timeout_ms: i32 = t: {
2675 if (b.completed.head != .none) {
2676 // It is legal to call batchWait with already completed
2677 // operations in the ring. In such case, we need to avoid
2678 // blocking in the poll syscall, but we can still take this
2679 // opportunity to find additional ready operations.
2680 break :t 0;
2681 }
2682 break :t std.math.maxInt(i32);
2683 };
2684 const syscall = try Syscall.start();
2685 const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms);
2686 syscall.finish();
2687 switch (posix.errno(rc)) {
2688 .SUCCESS => {
2689 if (rc == 0) {
2690 if (b.completed.head != .none) {
2691 // Since there are already completions available in the
2692 // queue, this is neither a timeout nor a case for
2693 // retrying.
2694 return;
2695 }
2696 continue;
2697 }
2698 var prev_index: Io.Operation.OptionalIndex = .none;
2699 var index = b.submitted.head;
2700 for (poll_buffer[0..poll_len]) |poll_entry| {
2701 const storage = &b.storage[index.toIndex()];
2702 const submission = &storage.submission;
2703 const next_index = submission.node.next;
2704 if (poll_entry.revents != 0) {
2705 const result = try operate(t, submission.operation);
2706
2707 switch (prev_index) {
2708 .none => b.submitted.head = next_index,
2709 else => b.storage[prev_index.toIndex()].submission.node.next = next_index,
2710 }
2711 if (next_index == .none) b.submitted.tail = prev_index;
2712
2713 switch (b.completed.tail) {
2714 .none => b.completed.head = index,
2715 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2716 }
2717 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2718 b.completed.tail = index;
2719 } else prev_index = index;
2720 index = next_index;
2721 }
2722 assert(index == .none);
2723 return;
2724 },
2725 .INTR => continue,
2726 else => break,
2727 }
2728 },
2729 }
2730 }
2731
2732 var tail_index = b.completed.tail;
2733 defer b.completed.tail = tail_index;
2734 var index = b.submitted.head;
2735 errdefer b.submitted.head = index;
2736 while (index != .none) {
2737 const storage = &b.storage[index.toIndex()];
2738 const submission = &storage.submission;
2739 const next_index = submission.node.next;
2740 const result = try operate(t, submission.operation);
2741
2742 switch (tail_index) {
2743 .none => b.completed.head = index,
2744 else => b.storage[tail_index.toIndex()].completion.node.next = index,
2745 }
2746 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2747 tail_index = index;
2748 index = next_index;
2749 }
2750 b.submitted = .{ .head = .none, .tail = .none };
2751}
2752
2753fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void {
2754 const t: *Threaded = @ptrCast(@alignCast(userdata));
2755 if (is_windows) {
2756 const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(io(t));
2757 try batchDrainSubmittedWindows(t, b, true);
2758 while (b.pending.head != .none and b.completed.head == .none) {
2759 var delay_interval: windows.LARGE_INTEGER = interval: {
2760 const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER);
2761 break :interval timeoutToWindowsInterval(.{ .deadline = d }).?;
2762 };
2763 const alertable_syscall = try AlertableSyscall.start();
2764 const delay_rc = windows.ntdll.NtDelayExecution(.TRUE, &delay_interval);
2765 alertable_syscall.finish();
2766 switch (delay_rc) {
2767 .SUCCESS, .TIMEOUT => {
2768 // The thread woke due to the timeout. Although spurious
2769 // timeouts are OK, when no deadline is passed we must not
2770 // return `error.Timeout`.
2771 if (timeout != .none and b.completed.head == .none) return error.Timeout;
2772 },
2773 else => {},
2774 }
2775 }
2776 return;
2777 }
2778 if (native_os == .wasi) {
2779 // TODO call poll_oneoff
2780 return error.ConcurrencyUnavailable;
2781 }
2782 if (!have_poll) return error.ConcurrencyUnavailable;
2783 var poll_buffer: [poll_buffer_len]posix.pollfd = undefined;
2784 var poll_storage: struct {
2785 gpa: Allocator,
2786 batch: *Io.Batch,
2787 slice: []posix.pollfd,
2788 len: u32,
2789
2790 fn add(storage: *@This(), fd: File.Handle, events: @FieldType(posix.pollfd, "events")) Io.ConcurrentError!void {
2791 const len = storage.len;
2792 if (len == poll_buffer_len) {
2793 const slice: []posix.pollfd = if (storage.batch.userdata) |batch_userdata|
2794 @as([*]posix.pollfd, @ptrCast(@alignCast(batch_userdata)))[0..storage.batch.storage.len]
2795 else allocation: {
2796 const allocation = storage.gpa.alloc(posix.pollfd, storage.batch.storage.len) catch
2797 return error.ConcurrencyUnavailable;
2798 storage.batch.userdata = allocation.ptr;
2799 break :allocation allocation;
2800 };
2801 @memcpy(slice[0..poll_buffer_len], storage.slice);
2802 storage.slice = slice;
2803 }
2804 storage.slice[len] = .{
2805 .fd = fd,
2806 .events = events,
2807 };
2808 storage.len = len + 1;
2809 }
2810 } = .{ .gpa = t.allocator, .batch = b, .slice = &poll_buffer, .len = 0 };
2811 {
2812 var index = b.submitted.head;
2813 while (index != .none) {
2814 const storage = &b.storage[index.toIndex()];
2815 const submission = storage.submission;
2816 switch (submission.operation) {
2817 .file_read_streaming => |o| try poll_storage.add(o.file.handle, posix.POLL.IN | posix.POLL.ERR),
2818 .file_write_streaming => |o| try poll_storage.add(o.file.handle, posix.POLL.OUT | posix.POLL.ERR),
2819 .device_io_control => |o| try poll_storage.add(o.file.handle, posix.POLL.IN | posix.POLL.OUT | posix.POLL.ERR),
2820 .net_receive => |*o| nb: {
2821 var data_i: usize = 0;
2822 const result: Io.Operation.Result = .{ .net_receive = for (o.message_buffer, 0..) |*msg, msg_i| {
2823 const remaining_data_buffer = o.data_buffer[data_i..];
2824 netReceivePosix(o.socket_handle, msg, remaining_data_buffer, o.flags, true) catch |err| switch (err) {
2825 error.Canceled => |e| return e,
2826 error.WouldBlock => {
2827 if (msg_i != 0) break .{ null, msg_i };
2828 try poll_storage.add(o.socket_handle, posix.POLL.IN | posix.POLL.ERR);
2829 break :nb;
2830 },
2831 else => |e| break .{ e, 0 },
2832 };
2833 data_i += msg.data.len;
2834 } else .{ null, o.message_buffer.len } };
2835 switch (b.completed.tail) {
2836 .none => b.completed.head = index,
2837 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2838 }
2839 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2840 b.completed.tail = index;
2841 },
2842 .net_send => |*o| nb: {
2843 const result: Io.Operation.Result = .{
2844 .net_send = o: {
2845 const send_err, const sent = netSendPosix(t, o.socket_handle, o.messages, o.flags, true);
2846 if (send_err) |err| switch (err) {
2847 error.Canceled => |e| if (sent == 0) {
2848 return e;
2849 } else {
2850 // Leave the `error.Canceled` for later, but don't try to send any more messages.
2851 recancelInner();
2852 break :o .{ null, sent };
2853 },
2854 error.WouldBlock => {
2855 if (sent != 0) break :o .{ null, sent };
2856 try poll_storage.add(o.socket_handle, posix.POLL.OUT | posix.POLL.ERR);
2857 break :nb;
2858 },
2859 else => |e| break :o .{ e, sent },
2860 };
2861 break :o .{ null, sent };
2862 },
2863 };
2864 switch (b.completed.tail) {
2865 .none => b.completed.head = index,
2866 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2867 }
2868 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2869 b.completed.tail = index;
2870 },
2871 .net_read => |o| try poll_storage.add(o.socket_handle, posix.POLL.IN | posix.POLL.ERR),
2872 .net_write => |o| try poll_storage.add(o.socket_handle, posix.POLL.OUT | posix.POLL.ERR),
2873 }
2874 index = submission.node.next;
2875 }
2876 }
2877 switch (poll_storage.len) {
2878 0 => return,
2879 1 => if (timeout == .none and b.completed.head == .none) {
2880 const index = b.submitted.head;
2881 const storage = &b.storage[index.toIndex()];
2882 const result = try operate(t, storage.submission.operation);
2883
2884 b.submitted = .{ .head = .none, .tail = .none };
2885
2886 switch (b.completed.tail) {
2887 .none => b.completed.head = index,
2888 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2889 }
2890 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2891 b.completed.tail = index;
2892 return;
2893 },
2894 else => {},
2895 }
2896 const t_io = io(t);
2897 const deadline = timeout.toTimestamp(t_io);
2898 while (true) {
2899 const timeout_ms: i32 = t: {
2900 if (b.completed.head != .none) {
2901 // It is legal to call batchWait with already completed
2902 // operations in the ring. In such case, we need to avoid
2903 // blocking in the poll syscall, but we can still take this
2904 // opportunity to find additional ready operations.
2905 break :t 0;
2906 }
2907 const d = deadline orelse break :t -1;
2908 const duration = d.durationFromNow(t_io);
2909 break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32));
2910 };
2911 const syscall = try Syscall.start();
2912 const rc = posix.system.poll(poll_storage.slice.ptr, poll_storage.len, timeout_ms);
2913 syscall.finish();
2914 switch (posix.errno(rc)) {
2915 .SUCCESS => {
2916 if (rc == 0) {
2917 if (b.completed.head != .none) {
2918 // Since there are already completions available in the
2919 // queue, this is neither a timeout nor a case for
2920 // retrying.
2921 return;
2922 }
2923 // Although spurious timeouts are OK, when no deadline is
2924 // passed we must not return `error.Timeout`.
2925 if (deadline == null) continue;
2926 return error.Timeout;
2927 }
2928 var prev_index: Io.Operation.OptionalIndex = .none;
2929 var index = b.submitted.head;
2930 for (poll_storage.slice[0..poll_storage.len]) |poll_entry| {
2931 const submission = &b.storage[index.toIndex()].submission;
2932 const next_index = submission.node.next;
2933 if (poll_entry.revents != 0) {
2934 const result = try operate(t, submission.operation);
2935
2936 switch (prev_index) {
2937 .none => b.submitted.head = next_index,
2938 else => b.storage[prev_index.toIndex()].submission.node.next = next_index,
2939 }
2940 if (next_index == .none) b.submitted.tail = prev_index;
2941
2942 switch (b.completed.tail) {
2943 .none => b.completed.head = index,
2944 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2945 }
2946 b.completed.tail = index;
2947 b.storage[index.toIndex()] = .{ .completion = .{
2948 .node = .{ .next = .none },
2949 .result = result,
2950 } };
2951 } else prev_index = index;
2952 index = next_index;
2953 }
2954 assert(index == .none);
2955 return;
2956 },
2957 .INTR => continue,
2958 else => return error.ConcurrencyUnavailable,
2959 }
2960 }
2961}
2962
2963const WindowsBatchOperationUserdata = extern struct {
2964 file: windows.HANDLE,
2965 iosb: windows.IO_STATUS_BLOCK,
2966
2967 const Erased = Io.Operation.Storage.Pending.Userdata;
2968
2969 comptime {
2970 assert(@sizeOf(WindowsBatchOperationUserdata) <= @sizeOf(Erased));
2971 }
2972
2973 fn toErased(userdata: *WindowsBatchOperationUserdata) *Erased {
2974 return @ptrCast(userdata);
2975 }
2976
2977 fn fromErased(erased: *Erased) *WindowsBatchOperationUserdata {
2978 return @ptrCast(erased);
2979 }
2980};
2981
2982fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void {
2983 const t: *Threaded = @ptrCast(@alignCast(userdata));
2984 if (is_windows) {
2985 if (b.pending.head == .none) return;
2986 waitForApcOrAlert();
2987 var index = b.pending.head;
2988 while (index != .none) {
2989 const pending = &b.storage[index.toIndex()].pending;
2990 const operation_userdata: *WindowsBatchOperationUserdata = .fromErased(&pending.userdata);
2991 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
2992 _ = windows.ntdll.NtCancelIoFileEx(operation_userdata.file, &operation_userdata.iosb, &cancel_iosb);
2993 index = pending.node.next;
2994 }
2995 while (b.pending.head != .none) waitForApcOrAlert();
2996 } else if (b.userdata) |batch_userdata| {
2997 const poll_storage: [*]posix.pollfd = @ptrCast(@alignCast(batch_userdata));
2998 t.allocator.free(poll_storage[0..b.storage.len]);
2999 b.userdata = null;
3000 }
3001}
3002
3003fn batchCompleteBlockingWindows(
3004 b: *Io.Batch,
3005 operation_userdata: *WindowsBatchOperationUserdata,
3006 result: Io.Operation.Result,
3007) void {
3008 const erased_userdata = operation_userdata.toErased();
3009 const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("userdata", erased_userdata);
3010 switch (pending.node.prev) {
3011 .none => b.pending.head = pending.node.next,
3012 else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next,
3013 }
3014 switch (pending.node.next) {
3015 .none => b.pending.tail = pending.node.prev,
3016 else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev,
3017 }
3018 const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending);
3019 const index: Io.Operation.OptionalIndex = .fromIndex(storage - b.storage.ptr);
3020 switch (b.completed.tail) {
3021 .none => b.completed.head = index,
3022 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
3023 }
3024 b.completed.tail = index;
3025 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
3026}
3027
3028fn batchApc(
3029 apc_context: ?*anyopaque,
3030 iosb: *windows.IO_STATUS_BLOCK,
3031 _: windows.ULONG,
3032) align(apc_align) callconv(.winapi) void {
3033 const b: *Io.Batch = @ptrCast(@alignCast(apc_context));
3034 const operation_userdata: *WindowsBatchOperationUserdata = @fieldParentPtr("iosb", iosb);
3035 const erased_userdata = operation_userdata.toErased();
3036 const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("userdata", erased_userdata);
3037 switch (pending.node.prev) {
3038 .none => b.pending.head = pending.node.next,
3039 else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next,
3040 }
3041 switch (pending.node.next) {
3042 .none => b.pending.tail = pending.node.prev,
3043 else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev,
3044 }
3045 const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending);
3046 const index: Io.Operation.OptionalIndex = .fromIndex(storage - b.storage.ptr);
3047 switch (iosb.u.Status) {
3048 .CANCELLED => {
3049 const tail_index = b.unused.tail;
3050 switch (tail_index) {
3051 .none => b.unused.head = index,
3052 else => b.storage[tail_index.toIndex()].unused.next = index,
3053 }
3054 storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } };
3055 b.unused.tail = index;
3056 },
3057 else => {
3058 switch (b.completed.tail) {
3059 .none => b.completed.head = index,
3060 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
3061 }
3062 b.completed.tail = index;
3063 const result: Io.Operation.Result = switch (pending.tag) {
3064 .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) },
3065 .file_write_streaming => .{ .file_write_streaming = ntWriteFileResult(iosb) },
3066 .device_io_control => .{ .device_io_control = iosb.* },
3067 .net_receive => unreachable,
3068 .net_send => unreachable,
3069 .net_read => unreachable,
3070 .net_write => unreachable,
3071 };
3072 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
3073 },
3074 }
3075}
3076
3077/// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable.
3078fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io.ConcurrentError || Io.Cancelable)!void {
3079 var index = b.submitted.head;
3080 errdefer b.submitted.head = index;
3081 while (index != .none) {
3082 const storage = &b.storage[index.toIndex()];
3083 const submission = storage.submission;
3084 storage.* = .{ .pending = .{
3085 .node = .{ .prev = b.pending.tail, .next = .none },
3086 .tag = submission.operation,
3087 .userdata = undefined,
3088 } };
3089 switch (b.pending.tail) {
3090 .none => b.pending.head = index,
3091 else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index,
3092 }
3093 b.pending.tail = index;
3094 const operation_userdata: *WindowsBatchOperationUserdata = .fromErased(&storage.pending.userdata);
3095 errdefer {
3096 operation_userdata.iosb = .{ .u = .{ .Status = .CANCELLED }, .Information = undefined };
3097 batchApc(b, &operation_userdata.iosb, 0);
3098 }
3099 switch (submission.operation) {
3100 .file_read_streaming => |o| o: {
3101 var data_index: usize = 0;
3102 while (o.data.len - data_index != 0 and o.data[data_index].len == 0) data_index += 1;
3103 if (o.data.len - data_index == 0) {
3104 operation_userdata.iosb = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 };
3105 batchApc(b, &operation_userdata.iosb, 0);
3106 break :o;
3107 }
3108 const buffer = o.data[data_index];
3109 const short_buffer_len = std.math.lossyCast(u32, buffer.len);
3110
3111 if (o.file.flags.nonblocking) {
3112 operation_userdata.file = o.file.handle;
3113 switch (windows.ntdll.NtReadFile(
3114 o.file.handle,
3115 null, // event
3116 &batchApc,
3117 b,
3118 &operation_userdata.iosb,
3119 buffer.ptr,
3120 short_buffer_len,
3121 null, // byte offset
3122 null, // key
3123 )) {
3124 .PENDING, .SUCCESS => {},
3125 .CANCELLED => unreachable,
3126 else => |status| {
3127 operation_userdata.iosb.u.Status = status;
3128 batchApc(b, &operation_userdata.iosb, 0);
3129 },
3130 }
3131 } else {
3132 if (concurrency) return error.ConcurrencyUnavailable;
3133
3134 const syscall: Syscall = try .start();
3135 while (true) switch (windows.ntdll.NtReadFile(
3136 o.file.handle,
3137 null, // event
3138 null, // APC routine
3139 null, // APC context
3140 &operation_userdata.iosb,
3141 buffer.ptr,
3142 short_buffer_len,
3143 null, // byte offset
3144 null, // key
3145 )) {
3146 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
3147 .CANCELLED => {
3148 try syscall.checkCancel();
3149 continue;
3150 },
3151 else => |status| {
3152 syscall.finish();
3153 operation_userdata.iosb.u.Status = status;
3154 batchApc(b, &operation_userdata.iosb, 0);
3155 break;
3156 },
3157 };
3158 }
3159 },
3160 .file_write_streaming => |o| o: {
3161 const buffer = windowsWriteBuffer(o.header, o.data, o.splat);
3162 if (buffer.len == 0) {
3163 operation_userdata.iosb = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 };
3164 batchApc(b, &operation_userdata.iosb, 0);
3165 break :o;
3166 }
3167 if (o.file.flags.nonblocking) {
3168 operation_userdata.file = o.file.handle;
3169 switch (windows.ntdll.NtWriteFile(
3170 o.file.handle,
3171 null, // event
3172 &batchApc,
3173 b,
3174 &operation_userdata.iosb,
3175 buffer.ptr,
3176 @intCast(buffer.len),
3177 null, // byte offset
3178 null, // key
3179 )) {
3180 .PENDING, .SUCCESS => {},
3181 .CANCELLED => unreachable,
3182 else => |status| {
3183 operation_userdata.iosb.u.Status = status;
3184 batchApc(b, &operation_userdata.iosb, 0);
3185 },
3186 }
3187 } else {
3188 if (concurrency) return error.ConcurrencyUnavailable;
3189
3190 const syscall: Syscall = try .start();
3191 while (true) switch (windows.ntdll.NtWriteFile(
3192 o.file.handle,
3193 null, // event
3194 null, // APC routine
3195 null, // APC context
3196 &operation_userdata.iosb,
3197 buffer.ptr,
3198 @intCast(buffer.len),
3199 null, // byte offset
3200 null, // key
3201 )) {
3202 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
3203 .CANCELLED => {
3204 try syscall.checkCancel();
3205 continue;
3206 },
3207 else => |status| {
3208 syscall.finish();
3209 operation_userdata.iosb.u.Status = status;
3210 batchApc(b, &operation_userdata.iosb, 0);
3211 break;
3212 },
3213 };
3214 }
3215 },
3216 .device_io_control => |o| {
3217 const NtControlFile = switch (o.code.DeviceType) {
3218 .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile,
3219 else => &windows.ntdll.NtDeviceIoControlFile,
3220 };
3221 if (o.file.flags.nonblocking) {
3222 operation_userdata.file = o.file.handle;
3223 switch (NtControlFile(
3224 o.file.handle,
3225 null, // event
3226 &batchApc,
3227 b,
3228 &operation_userdata.iosb,
3229 o.code,
3230 if (o.in.len > 0) o.in.ptr else null,
3231 @intCast(o.in.len),
3232 if (o.out.len > 0) o.out.ptr else null,
3233 @intCast(o.out.len),
3234 )) {
3235 .PENDING, .SUCCESS => {},
3236 .CANCELLED => unreachable,
3237 else => |status| {
3238 operation_userdata.iosb.u.Status = status;
3239 batchApc(b, &operation_userdata.iosb, 0);
3240 },
3241 }
3242 } else {
3243 if (concurrency) return error.ConcurrencyUnavailable;
3244
3245 const syscall: Syscall = try .start();
3246 while (true) switch (NtControlFile(
3247 o.file.handle,
3248 null, // event
3249 null, // APC routine
3250 null, // APC context
3251 &operation_userdata.iosb,
3252 o.code,
3253 if (o.in.len > 0) o.in.ptr else null,
3254 @intCast(o.in.len),
3255 if (o.out.len > 0) o.out.ptr else null,
3256 @intCast(o.out.len),
3257 )) {
3258 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
3259 .CANCELLED => {
3260 try syscall.checkCancel();
3261 continue;
3262 },
3263 else => |status| {
3264 syscall.finish();
3265 operation_userdata.iosb.u.Status = status;
3266 batchApc(b, &operation_userdata.iosb, 0);
3267 break;
3268 },
3269 };
3270 }
3271 },
3272 .net_receive => |*o| {
3273 // TODO integrate with overlapped I/O or equivalent to avoid this error
3274 if (concurrency) return error.ConcurrencyUnavailable;
3275 batchCompleteBlockingWindows(b, operation_userdata, .{
3276 .net_receive = netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags),
3277 });
3278 },
3279 .net_send => |*o| {
3280 // TODO integrate with overlapped I/O or equivalent to avoid this error
3281 if (concurrency) return error.ConcurrencyUnavailable;
3282 batchCompleteBlockingWindows(b, operation_userdata, .{
3283 .net_send = netSendWindows(t, o.socket_handle, o.messages, o.flags),
3284 });
3285 },
3286 .net_read => |*o| {
3287 // TODO integrate with overlapped I/O or equivalent to avoid this error
3288 if (concurrency) return error.ConcurrencyUnavailable;
3289 batchCompleteBlockingWindows(b, operation_userdata, .{
3290 .net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) {
3291 error.Canceled => |e| return e,
3292 else => |e| e,
3293 },
3294 });
3295 },
3296 .net_write => |*o| {
3297 // TODO integrate with overlapped I/O or equivalent to avoid this error
3298 if (concurrency) return error.ConcurrencyUnavailable;
3299 batchCompleteBlockingWindows(b, operation_userdata, .{
3300 .net_write = netWriteWindows(o.socket_handle, o.header, o.data, o.splat) catch |err| switch (err) {
3301 error.Canceled => |e| return e,
3302 else => |e| e,
3303 },
3304 });
3305 },
3306 }
3307 index = submission.node.next;
3308 }
3309 b.submitted = .{ .head = .none, .tail = .none };
3310}
3311
3312/// Since Windows only supports writing one contiguous buffer, returns the
3313/// first one, while also limiting it to a length representable by 32-bit
3314/// unsigned integer.
3315fn windowsWriteBuffer(header: []const u8, data: []const []const u8, splat: usize) []const u8 {
3316 const buffer = b: {
3317 if (header.len != 0) break :b header;
3318 for (data[0 .. data.len - 1]) |buffer| {
3319 if (buffer.len != 0) break :b buffer;
3320 }
3321 if (splat == 0) return &.{};
3322 break :b data[data.len - 1];
3323 };
3324 return buffer[0..std.math.lossyCast(u32, buffer.len)];
3325}
3326
3327fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void {
3328 const ct = complete_tail.*;
3329 const len: u31 = @intCast(ring.len);
3330 ring[ct.index(len)] = op;
3331 complete_tail.* = ct.next(len);
3332}
3333
3334const dirCreateDir = switch (native_os) {
3335 .windows => dirCreateDirWindows,
3336 .wasi => dirCreateDirWasi,
3337 else => dirCreateDirPosix,
3338};
3339
3340fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
3341 const t: *Threaded = @ptrCast(@alignCast(userdata));
3342 _ = t;
3343
3344 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3345 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3346
3347 const syscall: Syscall = try .start();
3348 while (true) {
3349 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) {
3350 .SUCCESS => {
3351 syscall.finish();
3352 return;
3353 },
3354 .INTR => {
3355 try syscall.checkCancel();
3356 continue;
3357 },
3358 .ACCES => return syscall.fail(error.AccessDenied),
3359 .PERM => return syscall.fail(error.PermissionDenied),
3360 .DQUOT => return syscall.fail(error.DiskQuota),
3361 .EXIST => return syscall.fail(error.PathAlreadyExists),
3362 .LOOP => return syscall.fail(error.SymLinkLoop),
3363 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
3364 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
3365 .NOENT => return syscall.fail(error.FileNotFound),
3366 .NOMEM => return syscall.fail(error.SystemResources),
3367 .NOSPC => return syscall.fail(error.NoSpaceLeft),
3368 .NOTDIR => return syscall.fail(error.NotDir),
3369 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
3370 // dragonfly: when dir_fd is unlinked from filesystem
3371 .NOTCONN => return syscall.fail(error.FileNotFound),
3372 .ILSEQ => return syscall.fail(error.BadPathName),
3373 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
3374 .FAULT => |err| return syscall.errnoBug(err),
3375 else => |err| return syscall.unexpectedErrno(err),
3376 }
3377 }
3378}
3379
3380fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
3381 if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions);
3382 const t: *Threaded = @ptrCast(@alignCast(userdata));
3383 _ = t;
3384 const syscall: Syscall = try .start();
3385 while (true) {
3386 switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) {
3387 .SUCCESS => {
3388 syscall.finish();
3389 return;
3390 },
3391 .INTR => {
3392 try syscall.checkCancel();
3393 continue;
3394 },
3395 else => |e| {
3396 syscall.finish();
3397 switch (e) {
3398 .ACCES => return error.AccessDenied,
3399 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3400 .PERM => return error.PermissionDenied,
3401 .DQUOT => return error.DiskQuota,
3402 .EXIST => return error.PathAlreadyExists,
3403 .FAULT => |err| return errnoBug(err),
3404 .LOOP => return error.SymLinkLoop,
3405 .MLINK => return error.LinkQuotaExceeded,
3406 .NAMETOOLONG => return error.NameTooLong,
3407 .NOENT => return error.FileNotFound,
3408 .NOMEM => return error.SystemResources,
3409 .NOSPC => return error.NoSpaceLeft,
3410 .NOTDIR => return error.NotDir,
3411 .ROFS => return error.ReadOnlyFileSystem,
3412 .NOTCAPABLE => return error.AccessDenied,
3413 .ILSEQ => return error.BadPathName,
3414 else => |err| return posix.unexpectedErrno(err),
3415 }
3416 },
3417 }
3418 }
3419}
3420
3421fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
3422 const t: *Threaded = @ptrCast(@alignCast(userdata));
3423 _ = t;
3424 _ = permissions; // TODO use this value
3425
3426 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
3427 const attr: windows.OBJECT.ATTRIBUTES = .{
3428 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle,
3429 .Attributes = .{ .INHERIT = false },
3430 .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w.span())),
3431 .SecurityDescriptor = null,
3432 .SecurityQualityOfService = null,
3433 };
3434
3435 var sub_dir_handle: windows.HANDLE = undefined;
3436 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
3437 var attempt: u5 = 0;
3438 var syscall: Syscall = try .start();
3439 while (true) switch (windows.ntdll.NtCreateFile(
3440 &sub_dir_handle,
3441 .{
3442 .GENERIC = .{ .READ = true },
3443 .STANDARD = .{ .SYNCHRONIZE = true },
3444 },
3445 &attr,
3446 &io_status_block,
3447 null,
3448 .{ .NORMAL = true },
3449 .VALID_FLAGS,
3450 .CREATE,
3451 .{
3452 .DIRECTORY_FILE = true,
3453 .NON_DIRECTORY_FILE = false,
3454 .IO = .SYNCHRONOUS_NONALERT,
3455 .OPEN_REPARSE_POINT = false,
3456 },
3457 null,
3458 0,
3459 )) {
3460 .SUCCESS => {
3461 syscall.finish();
3462 windows.CloseHandle(sub_dir_handle);
3463 return;
3464 },
3465 .CANCELLED => {
3466 try syscall.checkCancel();
3467 continue;
3468 },
3469 .SHARING_VIOLATION => {
3470 // This occurs if the file attempting to be opened is a running
3471 // executable. However, there's a kernel bug: the error may be
3472 // incorrectly returned for an indeterminate amount of time
3473 // after an executable file is closed. Here we work around the
3474 // kernel bug with retry attempts.
3475 syscall.finish();
3476 if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected;
3477 try parking_sleep.sleep(.{ .duration = .{
3478 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
3479 .clock = .awake,
3480 } });
3481 attempt += 1;
3482 syscall = try .start();
3483 continue;
3484 },
3485 .DELETE_PENDING => {
3486 // This error means that there *was* a file in this location on
3487 // the file system, but it was deleted. However, the OS is not
3488 // finished with the deletion operation, and so this CreateFile
3489 // call has failed. There is not really a sane way to handle
3490 // this other than retrying the creation after the OS finishes
3491 // the deletion.
3492 syscall.finish();
3493 if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected;
3494 try parking_sleep.sleep(.{ .duration = .{
3495 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
3496 .clock = .awake,
3497 } });
3498 attempt += 1;
3499 syscall = try .start();
3500 continue;
3501 },
3502 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
3503 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
3504 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
3505 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
3506 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
3507 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3508 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
3509 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
3510 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
3511 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
3512 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
3513 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
3514 else => |status| return syscall.unexpectedNtstatus(status),
3515 };
3516}
3517
3518fn dirCreateDirPath(
3519 userdata: ?*anyopaque,
3520 dir: Dir,
3521 sub_path: []const u8,
3522 permissions: Dir.Permissions,
3523) Dir.CreateDirPathError!Dir.CreatePathStatus {
3524 const t: *Threaded = @ptrCast(@alignCast(userdata));
3525
3526 var it = Dir.path.componentIterator(sub_path);
3527 var status: Dir.CreatePathStatus = .existed;
3528 var component = it.last() orelse return error.BadPathName;
3529 while (true) {
3530 if (dirCreateDir(t, dir, component.path, permissions)) |_| {
3531 status = .created;
3532 } else |err| switch (err) {
3533 error.PathAlreadyExists => {
3534 // It is important to return an error if it's not a directory
3535 // because otherwise a dangling symlink could cause an infinite
3536 // loop.
3537 const kind = try filePathKind(t, dir, component.path);
3538 if (kind != .directory) return error.NotDir;
3539 },
3540 error.FileNotFound => |e| {
3541 component = it.previous() orelse return e;
3542 continue;
3543 },
3544 else => |e| return e,
3545 }
3546 component = it.next() orelse return status;
3547 }
3548}
3549
3550const dirCreateDirPathOpen = switch (native_os) {
3551 .windows => dirCreateDirPathOpenWindows,
3552 .wasi => dirCreateDirPathOpenWasi,
3553 else => dirCreateDirPathOpenPosix,
3554};
3555
3556fn dirCreateDirPathOpenPosix(
3557 userdata: ?*anyopaque,
3558 dir: Dir,
3559 sub_path: []const u8,
3560 permissions: Dir.Permissions,
3561 options: Dir.OpenOptions,
3562) Dir.CreateDirPathOpenError!Dir {
3563 const t: *Threaded = @ptrCast(@alignCast(userdata));
3564 const t_io = io(t);
3565 return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) {
3566 error.FileNotFound => {
3567 _ = try dir.createDirPathStatus(t_io, sub_path, permissions);
3568 return dirOpenDirPosix(t, dir, sub_path, options);
3569 },
3570 else => |e| return e,
3571 };
3572}
3573
3574fn dirCreateDirPathOpenWindows(
3575 userdata: ?*anyopaque,
3576 dir: Dir,
3577 sub_path: []const u8,
3578 permissions: Dir.Permissions,
3579 options: Dir.OpenOptions,
3580) Dir.CreateDirPathOpenError!Dir {
3581 const t: *Threaded = @ptrCast(@alignCast(userdata));
3582 const w = windows;
3583
3584 _ = permissions; // TODO apply these permissions
3585
3586 var it = Dir.path.componentIterator(sub_path);
3587 // If there are no components in the path, then create a dummy component with the full path.
3588 var component: Dir.path.NativeComponentIterator.Component = it.last() orelse .{
3589 .name = "",
3590 .path = sub_path,
3591 };
3592
3593 components: while (true) {
3594 const sub_path_w = try sliceToPrefixedFileW(dir.handle, component.path, .{});
3595 const attr: windows.OBJECT.ATTRIBUTES = .{
3596 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle,
3597 .ObjectName = @constCast(&sub_path_w.string()),
3598 };
3599 const is_last = it.peekNext() == null;
3600 var result: Dir = .{ .handle = undefined };
3601 var iosb: w.IO_STATUS_BLOCK = undefined;
3602 const syscall: Syscall = try .start();
3603 while (true) switch (w.ntdll.NtCreateFile(
3604 &result.handle,
3605 .{
3606 .SPECIFIC = .{ .FILE_DIRECTORY = .{
3607 .LIST = options.iterate,
3608 .READ_EA = true,
3609 .READ_ATTRIBUTES = true,
3610 .TRAVERSE = true,
3611 } },
3612 .STANDARD = .{
3613 .RIGHTS = .READ,
3614 .SYNCHRONIZE = true,
3615 },
3616 },
3617 &attr,
3618 &iosb,
3619 null,
3620 .{ .NORMAL = true },
3621 .VALID_FLAGS,
3622 if (is_last) .OPEN_IF else .CREATE,
3623 .{
3624 .DIRECTORY_FILE = true,
3625 .IO = .SYNCHRONOUS_NONALERT,
3626 .OPEN_FOR_BACKUP_INTENT = true,
3627 .OPEN_REPARSE_POINT = !options.follow_symlinks,
3628 },
3629 null,
3630 0,
3631 )) {
3632 .SUCCESS => {
3633 syscall.finish();
3634 component = it.next() orelse return result;
3635 w.CloseHandle(result.handle);
3636 continue :components;
3637 },
3638 .CANCELLED => {
3639 try syscall.checkCancel();
3640 continue;
3641 },
3642 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
3643 .OBJECT_NAME_COLLISION => {
3644 syscall.finish();
3645 assert(!is_last);
3646 // stat the file and return an error if it's not a directory
3647 // this is important because otherwise a dangling symlink
3648 // could cause an infinite loop
3649 const fstat = try dirStatFileWindows(t, dir, component.path, .{
3650 .follow_symlinks = options.follow_symlinks,
3651 });
3652 if (fstat.kind != .directory) return error.NotDir;
3653
3654 component = it.next().?;
3655 continue :components;
3656 },
3657
3658 .OBJECT_NAME_NOT_FOUND,
3659 .OBJECT_PATH_NOT_FOUND,
3660 => {
3661 syscall.finish();
3662 component = it.previous() orelse return error.FileNotFound;
3663 continue :components;
3664 },
3665
3666 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
3667 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
3668 // and the directory is trying to be opened for iteration.
3669 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3670 .DISK_FULL => return syscall.fail(error.NoSpaceLeft),
3671 .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s),
3672 else => |s| return syscall.unexpectedNtstatus(s),
3673 };
3674 }
3675}
3676
3677fn dirCreateDirPathOpenWasi(
3678 userdata: ?*anyopaque,
3679 dir: Dir,
3680 sub_path: []const u8,
3681 permissions: Dir.Permissions,
3682 options: Dir.OpenOptions,
3683) Dir.CreateDirPathOpenError!Dir {
3684 const t: *Threaded = @ptrCast(@alignCast(userdata));
3685 const t_io = io(t);
3686 return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) {
3687 error.FileNotFound => {
3688 _ = try dir.createDirPathStatus(t_io, sub_path, permissions);
3689 return dirOpenDirWasi(t, dir, sub_path, options);
3690 },
3691 else => |e| return e,
3692 };
3693}
3694
3695fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
3696 const t: *Threaded = @ptrCast(@alignCast(userdata));
3697 return fileStat(t, .{
3698 .handle = dir.handle,
3699 .flags = .{ .nonblocking = false },
3700 });
3701}
3702
3703const dirStatFile = switch (native_os) {
3704 .linux => dirStatFileLinux,
3705 .windows => dirStatFileWindows,
3706 .wasi => dirStatFileWasi,
3707 else => dirStatFilePosix,
3708};
3709
3710fn dirStatFileLinux(
3711 userdata: ?*anyopaque,
3712 dir: Dir,
3713 sub_path: []const u8,
3714 options: Dir.StatFileOptions,
3715) Dir.StatFileError!File.Stat {
3716 const t: *Threaded = @ptrCast(@alignCast(userdata));
3717 _ = t;
3718 const linux = std.os.linux;
3719 const sys = if (statx_use_c) std.c else std.os.linux;
3720
3721 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3722 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3723
3724 const flags: u32 = linux.AT.NO_AUTOMOUNT |
3725 @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
3726
3727 const syscall: Syscall = try .start();
3728 while (true) {
3729 var statx = std.mem.zeroes(linux.Statx);
3730 switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_request, &statx))) {
3731 .SUCCESS => {
3732 syscall.finish();
3733 return statFromLinux(&statx);
3734 },
3735 .INTR => {
3736 try syscall.checkCancel();
3737 continue;
3738 },
3739 else => |e| {
3740 syscall.finish();
3741 switch (e) {
3742 .ACCES => return error.AccessDenied,
3743 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3744 .FAULT => |err| return errnoBug(err),
3745 .INVAL => |err| return errnoBug(err),
3746 .LOOP => return error.SymLinkLoop,
3747 .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above.
3748 .NOENT => return error.FileNotFound,
3749 .NOTDIR => return error.NotDir,
3750 .NOMEM => return error.SystemResources,
3751 else => |err| return posix.unexpectedErrno(err),
3752 }
3753 },
3754 }
3755 }
3756}
3757
3758fn dirStatFilePosix(
3759 userdata: ?*anyopaque,
3760 dir: Dir,
3761 sub_path: []const u8,
3762 options: Dir.StatFileOptions,
3763) Dir.StatFileError!File.Stat {
3764 const t: *Threaded = @ptrCast(@alignCast(userdata));
3765 _ = t;
3766
3767 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3768 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3769
3770 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
3771
3772 return posixStatFile(dir.handle, sub_path_posix, flags);
3773}
3774
3775fn posixStatFile(dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat {
3776 const syscall: Syscall = try .start();
3777 while (true) {
3778 var stat = std.mem.zeroes(posix.Stat);
3779 switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) {
3780 .SUCCESS => {
3781 syscall.finish();
3782 return statFromPosix(&stat);
3783 },
3784 .INTR => {
3785 try syscall.checkCancel();
3786 continue;
3787 },
3788 else => |e| {
3789 syscall.finish();
3790 switch (e) {
3791 .INVAL => |err| return errnoBug(err),
3792 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3793 .NOMEM => return error.SystemResources,
3794 .ACCES => return error.AccessDenied,
3795 .PERM => return error.PermissionDenied,
3796 .FAULT => |err| return errnoBug(err),
3797 .NAMETOOLONG => return error.NameTooLong,
3798 .LOOP => return error.SymLinkLoop,
3799 .NOENT => return error.FileNotFound,
3800 .NOTDIR => return error.FileNotFound,
3801 .ILSEQ => return error.BadPathName,
3802 else => |err| return posix.unexpectedErrno(err),
3803 }
3804 },
3805 }
3806 }
3807}
3808
3809fn dirStatFileWindows(
3810 userdata: ?*anyopaque,
3811 dir: Dir,
3812 sub_path: []const u8,
3813 options: Dir.StatFileOptions,
3814) Dir.StatFileError!File.Stat {
3815 const t: *Threaded = @ptrCast(@alignCast(userdata));
3816 const file = try dirOpenFileWindows(t, dir, sub_path, .{
3817 .follow_symlinks = options.follow_symlinks,
3818 });
3819 defer windows.CloseHandle(file.handle);
3820 return fileStatWindows(t, file);
3821}
3822
3823fn dirStatFileWasi(
3824 userdata: ?*anyopaque,
3825 dir: Dir,
3826 sub_path: []const u8,
3827 options: Dir.StatFileOptions,
3828) Dir.StatFileError!File.Stat {
3829 if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options);
3830 const t: *Threaded = @ptrCast(@alignCast(userdata));
3831 _ = t;
3832 const wasi = std.os.wasi;
3833 const flags: wasi.lookupflags_t = .{
3834 .SYMLINK_FOLLOW = options.follow_symlinks,
3835 };
3836 var stat: wasi.filestat_t = undefined;
3837 const syscall: Syscall = try .start();
3838 while (true) {
3839 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
3840 .SUCCESS => {
3841 syscall.finish();
3842 return statFromWasi(&stat);
3843 },
3844 .INTR => {
3845 try syscall.checkCancel();
3846 continue;
3847 },
3848 else => |e| {
3849 syscall.finish();
3850 switch (e) {
3851 .INVAL => |err| return errnoBug(err),
3852 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3853 .NOMEM => return error.SystemResources,
3854 .ACCES => return error.AccessDenied,
3855 .FAULT => |err| return errnoBug(err),
3856 .NAMETOOLONG => return error.NameTooLong,
3857 .NOENT => return error.FileNotFound,
3858 .NOTDIR => return error.FileNotFound,
3859 .NOTCAPABLE => return error.AccessDenied,
3860 .ILSEQ => return error.BadPathName,
3861 else => |err| return posix.unexpectedErrno(err),
3862 }
3863 },
3864 }
3865 }
3866}
3867
3868fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind {
3869 if (native_os == .linux) {
3870 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3871 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3872
3873 const linux = std.os.linux;
3874 const syscall: Syscall = try .start();
3875 while (true) {
3876 var statx = std.mem.zeroes(linux.Statx);
3877 switch (linux.errno(linux.statx(
3878 dir.handle,
3879 sub_path_posix,
3880 linux.AT.NO_AUTOMOUNT | linux.AT.SYMLINK_NOFOLLOW,
3881 .{ .TYPE = true },
3882 &statx,
3883 ))) {
3884 .SUCCESS => {
3885 syscall.finish();
3886 if (!statx.mask.TYPE) return error.Unexpected;
3887 return statxKind(statx.mode);
3888 },
3889 .INTR => {
3890 try syscall.checkCancel();
3891 continue;
3892 },
3893 .NOMEM => return syscall.fail(error.SystemResources),
3894 else => |err| return syscall.unexpectedErrno(err),
3895 }
3896 }
3897 }
3898
3899 const stat = try dirStatFile(t, dir, sub_path, .{ .follow_symlinks = false });
3900 return stat.kind;
3901}
3902
3903fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
3904 const t: *Threaded = @ptrCast(@alignCast(userdata));
3905
3906 if (native_os == .linux) {
3907 const linux = std.os.linux;
3908
3909 const syscall: Syscall = try .start();
3910 while (true) {
3911 var statx = std.mem.zeroes(linux.Statx);
3912 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) {
3913 .SUCCESS => {
3914 syscall.finish();
3915 if (!statx.mask.SIZE) return error.Unexpected;
3916 return statx.size;
3917 },
3918 .INTR => {
3919 try syscall.checkCancel();
3920 continue;
3921 },
3922 else => |e| {
3923 syscall.finish();
3924 switch (e) {
3925 .ACCES => |err| return errnoBug(err),
3926 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3927 .FAULT => |err| return errnoBug(err),
3928 .INVAL => |err| return errnoBug(err),
3929 .LOOP => |err| return errnoBug(err),
3930 .NAMETOOLONG => |err| return errnoBug(err),
3931 .NOENT => |err| return errnoBug(err),
3932 .NOMEM => return error.SystemResources,
3933 .NOTDIR => |err| return errnoBug(err),
3934 else => |err| return posix.unexpectedErrno(err),
3935 }
3936 },
3937 }
3938 }
3939 } else if (is_windows) {
3940 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
3941 var info: windows.FILE.STANDARD_INFORMATION = undefined;
3942 const syscall: Syscall = try .start();
3943 while (true) switch (windows.ntdll.NtQueryInformationFile(
3944 file.handle,
3945 &io_status_block,
3946 &info,
3947 @sizeOf(windows.FILE.STANDARD_INFORMATION),
3948 .Standard,
3949 )) {
3950 .SUCCESS => break syscall.finish(),
3951 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
3952 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
3953 .CANCELLED => {
3954 try syscall.checkCancel();
3955 continue;
3956 },
3957 else => |s| return syscall.unexpectedNtstatus(s),
3958 };
3959 return @as(u64, @bitCast(info.EndOfFile));
3960 }
3961
3962 const stat = try fileStat(t, file);
3963 return stat.size;
3964}
3965
3966const fileStat = switch (native_os) {
3967 .linux => fileStatLinux,
3968 .windows => fileStatWindows,
3969 .wasi => fileStatWasi,
3970 else => fileStatPosix,
3971};
3972
3973fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
3974 const t: *Threaded = @ptrCast(@alignCast(userdata));
3975 _ = t;
3976
3977 if (posix.Stat == void) return error.Streaming;
3978
3979 const syscall: Syscall = try .start();
3980 while (true) {
3981 var stat = std.mem.zeroes(posix.Stat);
3982 switch (posix.errno(fstat_sym(file.handle, &stat))) {
3983 .SUCCESS => {
3984 syscall.finish();
3985 return statFromPosix(&stat);
3986 },
3987 .INTR => {
3988 try syscall.checkCancel();
3989 continue;
3990 },
3991 else => |e| {
3992 syscall.finish();
3993 switch (e) {
3994 .INVAL => |err| return errnoBug(err),
3995 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3996 .NOMEM => return error.SystemResources,
3997 .ACCES => return error.AccessDenied,
3998 else => |err| return posix.unexpectedErrno(err),
3999 }
4000 },
4001 }
4002 }
4003}
4004
4005fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4006 const t: *Threaded = @ptrCast(@alignCast(userdata));
4007 _ = t;
4008 const linux = std.os.linux;
4009 const sys = if (statx_use_c) std.c else std.os.linux;
4010
4011 const syscall: Syscall = try .start();
4012 while (true) {
4013 var statx = std.mem.zeroes(linux.Statx);
4014 switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_request, &statx))) {
4015 .SUCCESS => {
4016 syscall.finish();
4017 return statFromLinux(&statx);
4018 },
4019 .INTR => {
4020 try syscall.checkCancel();
4021 continue;
4022 },
4023 else => |e| {
4024 syscall.finish();
4025 switch (e) {
4026 .ACCES => |err| return errnoBug(err),
4027 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4028 .FAULT => |err| return errnoBug(err),
4029 .INVAL => |err| return errnoBug(err),
4030 .LOOP => |err| return errnoBug(err),
4031 .NAMETOOLONG => |err| return errnoBug(err),
4032 .NOENT => |err| return errnoBug(err),
4033 .NOMEM => return error.SystemResources,
4034 .NOTDIR => |err| return errnoBug(err),
4035 else => |err| return posix.unexpectedErrno(err),
4036 }
4037 },
4038 }
4039 }
4040}
4041
4042fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4043 const t: *Threaded = @ptrCast(@alignCast(userdata));
4044
4045 const block_size: u32 = if (t.systemBasicInformation()) |sbi|
4046 @intCast(@max(sbi.PageSize, sbi.AllocationGranularity))
4047 else
4048 std.heap.page_size_max;
4049
4050 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
4051 var info: windows.FILE.ALL_INFORMATION = undefined;
4052 {
4053 const syscall: Syscall = try .start();
4054 while (true) switch (windows.ntdll.NtQueryInformationFile(
4055 file.handle,
4056 &io_status_block,
4057 &info,
4058 @sizeOf(windows.FILE.ALL_INFORMATION),
4059 .All,
4060 )) {
4061 .SUCCESS => break syscall.finish(),
4062 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
4063 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
4064 // (name, volume name, etc) we don't care about.
4065 .BUFFER_OVERFLOW => break syscall.finish(),
4066 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
4067 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4068 .CANCELLED => {
4069 try syscall.checkCancel();
4070 continue;
4071 },
4072 else => |s| return syscall.unexpectedNtstatus(s),
4073 };
4074 }
4075 return .{
4076 .inode = info.InternalInformation.IndexNumber,
4077 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
4078 .permissions = .default_file,
4079 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {
4080 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;
4081 const syscall: Syscall = try .start();
4082 while (true) switch (windows.ntdll.NtQueryInformationFile(
4083 file.handle,
4084 &io_status_block,
4085 &tag_info,
4086 @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO),
4087 .AttributeTag,
4088 )) {
4089 .SUCCESS => break syscall.finish(),
4090 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
4091 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
4092 .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err),
4093 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4094 .CANCELLED => {
4095 try syscall.checkCancel();
4096 continue;
4097 },
4098 else => |s| return syscall.unexpectedNtstatus(s),
4099 };
4100 if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link;
4101 // Unknown reparse point
4102 break :reparse_point .unknown;
4103 } else if (info.BasicInformation.FileAttributes.DIRECTORY)
4104 .directory
4105 else
4106 .file,
4107 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
4108 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
4109 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
4110 .nlink = info.StandardInformation.NumberOfLinks,
4111 .block_size = block_size,
4112 };
4113}
4114
4115fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION {
4116 if (!t.system_basic_information.initialized.load(.acquire)) {
4117 mutexLock(&t.mutex);
4118 defer mutexUnlock(&t.mutex);
4119
4120 switch (windows.ntdll.NtQuerySystemInformation(
4121 .Basic,
4122 &t.system_basic_information.buffer,
4123 @sizeOf(windows.SYSTEM.BASIC_INFORMATION),
4124 null,
4125 )) {
4126 .SUCCESS => {},
4127 else => return null,
4128 }
4129
4130 t.system_basic_information.initialized.store(true, .release);
4131 }
4132 return &t.system_basic_information.buffer;
4133}
4134
4135fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4136 if (builtin.link_libc) return fileStatPosix(userdata, file);
4137
4138 const t: *Threaded = @ptrCast(@alignCast(userdata));
4139 _ = t;
4140
4141 const syscall: Syscall = try .start();
4142 while (true) {
4143 var stat: std.os.wasi.filestat_t = undefined;
4144 switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) {
4145 .SUCCESS => {
4146 syscall.finish();
4147 return statFromWasi(&stat);
4148 },
4149 .INTR => {
4150 try syscall.checkCancel();
4151 continue;
4152 },
4153 else => |e| {
4154 syscall.finish();
4155 switch (e) {
4156 .INVAL => |err| return errnoBug(err),
4157 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4158 .NOMEM => return error.SystemResources,
4159 .ACCES => return error.AccessDenied,
4160 .NOTCAPABLE => return error.AccessDenied,
4161 else => |err| return posix.unexpectedErrno(err),
4162 }
4163 },
4164 }
4165 }
4166}
4167
4168const dirAccess = switch (native_os) {
4169 .windows => dirAccessWindows,
4170 .wasi => dirAccessWasi,
4171 else => dirAccessPosix,
4172};
4173
4174fn dirAccessPosix(
4175 userdata: ?*anyopaque,
4176 dir: Dir,
4177 sub_path: []const u8,
4178 options: Dir.AccessOptions,
4179) Dir.AccessError!void {
4180 const t: *Threaded = @ptrCast(@alignCast(userdata));
4181 _ = t;
4182
4183 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4184 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
4185
4186 const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0);
4187
4188 const mode: u32 =
4189 @as(u32, if (options.read) posix.R_OK else 0) |
4190 @as(u32, if (options.write) posix.W_OK else 0) |
4191 @as(u32, if (options.execute) posix.X_OK else 0);
4192
4193 const syscall: Syscall = try .start();
4194 while (true) {
4195 switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) {
4196 .SUCCESS => {
4197 syscall.finish();
4198 return;
4199 },
4200 .INTR => {
4201 try syscall.checkCancel();
4202 continue;
4203 },
4204 else => |e| {
4205 syscall.finish();
4206 switch (e) {
4207 .ACCES => return error.AccessDenied,
4208 .PERM => return error.PermissionDenied,
4209 .ROFS => return error.ReadOnlyFileSystem,
4210 .LOOP => return error.SymLinkLoop,
4211 .TXTBSY => return error.FileBusy,
4212 .NOTDIR => return error.FileNotFound,
4213 .NOENT => return error.FileNotFound,
4214 .NAMETOOLONG => return error.NameTooLong,
4215 .INVAL => |err| return errnoBug(err),
4216 .FAULT => |err| return errnoBug(err),
4217 .IO => return error.InputOutput,
4218 .NOMEM => return error.SystemResources,
4219 .ILSEQ => return error.BadPathName,
4220 else => |err| return posix.unexpectedErrno(err),
4221 }
4222 },
4223 }
4224 }
4225}
4226
4227fn dirAccessWasi(
4228 userdata: ?*anyopaque,
4229 dir: Dir,
4230 sub_path: []const u8,
4231 options: Dir.AccessOptions,
4232) Dir.AccessError!void {
4233 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
4234 const t: *Threaded = @ptrCast(@alignCast(userdata));
4235 _ = t;
4236 const wasi = std.os.wasi;
4237 const flags: wasi.lookupflags_t = .{
4238 .SYMLINK_FOLLOW = options.follow_symlinks,
4239 };
4240 var stat: wasi.filestat_t = undefined;
4241
4242 const syscall: Syscall = try .start();
4243 while (true) {
4244 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
4245 .SUCCESS => {
4246 syscall.finish();
4247 break;
4248 },
4249 .INTR => {
4250 try syscall.checkCancel();
4251 continue;
4252 },
4253 else => |e| {
4254 syscall.finish();
4255 switch (e) {
4256 .INVAL => |err| return errnoBug(err),
4257 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4258 .NOMEM => return error.SystemResources,
4259 .ACCES => return error.AccessDenied,
4260 .FAULT => |err| return errnoBug(err),
4261 .NAMETOOLONG => return error.NameTooLong,
4262 .NOENT => return error.FileNotFound,
4263 .NOTDIR => return error.FileNotFound,
4264 .NOTCAPABLE => return error.AccessDenied,
4265 .ILSEQ => return error.BadPathName,
4266 else => |err| return posix.unexpectedErrno(err),
4267 }
4268 },
4269 }
4270 }
4271
4272 if (!options.read and !options.write and !options.execute)
4273 return;
4274
4275 var directory: wasi.fdstat_t = undefined;
4276 if (wasi.fd_fdstat_get(dir.handle, &directory) != .SUCCESS)
4277 return error.AccessDenied;
4278
4279 var rights: wasi.rights_t = .{};
4280 if (options.read) {
4281 if (stat.filetype == .DIRECTORY) {
4282 rights.FD_READDIR = true;
4283 } else {
4284 rights.FD_READ = true;
4285 }
4286 }
4287 if (options.write)
4288 rights.FD_WRITE = true;
4289
4290 // No validation for execution.
4291
4292 // https://github.com/ziglang/zig/issues/18882
4293 const rights_int: u64 = @bitCast(rights);
4294 const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
4295 if ((rights_int & inheriting_int) != rights_int)
4296 return error.AccessDenied;
4297}
4298
4299fn dirAccessWindows(
4300 userdata: ?*anyopaque,
4301 dir: Dir,
4302 sub_path: []const u8,
4303 options: Dir.AccessOptions,
4304) Dir.AccessError!void {
4305 const t: *Threaded = @ptrCast(@alignCast(userdata));
4306 _ = t;
4307
4308 _ = options; // TODO
4309
4310 if (std.mem.eql(u8, sub_path, ".") or std.mem.eql(u8, sub_path, "..")) return;
4311 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
4312 const attr: windows.OBJECT.ATTRIBUTES = .{
4313 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle,
4314 .ObjectName = @constCast(&sub_path_w.string()),
4315 };
4316 var basic_info: windows.FILE.BASIC_INFORMATION = undefined;
4317 const syscall: Syscall = try .start();
4318 while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
4319 .SUCCESS => return syscall.finish(),
4320 .CANCELLED => {
4321 try syscall.checkCancel();
4322 continue;
4323 },
4324 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
4325 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
4326 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
4327 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
4328 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4329 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
4330 else => |rc| return syscall.unexpectedNtstatus(rc),
4331 };
4332}
4333
4334const dirCreateFile = switch (native_os) {
4335 .windows => dirCreateFileWindows,
4336 .wasi => dirCreateFileWasi,
4337 else => dirCreateFilePosix,
4338};
4339
4340fn dirCreateFilePosix(
4341 userdata: ?*anyopaque,
4342 dir: Dir,
4343 sub_path: []const u8,
4344 options: Dir.CreateFileOptions,
4345) File.OpenError!File {
4346 const t: *Threaded = @ptrCast(@alignCast(userdata));
4347 _ = t;
4348
4349 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4350 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
4351
4352 var flags: posix.O = .{
4353 .ACCMODE = if (options.read) .RDWR else .WRONLY,
4354 .CREAT = true,
4355 .TRUNC = options.truncate,
4356 .EXCL = options.exclusive,
4357 };
4358 if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
4359 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
4360 if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath;
4361
4362 // Use the O locking flags if the os supports them to acquire the lock
4363 // atomically. Note that the NONBLOCK flag is removed after the openat()
4364 // call is successful.
4365 if (have_flock_open_flags) switch (options.lock) {
4366 .none => {},
4367 .shared => {
4368 flags.SHLOCK = true;
4369 flags.NONBLOCK = options.lock_nonblocking;
4370 },
4371 .exclusive => {
4372 flags.EXLOCK = true;
4373 flags.NONBLOCK = options.lock_nonblocking;
4374 },
4375 };
4376
4377 const fd: posix.fd_t = fd: {
4378 const syscall: Syscall = try .start();
4379 while (true) {
4380 const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
4381 switch (posix.errno(rc)) {
4382 .SUCCESS => {
4383 syscall.finish();
4384 break :fd @intCast(rc);
4385 },
4386 .INTR => {
4387 try syscall.checkCancel();
4388 continue;
4389 },
4390 else => |e| {
4391 syscall.finish();
4392 switch (e) {
4393 .FAULT => |err| return errnoBug(err),
4394 .INVAL => return error.BadPathName,
4395 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4396 .ACCES => return error.AccessDenied,
4397 .FBIG => return error.FileTooBig,
4398 .OVERFLOW => return error.FileTooBig,
4399 .ISDIR => return error.IsDir,
4400 .LOOP => return error.SymLinkLoop,
4401 .MFILE => return error.ProcessFdQuotaExceeded,
4402 .NAMETOOLONG => return error.NameTooLong,
4403 .NFILE => return error.SystemFdQuotaExceeded,
4404 .NODEV => return error.NoDevice,
4405 .NOENT => return error.FileNotFound,
4406 .SRCH => return error.FileNotFound, // Linux when accessing procfs.
4407 .NOMEM => return error.SystemResources,
4408 .NOSPC => return error.NoSpaceLeft,
4409 .NOTDIR => return error.NotDir,
4410 .PERM => return error.PermissionDenied,
4411 .EXIST => return error.PathAlreadyExists,
4412 .BUSY => return error.DeviceBusy,
4413 .OPNOTSUPP => return error.FileLocksUnsupported,
4414 .AGAIN => return error.WouldBlock,
4415 .TXTBSY => return error.FileBusy,
4416 .NXIO => return error.NoDevice,
4417 .ROFS => return error.ReadOnlyFileSystem,
4418 .ILSEQ => return error.BadPathName,
4419 else => |err| return posix.unexpectedErrno(err),
4420 }
4421 },
4422 }
4423 }
4424 };
4425 errdefer closeFd(fd);
4426
4427 if (have_flock and !have_flock_open_flags and options.lock != .none) {
4428 const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
4429 const lock_flags = switch (options.lock) {
4430 .none => unreachable,
4431 .shared => posix.LOCK.SH | lock_nonblocking,
4432 .exclusive => posix.LOCK.EX | lock_nonblocking,
4433 };
4434
4435 const syscall: Syscall = try .start();
4436 while (true) {
4437 switch (posix.errno(posix.system.flock(fd, lock_flags))) {
4438 .SUCCESS => {
4439 syscall.finish();
4440 break;
4441 },
4442 .INTR => {
4443 try syscall.checkCancel();
4444 continue;
4445 },
4446 else => |e| {
4447 syscall.finish();
4448 switch (e) {
4449 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4450 .INVAL => |err| return errnoBug(err), // invalid parameters
4451 .NOLCK => return error.SystemResources,
4452 .AGAIN => return error.WouldBlock,
4453 .OPNOTSUPP => return error.FileLocksUnsupported,
4454 else => |err| return posix.unexpectedErrno(err),
4455 }
4456 },
4457 }
4458 }
4459 }
4460
4461 if (have_flock_open_flags and options.lock_nonblocking) {
4462 var fl_flags: usize = fl: {
4463 const syscall: Syscall = try .start();
4464 while (true) {
4465 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
4466 switch (posix.errno(rc)) {
4467 .SUCCESS => {
4468 syscall.finish();
4469 break :fl @intCast(rc);
4470 },
4471 .INTR => {
4472 try syscall.checkCancel();
4473 continue;
4474 },
4475 else => |err| {
4476 syscall.finish();
4477 return posix.unexpectedErrno(err);
4478 },
4479 }
4480 }
4481 };
4482
4483 fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
4484
4485 const syscall: Syscall = try .start();
4486 while (true) {
4487 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
4488 .SUCCESS => {
4489 syscall.finish();
4490 break;
4491 },
4492 .INTR => {
4493 try syscall.checkCancel();
4494 continue;
4495 },
4496 else => |err| {
4497 syscall.finish();
4498 return posix.unexpectedErrno(err);
4499 },
4500 }
4501 }
4502 }
4503
4504 return .{
4505 .handle = fd,
4506 .flags = .{ .nonblocking = false },
4507 };
4508}
4509
4510fn dirCreateFileWindows(
4511 userdata: ?*anyopaque,
4512 dir: Dir,
4513 sub_path: []const u8,
4514 flags: Dir.CreateFileOptions,
4515) File.OpenError!File {
4516 const t: *Threaded = @ptrCast(@alignCast(userdata));
4517 _ = t;
4518
4519 if (std.mem.eql(u8, sub_path, ".")) return error.IsDir;
4520 if (std.mem.eql(u8, sub_path, "..")) return error.IsDir;
4521
4522 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
4523 const attr: windows.OBJECT.ATTRIBUTES = .{
4524 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle,
4525 .ObjectName = @constCast(&sub_path_w.string()),
4526 };
4527 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive)
4528 .CREATE
4529 else if (flags.truncate)
4530 .OVERWRITE_IF
4531 else
4532 .OPEN_IF;
4533
4534 const access_mask: windows.ACCESS_MASK = .{
4535 .STANDARD = .{ .SYNCHRONIZE = true },
4536 .GENERIC = .{
4537 .WRITE = true,
4538 .READ = flags.read,
4539 },
4540 };
4541
4542 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
4543 var attempt: u5 = 0;
4544 var handle: windows.HANDLE = undefined;
4545 var syscall: Syscall = try .start();
4546 while (true) switch (windows.ntdll.NtCreateFile(
4547 &handle,
4548 access_mask,
4549 &attr,
4550 &io_status_block,
4551 null,
4552 .{ .NORMAL = true },
4553 .VALID_FLAGS, // share access
4554 create_disposition,
4555 .{
4556 .NON_DIRECTORY_FILE = true,
4557 .IO = .SYNCHRONOUS_NONALERT,
4558 },
4559 null,
4560 0,
4561 )) {
4562 .SUCCESS => {
4563 syscall.finish();
4564 break;
4565 },
4566 .CANCELLED => {
4567 try syscall.checkCancel();
4568 continue;
4569 },
4570 .SHARING_VIOLATION => {
4571 // This occurs if the file attempting to be opened is a running
4572 // executable. However, there's a kernel bug: the error may be
4573 // incorrectly returned for an indeterminate amount of time
4574 // after an executable file is closed. Here we work around the
4575 // kernel bug with retry attempts.
4576 syscall.finish();
4577 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
4578 try parking_sleep.sleep(.{ .duration = .{
4579 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
4580 .clock = .awake,
4581 } });
4582 attempt += 1;
4583 syscall = try .start();
4584 continue;
4585 },
4586 .DELETE_PENDING => {
4587 // This error means that there *was* a file in this location on
4588 // the file system, but it was deleted. However, the OS is not
4589 // finished with the deletion operation, and so this CreateFile
4590 // call has failed. Here, we simulate the kernel bug being
4591 // fixed by sleeping and retrying until the error goes away.
4592 syscall.finish();
4593 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
4594 try parking_sleep.sleep(.{ .duration = .{
4595 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
4596 .clock = .awake,
4597 } });
4598 attempt += 1;
4599 syscall = try .start();
4600 continue;
4601 },
4602 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
4603 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
4604 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
4605 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
4606 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
4607 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
4608 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4609 .PIPE_BUSY => return syscall.fail(error.PipeBusy),
4610 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
4611 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
4612 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
4613 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
4614 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
4615 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
4616 .DISK_FULL => return syscall.fail(error.NoSpaceLeft),
4617 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
4618 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
4619 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
4620 else => |status| return syscall.unexpectedNtstatus(status),
4621 };
4622 errdefer windows.CloseHandle(handle);
4623
4624 const exclusive = switch (flags.lock) {
4625 .none => return .{
4626 .handle = handle,
4627 .flags = .{ .nonblocking = false },
4628 },
4629 .shared => false,
4630 .exclusive => true,
4631 };
4632
4633 syscall = try .start();
4634 while (true) switch (windows.ntdll.NtLockFile(
4635 handle,
4636 null,
4637 null,
4638 null,
4639 &io_status_block,
4640 &windows_lock_range_off,
4641 &windows_lock_range_len,
4642 null,
4643 .fromBool(flags.lock_nonblocking),
4644 .fromBool(exclusive),
4645 )) {
4646 .SUCCESS => {
4647 syscall.finish();
4648 return .{
4649 .handle = handle,
4650 .flags = .{ .nonblocking = false },
4651 };
4652 },
4653 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
4654 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
4655 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
4656 else => |status| return syscall.unexpectedNtstatus(status),
4657 };
4658}
4659
4660fn dirCreateFileWasi(
4661 userdata: ?*anyopaque,
4662 dir: Dir,
4663 sub_path: []const u8,
4664 flags: Dir.CreateFileOptions,
4665) File.OpenError!File {
4666 const t: *Threaded = @ptrCast(@alignCast(userdata));
4667 _ = t;
4668 const wasi = std.os.wasi;
4669 const lookup_flags: wasi.lookupflags_t = .{};
4670 const oflags: wasi.oflags_t = .{
4671 .CREAT = true,
4672 .TRUNC = flags.truncate,
4673 .EXCL = flags.exclusive,
4674 };
4675 const fdflags: wasi.fdflags_t = .{};
4676 const base: wasi.rights_t = .{
4677 .FD_READ = flags.read,
4678 .FD_WRITE = true,
4679 .FD_DATASYNC = true,
4680 .FD_SEEK = true,
4681 .FD_TELL = true,
4682 .FD_FDSTAT_SET_FLAGS = true,
4683 .FD_SYNC = true,
4684 .FD_ALLOCATE = true,
4685 .FD_ADVISE = true,
4686 .FD_FILESTAT_SET_TIMES = true,
4687 .FD_FILESTAT_SET_SIZE = true,
4688 .FD_FILESTAT_GET = true,
4689 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or
4690 // FD_WRITE is also set.
4691 .POLL_FD_READWRITE = true,
4692 };
4693 const inheriting: wasi.rights_t = .{};
4694 var fd: posix.fd_t = undefined;
4695 const syscall: Syscall = try .start();
4696 while (true) {
4697 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
4698 .SUCCESS => {
4699 syscall.finish();
4700 return .{
4701 .handle = fd,
4702 .flags = .{ .nonblocking = false },
4703 };
4704 },
4705 .INTR => {
4706 try syscall.checkCancel();
4707 continue;
4708 },
4709 else => |e| {
4710 syscall.finish();
4711 switch (e) {
4712 .FAULT => |err| return errnoBug(err),
4713 .INVAL => return error.BadPathName,
4714 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4715 .ACCES => return error.AccessDenied,
4716 .FBIG => return error.FileTooBig,
4717 .OVERFLOW => return error.FileTooBig,
4718 .ISDIR => return error.IsDir,
4719 .LOOP => return error.SymLinkLoop,
4720 .MFILE => return error.ProcessFdQuotaExceeded,
4721 .NAMETOOLONG => return error.NameTooLong,
4722 .NFILE => return error.SystemFdQuotaExceeded,
4723 .NODEV => return error.NoDevice,
4724 .NOENT => return error.FileNotFound,
4725 .NOMEM => return error.SystemResources,
4726 .NOSPC => return error.NoSpaceLeft,
4727 .NOTDIR => return error.NotDir,
4728 .PERM => return error.PermissionDenied,
4729 .EXIST => return error.PathAlreadyExists,
4730 .BUSY => return error.DeviceBusy,
4731 .NOTCAPABLE => return error.AccessDenied,
4732 .ILSEQ => return error.BadPathName,
4733 else => |err| return posix.unexpectedErrno(err),
4734 }
4735 },
4736 }
4737 }
4738}
4739
4740fn dirCreateFileAtomic(
4741 userdata: ?*anyopaque,
4742 dir: Dir,
4743 dest_path: []const u8,
4744 options: Dir.CreateFileAtomicOptions,
4745) Dir.CreateFileAtomicError!File.Atomic {
4746 const t: *Threaded = @ptrCast(@alignCast(userdata));
4747 const t_io = io(t);
4748
4749 // Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's
4750 // useless when we have to make up a bogus path name to do the rename()
4751 // anyway.
4752 if (native_os == .linux and !options.replace) tmpfile: {
4753 const flags: posix.O = if (@hasField(posix.O, "TMPFILE")) .{
4754 .ACCMODE = .RDWR,
4755 .TMPFILE = true,
4756 .DIRECTORY = true,
4757 .CLOEXEC = true,
4758 } else if (@hasField(posix.O, "TMPFILE0") and !@hasField(posix.O, "TMPFILE2")) .{
4759 .ACCMODE = .RDWR,
4760 .TMPFILE0 = true,
4761 .TMPFILE1 = true,
4762 .DIRECTORY = true,
4763 .CLOEXEC = true,
4764 } else break :tmpfile;
4765
4766 const dest_dirname = Dir.path.dirname(dest_path);
4767 if (dest_dirname) |dirname| {
4768 // This has a nice side effect of preemptively triggering EISDIR or
4769 // ENOENT, avoiding the ambiguity below.
4770 if (options.make_path) dir.createDirPath(t_io, dirname) catch |err| switch (err) {
4771 // None of these make sense in this context.
4772 error.IsDir,
4773 error.Streaming,
4774 error.DiskQuota,
4775 error.PathAlreadyExists,
4776 error.LinkQuotaExceeded,
4777 error.PipeBusy,
4778 error.FileTooBig,
4779 error.DeviceBusy,
4780 error.FileLocksUnsupported,
4781 error.FileBusy,
4782 => return error.Unexpected,
4783
4784 else => |e| return e,
4785 };
4786 }
4787
4788 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4789 const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer);
4790
4791 const syscall: Syscall = try .start();
4792 while (true) {
4793 const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
4794 switch (posix.errno(rc)) {
4795 .SUCCESS => {
4796 syscall.finish();
4797 return .{
4798 .file = .{
4799 .handle = @intCast(rc),
4800 .flags = .{ .nonblocking = false },
4801 },
4802 .file_basename_hex = 0,
4803 .dest_sub_path = dest_path,
4804 .file_open = true,
4805 .file_exists = false,
4806 .close_dir_on_deinit = false,
4807 .dir = dir,
4808 };
4809 },
4810 .INTR => {
4811 try syscall.checkCancel();
4812 continue;
4813 },
4814 .ISDIR, .NOENT, .OPNOTSUPP => {
4815 // Ambiguous error code. It might mean the file system
4816 // does not support O_TMPFILE. Therefore, we must fall
4817 // back to not using O_TMPFILE.
4818 syscall.finish();
4819 break :tmpfile;
4820 },
4821 .INVAL => return syscall.fail(error.BadPathName),
4822 .ACCES => return syscall.fail(error.AccessDenied),
4823 .LOOP => return syscall.fail(error.SymLinkLoop),
4824 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
4825 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
4826 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
4827 .NODEV => return syscall.fail(error.NoDevice),
4828 .NOMEM => return syscall.fail(error.SystemResources),
4829 .NOSPC => return syscall.fail(error.NoSpaceLeft),
4830 .NOTDIR => return syscall.fail(error.NotDir),
4831 .PERM => return syscall.fail(error.PermissionDenied),
4832 .AGAIN => return syscall.fail(error.WouldBlock),
4833 .NXIO => return syscall.fail(error.NoDevice),
4834 .ILSEQ => return syscall.fail(error.BadPathName),
4835 else => |err| return syscall.unexpectedErrno(err),
4836 }
4837 }
4838 }
4839
4840 if (Dir.path.dirname(dest_path)) |dirname| {
4841 const new_dir = if (options.make_path)
4842 dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) {
4843 // None of these make sense in this context.
4844 error.IsDir,
4845 error.Streaming,
4846 error.DiskQuota,
4847 error.PathAlreadyExists,
4848 error.LinkQuotaExceeded,
4849 error.PipeBusy,
4850 error.FileTooBig,
4851 error.FileLocksUnsupported,
4852 error.DeviceBusy,
4853 => return error.Unexpected,
4854
4855 else => |e| return e,
4856 }
4857 else
4858 try dir.openDir(t_io, dirname, .{});
4859
4860 return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true);
4861 }
4862
4863 return atomicFileInit(t_io, dest_path, options.permissions, dir, false);
4864}
4865
4866fn atomicFileInit(
4867 t_io: Io,
4868 dest_basename: []const u8,
4869 permissions: File.Permissions,
4870 dir: Dir,
4871 close_dir_on_deinit: bool,
4872) Dir.CreateFileAtomicError!File.Atomic {
4873 while (true) {
4874 var random_integer: u64 = undefined;
4875 t_io.random(@ptrCast(&random_integer));
4876 const tmp_sub_path = std.fmt.hex(random_integer);
4877 const file = dir.createFile(t_io, &tmp_sub_path, .{
4878 .permissions = permissions,
4879 .exclusive = true,
4880 }) catch |err| switch (err) {
4881 error.PathAlreadyExists => continue,
4882 error.DeviceBusy => continue,
4883 error.FileBusy => continue,
4884
4885 error.IsDir => return error.Unexpected, // No path components.
4886 error.FileTooBig => return error.Unexpected, // Creating, not opening.
4887 error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks.
4888 error.PipeBusy => return error.Unexpected, // Not opening a pipe.
4889
4890 else => |e| return e,
4891 };
4892 return .{
4893 .file = file,
4894 .file_basename_hex = random_integer,
4895 .dest_sub_path = dest_basename,
4896 .file_open = true,
4897 .file_exists = true,
4898 .close_dir_on_deinit = close_dir_on_deinit,
4899 .dir = dir,
4900 };
4901 }
4902}
4903
4904const dirOpenFile = switch (native_os) {
4905 .windows => dirOpenFileWindows,
4906 .wasi => dirOpenFileWasi,
4907 else => dirOpenFilePosix,
4908};
4909
4910fn dirOpenFilePosix(
4911 userdata: ?*anyopaque,
4912 dir: Dir,
4913 sub_path: []const u8,
4914 options: Dir.OpenFileOptions,
4915) File.OpenError!File {
4916 const t: *Threaded = @ptrCast(@alignCast(userdata));
4917
4918 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4919 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
4920
4921 var flags: posix.O = switch (native_os) {
4922 .wasi => .{
4923 .read = options.mode != .write_only,
4924 .write = options.mode != .read_only,
4925 .NOFOLLOW = !options.follow_symlinks,
4926 },
4927 else => .{
4928 .ACCMODE = switch (options.mode) {
4929 .read_only => .RDONLY,
4930 .write_only => .WRONLY,
4931 .read_write => .RDWR,
4932 },
4933 .NOFOLLOW = !options.follow_symlinks,
4934 },
4935 };
4936 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
4937 if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
4938 if (@hasField(posix.O, "NOCTTY")) flags.NOCTTY = !options.allow_ctty;
4939 if (@hasField(posix.O, "PATH")) flags.PATH = options.path_only;
4940 if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath;
4941
4942 // Use the O locking options if the os supports them to acquire the lock
4943 // atomically. Note that the NONBLOCK flag is removed after the openat()
4944 // call is successful.
4945 if (have_flock_open_flags) switch (options.lock) {
4946 .none => {},
4947 .shared => {
4948 flags.SHLOCK = true;
4949 flags.NONBLOCK = options.lock_nonblocking;
4950 },
4951 .exclusive => {
4952 flags.EXLOCK = true;
4953 flags.NONBLOCK = options.lock_nonblocking;
4954 },
4955 };
4956
4957 const mode: posix.mode_t = 0;
4958
4959 const fd: posix.fd_t = fd: {
4960 const syscall: Syscall = try .start();
4961 while (true) {
4962 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
4963 switch (posix.errno(rc)) {
4964 .SUCCESS => {
4965 syscall.finish();
4966 break :fd @intCast(rc);
4967 },
4968 .INTR => {
4969 try syscall.checkCancel();
4970 continue;
4971 },
4972 else => |e| {
4973 syscall.finish();
4974 switch (e) {
4975 .FAULT => |err| return errnoBug(err),
4976 .INVAL => return error.BadPathName,
4977 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4978 .ACCES => return error.AccessDenied,
4979 .FBIG => return error.FileTooBig,
4980 .OVERFLOW => return error.FileTooBig,
4981 .ISDIR => return error.IsDir,
4982 .LOOP => return error.SymLinkLoop,
4983 .MFILE => return error.ProcessFdQuotaExceeded,
4984 .NAMETOOLONG => return error.NameTooLong,
4985 .NFILE => return error.SystemFdQuotaExceeded,
4986 .NODEV => return error.NoDevice,
4987 .NOENT => return error.FileNotFound,
4988 .SRCH => return error.FileNotFound, // Linux when opening procfs files.
4989 .NOMEM => return error.SystemResources,
4990 .NOSPC => return error.NoSpaceLeft,
4991 .NOTDIR => return error.NotDir,
4992 .PERM => return error.PermissionDenied,
4993 .EXIST => return error.PathAlreadyExists,
4994 .BUSY => return error.DeviceBusy,
4995 .OPNOTSUPP => return error.FileLocksUnsupported,
4996 .AGAIN => return error.WouldBlock,
4997 .TXTBSY => return error.FileBusy,
4998 .NXIO => return error.NoDevice,
4999 .ROFS => return error.ReadOnlyFileSystem,
5000 .ILSEQ => return error.BadPathName,
5001 else => |err| return posix.unexpectedErrno(err),
5002 }
5003 },
5004 }
5005 }
5006 };
5007 errdefer closeFd(fd);
5008
5009 if (!options.allow_directory) {
5010 const is_dir = is_dir: {
5011 const stat = fileStat(t, .{
5012 .handle = fd,
5013 .flags = .{ .nonblocking = false },
5014 }) catch |err| switch (err) {
5015 // The directory-ness is either unknown or unknowable
5016 error.Streaming => break :is_dir false,
5017 else => |e| return e,
5018 };
5019 break :is_dir stat.kind == .directory;
5020 };
5021 if (is_dir) return error.IsDir;
5022 }
5023
5024 if (have_flock and !have_flock_open_flags and options.lock != .none) {
5025 const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
5026 const lock_flags = switch (options.lock) {
5027 .none => unreachable,
5028 .shared => posix.LOCK.SH | lock_nonblocking,
5029 .exclusive => posix.LOCK.EX | lock_nonblocking,
5030 };
5031 const syscall: Syscall = try .start();
5032 while (true) {
5033 switch (posix.errno(posix.system.flock(fd, lock_flags))) {
5034 .SUCCESS => {
5035 syscall.finish();
5036 break;
5037 },
5038 .INTR => {
5039 try syscall.checkCancel();
5040 continue;
5041 },
5042 else => |e| {
5043 syscall.finish();
5044 switch (e) {
5045 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5046 .INVAL => |err| return errnoBug(err), // invalid parameters
5047 .NOLCK => return error.SystemResources,
5048 .AGAIN => return error.WouldBlock,
5049 .OPNOTSUPP => return error.FileLocksUnsupported,
5050 else => |err| return posix.unexpectedErrno(err),
5051 }
5052 },
5053 }
5054 }
5055 }
5056
5057 if (have_flock_open_flags and options.lock_nonblocking) {
5058 var fl_flags: usize = fl: {
5059 const syscall: Syscall = try .start();
5060 while (true) {
5061 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
5062 switch (posix.errno(rc)) {
5063 .SUCCESS => {
5064 syscall.finish();
5065 break :fl @intCast(rc);
5066 },
5067 .INTR => {
5068 try syscall.checkCancel();
5069 continue;
5070 },
5071 else => |err| {
5072 syscall.finish();
5073 return posix.unexpectedErrno(err);
5074 },
5075 }
5076 }
5077 };
5078
5079 fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
5080
5081 const syscall: Syscall = try .start();
5082 while (true) {
5083 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
5084 .SUCCESS => {
5085 syscall.finish();
5086 break;
5087 },
5088 .INTR => {
5089 try syscall.checkCancel();
5090 continue;
5091 },
5092 else => |err| {
5093 syscall.finish();
5094 return posix.unexpectedErrno(err);
5095 },
5096 }
5097 }
5098 }
5099
5100 return .{
5101 .handle = fd,
5102 .flags = .{ .nonblocking = false },
5103 };
5104}
5105
5106fn dirOpenFileWindows(
5107 userdata: ?*anyopaque,
5108 dir: Dir,
5109 sub_path: []const u8,
5110 flags: Dir.OpenFileOptions,
5111) File.OpenError!File {
5112 const t: *Threaded = @ptrCast(@alignCast(userdata));
5113 _ = t;
5114 const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
5115 const sub_path_w = sub_path_w_array.span();
5116 const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;
5117 return dirOpenFileWtf16(dir_handle, sub_path_w, flags);
5118}
5119
5120pub fn dirOpenFileWtf16(
5121 dir_handle: ?windows.HANDLE,
5122 sub_path_w: []const u16,
5123 flags: Dir.OpenFileOptions,
5124) File.OpenError!File {
5125 const allow_directory = flags.allow_directory and !flags.isWrite();
5126 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
5127 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
5128 const w = windows;
5129
5130 var io_status_block: w.IO_STATUS_BLOCK = undefined;
5131 var attempt: u5 = 0;
5132 var syscall: Syscall = try .start();
5133 const handle = while (true) {
5134 var result: w.HANDLE = undefined;
5135 switch (w.ntdll.NtCreateFile(
5136 &result,
5137 .{
5138 .STANDARD = .{ .SYNCHRONIZE = true },
5139 .GENERIC = .{
5140 .READ = flags.isRead(),
5141 .WRITE = flags.isWrite(),
5142 },
5143 },
5144 &.{
5145 .RootDirectory = dir_handle,
5146 .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)),
5147 },
5148 &io_status_block,
5149 null,
5150 .{ .NORMAL = true },
5151 .VALID_FLAGS,
5152 .OPEN,
5153 .{
5154 .IO = .SYNCHRONOUS_NONALERT,
5155 .NON_DIRECTORY_FILE = !allow_directory,
5156 .OPEN_REPARSE_POINT = !flags.follow_symlinks,
5157 },
5158 null,
5159 0,
5160 )) {
5161 .SUCCESS => {
5162 syscall.finish();
5163 break result;
5164 },
5165 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
5166 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
5167 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
5168 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
5169 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
5170 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
5171 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
5172 .CANCELLED => {
5173 try syscall.checkCancel();
5174 continue;
5175 },
5176 .SHARING_VIOLATION => {
5177 // This occurs if the file attempting to be opened is a running
5178 // executable. However, there's a kernel bug: the error may be
5179 // incorrectly returned for an indeterminate amount of time
5180 // after an executable file is closed. Here we work around the
5181 // kernel bug with retry attempts.
5182 syscall.finish();
5183 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
5184 try parking_sleep.sleep(.{ .duration = .{
5185 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
5186 .clock = .awake,
5187 } });
5188 attempt += 1;
5189 syscall = try .start();
5190 continue;
5191 },
5192 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
5193 .PIPE_BUSY => return syscall.fail(error.PipeBusy),
5194 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
5195 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
5196 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
5197 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
5198 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
5199 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
5200 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
5201 .DELETE_PENDING => {
5202 // This error means that there *was* a file in this location on
5203 // the file system, but it was deleted. However, the OS is not
5204 // finished with the deletion operation, and so this CreateFile
5205 // call has failed. Here, we simulate the kernel bug being
5206 // fixed by sleeping and retrying until the error goes away.
5207 syscall.finish();
5208 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
5209 try parking_sleep.sleep(.{ .duration = .{
5210 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
5211 .clock = .awake,
5212 } });
5213 attempt += 1;
5214 syscall = try .start();
5215 continue;
5216 },
5217 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
5218 else => |rc| return syscall.unexpectedNtstatus(rc),
5219 }
5220 };
5221 errdefer w.CloseHandle(handle);
5222
5223 const exclusive = switch (flags.lock) {
5224 .none => return .{
5225 .handle = handle,
5226 .flags = .{ .nonblocking = false },
5227 },
5228 .shared => false,
5229 .exclusive => true,
5230 };
5231 syscall = try .start();
5232 while (true) switch (w.ntdll.NtLockFile(
5233 handle,
5234 null,
5235 null,
5236 null,
5237 &io_status_block,
5238 &windows_lock_range_off,
5239 &windows_lock_range_len,
5240 null,
5241 .fromBool(flags.lock_nonblocking),
5242 .fromBool(exclusive),
5243 )) {
5244 .SUCCESS => break syscall.finish(),
5245 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
5246 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
5247 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
5248 else => |status| return syscall.unexpectedNtstatus(status),
5249 };
5250 return .{
5251 .handle = handle,
5252 .flags = .{ .nonblocking = false },
5253 };
5254}
5255
5256fn dirOpenFileWasi(
5257 userdata: ?*anyopaque,
5258 dir: Dir,
5259 sub_path: []const u8,
5260 flags: Dir.OpenFileOptions,
5261) File.OpenError!File {
5262 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags);
5263 const t: *Threaded = @ptrCast(@alignCast(userdata));
5264 const wasi = std.os.wasi;
5265 var base: std.os.wasi.rights_t = .{};
5266 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE
5267 // is also set.
5268 if (flags.isRead()) {
5269 base.FD_READ = true;
5270 base.FD_TELL = true;
5271 base.FD_SEEK = true;
5272 base.FD_FILESTAT_GET = true;
5273 base.POLL_FD_READWRITE = true;
5274 }
5275 if (flags.isWrite()) {
5276 base.FD_WRITE = true;
5277 base.FD_TELL = true;
5278 base.FD_SEEK = true;
5279 base.FD_DATASYNC = true;
5280 base.FD_FDSTAT_SET_FLAGS = true;
5281 base.FD_SYNC = true;
5282 base.FD_ALLOCATE = true;
5283 base.FD_ADVISE = true;
5284 base.FD_FILESTAT_SET_TIMES = true;
5285 base.FD_FILESTAT_SET_SIZE = true;
5286 base.POLL_FD_READWRITE = true;
5287 }
5288 const lookup_flags: wasi.lookupflags_t = .{};
5289 const oflags: wasi.oflags_t = .{};
5290 const inheriting: wasi.rights_t = .{};
5291 const fdflags: wasi.fdflags_t = .{};
5292 var fd: posix.fd_t = undefined;
5293 const syscall: Syscall = try .start();
5294 while (true) {
5295 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
5296 .SUCCESS => {
5297 syscall.finish();
5298 break;
5299 },
5300 .INTR => {
5301 try syscall.checkCancel();
5302 continue;
5303 },
5304 else => |e| {
5305 syscall.finish();
5306 switch (e) {
5307 .FAULT => |err| return errnoBug(err),
5308 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5309 .ACCES => return error.AccessDenied,
5310 .FBIG => return error.FileTooBig,
5311 .OVERFLOW => return error.FileTooBig,
5312 .ISDIR => return error.IsDir,
5313 .LOOP => return error.SymLinkLoop,
5314 .MFILE => return error.ProcessFdQuotaExceeded,
5315 .NFILE => return error.SystemFdQuotaExceeded,
5316 .NODEV => return error.NoDevice,
5317 .NOENT => return error.FileNotFound,
5318 .NOMEM => return error.SystemResources,
5319 .NOTDIR => return error.NotDir,
5320 .PERM => return error.PermissionDenied,
5321 .BUSY => return error.DeviceBusy,
5322 .NOTCAPABLE => return error.AccessDenied,
5323 .NAMETOOLONG => return error.NameTooLong,
5324 .INVAL => return error.BadPathName,
5325 .ILSEQ => return error.BadPathName,
5326 else => |err| return posix.unexpectedErrno(err),
5327 }
5328 },
5329 }
5330 }
5331 errdefer closeFd(fd);
5332
5333 if (!flags.allow_directory) {
5334 const is_dir = is_dir: {
5335 const stat = fileStat(t, .{ .handle = fd, .flags = .{ .nonblocking = false } }) catch |err| switch (err) {
5336 // The directory-ness is either unknown or unknowable
5337 error.Streaming => break :is_dir false,
5338 else => |e| return e,
5339 };
5340 break :is_dir stat.kind == .directory;
5341 };
5342 if (is_dir) return error.IsDir;
5343 }
5344
5345 return .{
5346 .handle = fd,
5347 .flags = .{ .nonblocking = false },
5348 };
5349}
5350
5351const dirOpenDir = switch (native_os) {
5352 .wasi => dirOpenDirWasi,
5353 .haiku => dirOpenDirHaiku,
5354 else => dirOpenDirPosix,
5355};
5356
5357/// This function is also used for WASI when libc is linked.
5358fn dirOpenDirPosix(
5359 userdata: ?*anyopaque,
5360 dir: Dir,
5361 sub_path: []const u8,
5362 options: Dir.OpenOptions,
5363) Dir.OpenError!Dir {
5364 const t: *Threaded = @ptrCast(@alignCast(userdata));
5365 _ = t;
5366
5367 if (is_windows) {
5368 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
5369 return dirOpenDirWindows(dir, sub_path_w.span(), options);
5370 }
5371
5372 var path_buffer: [posix.PATH_MAX]u8 = undefined;
5373 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
5374
5375 var flags: posix.O = switch (native_os) {
5376 .wasi => .{
5377 .read = true,
5378 .NOFOLLOW = !options.follow_symlinks,
5379 .DIRECTORY = true,
5380 },
5381 else => .{
5382 .ACCMODE = .RDONLY,
5383 .NOFOLLOW = !options.follow_symlinks,
5384 .DIRECTORY = true,
5385 .CLOEXEC = true,
5386 },
5387 };
5388
5389 if (@hasField(posix.O, "PATH") and !options.iterate)
5390 flags.PATH = true;
5391
5392 const mode: posix.mode_t = 0;
5393
5394 const syscall: Syscall = try .start();
5395 while (true) {
5396 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
5397 switch (posix.errno(rc)) {
5398 .SUCCESS => {
5399 syscall.finish();
5400 return .{ .handle = @intCast(rc) };
5401 },
5402 .INTR => {
5403 try syscall.checkCancel();
5404 continue;
5405 },
5406 .INVAL => return syscall.fail(error.BadPathName),
5407 .ACCES => return syscall.fail(error.AccessDenied),
5408 .LOOP => return syscall.fail(error.SymLinkLoop),
5409 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
5410 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
5411 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
5412 .NODEV => return syscall.fail(error.NoDevice),
5413 .NOENT => return syscall.fail(error.FileNotFound),
5414 .NOMEM => return syscall.fail(error.SystemResources),
5415 .NOTDIR => return syscall.fail(error.NotDir),
5416 .PERM => return syscall.fail(error.PermissionDenied),
5417 .NXIO => return syscall.fail(error.NoDevice),
5418 .ILSEQ => return syscall.fail(error.BadPathName),
5419 .FAULT => |err| return syscall.errnoBug(err),
5420 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
5421 .BUSY => |err| return syscall.errnoBug(err), // O_EXCL not passed
5422 else => |err| return syscall.unexpectedErrno(err),
5423 }
5424 }
5425}
5426
5427fn dirOpenDirHaiku(
5428 userdata: ?*anyopaque,
5429 dir: Dir,
5430 sub_path: []const u8,
5431 options: Dir.OpenOptions,
5432) Dir.OpenError!Dir {
5433 const t: *Threaded = @ptrCast(@alignCast(userdata));
5434 _ = t;
5435
5436 var path_buffer: [posix.PATH_MAX]u8 = undefined;
5437 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
5438
5439 _ = options;
5440
5441 const syscall: Syscall = try .start();
5442 while (true) {
5443 const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix);
5444 if (rc >= 0) {
5445 syscall.finish();
5446 return .{ .handle = rc };
5447 }
5448 switch (@as(posix.E, @fromBackingInt(@intCast(rc)))) {
5449 .INTR => {
5450 try syscall.checkCancel();
5451 continue;
5452 },
5453 else => |e| {
5454 syscall.finish();
5455 switch (e) {
5456 .FAULT => |err| return errnoBug(err),
5457 .INVAL => |err| return errnoBug(err),
5458 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5459 .ACCES => return error.AccessDenied,
5460 .LOOP => return error.SymLinkLoop,
5461 .MFILE => return error.ProcessFdQuotaExceeded,
5462 .NAMETOOLONG => return error.NameTooLong,
5463 .NFILE => return error.SystemFdQuotaExceeded,
5464 .NODEV => return error.NoDevice,
5465 .NOENT => return error.FileNotFound,
5466 .NOMEM => return error.SystemResources,
5467 .NOTDIR => return error.NotDir,
5468 .PERM => return error.PermissionDenied,
5469 .BUSY => |err| return errnoBug(err),
5470 else => |err| return posix.unexpectedErrno(err),
5471 }
5472 },
5473 }
5474 }
5475}
5476
5477pub fn dirOpenDirWindows(
5478 dir: Dir,
5479 sub_path_w: []const u16,
5480 options: Dir.OpenOptions,
5481) Dir.OpenError!Dir {
5482 const w = windows;
5483
5484 var io_status_block: w.IO_STATUS_BLOCK = undefined;
5485 var result: Dir = .{ .handle = undefined };
5486
5487 const attr: w.OBJECT.ATTRIBUTES = .{
5488 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5489 .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)),
5490 };
5491
5492 const syscall: Syscall = try .start();
5493 while (true) switch (w.ntdll.NtCreateFile(
5494 &result.handle,
5495 // TODO remove some of these flags if options.access_sub_paths is false
5496 .{
5497 .SPECIFIC = .{ .FILE_DIRECTORY = .{
5498 .LIST = options.iterate,
5499 .READ_EA = true,
5500 .TRAVERSE = true,
5501 .READ_ATTRIBUTES = true,
5502 } },
5503 .STANDARD = .{
5504 .RIGHTS = .READ,
5505 .SYNCHRONIZE = true,
5506 },
5507 },
5508 &attr,
5509 &io_status_block,
5510 null,
5511 .{ .NORMAL = true },
5512 .VALID_FLAGS,
5513 .OPEN,
5514 .{
5515 .DIRECTORY_FILE = true,
5516 .IO = .SYNCHRONOUS_NONALERT,
5517 .OPEN_FOR_BACKUP_INTENT = true,
5518 .OPEN_REPARSE_POINT = !options.follow_symlinks,
5519 },
5520 null,
5521 0,
5522 )) {
5523 .SUCCESS => {
5524 syscall.finish();
5525 return result;
5526 },
5527 .CANCELLED => {
5528 try syscall.checkCancel();
5529 continue;
5530 },
5531 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
5532 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
5533 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
5534 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
5535 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
5536 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
5537 // and the directory is trying to be opened for iteration.
5538 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
5539 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
5540 else => |rc| return syscall.unexpectedNtstatus(rc),
5541 };
5542}
5543
5544fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
5545 const t: *Threaded = @ptrCast(@alignCast(userdata));
5546 _ = t;
5547 for (dirs) |dir| {
5548 if (is_windows) {
5549 windows.CloseHandle(dir.handle);
5550 } else {
5551 closeFd(dir.handle);
5552 }
5553 }
5554}
5555
5556const dirRead = switch (native_os) {
5557 .linux => dirReadLinux,
5558 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin,
5559 .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd,
5560 .illumos => dirReadIllumos,
5561 .haiku => dirReadHaiku,
5562 .windows => dirReadWindows,
5563 .wasi => dirReadWasi,
5564 else => dirReadUnimplemented,
5565};
5566
5567fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
5568 const linux = std.os.linux;
5569 const t: *Threaded = @ptrCast(@alignCast(userdata));
5570 _ = t;
5571 var buffer_index: usize = 0;
5572 while (buffer.len - buffer_index != 0) {
5573 if (dr.end - dr.index == 0) {
5574 // Refill the buffer, unless we've already created references to
5575 // buffered data.
5576 if (buffer_index != 0) break;
5577 if (dr.state == .reset) {
5578 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
5579 error.Unseekable => return error.Unexpected,
5580 else => |e| return e,
5581 };
5582 dr.state = .reading;
5583 }
5584 const syscall: Syscall = try .start();
5585 const n = while (true) {
5586 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, @min(dr.buffer.len, std.math.maxInt(c_uint)));
5587 switch (linux.errno(rc)) {
5588 .SUCCESS => {
5589 syscall.finish();
5590 break rc;
5591 },
5592 .INTR => {
5593 try syscall.checkCancel();
5594 continue;
5595 },
5596 else => |e| {
5597 syscall.finish();
5598 switch (e) {
5599 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
5600 .FAULT => |err| return errnoBug(err),
5601 .NOTDIR => |err| return errnoBug(err),
5602 // To be consistent across platforms, iteration
5603 // ends if the directory being iterated is deleted
5604 // during iteration. This matches the behavior of
5605 // non-Linux, non-WASI UNIX platforms.
5606 .NOENT => {
5607 dr.state = .finished;
5608 return 0;
5609 },
5610 // This can occur when reading /proc/$PID/net, or
5611 // if the provided buffer is too small. Neither
5612 // scenario is intended to be handled by this API.
5613 .INVAL => return error.Unexpected,
5614 .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory.
5615 else => |err| return posix.unexpectedErrno(err),
5616 }
5617 },
5618 }
5619 };
5620 if (n == 0) {
5621 dr.state = .finished;
5622 return 0;
5623 }
5624 dr.index = 0;
5625 dr.end = n;
5626 }
5627 // Linux aligns the header by padding after the null byte of the name
5628 // to align the next entry. This means we can find the end of the name
5629 // by looking at only the 8 bytes before the next record. However since
5630 // file names are usually short it's better to keep the machine code
5631 // simpler.
5632 //
5633 // Furthermore, I observed qemu user mode to not align this struct, so
5634 // this code makes the conservative choice to not assume alignment.
5635 const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]);
5636 const next_index = dr.index + linux_entry.reclen;
5637 dr.index = next_index;
5638 const name_ptr: [*]u8 = &linux_entry.name;
5639 const padded_name = name_ptr[0 .. linux_entry.reclen - @offsetOf(linux.dirent64, "name")];
5640 const name_len = std.mem.findScalar(u8, padded_name, 0).?;
5641 const name = name_ptr[0..name_len :0];
5642
5643 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
5644
5645 const entry_kind: File.Kind = switch (linux_entry.type) {
5646 linux.DT.BLK => .block_device,
5647 linux.DT.CHR => .character_device,
5648 linux.DT.DIR => .directory,
5649 linux.DT.FIFO => .named_pipe,
5650 linux.DT.LNK => .sym_link,
5651 linux.DT.REG => .file,
5652 linux.DT.SOCK => .unix_domain_socket,
5653 else => .unknown,
5654 };
5655 buffer[buffer_index] = .{
5656 .name = name,
5657 .kind = entry_kind,
5658 .inode = linux_entry.ino,
5659 };
5660 buffer_index += 1;
5661 }
5662 return buffer_index;
5663}
5664
5665fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
5666 const t: *Threaded = @ptrCast(@alignCast(userdata));
5667 _ = t;
5668 const Header = extern struct {
5669 seek: i64,
5670 };
5671 const header: *Header = @ptrCast(dr.buffer.ptr);
5672 const header_end: usize = @sizeOf(Header);
5673 if (dr.index < header_end) {
5674 // Initialize header.
5675 dr.index = header_end;
5676 dr.end = header_end;
5677 header.* = .{ .seek = 0 };
5678 }
5679 var buffer_index: usize = 0;
5680 while (buffer.len - buffer_index != 0) {
5681 if (dr.end - dr.index == 0) {
5682 // Refill the buffer, unless we've already created references to
5683 // buffered data.
5684 if (buffer_index != 0) break;
5685 if (dr.state == .reset) {
5686 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
5687 error.Unseekable => return error.Unexpected,
5688 else => |e| return e,
5689 };
5690 dr.state = .reading;
5691 }
5692 const dents_buffer = dr.buffer[header_end..];
5693 const syscall: Syscall = try .start();
5694 const n: usize = while (true) {
5695 const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek);
5696 switch (posix.errno(rc)) {
5697 .SUCCESS => {
5698 syscall.finish();
5699 break @intCast(rc);
5700 },
5701 .INTR => {
5702 try syscall.checkCancel();
5703 continue;
5704 },
5705 else => |e| {
5706 syscall.finish();
5707 switch (e) {
5708 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
5709 .FAULT => |err| return errnoBug(err),
5710 .NOTDIR => |err| return errnoBug(err),
5711 .INVAL => |err| return errnoBug(err),
5712 else => |err| return posix.unexpectedErrno(err),
5713 }
5714 },
5715 }
5716 };
5717 if (n == 0) {
5718 dr.state = .finished;
5719 return 0;
5720 }
5721 dr.index = header_end;
5722 dr.end = header_end + n;
5723 }
5724 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
5725 const next_index = dr.index + darwin_entry.reclen;
5726 dr.index = next_index;
5727
5728 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
5729 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0))
5730 continue;
5731
5732 const entry_kind: File.Kind = switch (darwin_entry.type) {
5733 posix.DT.BLK => .block_device,
5734 posix.DT.CHR => .character_device,
5735 posix.DT.DIR => .directory,
5736 posix.DT.FIFO => .named_pipe,
5737 posix.DT.LNK => .sym_link,
5738 posix.DT.REG => .file,
5739 posix.DT.SOCK => .unix_domain_socket,
5740 posix.DT.WHT => .whiteout,
5741 else => .unknown,
5742 };
5743 buffer[buffer_index] = .{
5744 .name = name,
5745 .kind = entry_kind,
5746 .inode = darwin_entry.ino,
5747 };
5748 buffer_index += 1;
5749 }
5750 return buffer_index;
5751}
5752
5753fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
5754 const t: *Threaded = @ptrCast(@alignCast(userdata));
5755 _ = t;
5756 var buffer_index: usize = 0;
5757 while (buffer.len - buffer_index != 0) {
5758 if (dr.end - dr.index == 0) {
5759 // Refill the buffer, unless we've already created references to
5760 // buffered data.
5761 if (buffer_index != 0) break;
5762 if (dr.state == .reset) {
5763 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
5764 error.Unseekable => return error.Unexpected,
5765 else => |e| return e,
5766 };
5767 dr.state = .reading;
5768 }
5769 const syscall: Syscall = try .start();
5770 const n: usize = while (true) {
5771 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
5772 switch (posix.errno(rc)) {
5773 .SUCCESS => {
5774 syscall.finish();
5775 break @intCast(rc);
5776 },
5777 .INTR => {
5778 try syscall.checkCancel();
5779 continue;
5780 },
5781 else => |e| {
5782 syscall.finish();
5783 switch (e) {
5784 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
5785 .FAULT => |err| return errnoBug(err),
5786 .NOTDIR => |err| return errnoBug(err),
5787 .INVAL => |err| return errnoBug(err),
5788 // Introduced in freebsd 13.2: directory unlinked
5789 // but still open. To be consistent, iteration ends
5790 // if the directory being iterated is deleted
5791 // during iteration.
5792 .NOENT => {
5793 dr.state = .finished;
5794 return 0;
5795 },
5796 else => |err| return posix.unexpectedErrno(err),
5797 }
5798 },
5799 }
5800 };
5801 if (n == 0) {
5802 dr.state = .finished;
5803 return 0;
5804 }
5805 dr.index = 0;
5806 dr.end = n;
5807 }
5808 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
5809 const next_index = dr.index +
5810 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
5811 dr.index = next_index;
5812
5813 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
5814
5815 const skip_zero_fileno = switch (native_os) {
5816 // fileno=0 is used to mark invalid entries or deleted files.
5817 .openbsd, .netbsd => true,
5818 else => false,
5819 };
5820 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or
5821 (skip_zero_fileno and bsd_entry.fileno == 0))
5822 {
5823 continue;
5824 }
5825
5826 const entry_kind: File.Kind = switch (bsd_entry.type) {
5827 posix.DT.BLK => .block_device,
5828 posix.DT.CHR => .character_device,
5829 posix.DT.DIR => .directory,
5830 posix.DT.FIFO => .named_pipe,
5831 posix.DT.LNK => .sym_link,
5832 posix.DT.REG => .file,
5833 posix.DT.SOCK => .unix_domain_socket,
5834 posix.DT.WHT => .whiteout,
5835 else => .unknown,
5836 };
5837 buffer[buffer_index] = .{
5838 .name = name,
5839 .kind = entry_kind,
5840 .inode = bsd_entry.fileno,
5841 };
5842 buffer_index += 1;
5843 }
5844 return buffer_index;
5845}
5846
5847fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
5848 const t: *Threaded = @ptrCast(@alignCast(userdata));
5849 _ = t;
5850 var buffer_index: usize = 0;
5851 while (buffer.len - buffer_index != 0) {
5852 if (dr.end - dr.index == 0) {
5853 // Refill the buffer, unless we've already created references to
5854 // buffered data.
5855 if (buffer_index != 0) break;
5856 if (dr.state == .reset) {
5857 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) {
5858 error.Unseekable => return error.Unexpected,
5859 else => |e| return e,
5860 };
5861 dr.state = .reading;
5862 }
5863 const syscall: Syscall = try .start();
5864 const n: usize = while (true) {
5865 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
5866 switch (posix.errno(rc)) {
5867 .SUCCESS => {
5868 syscall.finish();
5869 break rc;
5870 },
5871 .INTR => {
5872 try syscall.checkCancel();
5873 continue;
5874 },
5875 else => |e| {
5876 syscall.finish();
5877 switch (e) {
5878 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
5879 .FAULT => |err| return errnoBug(err),
5880 .NOTDIR => |err| return errnoBug(err),
5881 .INVAL => |err| return errnoBug(err),
5882 else => |err| return posix.unexpectedErrno(err),
5883 }
5884 },
5885 }
5886 };
5887 if (n == 0) {
5888 dr.state = .finished;
5889 return 0;
5890 }
5891 dr.index = 0;
5892 dr.end = n;
5893 }
5894 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
5895 const next_index = dr.index + entry.reclen;
5896 dr.index = next_index;
5897
5898 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
5899 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
5900
5901 // illumos dirent doesn't expose type, so we have to call stat to get it.
5902 const stat = try posixStatFile(dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
5903
5904 buffer[buffer_index] = .{
5905 .name = name,
5906 .kind = stat.kind,
5907 .inode = entry.ino,
5908 };
5909 buffer_index += 1;
5910 }
5911 return buffer_index;
5912}
5913
5914fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
5915 const t: *Threaded = @ptrCast(@alignCast(userdata));
5916 _ = t;
5917 var buffer_index: usize = 0;
5918 while (buffer.len - buffer_index != 0) {
5919 if (dr.end - dr.index == 0) {
5920 // Refill the buffer, unless we've already created references to
5921 // buffered data.
5922 if (buffer_index != 0) break;
5923 if (dr.state == .reset) {
5924 const syscall: Syscall = try .start();
5925 while (true) {
5926 const rc = posix.system._kern_rewind_dir(dr.dir.handle);
5927 switch (@as(posix.E, @fromBackingInt(@intCast(@min(rc, 0))))) {
5928 .SUCCESS => {
5929 syscall.finish();
5930 break;
5931 },
5932 .INTR => {
5933 try syscall.checkCancel();
5934 continue;
5935 },
5936 else => |e| {
5937 syscall.finish();
5938 switch (e) {
5939 else => |err| return posix.unexpectedErrno(err),
5940 }
5941 },
5942 }
5943 }
5944 dr.state = .reading;
5945 }
5946 const syscall: Syscall = try .start();
5947 const n: usize = while (true) {
5948 const rc = posix.system._kern_read_dir(dr.dir.handle, dr.buffer.ptr, dr.buffer.len, @truncate(dr.buffer.len / @sizeOf(posix.system.DirEnt)));
5949 switch (@as(posix.E, @fromBackingInt(@intCast(@min(rc, 0))))) {
5950 .SUCCESS => {
5951 syscall.finish();
5952 break @intCast(rc);
5953 },
5954 .INTR => {
5955 try syscall.checkCancel();
5956 continue;
5957 },
5958 else => |e| {
5959 syscall.finish();
5960 switch (e) {
5961 else => |err| return posix.unexpectedErrno(err),
5962 }
5963 },
5964 }
5965 };
5966 if (n == 0) {
5967 dr.state = .finished;
5968 return 0;
5969 }
5970 dr.index = 0;
5971 // _kern_read_dir returns entry count, but Dir.Reader is designed for byte count
5972 dr.end = 0;
5973 var i: usize = 0;
5974 while (i < n) : (i += 1) {
5975 const entry = @as(*align(1) posix.system.DirEnt, @ptrCast(&dr.buffer[dr.end]));
5976 dr.end += entry.reclen;
5977 }
5978 }
5979 const entry = @as(*align(1) posix.system.DirEnt, @ptrCast(&dr.buffer[dr.index]));
5980 const next_index = dr.index + entry.reclen;
5981 dr.index = next_index;
5982
5983 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
5984 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or entry.ino == 0) continue;
5985
5986 // haiku dirent doesn't expose type, so we have to call stat to get it.
5987 var stat: std.c.Stat = undefined;
5988 {
5989 const syscall: Syscall = try .start();
5990 while (true) {
5991 const rc = posix.system._kern_read_stat(dr.dir.handle, name, false, &stat, @sizeOf(std.c.Stat));
5992 switch (@as(posix.E, @fromBackingInt(@intCast(@min(rc, 0))))) {
5993 .SUCCESS => {
5994 syscall.finish();
5995 break;
5996 },
5997 .INTR => {
5998 try syscall.checkCancel();
5999 continue;
6000 },
6001 else => |e| {
6002 syscall.finish();
6003 switch (e) {
6004 else => |err| return posix.unexpectedErrno(err),
6005 }
6006 },
6007 }
6008 }
6009 }
6010
6011 const entry_kind: File.Kind = switch (stat.mode & posix.S.IFMT) {
6012 posix.S.IFBLK => .block_device,
6013 posix.S.IFCHR => .character_device,
6014 posix.S.IFDIR => .directory,
6015 posix.S.IFIFO => .named_pipe,
6016 posix.S.IFLNK => .sym_link,
6017 posix.S.IFREG => .file,
6018 else => .unknown,
6019 };
6020 buffer[buffer_index] = .{
6021 .name = name,
6022 .kind = entry_kind,
6023 .inode = entry.ino,
6024 };
6025 buffer_index += 1;
6026 }
6027 return buffer_index;
6028}
6029
6030fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
6031 const t: *Threaded = @ptrCast(@alignCast(userdata));
6032 _ = t;
6033 const w = windows;
6034
6035 // We want to be able to use the `dr.buffer` for both the NtQueryDirectoryFile call (which
6036 // returns WTF-16 names) *and* as a buffer for storing those WTF-16 names as WTF-8 to be able
6037 // to return them in `Dir.Entry.name`. However, the problem that needs to be overcome in order to do
6038 // that is that each WTF-16 code unit can be encoded as a maximum of 3 WTF-8 bytes, which means
6039 // that it's not guaranteed that the memory used for the WTF-16 name will be sufficient
6040 // for the WTF-8 encoding of the same name (for example, € is encoded as one WTF-16 code unit,
6041 // [2 bytes] but encoded in WTF-8 as 3 bytes).
6042 //
6043 // The approach taken here is to "reserve" enough space in the `dr.buffer` to ensure that
6044 // at least one entry with the maximum possible WTF-8 name length can be stored without clobbering
6045 // any entries that follow it. That is, we determine how much space is needed to allow that,
6046 // and then only provide the remaining portion of `dr.buffer` to the NtQueryDirectoryFile
6047 // call. The WTF-16 names can then be safely converted using the full `dr.buffer` slice, making
6048 // sure that each name can only potentially overwrite the data of its own entry.
6049 //
6050 // The worst case, where an entry's name is both the maximum length of a component and
6051 // made up entirely of code points that are encoded as one WTF-16 code unit/three WTF-8 bytes,
6052 // would therefore look like the diagram below, and only one entry would be able to be returned:
6053 //
6054 // | reserved | remaining unreserved buffer |
6055 // | entry 1 | entry 2 | ... |
6056 // | wtf-8 name of entry 1 |
6057 //
6058 // However, in the average case we will be able to store more than one WTF-8 name at a time in the
6059 // available buffer and therefore we will be able to populate more than one `Dir.Entry` at a time.
6060 // That might look something like this (where name 1, name 2, etc are the converted WTF-8 names):
6061 //
6062 // | reserved | remaining unreserved buffer |
6063 // | entry 1 | entry 2 | ... |
6064 // | name 1 | name 2 | name 3 | name 4 | ... |
6065 //
6066 // Note: More than the minimum amount of space could be reserved to make the "worst case"
6067 // less likely, but since the worst-case also requires a maximum length component to matter,
6068 // it's unlikely for it to become a problem in normal scenarios even if all names on the filesystem
6069 // are made up of non-ASCII characters that have the "one WTF-16 code unit <-> three WTF-8 bytes"
6070 // property (e.g. code points >= U+0800 and <= U+FFFF), as it's unlikely for a significant
6071 // number of components to be maximum length.
6072
6073 // We need `3 * NAME_MAX` bytes to store a max-length component as WTF-8 safely.
6074 // Because needing to store a max-length component depends on a `FileName` *with* the maximum
6075 // component length, we know that the corresponding populated `FILE_BOTH_DIR_INFORMATION` will
6076 // be of size `@sizeOf(w.FILE_BOTH_DIR_INFORMATION) + 2 * NAME_MAX` bytes, so we only need to
6077 // reserve enough to get us to up to having `3 * NAME_MAX` bytes available when taking into account
6078 // that we have the ability to write over top of the reserved memory + the full footprint of that
6079 // particular `FILE_BOTH_DIR_INFORMATION`.
6080 const max_info_len = @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2;
6081 const info_align = @alignOf(w.FILE_BOTH_DIR_INFORMATION);
6082 const reserve_needed = std.mem.alignForward(usize, Dir.max_name_bytes, info_align) - max_info_len;
6083 const unreserved_start = std.mem.alignForward(usize, reserve_needed, info_align);
6084 const unreserved_buffer = dr.buffer[unreserved_start..];
6085 // This is enforced by `Dir.Reader`
6086 assert(unreserved_buffer.len >= max_info_len);
6087
6088 var name_index: usize = 0;
6089 var buffer_index: usize = 0;
6090 while (buffer.len - buffer_index != 0) {
6091 if (dr.end - dr.index == 0) {
6092 // Refill the buffer, unless we've already created references to
6093 // buffered data.
6094 if (buffer_index != 0) break;
6095
6096 var io_status_block: w.IO_STATUS_BLOCK = undefined;
6097 const syscall: Syscall = try .start();
6098 const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile(
6099 dr.dir.handle,
6100 null,
6101 null,
6102 null,
6103 &io_status_block,
6104 unreserved_buffer.ptr,
6105 std.math.lossyCast(w.ULONG, unreserved_buffer.len),
6106 .BothDirectory,
6107 .FALSE,
6108 null,
6109 .fromBool(dr.state == .reset),
6110 )) {
6111 .CANCELLED => {
6112 try syscall.checkCancel();
6113 continue;
6114 },
6115 else => |rc| {
6116 syscall.finish();
6117 break rc;
6118 },
6119 };
6120 dr.state = .reading;
6121 if (io_status_block.Information == 0) {
6122 dr.state = .finished;
6123 return 0;
6124 }
6125 dr.index = 0;
6126 dr.end = io_status_block.Information;
6127 switch (rc) {
6128 .SUCCESS => {},
6129 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
6130 else => return w.unexpectedStatus(rc),
6131 }
6132 }
6133
6134 // While the official API docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
6135 // this may not always be the case (e.g. due to faulty VM/sandboxing tools)
6136 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&unreserved_buffer[dr.index]));
6137 const backtrack_index = dr.index;
6138 if (dir_info.NextEntryOffset != 0) {
6139 dr.index += dir_info.NextEntryOffset;
6140 } else {
6141 dr.index = dr.end;
6142 }
6143
6144 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
6145
6146 if (std.mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or std.mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) {
6147 continue;
6148 }
6149
6150 // Read any relevant information from the `dir_info` now since it's possible the WTF-8
6151 // name will overwrite it.
6152 const kind: File.Kind = blk: {
6153 const attrs = dir_info.FileAttributes;
6154 if (attrs.REPARSE_POINT) break :blk .sym_link;
6155 if (attrs.DIRECTORY) break :blk .directory;
6156 break :blk .file;
6157 };
6158 const inode: File.INode = dir_info.FileIndex;
6159
6160 // If there's no more space for WTF-8 names without bleeding over into
6161 // the remaining unprocessed entries, then backtrack and return what we have so far.
6162 if (name_index + std.unicode.calcWtf8Len(name_wtf16le) > unreserved_start + dr.index) {
6163 // We should always be able to fit at least one entry into the buffer no matter what
6164 assert(buffer_index != 0);
6165 dr.index = backtrack_index;
6166 break;
6167 }
6168
6169 const name_buf = dr.buffer[name_index..];
6170 const name_wtf8_len = std.unicode.wtf16LeToWtf8(name_buf, name_wtf16le);
6171 const name_wtf8 = name_buf[0..name_wtf8_len];
6172 name_index += name_wtf8_len;
6173
6174 buffer[buffer_index] = .{
6175 .name = name_wtf8,
6176 .kind = kind,
6177 .inode = inode,
6178 };
6179 buffer_index += 1;
6180 }
6181
6182 return buffer_index;
6183}
6184
6185fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
6186 // We intentinally use fd_readdir even when linked with libc, since its
6187 // implementation is exactly the same as below, and we avoid the code
6188 // complexity here.
6189 const wasi = std.os.wasi;
6190 const t: *Threaded = @ptrCast(@alignCast(userdata));
6191 _ = t;
6192 const Header = extern struct {
6193 cookie: u64,
6194 };
6195 const header: *align(@alignOf(usize)) Header = @ptrCast(dr.buffer.ptr);
6196 const header_end: usize = @sizeOf(Header);
6197 if (dr.index < header_end) {
6198 // Initialize header.
6199 dr.index = header_end;
6200 dr.end = header_end;
6201 header.* = .{ .cookie = wasi.DIRCOOKIE_START };
6202 }
6203 var buffer_index: usize = 0;
6204 while (buffer.len - buffer_index != 0) {
6205 // According to the WASI spec, the last entry might be truncated, so we
6206 // need to check if the remaining buffer contains the whole dirent.
6207 if (dr.end - dr.index < @sizeOf(wasi.dirent_t)) {
6208 // Refill the buffer, unless we've already created references to
6209 // buffered data.
6210 if (buffer_index != 0) break;
6211 if (dr.state == .reset) {
6212 header.* = .{ .cookie = wasi.DIRCOOKIE_START };
6213 dr.state = .reading;
6214 }
6215 const dents_buffer = dr.buffer[header_end..];
6216 var n: usize = undefined;
6217 const syscall: Syscall = try .start();
6218 while (true) {
6219 switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) {
6220 .SUCCESS => {
6221 syscall.finish();
6222 break;
6223 },
6224 .INTR => {
6225 try syscall.checkCancel();
6226 continue;
6227 },
6228 else => |e| {
6229 syscall.finish();
6230 switch (e) {
6231 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
6232 .FAULT => |err| return errnoBug(err),
6233 .NOTDIR => |err| return errnoBug(err),
6234 .INVAL => |err| return errnoBug(err),
6235 // To be consistent across platforms, iteration
6236 // ends if the directory being iterated is deleted
6237 // during iteration. This matches the behavior of
6238 // non-Linux, non-WASI UNIX platforms.
6239 .NOENT => {
6240 dr.state = .finished;
6241 return 0;
6242 },
6243 .NOTCAPABLE => return error.AccessDenied,
6244 else => |err| return posix.unexpectedErrno(err),
6245 }
6246 },
6247 }
6248 }
6249 if (n == 0) {
6250 dr.state = .finished;
6251 return 0;
6252 }
6253 dr.index = header_end;
6254 dr.end = header_end + n;
6255 }
6256 const entry: *align(1) wasi.dirent_t = @ptrCast(&dr.buffer[dr.index]);
6257 const entry_size = @sizeOf(wasi.dirent_t);
6258 const name_index = dr.index + entry_size;
6259 if (name_index + entry.namlen > dr.end) {
6260 // This case, the name is truncated, so we need to call readdir to store the entire name.
6261 dr.end = dr.index; // Force fd_readdir in the next loop.
6262 continue;
6263 }
6264 const name = dr.buffer[name_index..][0..entry.namlen];
6265 const next_index = name_index + entry.namlen;
6266 dr.index = next_index;
6267 header.cookie = entry.next;
6268
6269 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, ".."))
6270 continue;
6271
6272 const entry_kind: File.Kind = switch (entry.type) {
6273 .BLOCK_DEVICE => .block_device,
6274 .CHARACTER_DEVICE => .character_device,
6275 .DIRECTORY => .directory,
6276 .SYMBOLIC_LINK => .sym_link,
6277 .REGULAR_FILE => .file,
6278 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
6279 else => .unknown,
6280 };
6281 buffer[buffer_index] = .{
6282 .name = name,
6283 .kind = entry_kind,
6284 .inode = entry.ino,
6285 };
6286 buffer_index += 1;
6287 }
6288 return buffer_index;
6289}
6290
6291fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
6292 _ = userdata;
6293 _ = dir_reader;
6294 _ = buffer;
6295 return error.Unexpected;
6296}
6297
6298const dirRealPathFile = switch (native_os) {
6299 .windows => dirRealPathFileWindows,
6300 else => dirRealPathFilePosix,
6301};
6302
6303fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
6304 const t: *Threaded = @ptrCast(@alignCast(userdata));
6305 _ = t;
6306
6307 var path_name_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
6308
6309 const h_file = handle: {
6310 if (OpenFile(path_name_w.span(), .{
6311 .dir = dir.handle,
6312 .access_mask = .{
6313 .GENERIC = .{ .READ = true },
6314 .STANDARD = .{ .SYNCHRONIZE = true },
6315 },
6316 .creation = .OPEN,
6317 .filter = .any,
6318 })) |handle| {
6319 break :handle handle;
6320 } else |err| switch (err) {
6321 error.WouldBlock => unreachable,
6322 else => |e| return e,
6323 }
6324 };
6325 defer windows.CloseHandle(h_file);
6326
6327 // We can re-use the path buffer for the WTF-16 representation since
6328 // we don't need the prefixed path anymore
6329 return realPathWindowsBuf(h_file, out_buffer, &path_name_w.data);
6330}
6331
6332fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
6333 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
6334 return realPathWindowsBuf(h_file, out_buffer, &wide_buf);
6335}
6336
6337fn realPathWindowsBuf(h_file: windows.HANDLE, out_buffer: []u8, wtf16_buffer: []u16) File.RealPathError!usize {
6338 const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, wtf16_buffer);
6339
6340 const len = std.unicode.calcWtf8Len(wide_slice);
6341 if (len > out_buffer.len)
6342 return error.NameTooLong;
6343
6344 return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
6345}
6346
6347/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`.
6348/// Defaults to DOS volume names.
6349pub const GetFinalPathNameByHandleFormat = struct {
6350 volume_name: enum {
6351 /// Format as DOS volume name
6352 Dos,
6353 /// Format as NT volume name
6354 Nt,
6355 } = .Dos,
6356};
6357
6358pub const GetFinalPathNameByHandleError = error{
6359 AccessDenied,
6360 FileNotFound,
6361 NameTooLong,
6362 /// The volume does not contain a recognized file system. File system
6363 /// drivers might not be loaded, or the volume may be corrupt.
6364 UnrecognizedVolume,
6365} || Io.Cancelable || Io.UnexpectedError;
6366
6367/// Returns canonical (normalized) path of handle.
6368/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include
6369/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
6370/// If DOS volume name format is selected, note that this function does *not* prepend
6371/// `\\?\` prefix to the resultant path.
6372pub fn GetFinalPathNameByHandle(
6373 hFile: windows.HANDLE,
6374 fmt: GetFinalPathNameByHandleFormat,
6375 out_buffer: []u16,
6376) GetFinalPathNameByHandleError![]u16 {
6377 const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) {
6378 // we assume InvalidHandle is close enough to FileNotFound in semantics
6379 // to not further complicate the error set
6380 error.InvalidHandle => return error.FileNotFound,
6381 else => |e| return e,
6382 };
6383
6384 switch (fmt.volume_name) {
6385 .Nt => {
6386 // the returned path is already in .Nt format
6387 return final_path;
6388 },
6389 .Dos => {
6390 // parse the string to separate volume path from file path
6391 const device_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\");
6392
6393 // We aren't entirely sure of the structure of the path returned by
6394 // QueryObjectName in all contexts/environments.
6395 // This code is written to cover the various cases that have
6396 // been encountered and solved appropriately. But note that there's
6397 // no easy way to verify that they have all been tackled!
6398 // (Unless you, the reader knows of one then please do action that!)
6399 if (!std.mem.startsWith(u16, final_path, device_prefix)) {
6400 // Wine seems to return NT namespaced paths starting with \??\ from QueryObjectName
6401 // (e.g. `\??\Z:\some\path\to\a\file.txt`), in which case we can just strip the
6402 // prefix to turn it into an absolute path.
6403 // https://github.com/ziglang/zig/issues/26029
6404 // https://bugs.winehq.org/show_bug.cgi?id=39569
6405 return windows.ntToWin32Namespace(final_path, out_buffer) catch |err| switch (err) {
6406 error.NotNtPath => return error.Unexpected,
6407 error.NameTooLong => |e| return e,
6408 };
6409 }
6410
6411 const file_path_begin_index = std.mem.findPos(u16, final_path, device_prefix.len, &[_]u16{'\\'}) orelse unreachable;
6412 const volume_name_u16 = final_path[0..file_path_begin_index];
6413 const device_name_u16 = volume_name_u16[device_prefix.len..];
6414 const file_name_u16 = final_path[file_path_begin_index..];
6415
6416 // MUP is Multiple UNC Provider, and indicates that the path is a UNC
6417 // path. In this case, the canonical UNC path can be gotten by just
6418 // dropping the \Device\Mup\ and making sure the path begins with \\
6419 if (std.mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) {
6420 out_buffer[0] = '\\';
6421 @memmove(out_buffer[1..][0..file_name_u16.len], file_name_u16);
6422 return out_buffer[0 .. 1 + file_name_u16.len];
6423 }
6424
6425 // Get DOS volume name. DOS volume names are actually symbolic link objects to the
6426 // actual NT volume. For example:
6427 // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
6428 const MIN_SIZE = @sizeOf(windows.MOUNTMGR_MOUNT_POINT) + windows.MAX_PATH;
6429 // We initialize the input buffer to all zeros for convenience since
6430 // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this.
6431 var input_buf: [MIN_SIZE]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINT)) = @splat(0);
6432 var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINTS)) = undefined;
6433
6434 // This surprising path is a filesystem path to the mount manager on Windows.
6435 // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
6436 // This is the NT namespaced version of \\.\MountPointManager
6437 const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager");
6438 const mgmt_handle = OpenFile(mgmt_path_u16, .{
6439 .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } },
6440 .creation = .OPEN,
6441 }) catch |err| switch (err) {
6442 error.IsDir => return error.Unexpected,
6443 error.NotDir => return error.Unexpected,
6444 error.NoDevice => return error.Unexpected,
6445 error.AccessDenied => return error.Unexpected,
6446 error.PipeBusy => return error.Unexpected,
6447 error.FileBusy => return error.Unexpected,
6448 error.PathAlreadyExists => return error.Unexpected,
6449 error.WouldBlock => return error.Unexpected,
6450 error.NetworkNotFound => return error.Unexpected,
6451 error.AntivirusInterference => return error.Unexpected,
6452 error.BadPathName => return error.Unexpected,
6453 else => |e| return e,
6454 };
6455 defer windows.CloseHandle(mgmt_handle);
6456
6457 var input_struct: *windows.MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]);
6458 input_struct.DeviceNameOffset = @sizeOf(windows.MOUNTMGR_MOUNT_POINT);
6459 input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2);
6460 @memcpy(input_buf[@sizeOf(windows.MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr)));
6461
6462 switch ((try deviceIoControl(&.{
6463 .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = false } },
6464 .code = windows.IOCTL.MOUNTMGR.QUERY_POINTS,
6465 .in = &input_buf,
6466 .out = &output_buf,
6467 })).u.Status) {
6468 .SUCCESS => {},
6469 .CANCELLED => unreachable,
6470 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
6471 else => |status| return windows.unexpectedStatus(status),
6472 }
6473 const mount_points_struct: *const windows.MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]);
6474
6475 const mount_points = @as(
6476 [*]const windows.MOUNTMGR_MOUNT_POINT,
6477 @ptrCast(&mount_points_struct.MountPoints[0]),
6478 )[0..mount_points_struct.NumberOfMountPoints];
6479
6480 for (mount_points) |mount_point| {
6481 const symlink = @as(
6482 [*]const u16,
6483 @ptrCast(@alignCast(&output_buf[mount_point.SymbolicLinkNameOffset])),
6484 )[0 .. mount_point.SymbolicLinkNameLength / 2];
6485
6486 // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
6487 // with traditional DOS drive letters, so pick the first one available.
6488 var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\");
6489 const prefix = prefix_buf[0..prefix_buf.len];
6490
6491 if (std.mem.startsWith(u16, symlink, prefix)) {
6492 const drive_letter = symlink[prefix.len..];
6493
6494 if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;
6495
6496 @memcpy(out_buffer[0..drive_letter.len], drive_letter);
6497 @memmove(out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16);
6498 const total_len = drive_letter.len + file_name_u16.len;
6499
6500 // Validate that DOS does not contain any spurious nul bytes.
6501 assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null);
6502
6503 return out_buffer[0..total_len];
6504 } else if (mountmgrIsVolumeName(symlink)) {
6505 // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d},
6506 // then it is a volume mounted as a path rather than a drive letter. We need to
6507 // query the mount manager again to get the DOS path for the volume.
6508
6509 // 49 is the maximum length accepted by mountmgrIsVolumeName
6510 const vol_input_size = @sizeOf(windows.MOUNTMGR_TARGET_NAME) + (49 * 2);
6511 var vol_input_buf: [vol_input_size]u8 align(@alignOf(windows.MOUNTMGR_TARGET_NAME)) = @splat(0);
6512 // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path,
6513 // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>).
6514 // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here.
6515 const min_output_size = @sizeOf(windows.MOUNTMGR_VOLUME_PATHS) + (windows.PATH_MAX_WIDE * 2);
6516 var vol_output_buf: [min_output_size]u8 align(@alignOf(windows.MOUNTMGR_VOLUME_PATHS)) = undefined;
6517
6518 var vol_input_struct: *windows.MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]);
6519 vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2);
6520 @memcpy(@as([*]windows.WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink);
6521
6522 switch ((try deviceIoControl(&.{
6523 .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = true } },
6524 .code = windows.IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH,
6525 .in = &vol_input_buf,
6526 .out = &vol_output_buf,
6527 })).u.Status) {
6528 .SUCCESS => {},
6529 .CANCELLED => unreachable,
6530 .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume,
6531 else => |status| return windows.unexpectedStatus(status),
6532 }
6533 const volume_paths_struct: *const windows.MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]);
6534 const volume_path = std.mem.sliceTo(@as(
6535 [*]const u16,
6536 &volume_paths_struct.MultiSz,
6537 )[0 .. volume_paths_struct.MultiSzLength / 2], 0);
6538
6539 if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong;
6540
6541 // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where
6542 // we want to place the filename before returning. Here are the possible overlapping cases:
6543 //
6544 // out_buffer: [filename]
6545 // dest: [___(a)___] [___(b)___]
6546 //
6547 // In the case of (a), we need to copy forwards, and in the case of (b) we need
6548 // to copy backwards. We also need to do this before copying the volume path because
6549 // it could overwrite the file_name_u16 memory.
6550 const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len];
6551 @memmove(file_name_dest, file_name_u16);
6552 @memcpy(out_buffer[0..volume_path.len], volume_path);
6553 const total_len = volume_path.len + file_name_u16.len;
6554
6555 // Validate that DOS does not contain any spurious nul bytes.
6556 assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null);
6557
6558 return out_buffer[0..total_len];
6559 }
6560 }
6561
6562 // If we've ended up here, then something went wrong/is corrupted in the OS,
6563 // so error out!
6564 return error.FileNotFound;
6565 },
6566 }
6567}
6568
6569test GetFinalPathNameByHandle {
6570 if (builtin.os.tag != .windows)
6571 return;
6572
6573 //any file will do
6574 var tmp = std.testing.tmpDir(.{});
6575 defer tmp.cleanup();
6576 const handle = tmp.dir.handle;
6577 var buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
6578
6579 //check with sufficient size
6580 const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer);
6581 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer);
6582
6583 const required_len_in_u16 = nt_path.len + @divExact(@intFromPtr(nt_path.ptr) - @intFromPtr(&buffer), 2) + 1;
6584 //check with insufficient size
6585 try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1]));
6586 try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1]));
6587
6588 //check with exactly-sufficient size
6589 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]);
6590 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]);
6591}
6592
6593/// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h
6594fn mountmgrIsVolumeName(name: []const u16) bool {
6595 return (name.len == 48 or (name.len == 49 and name[48] == std.mem.nativeToLittle(u16, '\\'))) and
6596 name[0] == std.mem.nativeToLittle(u16, '\\') and
6597 (name[1] == std.mem.nativeToLittle(u16, '?') or name[1] == std.mem.nativeToLittle(u16, '\\')) and
6598 name[2] == std.mem.nativeToLittle(u16, '?') and
6599 name[3] == std.mem.nativeToLittle(u16, '\\') and
6600 std.mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and
6601 name[19] == std.mem.nativeToLittle(u16, '-') and
6602 name[24] == std.mem.nativeToLittle(u16, '-') and
6603 name[29] == std.mem.nativeToLittle(u16, '-') and
6604 name[34] == std.mem.nativeToLittle(u16, '-') and
6605 name[47] == std.mem.nativeToLittle(u16, '}');
6606}
6607
6608test mountmgrIsVolumeName {
6609 @setEvalBranchQuota(2000);
6610 const L = std.unicode.utf8ToUtf16LeStringLiteral;
6611 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
6612 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
6613 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\")));
6614 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\")));
6615 try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}")));
6616 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo")));
6617 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}")));
6618}
6619
6620pub const QueryObjectNameError = error{
6621 AccessDenied,
6622 InvalidHandle,
6623 NameTooLong,
6624 Unexpected,
6625};
6626
6627pub fn QueryObjectName(handle: windows.HANDLE, out_buffer: []u16) QueryObjectNameError![]u16 {
6628 const out_buffer_aligned = std.mem.alignInSlice(out_buffer, @alignOf(windows.OBJECT.NAME_INFORMATION)) orelse return error.NameTooLong;
6629
6630 const info: *windows.OBJECT.NAME_INFORMATION = @ptrCast(out_buffer_aligned);
6631 // buffer size is specified in bytes
6632 const out_buffer_len = std.math.cast(windows.ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(windows.ULONG);
6633 // last argument would return the length required for full_buffer, not exposed here
6634 return switch (windows.ntdll.NtQueryObject(handle, .Name, info, out_buffer_len, null)) {
6635 .SUCCESS => {
6636 // info.Name from ObQueryNameString is documented to be empty if the object
6637 // was "unnamed", not sure if this can happen for file handles
6638 return if (info.Name.isEmpty()) error.Unexpected else info.Name.slice();
6639 },
6640 .ACCESS_DENIED => error.AccessDenied,
6641 .INVALID_HANDLE => error.InvalidHandle,
6642 // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH),
6643 // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL)
6644 .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong,
6645 else => |e| windows.unexpectedStatus(e),
6646 };
6647}
6648
6649test QueryObjectName {
6650 if (builtin.os.tag != .windows)
6651 return;
6652
6653 //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.
6654 var tmp = std.testing.tmpDir(.{});
6655 defer tmp.cleanup();
6656 const handle = tmp.dir.handle;
6657 var out_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
6658
6659 const result_path = try QueryObjectName(handle, &out_buffer);
6660 const required_len_in_u16 = result_path.len + @divExact(@intFromPtr(result_path.ptr) - @intFromPtr(&out_buffer), 2) + 1;
6661 //insufficient size
6662 try std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1]));
6663 //exactly-sufficient size
6664 _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]);
6665}
6666
6667const Wtf16ToPrefixedFileWError = error{
6668 AccessDenied,
6669 FileNotFound,
6670} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
6671
6672const Wtf16ToPrefixedFileWOptions = struct {
6673 allow_relative: bool = true,
6674};
6675
6676/// Converts the `path` to WTF16, null-terminated. If the path contains any
6677/// namespace prefix, or is anything but a relative path (rooted, drive relative,
6678/// etc) the result will have the NT-style prefix `\??\`.
6679///
6680/// Similar to RtlDosPathNameToNtPathName_U with a few differences:
6681/// - Does not allocate on the heap.
6682/// - Relative paths are kept as relative unless they contain too many ..
6683/// components, in which case they are resolved against the `dir` if it
6684/// is non-null, or the CWD if it is null.
6685/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
6686/// - . and space are not stripped from the end of relative paths (potential TODO)
6687pub fn wToPrefixedFileW(dir: ?windows.HANDLE, path: [:0]const u16, options: Wtf16ToPrefixedFileWOptions) Wtf16ToPrefixedFileWError!WindowsPathSpace {
6688 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
6689 if (windows.hasCommonNtPrefix(u16, path)) {
6690 // TODO: Figure out a way to design an API that can avoid the copy for NT,
6691 // since it is always returned fully unmodified.
6692 var path_space: WindowsPathSpace = undefined;
6693 path_space.data[0..nt_prefix.len].* = nt_prefix;
6694 const len_after_prefix = path.len - nt_prefix.len;
6695 @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]);
6696 path_space.len = path.len;
6697 path_space.data[path_space.len] = 0;
6698 return path_space;
6699 } else {
6700 const path_type = Dir.path.getWin32PathType(u16, path);
6701 var path_space: WindowsPathSpace = undefined;
6702 if (path_type == .local_device) switch (getLocalDevicePathType(u16, path)) {
6703 .verbatim => {
6704 path_space.data[0..nt_prefix.len].* = nt_prefix;
6705 const len_after_prefix = path.len - nt_prefix.len;
6706 @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]);
6707 path_space.len = path.len;
6708 path_space.data[path_space.len] = 0;
6709 return path_space;
6710 },
6711 .local_device, .fake_verbatim => {
6712 const path_byte_len = windows.ntdll.RtlGetFullPathName_U(
6713 path.ptr,
6714 path_space.data.len * 2,
6715 &path_space.data,
6716 null,
6717 );
6718 if (path_byte_len == 0) {
6719 // TODO: This may not be the right error
6720 return error.BadPathName;
6721 } else if (path_byte_len / 2 > path_space.data.len) {
6722 return error.NameTooLong;
6723 }
6724 path_space.len = path_byte_len / 2;
6725 // Both prefixes will be normalized but retained, so all
6726 // we need to do now is replace them with the NT prefix
6727 path_space.data[0..nt_prefix.len].* = nt_prefix;
6728 return path_space;
6729 },
6730 };
6731 if (options.allow_relative and path_type == .relative) relative: {
6732 // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
6733 // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
6734
6735 // TODO: Potentially strip all trailing . and space characters from the
6736 // end of the path. This is something that both RtlDosPathNameToNtPathName_U
6737 // and RtlGetFullPathName_U do. Technically, trailing . and spaces
6738 // are allowed, but such paths may not interact well with Windows (i.e.
6739 // files with these paths can't be deleted from explorer.exe, etc).
6740 // This could be something that normalizePath may want to do.
6741
6742 @memcpy(path_space.data[0..path.len], path);
6743 // Try to normalize, but if we get too many parent directories,
6744 // then we need to start over and use RtlGetFullPathName_U instead.
6745 path_space.len = windows.normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) {
6746 error.TooManyParentDirs => break :relative,
6747 };
6748 path_space.data[path_space.len] = 0;
6749 return path_space;
6750 }
6751 // We now know we are going to return an absolute NT path, so
6752 // we can unconditionally prefix it with the NT prefix.
6753 path_space.data[0..nt_prefix.len].* = nt_prefix;
6754 if (path_type == .root_local_device) {
6755 // `\\.` and `\\?` always get converted to `\??\` exactly, so
6756 // we can just stop here
6757 path_space.len = nt_prefix.len;
6758 path_space.data[path_space.len] = 0;
6759 return path_space;
6760 }
6761 const path_buf_offset = switch (path_type) {
6762 // UNC paths will always start with `\\`. However, we want to
6763 // end up with something like `\??\UNC\server\share`, so to get
6764 // RtlGetFullPathName to write into the spot we want the `server`
6765 // part to end up, we need to provide an offset such that
6766 // the `\\` part gets written where the `C\` of `UNC\` will be
6767 // in the final NT path.
6768 .unc_absolute => nt_prefix.len + 2,
6769 else => nt_prefix.len,
6770 };
6771 const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset);
6772 const path_to_get: [:0]const u16 = path_to_get: {
6773 // If dir is null, then we don't need to bother with GetFinalPathNameByHandle because
6774 // RtlGetFullPathName_U will resolve relative paths against the CWD for us.
6775 if (path_type != .relative or dir == null) {
6776 break :path_to_get path;
6777 }
6778 // We can also skip GetFinalPathNameByHandle if the handle matches
6779 // the handle returned by Io.Dir.cwd()
6780 if (dir.? == Io.Dir.cwd().handle) {
6781 break :path_to_get path;
6782 }
6783 // At this point, we know we have a relative path that had too many
6784 // `..` components to be resolved by normalizePath, so we need to
6785 // convert it into an absolute path and let RtlGetFullPathName_U
6786 // canonicalize it. We do this by getting the path of the `dir`
6787 // and appending the relative path to it.
6788 var dir_path_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;
6789 const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) {
6790 // This mapping is not correct; it is actually expected
6791 // that calling GetFinalPathNameByHandle might return
6792 // error.UnrecognizedVolume, and in fact has been observed
6793 // in the wild. The problem is that wToPrefixedFileW was
6794 // never intended to make *any* OS syscall APIs. It's only
6795 // supposed to convert a string to one that is eligible to
6796 // be used in the ntdll syscalls.
6797 //
6798 // To solve this, this function needs to no longer call
6799 // GetFinalPathNameByHandle under any conditions, or the
6800 // calling function needs to get reworked to not need to
6801 // call this function.
6802 //
6803 // This may involve making breaking API changes.
6804 error.UnrecognizedVolume => return error.Unexpected,
6805 else => |e| return e,
6806 };
6807 if (dir_path.len + 1 + path.len > windows.PATH_MAX_WIDE) {
6808 return error.NameTooLong;
6809 }
6810 // We don't have to worry about potentially doubling up path separators
6811 // here since RtlGetFullPathName_U will handle canonicalizing it.
6812 dir_path_buf[dir_path.len] = '\\';
6813 @memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path);
6814 const full_len = dir_path.len + 1 + path.len;
6815 dir_path_buf[full_len] = 0;
6816 break :path_to_get dir_path_buf[0..full_len :0];
6817 };
6818 const path_byte_len = windows.ntdll.RtlGetFullPathName_U(
6819 path_to_get.ptr,
6820 buf_len * 2,
6821 path_space.data[path_buf_offset..].ptr,
6822 null,
6823 );
6824 if (path_byte_len == 0) {
6825 // TODO: This may not be the right error
6826 return error.BadPathName;
6827 } else if (path_byte_len / 2 > buf_len) {
6828 return error.NameTooLong;
6829 }
6830 path_space.len = path_buf_offset + (path_byte_len / 2);
6831 if (path_type == .unc_absolute) {
6832 // Now add in the UNC, the `C` should overwrite the first `\` of the
6833 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
6834 assert(path_space.data[path_buf_offset] == '\\');
6835 assert(path_space.data[path_buf_offset + 1] == '\\');
6836 const unc = [_]u16{ 'U', 'N', 'C' };
6837 path_space.data[nt_prefix.len..][0..unc.len].* = unc;
6838 }
6839 return path_space;
6840 }
6841}
6842
6843const LocalDevicePathType = enum {
6844 /// `\\.\` (path separators can be `\` or `/`)
6845 local_device,
6846 /// `\\?\`
6847 /// When converted to an NT path, everything past the prefix is left
6848 /// untouched and `\\?\` is replaced by `\??\`.
6849 verbatim,
6850 /// `\\?\` without all path separators being `\`.
6851 /// This seems to be recognized as a prefix, but the 'verbatim' aspect
6852 /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path,
6853 /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't
6854 /// be treated as part of the final path])
6855 fake_verbatim,
6856};
6857
6858/// Only relevant for Win32 -> NT path conversion.
6859/// Asserts `path` is of type `Dir.path.Win32PathType.local_device`.
6860fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
6861 if (std.debug.runtime_safety) {
6862 assert(Dir.path.getWin32PathType(T, path) == .local_device);
6863 }
6864
6865 const backslash = std.mem.nativeToLittle(T, '\\');
6866 const all_backslash = path[0] == backslash and
6867 path[1] == backslash and
6868 path[3] == backslash;
6869 return switch (path[2]) {
6870 std.mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim,
6871 std.mem.nativeToLittle(T, '.') => .local_device,
6872 else => unreachable,
6873 };
6874}
6875
6876pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError;
6877
6878/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path.
6879/// https://wtf-8.codeberg.page/
6880pub fn sliceToPrefixedFileW(dir: ?windows.HANDLE, path: []const u8, options: Wtf16ToPrefixedFileWOptions) Wtf8ToPrefixedFileWError!WindowsPathSpace {
6881 var temp_path: WindowsPathSpace = undefined;
6882 temp_path.len = std.unicode.wtf8ToWtf16Le(&temp_path.data, path) catch |err| switch (err) {
6883 error.InvalidWtf8 => return error.BadPathName,
6884 };
6885 temp_path.data[temp_path.len] = 0;
6886 return wToPrefixedFileW(dir, temp_path.span(), options);
6887}
6888
6889pub const WindowsPathSpace = struct {
6890 data: [windows.PATH_MAX_WIDE:0]u16,
6891 len: usize,
6892
6893 pub fn span(wps: *const WindowsPathSpace) [:0]const u16 {
6894 return wps.data[0..wps.len :0];
6895 }
6896
6897 pub fn string(wps: *const WindowsPathSpace) windows.UNICODE_STRING {
6898 return .init(wps.span());
6899 }
6900};
6901
6902fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
6903 if (native_os == .wasi) return error.OperationUnsupported;
6904
6905 const t: *Threaded = @ptrCast(@alignCast(userdata));
6906 _ = t;
6907
6908 var path_buffer: [posix.PATH_MAX]u8 = undefined;
6909 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
6910
6911 if (builtin.link_libc and dir.handle == posix.AT.FDCWD) {
6912 if (out_buffer.len < posix.PATH_MAX) return error.NameTooLong;
6913 const syscall: Syscall = try .start();
6914 while (true) {
6915 if (std.c.realpath(sub_path_posix, out_buffer.ptr)) |redundant_pointer| {
6916 syscall.finish();
6917 assert(redundant_pointer == out_buffer.ptr);
6918 return std.mem.findScalar(u8, out_buffer, 0) orelse out_buffer.len;
6919 }
6920 const err: posix.E = @fromBackingInt(@intCast(std.c._errno().*));
6921 if (err == .INTR) {
6922 try syscall.checkCancel();
6923 continue;
6924 }
6925 syscall.finish();
6926 switch (err) {
6927 .INVAL => return errnoBug(err),
6928 .BADF => return errnoBug(err),
6929 .FAULT => return errnoBug(err),
6930 .ACCES => return error.AccessDenied,
6931 .NOENT => return error.FileNotFound,
6932 .OPNOTSUPP => return error.OperationUnsupported,
6933 .NOTDIR => return error.NotDir,
6934 .NAMETOOLONG => return error.NameTooLong,
6935 .LOOP => return error.SymLinkLoop,
6936 .IO => return error.InputOutput,
6937 else => return posix.unexpectedErrno(err),
6938 }
6939 }
6940 }
6941
6942 var flags: posix.O = .{};
6943 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
6944 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
6945 if (@hasField(posix.O, "PATH")) flags.PATH = true;
6946
6947 const mode: posix.mode_t = 0;
6948
6949 const syscall: Syscall = try .start();
6950 const fd: posix.fd_t = while (true) {
6951 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
6952 switch (posix.errno(rc)) {
6953 .SUCCESS => {
6954 syscall.finish();
6955 break @intCast(rc);
6956 },
6957 .INTR => {
6958 try syscall.checkCancel();
6959 continue;
6960 },
6961 else => |e| {
6962 syscall.finish();
6963 switch (e) {
6964 .FAULT => |err| return errnoBug(err),
6965 .INVAL => return error.BadPathName,
6966 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
6967 .ACCES => return error.AccessDenied,
6968 .FBIG => return error.FileTooBig,
6969 .OVERFLOW => return error.FileTooBig,
6970 .ISDIR => return error.IsDir,
6971 .LOOP => return error.SymLinkLoop,
6972 .MFILE => return error.ProcessFdQuotaExceeded,
6973 .NAMETOOLONG => return error.NameTooLong,
6974 .NFILE => return error.SystemFdQuotaExceeded,
6975 .NODEV => return error.NoDevice,
6976 .NOENT => return error.FileNotFound,
6977 .SRCH => return error.FileNotFound, // Linux when accessing procfs.
6978 .NOMEM => return error.SystemResources,
6979 .NOSPC => return error.NoSpaceLeft,
6980 .NOTDIR => return error.NotDir,
6981 .PERM => return error.PermissionDenied,
6982 .EXIST => return error.PathAlreadyExists,
6983 .BUSY => return error.DeviceBusy,
6984 .NXIO => return error.NoDevice,
6985 .ILSEQ => return error.BadPathName,
6986 else => |err| return posix.unexpectedErrno(err),
6987 }
6988 },
6989 }
6990 };
6991 defer closeFd(fd);
6992 return realPathPosix(fd, out_buffer);
6993}
6994
6995const dirRealPath = switch (native_os) {
6996 .windows => dirRealPathWindows,
6997 else => dirRealPathPosix,
6998};
6999
7000fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
7001 if (native_os == .wasi) return error.OperationUnsupported;
7002 const t: *Threaded = @ptrCast(@alignCast(userdata));
7003 _ = t;
7004 return realPathPosix(dir.handle, out_buffer);
7005}
7006
7007fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
7008 const t: *Threaded = @ptrCast(@alignCast(userdata));
7009 _ = t;
7010 return realPathWindows(dir.handle, out_buffer);
7011}
7012
7013const fileRealPath = switch (native_os) {
7014 .windows => fileRealPathWindows,
7015 else => fileRealPathPosix,
7016};
7017
7018fn fileRealPathWindows(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
7019 if (native_os == .wasi) return error.OperationUnsupported;
7020 const t: *Threaded = @ptrCast(@alignCast(userdata));
7021 _ = t;
7022 return realPathWindows(file.handle, out_buffer);
7023}
7024
7025fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
7026 if (native_os == .wasi) return error.OperationUnsupported;
7027 const t: *Threaded = @ptrCast(@alignCast(userdata));
7028 _ = t;
7029 return realPathPosix(file.handle, out_buffer);
7030}
7031
7032fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
7033 switch (native_os) {
7034 .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
7035 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;
7036 @memset(&sufficient_buffer, 0);
7037 const syscall: Syscall = try .start();
7038 while (true) {
7039 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) {
7040 .SUCCESS => {
7041 syscall.finish();
7042 break;
7043 },
7044 .INTR => {
7045 try syscall.checkCancel();
7046 continue;
7047 },
7048 else => |e| {
7049 syscall.finish();
7050 switch (e) {
7051 .ACCES => return error.AccessDenied,
7052 .BADF => return error.FileNotFound,
7053 .NOENT => return error.FileNotFound,
7054 .NOMEM => return error.SystemResources,
7055 .NOSPC => return error.NameTooLong,
7056 .RANGE => return error.NameTooLong,
7057 else => |err| return posix.unexpectedErrno(err),
7058 }
7059 },
7060 }
7061 }
7062 const n = std.mem.findScalar(u8, &sufficient_buffer, 0) orelse sufficient_buffer.len;
7063 if (n > out_buffer.len) return error.NameTooLong;
7064 @memcpy(out_buffer[0..n], sufficient_buffer[0..n]);
7065 return n;
7066 },
7067 .linux, .serenity, .illumos => {
7068 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
7069 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
7070 const proc_path = std.mem.printSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable;
7071 const syscall: Syscall = try .start();
7072 while (true) {
7073 const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len);
7074 switch (posix.errno(rc)) {
7075 .SUCCESS => {
7076 syscall.finish();
7077 const len: usize = @bitCast(rc);
7078 return len;
7079 },
7080 .INTR => {
7081 try syscall.checkCancel();
7082 continue;
7083 },
7084 else => |e| {
7085 syscall.finish();
7086 switch (e) {
7087 .ACCES => return error.AccessDenied,
7088 .FAULT => |err| return errnoBug(err),
7089 .IO => return error.FileSystem,
7090 .LOOP => return error.SymLinkLoop,
7091 .NAMETOOLONG => return error.NameTooLong,
7092 .NOENT => return error.FileNotFound,
7093 .NOMEM => return error.SystemResources,
7094 .NOTDIR => return error.NotDir,
7095 .ILSEQ => |err| return errnoBug(err),
7096 else => |err| return posix.unexpectedErrno(err),
7097 }
7098 },
7099 }
7100 }
7101 },
7102 .freebsd => {
7103 var k_file: std.c.kinfo_file = undefined;
7104 k_file.structsize = std.c.KINFO_FILE_SIZE;
7105 const syscall: Syscall = try .start();
7106 while (true) {
7107 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&k_file)))) {
7108 .SUCCESS => {
7109 syscall.finish();
7110 break;
7111 },
7112 .INTR => {
7113 try syscall.checkCancel();
7114 continue;
7115 },
7116 .BADF => {
7117 syscall.finish();
7118 return error.FileNotFound;
7119 },
7120 else => |err| {
7121 syscall.finish();
7122 return posix.unexpectedErrno(err);
7123 },
7124 }
7125 }
7126 const len = std.mem.findScalar(u8, &k_file.path, 0) orelse k_file.path.len;
7127 if (len == 0) return error.NameTooLong;
7128 @memcpy(out_buffer[0..len], k_file.path[0..len]);
7129 return len;
7130 },
7131 else => return error.OperationUnsupported,
7132 }
7133 comptime unreachable;
7134}
7135
7136fn fileHardLink(
7137 userdata: ?*anyopaque,
7138 file: File,
7139 new_dir: Dir,
7140 new_sub_path: []const u8,
7141 options: File.HardLinkOptions,
7142) File.HardLinkError!void {
7143 _ = userdata;
7144 if (native_os != .linux) return error.OperationUnsupported;
7145
7146 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
7147 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
7148
7149 const flags: u32 = if (options.follow_symlinks)
7150 posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH
7151 else
7152 posix.AT.EMPTY_PATH;
7153
7154 return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) {
7155 error.FileNotFound => {
7156 if (options.follow_symlinks) return error.FileNotFound;
7157 var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
7158 const proc_path = std.mem.printSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch
7159 unreachable;
7160 return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW);
7161 },
7162 else => |e| return e,
7163 };
7164}
7165
7166fn linkat(
7167 old_dir: posix.fd_t,
7168 old_path: [*:0]const u8,
7169 new_dir: posix.fd_t,
7170 new_path: [*:0]const u8,
7171 flags: u32,
7172) File.HardLinkError!void {
7173 const syscall: Syscall = try .start();
7174 while (true) {
7175 switch (posix.errno(posix.system.linkat(old_dir, old_path, new_dir, new_path, flags))) {
7176 .SUCCESS => return syscall.finish(),
7177 .INTR => {
7178 try syscall.checkCancel();
7179 continue;
7180 },
7181 .ACCES => return syscall.fail(error.AccessDenied),
7182 .DQUOT => return syscall.fail(error.DiskQuota),
7183 .EXIST => return syscall.fail(error.PathAlreadyExists),
7184 .IO => return syscall.fail(error.HardwareFailure),
7185 .LOOP => return syscall.fail(error.SymLinkLoop),
7186 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
7187 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
7188 .NOENT => return syscall.fail(error.FileNotFound),
7189 .NOMEM => return syscall.fail(error.SystemResources),
7190 .NOSPC => return syscall.fail(error.NoSpaceLeft),
7191 .NOTDIR => return syscall.fail(error.NotDir),
7192 .PERM => return syscall.fail(error.PermissionDenied),
7193 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
7194 .XDEV => return syscall.fail(error.CrossDevice),
7195 .ILSEQ => return syscall.fail(error.BadPathName),
7196 .FAULT => |err| return syscall.errnoBug(err),
7197 .INVAL => |err| return syscall.errnoBug(err),
7198 else => |err| return syscall.unexpectedErrno(err),
7199 }
7200 }
7201}
7202
7203const dirDeleteFile = switch (native_os) {
7204 .windows => dirDeleteFileWindows,
7205 .wasi => dirDeleteFileWasi,
7206 else => dirDeleteFilePosix,
7207};
7208
7209fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
7210 return dirDeleteWindows(userdata, dir, sub_path, false) catch |err| switch (err) {
7211 error.DirNotEmpty => unreachable,
7212 else => |e| return e,
7213 };
7214}
7215
7216fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
7217 if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path);
7218 const t: *Threaded = @ptrCast(@alignCast(userdata));
7219 _ = t;
7220 const wasi = std.os.wasi;
7221 const syscall: Syscall = try .start();
7222 while (true) {
7223 const res = wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len);
7224 switch (res) {
7225 .SUCCESS => {
7226 syscall.finish();
7227 return;
7228 },
7229 .INTR => {
7230 try syscall.checkCancel();
7231 continue;
7232 },
7233 .ACCES, .PERM => |e| {
7234 const original_error: Dir.DeleteFileError = switch (e) {
7235 .ACCES => error.AccessDenied,
7236 .PERM => error.PermissionDenied,
7237 else => unreachable,
7238 };
7239 var stat: wasi.filestat_t = undefined;
7240 while (true) {
7241 try syscall.checkCancel();
7242 switch (wasi.path_filestat_get(dir.handle, .{}, sub_path.ptr, sub_path.len, &stat)) {
7243 .SUCCESS => {
7244 syscall.finish();
7245 break;
7246 },
7247 .INTR => continue,
7248 else => {
7249 syscall.finish();
7250 return original_error;
7251 },
7252 }
7253 }
7254 if (stat.filetype == .DIRECTORY)
7255 return error.IsDir
7256 else
7257 return original_error;
7258 },
7259 else => |e| {
7260 syscall.finish();
7261 switch (e) {
7262 .BUSY => return error.FileBusy,
7263 .FAULT => |err| return errnoBug(err),
7264 .IO => return error.FileSystem,
7265 .ISDIR => return error.IsDir,
7266 .LOOP => return error.SymLinkLoop,
7267 .NAMETOOLONG => return error.NameTooLong,
7268 .NOENT => return error.FileNotFound,
7269 .NOTDIR => return error.NotDir,
7270 .NOMEM => return error.SystemResources,
7271 .ROFS => return error.ReadOnlyFileSystem,
7272 .NOTCAPABLE => return error.AccessDenied,
7273 .ILSEQ => return error.BadPathName,
7274 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
7275 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7276 else => |err| return posix.unexpectedErrno(err),
7277 }
7278 },
7279 }
7280 }
7281}
7282
7283fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
7284 const t: *Threaded = @ptrCast(@alignCast(userdata));
7285 _ = t;
7286
7287 var path_buffer: [posix.PATH_MAX]u8 = undefined;
7288 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
7289
7290 const syscall: Syscall = try .start();
7291 while (true) {
7292 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) {
7293 .SUCCESS => {
7294 syscall.finish();
7295 return;
7296 },
7297 .INTR => {
7298 try syscall.checkCancel();
7299 continue;
7300 },
7301 // Some systems return permission errors when trying to delete a
7302 // directory, so we need to handle that case specifically and
7303 // translate the error.
7304 .PERM => switch (native_os) {
7305 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
7306
7307 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them).
7308 var st = std.mem.zeroes(posix.Stat);
7309 while (true) {
7310 try syscall.checkCancel();
7311 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) {
7312 .SUCCESS => {
7313 syscall.finish();
7314 break;
7315 },
7316 .INTR => continue,
7317 else => {
7318 syscall.finish();
7319 return error.PermissionDenied;
7320 },
7321 }
7322 }
7323 const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR;
7324 if (is_dir)
7325 return error.IsDir
7326 else
7327 return error.PermissionDenied;
7328 },
7329 else => {
7330 syscall.finish();
7331 return error.PermissionDenied;
7332 },
7333 },
7334 else => |e| {
7335 syscall.finish();
7336 switch (e) {
7337 .ACCES => return error.AccessDenied,
7338 .BUSY => return error.FileBusy,
7339 .FAULT => |err| return errnoBug(err),
7340 .IO => return error.FileSystem,
7341 .ISDIR => return error.IsDir,
7342 .LOOP => return error.SymLinkLoop,
7343 .NAMETOOLONG => return error.NameTooLong,
7344 .NOENT => return error.FileNotFound,
7345 .NOTDIR => return error.NotDir,
7346 .NOMEM => return error.SystemResources,
7347 .ROFS => return error.ReadOnlyFileSystem,
7348 .EXIST => |err| return errnoBug(err),
7349 .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR
7350 .ILSEQ => return error.BadPathName,
7351 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
7352 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7353 else => |err| return posix.unexpectedErrno(err),
7354 }
7355 },
7356 }
7357 }
7358}
7359
7360const dirDeleteDir = switch (native_os) {
7361 .windows => dirDeleteDirWindows,
7362 .wasi => dirDeleteDirWasi,
7363 else => dirDeleteDirPosix,
7364};
7365
7366fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
7367 return dirDeleteWindows(userdata, dir, sub_path, true) catch |err| switch (err) {
7368 error.IsDir => unreachable,
7369 else => |e| return e,
7370 };
7371}
7372
7373fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) (Dir.DeleteDirError || Dir.DeleteFileError)!void {
7374 const t: *Threaded = @ptrCast(@alignCast(userdata));
7375 _ = t;
7376 const w = windows;
7377
7378 if (std.mem.eql(u8, sub_path, "..")) {
7379 // Can't remove the parent directory with an open handle.
7380 return error.FileBusy;
7381 }
7382 var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
7383 if (std.mem.eql(u8, sub_path, ".")) {
7384 // Windows does not recognize this, but it does work with empty string.
7385 sub_path_w.len = 0;
7386 }
7387 const attr: w.OBJECT.ATTRIBUTES = .{
7388 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle,
7389 .ObjectName = @constCast(&sub_path_w.string()),
7390 };
7391
7392 var io_status_block: w.IO_STATUS_BLOCK = undefined;
7393 var tmp_handle: w.HANDLE = undefined;
7394 {
7395 const syscall: Syscall = try .start();
7396 while (true) switch (w.ntdll.NtCreateFile(
7397 &tmp_handle,
7398 .{ .STANDARD = .{
7399 .RIGHTS = .{ .DELETE = true },
7400 .SYNCHRONIZE = true,
7401 } },
7402 &attr,
7403 &io_status_block,
7404 null,
7405 .{},
7406 .VALID_FLAGS,
7407 .OPEN,
7408 .{
7409 .DIRECTORY_FILE = remove_dir,
7410 .IO = .SYNCHRONOUS_NONALERT,
7411 .NON_DIRECTORY_FILE = !remove_dir,
7412 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
7413 },
7414 null,
7415 0,
7416 )) {
7417 .SUCCESS => break syscall.finish(),
7418 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err),
7419 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
7420 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
7421 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
7422 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
7423 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
7424 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
7425 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
7426 .SHARING_VIOLATION => return syscall.fail(error.FileBusy),
7427 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
7428 .DELETE_PENDING => return syscall.finish(),
7429 else => |rc| return syscall.unexpectedNtstatus(rc),
7430 };
7431 }
7432 defer w.CloseHandle(tmp_handle);
7433
7434 // FileDispositionInformationEx has varying levels of support:
7435 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
7436 // (INVALID_INFO_CLASS is returned if not supported)
7437 // - Requires the NTFS filesystem
7438 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
7439 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
7440 // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
7441 // (NOT_SUPPORTED is returned if a flag is unsupported)
7442 //
7443 // The strategy here is just to try using FileDispositionInformationEx and fall back to
7444 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
7445 const rc = rc: {
7446 // Deletion with posix semantics if the filesystem supports it.
7447 var info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{
7448 .DELETE = true,
7449 .POSIX_SEMANTICS = true,
7450 .IGNORE_READONLY_ATTRIBUTE = true,
7451 } };
7452
7453 const syscall: Syscall = try .start();
7454 while (true) switch (w.ntdll.NtSetInformationFile(
7455 tmp_handle,
7456 &io_status_block,
7457 &info,
7458 @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX),
7459 .DispositionEx,
7460 )) {
7461 .CANCELLED => {
7462 try syscall.checkCancel();
7463 continue;
7464 },
7465 // The filesystem does not support FileDispositionInformationEx
7466 .INVALID_PARAMETER,
7467 // The operating system does not support FileDispositionInformationEx
7468 .INVALID_INFO_CLASS,
7469 // The operating system does not support one of the flags
7470 .NOT_SUPPORTED,
7471 => break, // use fallback path below; `syscall` still active
7472
7473 // For all other statuses, fall down to the switch below to handle them.
7474 else => |rc| {
7475 syscall.finish();
7476 break :rc rc;
7477 },
7478 };
7479
7480 // Deletion with file pending semantics, which requires waiting or moving
7481 // files to get them removed (from here).
7482 var file_dispo: w.FILE.DISPOSITION.INFORMATION = .{ .DeleteFile = .TRUE };
7483
7484 while (true) switch (w.ntdll.NtSetInformationFile(
7485 tmp_handle,
7486 &io_status_block,
7487 &file_dispo,
7488 @sizeOf(w.FILE.DISPOSITION.INFORMATION),
7489 .Disposition,
7490 )) {
7491 .CANCELLED => {
7492 try syscall.checkCancel();
7493 continue;
7494 },
7495 else => |rc| {
7496 syscall.finish();
7497 break :rc rc;
7498 },
7499 };
7500 };
7501 switch (rc) {
7502 .SUCCESS => {},
7503 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
7504 .INVALID_PARAMETER => |err| return w.statusBug(err),
7505 .CANNOT_DELETE => return error.AccessDenied,
7506 .MEDIA_WRITE_PROTECTED => return error.AccessDenied,
7507 .ACCESS_DENIED => return error.AccessDenied,
7508 else => return w.unexpectedStatus(rc),
7509 }
7510}
7511
7512fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
7513 if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path);
7514
7515 const t: *Threaded = @ptrCast(@alignCast(userdata));
7516 _ = t;
7517
7518 const syscall: Syscall = try .start();
7519 while (true) {
7520 const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len);
7521 switch (res) {
7522 .SUCCESS => {
7523 syscall.finish();
7524 return;
7525 },
7526 .INTR => {
7527 try syscall.checkCancel();
7528 continue;
7529 },
7530 else => |e| {
7531 syscall.finish();
7532 switch (e) {
7533 .ACCES => return error.AccessDenied,
7534 .PERM => return error.PermissionDenied,
7535 .BUSY => return error.FileBusy,
7536 .FAULT => |err| return errnoBug(err),
7537 .IO => return error.FileSystem,
7538 .LOOP => return error.SymLinkLoop,
7539 .NAMETOOLONG => return error.NameTooLong,
7540 .NOENT => return error.FileNotFound,
7541 .NOTDIR => return error.NotDir,
7542 .NOMEM => return error.SystemResources,
7543 .ROFS => return error.ReadOnlyFileSystem,
7544 .NOTEMPTY => return error.DirNotEmpty,
7545 .NOTCAPABLE => return error.AccessDenied,
7546 .ILSEQ => return error.BadPathName,
7547 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
7548 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7549 else => |err| return posix.unexpectedErrno(err),
7550 }
7551 },
7552 }
7553 }
7554}
7555
7556fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
7557 const t: *Threaded = @ptrCast(@alignCast(userdata));
7558 _ = t;
7559
7560 var path_buffer: [posix.PATH_MAX]u8 = undefined;
7561 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
7562
7563 const syscall: Syscall = try .start();
7564 while (true) {
7565 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) {
7566 .SUCCESS => {
7567 syscall.finish();
7568 return;
7569 },
7570 .INTR => {
7571 try syscall.checkCancel();
7572 continue;
7573 },
7574 else => |e| {
7575 syscall.finish();
7576 switch (e) {
7577 .ACCES => return error.AccessDenied,
7578 .PERM => return error.PermissionDenied,
7579 .BUSY => return error.FileBusy,
7580 .FAULT => |err| return errnoBug(err),
7581 .IO => return error.FileSystem,
7582 .ISDIR => |err| return errnoBug(err),
7583 .LOOP => return error.SymLinkLoop,
7584 .NAMETOOLONG => return error.NameTooLong,
7585 .NOENT => return error.FileNotFound,
7586 .NOTDIR => return error.NotDir,
7587 .NOMEM => return error.SystemResources,
7588 .ROFS => return error.ReadOnlyFileSystem,
7589 .EXIST => |err| return errnoBug(err),
7590 .NOTEMPTY => return error.DirNotEmpty,
7591 .ILSEQ => return error.BadPathName,
7592 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
7593 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7594 else => |err| return posix.unexpectedErrno(err),
7595 }
7596 },
7597 }
7598 }
7599}
7600
7601const dirRename = switch (native_os) {
7602 .windows => dirRenameWindows,
7603 .wasi => dirRenameWasi,
7604 else => dirRenamePosix,
7605};
7606
7607fn dirRenameWindows(
7608 userdata: ?*anyopaque,
7609 old_dir: Dir,
7610 old_sub_path: []const u8,
7611 new_dir: Dir,
7612 new_sub_path: []const u8,
7613) Dir.RenameError!void {
7614 const t: *Threaded = @ptrCast(@alignCast(userdata));
7615 _ = t;
7616 return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) {
7617 error.PathAlreadyExists => return error.Unexpected,
7618 error.OperationUnsupported => return error.Unexpected,
7619 else => |e| return e,
7620 };
7621}
7622
7623fn dirRenamePreserve(
7624 userdata: ?*anyopaque,
7625 old_dir: Dir,
7626 old_sub_path: []const u8,
7627 new_dir: Dir,
7628 new_sub_path: []const u8,
7629) Dir.RenamePreserveError!void {
7630 const t: *Threaded = @ptrCast(@alignCast(userdata));
7631 if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);
7632 if (is_darwin) return dirRenamePreserveDarwin(old_dir, old_sub_path, new_dir, new_sub_path);
7633 if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);
7634 // Make a hard link then delete the original.
7635 try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });
7636 const prev = swapCancelProtection(t, .blocked);
7637 defer _ = swapCancelProtection(t, prev);
7638 dirDeleteFile(t, old_dir, old_sub_path) catch {};
7639}
7640
7641fn dirRenameWindowsInner(
7642 old_dir: Dir,
7643 old_sub_path: []const u8,
7644 new_dir: Dir,
7645 new_sub_path: []const u8,
7646 replace_if_exists: bool,
7647) Dir.RenamePreserveError!void {
7648 const w = windows;
7649 const old_path_w_buf = try sliceToPrefixedFileW(old_dir.handle, old_sub_path, .{});
7650 const old_path_w = old_path_w_buf.span();
7651 const new_path_w_buf = try sliceToPrefixedFileW(new_dir.handle, new_sub_path, .{});
7652 const new_path_w = new_path_w_buf.span();
7653
7654 const src_fd = src_fd: {
7655 if (OpenFile(old_path_w, .{
7656 .dir = old_dir.handle,
7657 .access_mask = .{
7658 .GENERIC = .{ .WRITE = true },
7659 .STANDARD = .{
7660 .RIGHTS = .{ .DELETE = true },
7661 .SYNCHRONIZE = true,
7662 },
7663 },
7664 .creation = .OPEN,
7665 .filter = .any, // This function is supposed to rename both files and directories.
7666 .follow_symlinks = false,
7667 })) |handle| {
7668 break :src_fd handle;
7669 } else |err| switch (err) {
7670 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
7671 else => |e| return e,
7672 }
7673 };
7674 defer w.CloseHandle(src_fd);
7675
7676 var rc: w.NTSTATUS = undefined;
7677 // FileRenameInformationEx has varying levels of support:
7678 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
7679 // (INVALID_INFO_CLASS is returned if not supported)
7680 // - Requires the NTFS filesystem
7681 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
7682 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
7683 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
7684 // (NOT_SUPPORTED is returned if a flag is unsupported)
7685 //
7686 // The strategy here is just to try using FileRenameInformationEx and fall back to
7687 // FileRenameInformation if the return value lets us know that some aspect of it is not supported.
7688 const need_fallback = need_fallback: {
7689 var rename_info: w.FILE.RENAME_INFORMATION = .init(.{
7690 .Flags = .{
7691 .REPLACE_IF_EXISTS = replace_if_exists,
7692 .POSIX_SEMANTICS = true,
7693 .IGNORE_READONLY_ATTRIBUTE = true,
7694 },
7695 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
7696 .FileName = new_path_w,
7697 });
7698 var io_status_block: w.IO_STATUS_BLOCK = undefined;
7699 const rename_info_buf = rename_info.toBuffer();
7700 rc = w.ntdll.NtSetInformationFile(
7701 src_fd,
7702 &io_status_block,
7703 rename_info_buf.ptr,
7704 @intCast(rename_info_buf.len),
7705 .RenameEx,
7706 );
7707 switch (rc) {
7708 .SUCCESS => return,
7709 // The filesystem does not support FileDispositionInformationEx
7710 .INVALID_PARAMETER,
7711 // The operating system does not support FileDispositionInformationEx
7712 .INVALID_INFO_CLASS,
7713 // The operating system does not support one of the flags
7714 .NOT_SUPPORTED,
7715 => break :need_fallback true,
7716 // For all other statuses, fall down to the switch below to handle them.
7717 else => break :need_fallback false,
7718 }
7719 };
7720
7721 if (need_fallback) {
7722 var rename_info: w.FILE.RENAME_INFORMATION = .init(.{
7723 .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists },
7724 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
7725 .FileName = new_path_w,
7726 });
7727 var io_status_block: w.IO_STATUS_BLOCK = undefined;
7728 const rename_info_buf = rename_info.toBuffer();
7729 rc = w.ntdll.NtSetInformationFile(
7730 src_fd,
7731 &io_status_block,
7732 rename_info_buf.ptr,
7733 @intCast(rename_info_buf.len),
7734 .Rename,
7735 );
7736 }
7737
7738 switch (rc) {
7739 .SUCCESS => {},
7740 .INVALID_HANDLE => |err| return w.statusBug(err),
7741 .INVALID_PARAMETER => |err| return w.statusBug(err),
7742 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
7743 .ACCESS_DENIED => return error.AccessDenied,
7744 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
7745 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
7746 .NOT_SAME_DEVICE => return error.CrossDevice,
7747 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
7748 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
7749 .FILE_IS_A_DIRECTORY => return error.IsDir,
7750 .NOT_A_DIRECTORY => return error.NotDir,
7751 else => return w.unexpectedStatus(rc),
7752 }
7753}
7754
7755fn dirRenameWasi(
7756 userdata: ?*anyopaque,
7757 old_dir: Dir,
7758 old_sub_path: []const u8,
7759 new_dir: Dir,
7760 new_sub_path: []const u8,
7761) Dir.RenameError!void {
7762 if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path);
7763
7764 const t: *Threaded = @ptrCast(@alignCast(userdata));
7765 _ = t;
7766
7767 const syscall: Syscall = try .start();
7768 while (true) {
7769 switch (std.os.wasi.path_rename(old_dir.handle, old_sub_path.ptr, old_sub_path.len, new_dir.handle, new_sub_path.ptr, new_sub_path.len)) {
7770 .SUCCESS => return syscall.finish(),
7771 .INTR => {
7772 try syscall.checkCancel();
7773 continue;
7774 },
7775 else => |e| {
7776 syscall.finish();
7777 switch (e) {
7778 .ACCES => return error.AccessDenied,
7779 .PERM => return error.PermissionDenied,
7780 .BUSY => return error.FileBusy,
7781 .DQUOT => return error.DiskQuota,
7782 .FAULT => |err| return errnoBug(err),
7783 .INVAL => |err| return errnoBug(err),
7784 .ISDIR => return error.IsDir,
7785 .LOOP => return error.SymLinkLoop,
7786 .MLINK => return error.LinkQuotaExceeded,
7787 .NAMETOOLONG => return error.NameTooLong,
7788 .NOENT => return error.FileNotFound,
7789 .NOTDIR => return error.NotDir,
7790 .NOMEM => return error.SystemResources,
7791 .NOSPC => return error.NoSpaceLeft,
7792 .EXIST => return error.DirNotEmpty,
7793 .NOTEMPTY => return error.DirNotEmpty,
7794 .ROFS => return error.ReadOnlyFileSystem,
7795 .XDEV => return error.CrossDevice,
7796 .NOTCAPABLE => return error.AccessDenied,
7797 .ILSEQ => return error.BadPathName,
7798 else => |err| return posix.unexpectedErrno(err),
7799 }
7800 },
7801 }
7802 }
7803}
7804
7805fn dirRenamePosix(
7806 userdata: ?*anyopaque,
7807 old_dir: Dir,
7808 old_sub_path: []const u8,
7809 new_dir: Dir,
7810 new_sub_path: []const u8,
7811) Dir.RenameError!void {
7812 const t: *Threaded = @ptrCast(@alignCast(userdata));
7813 _ = t;
7814
7815 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
7816 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
7817
7818 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
7819 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
7820
7821 return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix);
7822}
7823
7824fn dirRenamePreserveDarwin(
7825 old_dir: Dir,
7826 old_sub_path: []const u8,
7827 new_dir: Dir,
7828 new_sub_path: []const u8,
7829) Dir.RenamePreserveError!void {
7830 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
7831 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
7832 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
7833 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
7834
7835 const syscall: Syscall = try .start();
7836 while (true) {
7837 switch (posix.errno(std.c.renameatx_np(
7838 old_dir.handle,
7839 old_sub_path_posix,
7840 new_dir.handle,
7841 new_sub_path_posix,
7842 .{ .EXCL = true },
7843 ))) {
7844 .SUCCESS => {
7845 syscall.finish();
7846 break;
7847 },
7848 .INTR => {
7849 try syscall.checkCancel();
7850 continue;
7851 },
7852 .INVAL => |err| return syscall.errnoBug(err),
7853 .FAULT => |err| return syscall.errnoBug(err),
7854 .BADF => |err| return syscall.errnoBug(err),
7855 .ISDIR => |err| return syscall.errnoBug(err),
7856 .NOTEMPTY => |err| return syscall.errnoBug(err),
7857 .OPNOTSUPP => return syscall.fail(error.OperationUnsupported),
7858 .IO => return syscall.fail(error.HardwareFailure),
7859 .DEADLK => return syscall.fail(error.AccessDenied),
7860 .ACCES => return syscall.fail(error.AccessDenied),
7861 .DQUOT => return syscall.fail(error.DiskQuota),
7862 .EXIST => return syscall.fail(error.PathAlreadyExists),
7863 .LOOP => return syscall.fail(error.LinkQuotaExceeded),
7864 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
7865 .NOENT => return syscall.fail(error.FileNotFound),
7866 .NOSPC => return syscall.fail(error.NoSpaceLeft),
7867 .NOTDIR => return syscall.fail(error.NotDir),
7868 .PERM => return syscall.fail(error.PermissionDenied),
7869 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
7870 .XDEV => return syscall.fail(error.CrossDevice),
7871 else => |err| return syscall.unexpectedErrno(err),
7872 }
7873 }
7874}
7875
7876fn dirRenamePreserveLinux(
7877 old_dir: Dir,
7878 old_sub_path: []const u8,
7879 new_dir: Dir,
7880 new_sub_path: []const u8,
7881) Dir.RenamePreserveError!void {
7882 const linux = std.os.linux;
7883
7884 var old_path_buffer: [linux.PATH_MAX]u8 = undefined;
7885 var new_path_buffer: [linux.PATH_MAX]u8 = undefined;
7886
7887 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
7888 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
7889
7890 const syscall: Syscall = try .start();
7891 while (true) switch (linux.errno(linux.renameat2(
7892 old_dir.handle,
7893 old_sub_path_posix,
7894 new_dir.handle,
7895 new_sub_path_posix,
7896 .{ .NOREPLACE = true },
7897 ))) {
7898 .SUCCESS => return syscall.finish(),
7899 .INTR => {
7900 try syscall.checkCancel();
7901 continue;
7902 },
7903 .ACCES => return syscall.fail(error.AccessDenied),
7904 .PERM => return syscall.fail(error.PermissionDenied),
7905 .BUSY => return syscall.fail(error.FileBusy),
7906 .DQUOT => return syscall.fail(error.DiskQuota),
7907 .ISDIR => return syscall.fail(error.IsDir),
7908 .LOOP => return syscall.fail(error.SymLinkLoop),
7909 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
7910 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
7911 .NOENT => return syscall.fail(error.FileNotFound),
7912 .NOTDIR => return syscall.fail(error.NotDir),
7913 .NOMEM => return syscall.fail(error.SystemResources),
7914 .NOSPC => return syscall.fail(error.NoSpaceLeft),
7915 .EXIST => return syscall.fail(error.PathAlreadyExists),
7916 .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
7917 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
7918 .XDEV => return syscall.fail(error.CrossDevice),
7919 .ILSEQ => return syscall.fail(error.BadPathName),
7920 .FAULT => |err| return syscall.errnoBug(err),
7921 .INVAL => |err| return syscall.errnoBug(err),
7922 else => |err| return syscall.unexpectedErrno(err),
7923 };
7924}
7925
7926fn renameat(
7927 old_dir: posix.fd_t,
7928 old_sub_path: [*:0]const u8,
7929 new_dir: posix.fd_t,
7930 new_sub_path: [*:0]const u8,
7931) Dir.RenameError!void {
7932 const syscall: Syscall = try .start();
7933 while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
7934 .SUCCESS => return syscall.finish(),
7935 .INTR => {
7936 try syscall.checkCancel();
7937 continue;
7938 },
7939 .ACCES => return syscall.fail(error.AccessDenied),
7940 .PERM => return syscall.fail(error.PermissionDenied),
7941 .BUSY => return syscall.fail(error.FileBusy),
7942 .DQUOT => return syscall.fail(error.DiskQuota),
7943 .ISDIR => return syscall.fail(error.IsDir),
7944 .IO => return syscall.fail(error.HardwareFailure),
7945 .LOOP => return syscall.fail(error.SymLinkLoop),
7946 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
7947 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
7948 .NOENT => return syscall.fail(error.FileNotFound),
7949 .NOTDIR => return syscall.fail(error.NotDir),
7950 .NOMEM => return syscall.fail(error.SystemResources),
7951 .NOSPC => return syscall.fail(error.NoSpaceLeft),
7952 .EXIST => return syscall.fail(error.DirNotEmpty),
7953 .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
7954 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
7955 .XDEV => return syscall.fail(error.CrossDevice),
7956 .ILSEQ => return syscall.fail(error.BadPathName),
7957 .FAULT => |err| return syscall.errnoBug(err),
7958 .INVAL => |err| return syscall.errnoBug(err),
7959 else => |err| return syscall.unexpectedErrno(err),
7960 };
7961}
7962
7963const dirSymLink = switch (native_os) {
7964 .windows => dirSymLinkWindows,
7965 .wasi => dirSymLinkWasi,
7966 else => dirSymLinkPosix,
7967};
7968
7969fn dirSymLinkWindows(
7970 userdata: ?*anyopaque,
7971 dir: Dir,
7972 target_path: []const u8,
7973 sym_link_path: []const u8,
7974 flags: Dir.SymLinkFlags,
7975) Dir.SymLinkError!void {
7976 const t: *Threaded = @ptrCast(@alignCast(userdata));
7977 _ = t;
7978 const w = windows;
7979
7980 // Target path does not use sliceToPrefixedFileW because certain paths
7981 // are handled differently when creating a symlink than they would be
7982 // when converting to an NT namespaced path.
7983 var target_path_w: WindowsPathSpace = undefined;
7984 target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path);
7985 target_path_w.data[target_path_w.len] = 0;
7986 // However, we need to canonicalize any path separators to `\`, since if
7987 // the target path is relative, then it must use `\` as the path separator.
7988 std.mem.replaceScalar(
7989 u16,
7990 target_path_w.data[0..target_path_w.len],
7991 std.mem.nativeToLittle(u16, '/'),
7992 std.mem.nativeToLittle(u16, '\\'),
7993 );
7994
7995 const sym_link_path_w = try sliceToPrefixedFileW(dir.handle, sym_link_path, .{});
7996
7997 const SYMLINK_DATA = extern struct {
7998 ReparseTag: w.IO_REPARSE_TAG,
7999 ReparseDataLength: w.USHORT,
8000 Reserved: w.USHORT,
8001 SubstituteNameOffset: w.USHORT,
8002 SubstituteNameLength: w.USHORT,
8003 PrintNameOffset: w.USHORT,
8004 PrintNameLength: w.USHORT,
8005 Flags: w.ULONG,
8006 };
8007
8008 const symlink_handle = handle: {
8009 if (OpenFile(sym_link_path_w.span(), .{
8010 .access_mask = .{
8011 .GENERIC = .{ .READ = true, .WRITE = true },
8012 .STANDARD = .{ .SYNCHRONIZE = true },
8013 },
8014 .dir = dir.handle,
8015 .creation = .CREATE,
8016 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
8017 })) |handle| {
8018 break :handle handle;
8019 } else |err| switch (err) {
8020 error.IsDir => return error.PathAlreadyExists,
8021 error.NotDir => return error.Unexpected,
8022 error.WouldBlock => return error.Unexpected,
8023 error.PipeBusy => return error.Unexpected,
8024 error.FileBusy => return error.Unexpected,
8025 error.NoDevice => return error.Unexpected,
8026 error.AntivirusInterference => return error.Unexpected,
8027 else => |e| return e,
8028 }
8029 };
8030 defer w.CloseHandle(symlink_handle);
8031
8032 // Relevant portions of the documentation:
8033 // > Relative links are specified using the following conventions:
8034 // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32".
8035 // > - Current working directory–relative—for example, if the current working directory is
8036 // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt".
8037 // > Note: If you specify a current working directory–relative link, it is created as an absolute
8038 // > link, due to the way the current working directory is processed based on the user and the thread.
8039 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
8040 var is_target_absolute = false;
8041 const final_target_path = target_path: {
8042 if (w.hasCommonNtPrefix(u16, target_path_w.span())) {
8043 // Already an NT path, no need to do anything to it
8044 break :target_path target_path_w.span();
8045 } else {
8046 switch (Dir.path.getWin32PathType(u16, target_path_w.span())) {
8047 // Rooted paths need to avoid getting put through wToPrefixedFileW
8048 // (and they are treated as relative in this context)
8049 // Note: It seems that rooted paths in symbolic links are relative to
8050 // the drive that the symbolic exists on, not to the CWD's drive.
8051 // So, if the symlink is on C:\ and the CWD is on D:\,
8052 // it will still resolve the path relative to the root of
8053 // the C:\ drive.
8054 .rooted => break :target_path target_path_w.span(),
8055 // Keep relative paths relative, but anything else needs to get NT-prefixed.
8056 else => if (!Dir.path.isAbsoluteWindowsWtf16(target_path_w.span()))
8057 break :target_path target_path_w.span(),
8058 }
8059 }
8060 var prefixed_target_path = try wToPrefixedFileW(dir.handle, target_path_w.span(), .{});
8061 // We do this after prefixing to ensure that drive-relative paths are treated as absolute
8062 is_target_absolute = Dir.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
8063 break :target_path prefixed_target_path.span();
8064 };
8065
8066 // prepare reparse data buffer
8067 var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
8068 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
8069 const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2;
8070 const target_is_absolute = Dir.path.isAbsoluteWindowsWtf16(final_target_path);
8071 const symlink_data: SYMLINK_DATA = .{
8072 .ReparseTag = .SYMLINK,
8073 .ReparseDataLength = @intCast(buf_len - header_len),
8074 .Reserved = 0,
8075 .SubstituteNameOffset = @intCast(final_target_path.len * 2),
8076 .SubstituteNameLength = @intCast(final_target_path.len * 2),
8077 .PrintNameOffset = 0,
8078 .PrintNameLength = @intCast(final_target_path.len * 2),
8079 .Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0,
8080 };
8081
8082 @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data));
8083 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
8084 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
8085 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
8086 switch ((try deviceIoControl(&.{
8087 .file = .{ .handle = symlink_handle, .flags = .{ .nonblocking = false } },
8088 .code = .SET_REPARSE_POINT,
8089 .in = buffer[0..buf_len],
8090 })).u.Status) {
8091 .SUCCESS => {},
8092 .CANCELLED => unreachable,
8093 .INSUFFICIENT_RESOURCES => return error.SystemResources,
8094 .PRIVILEGE_NOT_HELD => return error.PermissionDenied,
8095 .ACCESS_DENIED => return error.AccessDenied,
8096 .INVALID_DEVICE_REQUEST => return error.FileSystem,
8097 else => |status| return w.unexpectedStatus(status),
8098 }
8099}
8100
8101fn dirSymLinkWasi(
8102 userdata: ?*anyopaque,
8103 dir: Dir,
8104 target_path: []const u8,
8105 sym_link_path: []const u8,
8106 flags: Dir.SymLinkFlags,
8107) Dir.SymLinkError!void {
8108 if (builtin.link_libc) return dirSymLinkPosix(userdata, dir, target_path, sym_link_path, flags);
8109
8110 const t: *Threaded = @ptrCast(@alignCast(userdata));
8111 _ = t;
8112
8113 const syscall: Syscall = try .start();
8114 while (true) {
8115 switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) {
8116 .SUCCESS => return syscall.finish(),
8117 .INTR => {
8118 try syscall.checkCancel();
8119 continue;
8120 },
8121 else => |e| {
8122 syscall.finish();
8123 switch (e) {
8124 .FAULT => |err| return errnoBug(err),
8125 .INVAL => |err| return errnoBug(err),
8126 .BADF => |err| return errnoBug(err),
8127 .ACCES => return error.AccessDenied,
8128 .PERM => return error.PermissionDenied,
8129 .DQUOT => return error.DiskQuota,
8130 .EXIST => return error.PathAlreadyExists,
8131 .IO => return error.FileSystem,
8132 .LOOP => return error.SymLinkLoop,
8133 .NAMETOOLONG => return error.NameTooLong,
8134 .NOENT => return error.FileNotFound,
8135 .NOTDIR => return error.NotDir,
8136 .NOMEM => return error.SystemResources,
8137 .NOSPC => return error.NoSpaceLeft,
8138 .ROFS => return error.ReadOnlyFileSystem,
8139 .NOTCAPABLE => return error.AccessDenied,
8140 .ILSEQ => return error.BadPathName,
8141 else => |err| return posix.unexpectedErrno(err),
8142 }
8143 },
8144 }
8145 }
8146}
8147
8148fn dirSymLinkPosix(
8149 userdata: ?*anyopaque,
8150 dir: Dir,
8151 target_path: []const u8,
8152 sym_link_path: []const u8,
8153 flags: Dir.SymLinkFlags,
8154) Dir.SymLinkError!void {
8155 _ = flags;
8156 const t: *Threaded = @ptrCast(@alignCast(userdata));
8157 _ = t;
8158
8159 var target_path_buffer: [posix.PATH_MAX]u8 = undefined;
8160 var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined;
8161
8162 const target_path_posix = try pathToPosix(target_path, &target_path_buffer);
8163 const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer);
8164
8165 const syscall: Syscall = try .start();
8166 while (true) {
8167 switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) {
8168 .SUCCESS => return syscall.finish(),
8169 .INTR => {
8170 try syscall.checkCancel();
8171 continue;
8172 },
8173 else => |e| {
8174 syscall.finish();
8175 switch (e) {
8176 .FAULT => |err| return errnoBug(err),
8177 .INVAL => |err| return errnoBug(err),
8178 .ACCES => return error.AccessDenied,
8179 .PERM => return error.PermissionDenied,
8180 .DQUOT => return error.DiskQuota,
8181 .EXIST => return error.PathAlreadyExists,
8182 .IO => return error.FileSystem,
8183 .LOOP => return error.SymLinkLoop,
8184 .NAMETOOLONG => return error.NameTooLong,
8185 .NOENT => return error.FileNotFound,
8186 .NOTDIR => return error.NotDir,
8187 .NOMEM => return error.SystemResources,
8188 .NOSPC => return error.NoSpaceLeft,
8189 .ROFS => return error.ReadOnlyFileSystem,
8190 .ILSEQ => return error.BadPathName,
8191 else => |err| return posix.unexpectedErrno(err),
8192 }
8193 },
8194 }
8195 }
8196}
8197
8198fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
8199 const t: *Threaded = @ptrCast(@alignCast(userdata));
8200 _ = t;
8201 switch (native_os) {
8202 .windows => return dirReadLinkWindows(dir, sub_path, buffer),
8203 .wasi => return dirReadLinkWasi(dir, sub_path, buffer),
8204 else => return dirReadLinkPosix(dir, sub_path, buffer),
8205 }
8206}
8207
8208fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
8209 // This gets used once for `sub_path` and then reused again temporarily
8210 // before converting back to `buffer`.
8211 var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{});
8212 const attr: windows.OBJECT.ATTRIBUTES = .{
8213 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle,
8214 .ObjectName = @constCast(&sub_path_w.string()),
8215 };
8216 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8217 var result_handle: windows.HANDLE = undefined;
8218 var attempt: u5 = 0;
8219 var syscall: Syscall = try .start();
8220 while (true) switch (windows.ntdll.NtCreateFile(
8221 &result_handle,
8222 .{
8223 .SPECIFIC = .{ .FILE = .{
8224 .READ_ATTRIBUTES = true,
8225 } },
8226 .STANDARD = .{ .SYNCHRONIZE = true },
8227 },
8228 &attr,
8229 &io_status_block,
8230 null,
8231 .{ .NORMAL = true },
8232 .VALID_FLAGS,
8233 .OPEN,
8234 .{
8235 .DIRECTORY_FILE = false,
8236 .NON_DIRECTORY_FILE = false,
8237 .IO = .SYNCHRONOUS_NONALERT,
8238 .OPEN_REPARSE_POINT = true,
8239 },
8240 null,
8241 0,
8242 )) {
8243 .SUCCESS => {
8244 syscall.finish();
8245 break;
8246 },
8247 .CANCELLED => {
8248 try syscall.checkCancel();
8249 continue;
8250 },
8251 .SHARING_VIOLATION => {
8252 // This occurs if the file attempting to be opened is a running
8253 // executable. However, there's a kernel bug: the error may be
8254 // incorrectly returned for an indeterminate amount of time
8255 // after an executable file is closed. Here we work around the
8256 // kernel bug with retry attempts.
8257 syscall.finish();
8258 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
8259 try parking_sleep.sleep(.{ .duration = .{
8260 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
8261 .clock = .awake,
8262 } });
8263 attempt += 1;
8264 syscall = try .start();
8265 continue;
8266 },
8267 .DELETE_PENDING => {
8268 // This error means that there *was* a file in this location on
8269 // the file system, but it was deleted. However, the OS is not
8270 // finished with the deletion operation, and so this CreateFile
8271 // call has failed. Here, we simulate the kernel bug being
8272 // fixed by sleeping and retrying until the error goes away.
8273 syscall.finish();
8274 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
8275 try parking_sleep.sleep(.{ .duration = .{
8276 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
8277 .clock = .awake,
8278 } });
8279 attempt += 1;
8280 syscall = try .start();
8281 continue;
8282 },
8283 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
8284 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
8285 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
8286 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
8287 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
8288 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.FileNotFound),
8289 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8290 .PIPE_BUSY => return syscall.fail(error.AccessDenied),
8291 .PIPE_NOT_AVAILABLE => return syscall.fail(error.FileNotFound),
8292 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
8293 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
8294 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
8295 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
8296 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
8297 else => |status| return syscall.unexpectedNtstatus(status),
8298 };
8299 defer windows.CloseHandle(result_handle);
8300
8301 var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined;
8302 switch ((try deviceIoControl(&.{
8303 .file = .{ .handle = result_handle, .flags = .{ .nonblocking = false } },
8304 .code = .GET_REPARSE_POINT,
8305 .out = &reparse_buf,
8306 })).u.Status) {
8307 .SUCCESS => {},
8308 .CANCELLED => unreachable,
8309 .NOT_A_REPARSE_POINT => return error.NotLink,
8310 else => |status| return windows.unexpectedStatus(status),
8311 }
8312
8313 const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf));
8314 const IoReparseTagInt = @typeInfo(windows.IO_REPARSE_TAG).@"struct".backing_integer.?;
8315 const result_w = switch (@as(IoReparseTagInt, @bitCast(reparse_struct.ReparseTag))) {
8316 @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.SYMLINK)) => r: {
8317 const buf: *const windows.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
8318 const offset = buf.SubstituteNameOffset >> 1;
8319 const len = buf.SubstituteNameLength >> 1;
8320 const path_buf = @as([*]const u16, &buf.PathBuffer);
8321 const is_relative = buf.Flags & windows.SYMLINK_FLAG_RELATIVE != 0;
8322 break :r try parseReadLinkPath(path_buf[offset..][0..len], is_relative, &sub_path_w.data);
8323 },
8324 @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.MOUNT_POINT)) => r: {
8325 const buf: *const windows.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
8326 const offset = buf.SubstituteNameOffset >> 1;
8327 const len = buf.SubstituteNameLength >> 1;
8328 const path_buf = @as([*]const u16, &buf.PathBuffer);
8329 break :r try parseReadLinkPath(path_buf[offset..][0..len], false, &sub_path_w.data);
8330 },
8331 else => return error.UnsupportedReparsePointType,
8332 };
8333 const len = std.unicode.calcWtf8Len(result_w);
8334 if (len > buffer.len) return error.NameTooLong;
8335
8336 return std.unicode.wtf16LeToWtf8(buffer, result_w);
8337}
8338
8339fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 {
8340 path: {
8341 if (is_relative) break :path;
8342 return windows.ntToWin32Namespace(path, out_buffer) catch |err| switch (err) {
8343 error.NameTooLong => |e| return e,
8344 error.NotNtPath => break :path,
8345 };
8346 }
8347 if (out_buffer.len < path.len) return error.NameTooLong;
8348 const dest = out_buffer[0..path.len];
8349 @memcpy(dest, path);
8350 return dest;
8351}
8352
8353fn dirReadLinkWasi(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
8354 if (builtin.link_libc) return dirReadLinkPosix(dir, sub_path, buffer);
8355
8356 var n: usize = undefined;
8357 const syscall: Syscall = try .start();
8358 while (true) {
8359 switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) {
8360 .SUCCESS => {
8361 syscall.finish();
8362 return n;
8363 },
8364 .INTR => {
8365 try syscall.checkCancel();
8366 continue;
8367 },
8368 else => |e| {
8369 syscall.finish();
8370 switch (e) {
8371 .ACCES => return error.AccessDenied,
8372 .FAULT => |err| return errnoBug(err),
8373 .INVAL => return error.NotLink,
8374 .IO => return error.FileSystem,
8375 .LOOP => return error.SymLinkLoop,
8376 .NAMETOOLONG => return error.NameTooLong,
8377 .NOENT => return error.FileNotFound,
8378 .NOMEM => return error.SystemResources,
8379 .NOTDIR => return error.NotDir,
8380 .NOTCAPABLE => return error.AccessDenied,
8381 .ILSEQ => return error.BadPathName,
8382 else => |err| return posix.unexpectedErrno(err),
8383 }
8384 },
8385 }
8386 }
8387}
8388
8389fn dirReadLinkPosix(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
8390 var sub_path_buffer: [posix.PATH_MAX]u8 = undefined;
8391 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
8392
8393 const syscall: Syscall = try .start();
8394 while (true) {
8395 const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
8396 switch (posix.errno(rc)) {
8397 .SUCCESS => {
8398 syscall.finish();
8399 const len: usize = @bitCast(rc);
8400 return len;
8401 },
8402 .INTR => {
8403 try syscall.checkCancel();
8404 continue;
8405 },
8406 else => |e| {
8407 syscall.finish();
8408 switch (e) {
8409 .ACCES => return error.AccessDenied,
8410 .FAULT => |err| return errnoBug(err),
8411 .INVAL => return error.NotLink,
8412 .IO => return error.FileSystem,
8413 .LOOP => return error.SymLinkLoop,
8414 .NAMETOOLONG => return error.NameTooLong,
8415 .NOENT => return error.FileNotFound,
8416 .NOMEM => return error.SystemResources,
8417 .NOTDIR => return error.NotDir,
8418 .ILSEQ => return error.BadPathName,
8419 else => |err| return posix.unexpectedErrno(err),
8420 }
8421 },
8422 }
8423 }
8424}
8425
8426const dirSetPermissions = switch (native_os) {
8427 .windows => dirSetPermissionsWindows,
8428 else => dirSetPermissionsPosix,
8429};
8430
8431fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
8432 const t: *Threaded = @ptrCast(@alignCast(userdata));
8433 _ = t;
8434 _ = dir;
8435 _ = permissions;
8436 @panic("TODO implement dirSetPermissionsWindows");
8437}
8438
8439fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
8440 if (@sizeOf(Dir.Permissions) == 0) return;
8441 const t: *Threaded = @ptrCast(@alignCast(userdata));
8442 _ = t;
8443 return setPermissionsPosix(dir.handle, permissions.toMode());
8444}
8445
8446fn dirSetFilePermissions(
8447 userdata: ?*anyopaque,
8448 dir: Dir,
8449 sub_path: []const u8,
8450 permissions: Dir.Permissions,
8451 options: Dir.SetFilePermissionsOptions,
8452) Dir.SetFilePermissionsError!void {
8453 if (@sizeOf(Dir.Permissions) == 0) return;
8454 if (is_windows) @panic("TODO implement dirSetFilePermissions windows");
8455 const t: *Threaded = @ptrCast(@alignCast(userdata));
8456
8457 var path_buffer: [posix.PATH_MAX]u8 = undefined;
8458 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
8459
8460 const mode = permissions.toMode();
8461 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
8462
8463 return posixFchmodat(t, dir.handle, sub_path_posix, mode, flags);
8464}
8465
8466fn posixFchmodat(
8467 t: *Threaded,
8468 dir_fd: posix.fd_t,
8469 path: [*:0]const u8,
8470 mode: posix.mode_t,
8471 flags: u32,
8472) Dir.SetFilePermissionsError!void {
8473 // No special handling for linux is needed if we can use the libc fallback
8474 // or `flags` is empty. Glibc only added the fallback in 2.32.
8475 if (have_fchmodat_flags or flags == 0) {
8476 const syscall: Syscall = try .start();
8477 while (true) {
8478 const rc = if (have_fchmodat_flags or builtin.link_libc)
8479 posix.system.fchmodat(dir_fd, path, mode, flags)
8480 else
8481 posix.system.fchmodat(dir_fd, path, mode);
8482 switch (posix.errno(rc)) {
8483 .SUCCESS => return syscall.finish(),
8484 .INTR => {
8485 try syscall.checkCancel();
8486 continue;
8487 },
8488 else => |e| {
8489 syscall.finish();
8490 switch (e) {
8491 .BADF => |err| return errnoBug(err),
8492 .FAULT => |err| return errnoBug(err),
8493 .INVAL => |err| return errnoBug(err),
8494 .ACCES => return error.AccessDenied,
8495 .IO => return error.InputOutput,
8496 .LOOP => return error.SymLinkLoop,
8497 .MFILE => return error.ProcessFdQuotaExceeded,
8498 .NAMETOOLONG => return error.NameTooLong,
8499 .NFILE => return error.SystemFdQuotaExceeded,
8500 .NOENT => return error.FileNotFound,
8501 .NOTDIR => return error.FileNotFound,
8502 .NOMEM => return error.SystemResources,
8503 .OPNOTSUPP => return error.OperationUnsupported,
8504 .PERM => return error.PermissionDenied,
8505 .ROFS => return error.ReadOnlyFileSystem,
8506 else => |err| return posix.unexpectedErrno(err),
8507 }
8508 },
8509 }
8510 }
8511 }
8512
8513 if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled)
8514 return fchmodatFallback(dir_fd, path, mode);
8515
8516 comptime assert(native_os == .linux);
8517
8518 const syscall: Syscall = try .start();
8519 while (true) {
8520 switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) {
8521 .SUCCESS => return syscall.finish(),
8522 .INTR => {
8523 try syscall.checkCancel();
8524 continue;
8525 },
8526 else => |e| {
8527 syscall.finish();
8528 switch (e) {
8529 .BADF => |err| return errnoBug(err),
8530 .FAULT => |err| return errnoBug(err),
8531 .INVAL => |err| return errnoBug(err),
8532 .ACCES => return error.AccessDenied,
8533 .IO => return error.InputOutput,
8534 .LOOP => return error.SymLinkLoop,
8535 .NOENT => return error.FileNotFound,
8536 .NOMEM => return error.SystemResources,
8537 .NOTDIR => return error.FileNotFound,
8538 .OPNOTSUPP => return error.OperationUnsupported,
8539 .PERM => return error.PermissionDenied,
8540 .ROFS => return error.ReadOnlyFileSystem,
8541 .NOSYS => {
8542 @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic);
8543 return fchmodatFallback(dir_fd, path, mode);
8544 },
8545 else => |err| return posix.unexpectedErrno(err),
8546 }
8547 },
8548 }
8549 }
8550}
8551
8552fn fchmodatFallback(
8553 dir_fd: posix.fd_t,
8554 path: [*:0]const u8,
8555 mode: posix.mode_t,
8556) Dir.SetFilePermissionsError!void {
8557 comptime assert(native_os == .linux);
8558
8559 // Fallback to changing permissions using procfs:
8560 //
8561 // 1. Open `path` as a `PATH` descriptor.
8562 // 2. Stat the fd and check if it isn't a symbolic link.
8563 // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
8564 // 4. Pass the procfs path to `chmod` with the `mode`.
8565 const path_fd: posix.fd_t = fd: {
8566 const syscall: Syscall = try .start();
8567 while (true) {
8568 const rc = posix.system.openat(dir_fd, path, .{
8569 .PATH = true,
8570 .NOFOLLOW = true,
8571 .CLOEXEC = true,
8572 }, @as(posix.mode_t, 0));
8573 switch (posix.errno(rc)) {
8574 .SUCCESS => {
8575 syscall.finish();
8576 break :fd @intCast(rc);
8577 },
8578 .INTR => {
8579 try syscall.checkCancel();
8580 continue;
8581 },
8582 else => |e| {
8583 syscall.finish();
8584 switch (e) {
8585 .FAULT => |err| return errnoBug(err),
8586 .INVAL => |err| return errnoBug(err),
8587 .ACCES => return error.AccessDenied,
8588 .PERM => return error.PermissionDenied,
8589 .LOOP => return error.SymLinkLoop,
8590 .MFILE => return error.ProcessFdQuotaExceeded,
8591 .NAMETOOLONG => return error.NameTooLong,
8592 .NFILE => return error.SystemFdQuotaExceeded,
8593 .NOENT => return error.FileNotFound,
8594 .NOMEM => return error.SystemResources,
8595 else => |err| return posix.unexpectedErrno(err),
8596 }
8597 },
8598 }
8599 }
8600 };
8601 defer closeFd(path_fd);
8602
8603 const path_mode = mode: {
8604 const sys = if (statx_use_c) std.c else std.os.linux;
8605 const syscall: Syscall = try .start();
8606 while (true) {
8607 var statx = std.mem.zeroes(std.os.linux.Statx);
8608 switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
8609 .SUCCESS => {
8610 syscall.finish();
8611 if (!statx.mask.TYPE) return error.Unexpected;
8612 break :mode statx.mode;
8613 },
8614 .INTR => {
8615 try syscall.checkCancel();
8616 continue;
8617 },
8618 else => |e| {
8619 syscall.finish();
8620 switch (e) {
8621 .ACCES => return error.AccessDenied,
8622 .LOOP => return error.SymLinkLoop,
8623 .NOMEM => return error.SystemResources,
8624 else => |err| return posix.unexpectedErrno(err),
8625 }
8626 },
8627 }
8628 }
8629 };
8630
8631 // Even though we only wanted TYPE, the kernel can still fill in the additional bits.
8632 if ((path_mode & posix.S.IFMT) == posix.S.IFLNK)
8633 return error.OperationUnsupported;
8634
8635 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
8636 const proc_path = std.mem.printSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable;
8637 const syscall: Syscall = try .start();
8638 while (true) {
8639 switch (posix.errno(posix.system.chmod(proc_path, mode))) {
8640 .SUCCESS => return syscall.finish(),
8641 .INTR => {
8642 try syscall.checkCancel();
8643 continue;
8644 },
8645 else => |e| {
8646 syscall.finish();
8647 switch (e) {
8648 .NOENT => return error.OperationUnsupported, // procfs not mounted.
8649 .BADF => |err| return errnoBug(err),
8650 .FAULT => |err| return errnoBug(err),
8651 .INVAL => |err| return errnoBug(err),
8652 .ACCES => return error.AccessDenied,
8653 .IO => return error.InputOutput,
8654 .LOOP => return error.SymLinkLoop,
8655 .NOMEM => return error.SystemResources,
8656 .NOTDIR => return error.FileNotFound,
8657 .PERM => return error.PermissionDenied,
8658 .ROFS => return error.ReadOnlyFileSystem,
8659 else => |err| return posix.unexpectedErrno(err),
8660 }
8661 },
8662 }
8663 }
8664}
8665
8666const dirSetOwner = switch (native_os) {
8667 .windows => dirSetOwnerUnsupported,
8668 else => dirSetOwnerPosix,
8669};
8670
8671fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void {
8672 _ = userdata;
8673 _ = dir;
8674 _ = owner;
8675 _ = group;
8676 return error.Unexpected;
8677}
8678
8679fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void {
8680 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
8681 const t: *Threaded = @ptrCast(@alignCast(userdata));
8682 _ = t;
8683 const uid = owner orelse ~@as(posix.uid_t, 0);
8684 const gid = group orelse ~@as(posix.gid_t, 0);
8685 return posixFchown(dir.handle, uid, gid);
8686}
8687
8688fn posixFchown(fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void {
8689 comptime assert(have_fchown);
8690 const syscall: Syscall = try .start();
8691 while (true) {
8692 switch (posix.errno(posix.system.fchown(fd, uid, gid))) {
8693 .SUCCESS => return syscall.finish(),
8694 .INTR => {
8695 try syscall.checkCancel();
8696 continue;
8697 },
8698 else => |e| {
8699 syscall.finish();
8700 switch (e) {
8701 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate`
8702 .FAULT => |err| return errnoBug(err),
8703 .INVAL => |err| return errnoBug(err),
8704 .ACCES => return error.AccessDenied,
8705 .IO => return error.InputOutput,
8706 .LOOP => return error.SymLinkLoop,
8707 .NOENT => return error.FileNotFound,
8708 .NOMEM => return error.SystemResources,
8709 .NOTDIR => return error.FileNotFound,
8710 .PERM => return error.PermissionDenied,
8711 .ROFS => return error.ReadOnlyFileSystem,
8712 else => |err| return posix.unexpectedErrno(err),
8713 }
8714 },
8715 }
8716 }
8717}
8718
8719fn dirSetFileOwner(
8720 userdata: ?*anyopaque,
8721 dir: Dir,
8722 sub_path: []const u8,
8723 owner: ?File.Uid,
8724 group: ?File.Gid,
8725 options: Dir.SetFileOwnerOptions,
8726) Dir.SetFileOwnerError!void {
8727 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
8728 const t: *Threaded = @ptrCast(@alignCast(userdata));
8729 _ = t;
8730
8731 var path_buffer: [posix.PATH_MAX]u8 = undefined;
8732 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
8733
8734 _ = dir;
8735 _ = sub_path_posix;
8736 _ = owner;
8737 _ = group;
8738 _ = options;
8739 @panic("TODO implement dirSetFileOwner");
8740}
8741
8742const fileSync = switch (native_os) {
8743 .windows => fileSyncWindows,
8744 .wasi => fileSyncWasi,
8745 else => fileSyncPosix,
8746};
8747
8748fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
8749 const t: *Threaded = @ptrCast(@alignCast(userdata));
8750 _ = t;
8751
8752 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8753 const syscall: Syscall = try .start();
8754 while (true) {
8755 switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) {
8756 .SUCCESS => break syscall.finish(),
8757 .CANCELLED => {
8758 try syscall.checkCancel();
8759 continue;
8760 },
8761 .INVALID_HANDLE => unreachable,
8762 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time
8763 .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput),
8764 else => |status| return syscall.unexpectedNtstatus(status),
8765 }
8766 }
8767}
8768
8769fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void {
8770 const t: *Threaded = @ptrCast(@alignCast(userdata));
8771 _ = t;
8772 const syscall: Syscall = try .start();
8773 while (true) {
8774 switch (posix.errno(posix.system.fsync(file.handle))) {
8775 .SUCCESS => return syscall.finish(),
8776 .INTR => {
8777 try syscall.checkCancel();
8778 continue;
8779 },
8780 else => |e| {
8781 syscall.finish();
8782 switch (e) {
8783 .BADF => |err| return errnoBug(err),
8784 .INVAL => |err| return errnoBug(err),
8785 .ROFS => |err| return errnoBug(err),
8786 .IO => return error.InputOutput,
8787 .NOSPC => return error.NoSpaceLeft,
8788 .DQUOT => return error.DiskQuota,
8789 else => |err| return posix.unexpectedErrno(err),
8790 }
8791 },
8792 }
8793 }
8794}
8795
8796fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void {
8797 const t: *Threaded = @ptrCast(@alignCast(userdata));
8798 _ = t;
8799 const syscall: Syscall = try .start();
8800 while (true) {
8801 switch (std.os.wasi.fd_sync(file.handle)) {
8802 .SUCCESS => return syscall.finish(),
8803 .INTR => {
8804 try syscall.checkCancel();
8805 continue;
8806 },
8807 else => |e| {
8808 syscall.finish();
8809 switch (e) {
8810 .BADF => |err| return errnoBug(err),
8811 .INVAL => |err| return errnoBug(err),
8812 .ROFS => |err| return errnoBug(err),
8813 .IO => return error.InputOutput,
8814 .NOSPC => return error.NoSpaceLeft,
8815 .DQUOT => return error.DiskQuota,
8816 else => |err| return posix.unexpectedErrno(err),
8817 }
8818 },
8819 }
8820 }
8821}
8822
8823fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
8824 const t: *Threaded = @ptrCast(@alignCast(userdata));
8825 _ = t;
8826 return isTty(file);
8827}
8828
8829fn isTty(file: File) Io.Cancelable!bool {
8830 if (is_windows) {
8831 var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE;
8832 switch ((try deviceIoControl(&.{
8833 .file = .{
8834 .handle = windows.peb().ProcessParameters.ConsoleHandle,
8835 .flags = .{ .nonblocking = false },
8836 },
8837 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO,
8838 .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})),
8839 })).u.Status) {
8840 .SUCCESS => return true,
8841 .CANCELLED => unreachable,
8842 .INVALID_HANDLE => return isCygwinPty(file),
8843 else => return false,
8844 }
8845 }
8846
8847 if (builtin.link_libc) {
8848 const syscall: Syscall = try .start();
8849 while (true) {
8850 const rc = posix.system.isatty(file.handle);
8851 switch (posix.errno(rc - 1)) {
8852 .SUCCESS => {
8853 syscall.finish();
8854 return true;
8855 },
8856 .INTR => {
8857 try syscall.checkCancel();
8858 continue;
8859 },
8860 else => {
8861 syscall.finish();
8862 return false;
8863 },
8864 }
8865 }
8866 }
8867
8868 if (native_os == .wasi) {
8869 var statbuf: std.os.wasi.fdstat_t = undefined;
8870 const err = std.os.wasi.fd_fdstat_get(file.handle, &statbuf);
8871 if (err != .SUCCESS)
8872 return false;
8873
8874 // A tty is a character device that we can't seek or tell on.
8875 if (statbuf.fs_filetype != .CHARACTER_DEVICE)
8876 return false;
8877 if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL)
8878 return false;
8879
8880 return true;
8881 }
8882
8883 if (native_os == .linux) {
8884 const linux = std.os.linux;
8885 const syscall: Syscall = try .start();
8886 while (true) {
8887 var wsz: posix.winsize = undefined;
8888 const fd: usize = @bitCast(@as(isize, file.handle));
8889 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
8890 switch (linux.errno(rc)) {
8891 .SUCCESS => {
8892 syscall.finish();
8893 return true;
8894 },
8895 .INTR => {
8896 try syscall.checkCancel();
8897 continue;
8898 },
8899 else => {
8900 syscall.finish();
8901 return false;
8902 },
8903 }
8904 }
8905 }
8906
8907 @compileError("unimplemented");
8908}
8909
8910fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void {
8911 const t: *Threaded = @ptrCast(@alignCast(userdata));
8912
8913 if (!is_windows) return if (!try supportsAnsiEscapeCodes(t, file)) error.NotTerminalDevice;
8914
8915 // For Windows Terminal, VT Sequences processing is enabled by default.
8916 const console: File = .{
8917 .handle = windows.peb().ProcessParameters.ConsoleHandle,
8918 .flags = .{ .nonblocking = false },
8919 };
8920 var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE;
8921 switch ((try deviceIoControl(&.{
8922 .file = console,
8923 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO,
8924 .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})),
8925 })).u.Status) {
8926 .SUCCESS => {},
8927 .CANCELLED => unreachable,
8928 .INVALID_HANDLE => return if (!try isCygwinPty(file)) error.NotTerminalDevice,
8929 else => return error.NotTerminalDevice,
8930 }
8931
8932 if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
8933
8934 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
8935 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
8936 //
8937 // Note: In Microsoft's example for enabling virtual terminal processing, it
8938 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
8939 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
8940 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
8941 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
8942 // Additionally, the default console mode in Windows Terminal does not have
8943 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
8944 // we end up matching the mode of Windows Terminal.
8945 var set_console_mode = windows.CONSOLE.USER_IO.SET_MODE(
8946 get_console_mode.Data | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
8947 );
8948 switch ((try deviceIoControl(&.{
8949 .file = console,
8950 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO,
8951 .in = @ptrCast(&set_console_mode.request(file, 0, .{}, 0, .{})),
8952 })).u.Status) {
8953 .SUCCESS => {},
8954 .CANCELLED => unreachable,
8955 else => |status| return windows.unexpectedStatus(status),
8956 }
8957}
8958
8959fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
8960 const t: *Threaded = @ptrCast(@alignCast(userdata));
8961 return supportsAnsiEscapeCodes(t, file);
8962}
8963
8964fn supportsAnsiEscapeCodes(t: *Threaded, file: File) Io.Cancelable!bool {
8965 if (is_windows) {
8966 var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE;
8967 return switch ((try deviceIoControl(&.{
8968 .file = .{
8969 .handle = windows.peb().ProcessParameters.ConsoleHandle,
8970 .flags = .{ .nonblocking = false },
8971 },
8972 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO,
8973 .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})),
8974 })).u.Status) {
8975 .SUCCESS => get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0,
8976 .CANCELLED => unreachable,
8977 .INVALID_HANDLE => isCygwinPty(file),
8978 else => false,
8979 };
8980 }
8981
8982 if (native_os == .wasi) {
8983 // WASI sanitizes stdout when fd is a tty so ANSI escape codes
8984 // will not be interpreted as actual cursor commands, and
8985 // stderr is always sanitized.
8986
8987 return false;
8988 }
8989
8990 if (try isTty(file)) {
8991 if (file.handle == posix.STDOUT_FILENO or file.handle == posix.STDERR_FILENO) {
8992 t.scanEnviron();
8993 if (t.environ.string.TERM) |term| {
8994 if (std.mem.eql(u8, term, "dumb")) {
8995 return false;
8996 }
8997 }
8998 }
8999
9000 return true;
9001 }
9002
9003 return false;
9004}
9005
9006fn isCygwinPty(file: File) Io.Cancelable!bool {
9007 if (!is_windows) return false;
9008
9009 const handle = file.handle;
9010
9011 // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats:
9012 // msys-[...]-ptyN-[...]
9013 // cygwin-[...]-ptyN-[...]
9014 //
9015 // Example: msys-1888ae32e00d56aa-pty0-to-master
9016
9017 // First, just check that the handle is a named pipe.
9018 // This allows us to avoid the more costly NtQueryInformationFile call
9019 // for handles that aren't named pipes.
9020 {
9021 var io_status: windows.IO_STATUS_BLOCK = undefined;
9022 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
9023 const syscall: Syscall = try .start();
9024 while (true) switch (windows.ntdll.NtQueryVolumeInformationFile(
9025 handle,
9026 &io_status,
9027 &device_info,
9028 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
9029 .Device,
9030 )) {
9031 .SUCCESS => break syscall.finish(),
9032 .CANCELLED => {
9033 try syscall.checkCancel();
9034 continue;
9035 },
9036 else => {
9037 syscall.finish();
9038 return false;
9039 },
9040 };
9041 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;
9042 }
9043
9044 const name_bytes_offset = @offsetOf(windows.FILE.NAME_INFORMATION, "FileName");
9045 // `NAME_MAX` UTF-16 code units (2 bytes each)
9046 // This buffer may not be long enough to handle *all* possible paths
9047 // (PATH_MAX_WIDE would be necessary for that), but because we only care
9048 // about certain paths and we know they must be within a reasonable length,
9049 // we can use this smaller buffer and just return false on any error from
9050 // NtQueryInformationFile.
9051 const num_name_bytes = windows.MAX_PATH * 2;
9052 var name_info_bytes: [name_bytes_offset + num_name_bytes]u8 align(@alignOf(windows.FILE.NAME_INFORMATION)) = @splat(0);
9053
9054 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9055 const syscall: Syscall = try .start();
9056 while (true) switch (windows.ntdll.NtQueryInformationFile(
9057 handle,
9058 &io_status_block,
9059 &name_info_bytes,
9060 @intCast(name_info_bytes.len),
9061 .Name,
9062 )) {
9063 .SUCCESS => break syscall.finish(),
9064 .CANCELLED => {
9065 try syscall.checkCancel();
9066 continue;
9067 },
9068 .INVALID_PARAMETER => unreachable,
9069 else => {
9070 syscall.finish();
9071 return false;
9072 },
9073 };
9074
9075 const name_info: *const windows.FILE.NAME_INFORMATION = @ptrCast(&name_info_bytes);
9076 const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength];
9077 const name_wide = std.mem.bytesAsSlice(u16, name_bytes);
9078 // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master
9079 return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or
9080 std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and
9081 std.mem.find(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null;
9082}
9083
9084fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void {
9085 const t: *Threaded = @ptrCast(@alignCast(userdata));
9086 _ = t;
9087
9088 const signed_len: i64 = @bitCast(length);
9089 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors.
9090
9091 if (is_windows) {
9092 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9093 var eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{
9094 .EndOfFile = signed_len,
9095 };
9096
9097 const syscall: Syscall = try .start();
9098 while (true) switch (windows.ntdll.NtSetInformationFile(
9099 file.handle,
9100 &io_status_block,
9101 &eof_info,
9102 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION),
9103 .EndOfFile,
9104 )) {
9105 .SUCCESS => return syscall.finish(),
9106 .CANCELLED => {
9107 try syscall.checkCancel();
9108 continue;
9109 },
9110 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing.
9111 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
9112 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
9113 .INVALID_PARAMETER => return syscall.fail(error.FileTooBig),
9114 else => |status| return syscall.unexpectedNtstatus(status),
9115 };
9116 }
9117
9118 if (native_os == .wasi and !builtin.link_libc) {
9119 const syscall: Syscall = try .start();
9120 while (true) {
9121 switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) {
9122 .SUCCESS => return syscall.finish(),
9123 .INTR => {
9124 try syscall.checkCancel();
9125 continue;
9126 },
9127 else => |e| {
9128 syscall.finish();
9129 switch (e) {
9130 .FBIG => return error.FileTooBig,
9131 .IO => return error.InputOutput,
9132 .PERM => return error.PermissionDenied,
9133 .TXTBSY => return error.FileBusy,
9134 .BADF => |err| return errnoBug(err), // Handle not open for writing
9135 .INVAL => return error.NonResizable,
9136 .NOTCAPABLE => return error.AccessDenied,
9137 else => |err| return posix.unexpectedErrno(err),
9138 }
9139 },
9140 }
9141 }
9142 }
9143
9144 const syscall: Syscall = try .start();
9145 while (true) {
9146 switch (posix.errno(ftruncate_sym(file.handle, signed_len))) {
9147 .SUCCESS => return syscall.finish(),
9148 .INTR => {
9149 try syscall.checkCancel();
9150 continue;
9151 },
9152 else => |e| {
9153 syscall.finish();
9154 switch (e) {
9155 .FBIG => return error.FileTooBig,
9156 .IO => return error.InputOutput,
9157 .PERM => return error.PermissionDenied,
9158 .TXTBSY => return error.FileBusy,
9159 .BADF => |err| return errnoBug(err), // Handle not open for writing.
9160 .INVAL => return error.NonResizable, // This is returned for /dev/null for example.
9161 else => |err| return posix.unexpectedErrno(err),
9162 }
9163 },
9164 }
9165 }
9166}
9167
9168fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void {
9169 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
9170 const t: *Threaded = @ptrCast(@alignCast(userdata));
9171 _ = t;
9172 const uid = owner orelse ~@as(posix.uid_t, 0);
9173 const gid = group orelse ~@as(posix.gid_t, 0);
9174 return posixFchown(file.handle, uid, gid);
9175}
9176
9177fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void {
9178 if (@sizeOf(File.Permissions) == 0) return;
9179 const t: *Threaded = @ptrCast(@alignCast(userdata));
9180 _ = t;
9181 switch (native_os) {
9182 .windows => {
9183 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9184 var info: windows.FILE.BASIC_INFORMATION = .{
9185 .CreationTime = 0,
9186 .LastAccessTime = 0,
9187 .LastWriteTime = 0,
9188 .ChangeTime = 0,
9189 .FileAttributes = permissions.toAttributes(),
9190 };
9191 const syscall: Syscall = try .start();
9192 while (true) switch (windows.ntdll.NtSetInformationFile(
9193 file.handle,
9194 &io_status_block,
9195 &info,
9196 @sizeOf(windows.FILE.BASIC_INFORMATION),
9197 .Basic,
9198 )) {
9199 .SUCCESS => return syscall.finish(),
9200 .CANCELLED => {
9201 try syscall.checkCancel();
9202 continue;
9203 },
9204 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err),
9205 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
9206 else => |status| return syscall.unexpectedNtstatus(status),
9207 };
9208 },
9209 .wasi => return error.Unexpected, // Unsupported OS.
9210 else => return setPermissionsPosix(file.handle, permissions.toMode()),
9211 }
9212}
9213
9214fn setPermissionsPosix(fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void {
9215 comptime assert(have_fchmod);
9216 const syscall: Syscall = try .start();
9217 while (true) {
9218 switch (posix.errno(posix.system.fchmod(fd, mode))) {
9219 .SUCCESS => return syscall.finish(),
9220 .INTR => {
9221 try syscall.checkCancel();
9222 continue;
9223 },
9224 else => |e| {
9225 syscall.finish();
9226 switch (e) {
9227 .BADF => |err| return errnoBug(err),
9228 .FAULT => |err| return errnoBug(err),
9229 .INVAL => |err| return errnoBug(err),
9230 .ACCES => return error.AccessDenied,
9231 .IO => return error.InputOutput,
9232 .LOOP => return error.SymLinkLoop,
9233 .NOENT => return error.FileNotFound,
9234 .NOMEM => return error.SystemResources,
9235 .NOTDIR => return error.FileNotFound,
9236 .PERM => return error.PermissionDenied,
9237 .ROFS => return error.ReadOnlyFileSystem,
9238 else => |err| return posix.unexpectedErrno(err),
9239 }
9240 },
9241 }
9242 }
9243}
9244
9245fn dirSetTimestamps(
9246 userdata: ?*anyopaque,
9247 dir: Dir,
9248 sub_path: []const u8,
9249 options: Dir.SetTimestampsOptions,
9250) Dir.SetTimestampsError!void {
9251 const t: *Threaded = @ptrCast(@alignCast(userdata));
9252 _ = t;
9253
9254 if (is_windows) {
9255 @panic("TODO implement dirSetTimestamps windows");
9256 }
9257
9258 if (native_os == .wasi and !builtin.link_libc) {
9259 @panic("TODO implement dirSetTimestamps wasi");
9260 }
9261
9262 var times_buffer: [2]posix.timespec = undefined;
9263 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: {
9264 times_buffer = .{
9265 setTimestampToPosix(options.access_timestamp),
9266 setTimestampToPosix(options.modify_timestamp),
9267 };
9268 break :p &times_buffer;
9269 };
9270
9271 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
9272
9273 var path_buffer: [posix.PATH_MAX]u8 = undefined;
9274 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
9275
9276 const syscall: Syscall = try .start();
9277 while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) {
9278 .SUCCESS => return syscall.finish(),
9279 .INTR => {
9280 try syscall.checkCancel();
9281 continue;
9282 },
9283 .BADF => |err| return syscall.errnoBug(err), // always a race condition
9284 .FAULT => |err| return syscall.errnoBug(err),
9285 .INVAL => |err| return syscall.errnoBug(err),
9286 .ACCES => return syscall.fail(error.AccessDenied),
9287 .PERM => return syscall.fail(error.PermissionDenied),
9288 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
9289 else => |err| return syscall.unexpectedErrno(err),
9290 };
9291}
9292
9293fn fileSetTimestamps(
9294 userdata: ?*anyopaque,
9295 file: File,
9296 options: File.SetTimestampsOptions,
9297) File.SetTimestampsError!void {
9298 const t: *Threaded = @ptrCast(@alignCast(userdata));
9299 _ = t;
9300
9301 if (is_windows) {
9302 const now_sys = if (options.access_timestamp == .now or options.modify_timestamp == .now)
9303 windows.ntdll.RtlGetSystemTimePrecise()
9304 else
9305 undefined;
9306 var iosb: windows.IO_STATUS_BLOCK = undefined;
9307 var info: windows.FILE.BASIC_INFORMATION = .{
9308 .CreationTime = 0,
9309 .LastAccessTime = switch (options.access_timestamp) {
9310 .unchanged => 0,
9311 .now => now_sys,
9312 .new => |ts| windows.toSysTime(ts),
9313 },
9314 .LastWriteTime = switch (options.modify_timestamp) {
9315 .unchanged => 0,
9316 .now => now_sys,
9317 .new => |ts| windows.toSysTime(ts),
9318 },
9319 .ChangeTime = 0,
9320 .FileAttributes = .{},
9321 };
9322 var syscall: Syscall = try .start();
9323 while (true) switch (windows.ntdll.NtSetInformationFile(
9324 file.handle,
9325 &iosb,
9326 &info,
9327 @sizeOf(windows.FILE.BASIC_INFORMATION),
9328 .Basic,
9329 )) {
9330 .SUCCESS => return syscall.finish(),
9331 .CANCELLED => try syscall.checkCancel(),
9332 else => |status| return syscall.unexpectedNtstatus(status),
9333 };
9334 }
9335
9336 if (native_os == .wasi and !builtin.link_libc) {
9337 var atime: std.os.wasi.timestamp_t = 0;
9338 var mtime: std.os.wasi.timestamp_t = 0;
9339 var flags: std.os.wasi.fstflags_t = .{};
9340
9341 switch (options.access_timestamp) {
9342 .unchanged => {},
9343 .now => flags.ATIM_NOW = true,
9344 .new => |ts| {
9345 atime = timestampToPosix(ts.nanoseconds).toTimestamp();
9346 flags.ATIM = true;
9347 },
9348 }
9349
9350 switch (options.modify_timestamp) {
9351 .unchanged => {},
9352 .now => flags.MTIM_NOW = true,
9353 .new => |ts| {
9354 mtime = timestampToPosix(ts.nanoseconds).toTimestamp();
9355 flags.MTIM = true;
9356 },
9357 }
9358
9359 const syscall: Syscall = try .start();
9360 while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) {
9361 .SUCCESS => return syscall.finish(),
9362 .INTR => {
9363 try syscall.checkCancel();
9364 continue;
9365 },
9366 .BADF => |err| return syscall.errnoBug(err), // File descriptor use-after-free.
9367 .FAULT => |err| return syscall.errnoBug(err),
9368 .INVAL => |err| return syscall.errnoBug(err),
9369 .ACCES => return syscall.fail(error.AccessDenied),
9370 .PERM => return syscall.fail(error.PermissionDenied),
9371 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
9372 else => |err| return syscall.unexpectedErrno(err),
9373 };
9374 }
9375
9376 var times_buffer: [2]posix.timespec = undefined;
9377 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: {
9378 times_buffer = .{
9379 setTimestampToPosix(options.access_timestamp),
9380 setTimestampToPosix(options.modify_timestamp),
9381 };
9382 break :p &times_buffer;
9383 };
9384
9385 const syscall: Syscall = try .start();
9386 while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) {
9387 .SUCCESS => return syscall.finish(),
9388 .INTR => {
9389 try syscall.checkCancel();
9390 continue;
9391 },
9392 .BADF => |err| return syscall.errnoBug(err), // always a race condition
9393 .FAULT => |err| return syscall.errnoBug(err),
9394 .INVAL => |err| return syscall.errnoBug(err),
9395 .ACCES => return syscall.fail(error.AccessDenied),
9396 .PERM => return syscall.fail(error.PermissionDenied),
9397 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
9398 else => |err| return syscall.unexpectedErrno(err),
9399 };
9400}
9401
9402const windows_lock_range_off: windows.LARGE_INTEGER = 0;
9403const windows_lock_range_len: windows.LARGE_INTEGER = 1;
9404
9405fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {
9406 if (native_os == .wasi) return error.FileLocksUnsupported;
9407 const t: *Threaded = @ptrCast(@alignCast(userdata));
9408 _ = t;
9409
9410 if (is_windows) {
9411 const exclusive = switch (lock) {
9412 .none => {
9413 // To match the non-Windows behavior, unlock
9414 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9415 while (true) switch (windows.ntdll.NtUnlockFile(
9416 file.handle,
9417 &io_status_block,
9418 &windows_lock_range_off,
9419 &windows_lock_range_len,
9420 0,
9421 )) {
9422 .SUCCESS => return,
9423 .CANCELLED => continue,
9424 .RANGE_NOT_LOCKED => return,
9425 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
9426 else => |status| return windows.unexpectedStatus(status),
9427 };
9428 },
9429 .shared => false,
9430 .exclusive => true,
9431 };
9432 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9433 const syscall: Syscall = try .start();
9434 while (true) switch (windows.ntdll.NtLockFile(
9435 file.handle,
9436 null,
9437 null,
9438 null,
9439 &io_status_block,
9440 &windows_lock_range_off,
9441 &windows_lock_range_len,
9442 null,
9443 .FALSE,
9444 .fromBool(exclusive),
9445 )) {
9446 .SUCCESS => return syscall.finish(),
9447 .CANCELLED => {
9448 try syscall.checkCancel();
9449 continue;
9450 },
9451 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
9452 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // passed FailImmediately=false
9453 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
9454 else => |status| return syscall.unexpectedNtstatus(status),
9455 };
9456 }
9457
9458 const operation: i32 = switch (lock) {
9459 .none => posix.LOCK.UN,
9460 .shared => posix.LOCK.SH,
9461 .exclusive => posix.LOCK.EX,
9462 };
9463 const syscall: Syscall = try .start();
9464 while (true) {
9465 switch (posix.errno(posix.system.flock(file.handle, operation))) {
9466 .SUCCESS => return syscall.finish(),
9467 .INTR => {
9468 try syscall.checkCancel();
9469 continue;
9470 },
9471 else => |e| {
9472 syscall.finish();
9473 switch (e) {
9474 .BADF => |err| return errnoBug(err),
9475 .INVAL => |err| return errnoBug(err), // invalid parameters
9476 .NOLCK => return error.SystemResources,
9477 .AGAIN => |err| return errnoBug(err),
9478 .OPNOTSUPP => return error.FileLocksUnsupported,
9479 else => |err| return posix.unexpectedErrno(err),
9480 }
9481 },
9482 }
9483 }
9484}
9485
9486fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {
9487 if (native_os == .wasi) return error.FileLocksUnsupported;
9488 const t: *Threaded = @ptrCast(@alignCast(userdata));
9489 _ = t;
9490
9491 if (is_windows) {
9492 const exclusive = switch (lock) {
9493 .none => {
9494 // To match the non-Windows behavior, unlock
9495 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9496 while (true) switch (windows.ntdll.NtUnlockFile(
9497 file.handle,
9498 &io_status_block,
9499 &windows_lock_range_off,
9500 &windows_lock_range_len,
9501 0,
9502 )) {
9503 .SUCCESS => return true,
9504 .CANCELLED => continue,
9505 .RANGE_NOT_LOCKED => return false,
9506 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
9507 else => |status| return windows.unexpectedStatus(status),
9508 };
9509 },
9510 .shared => false,
9511 .exclusive => true,
9512 };
9513 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9514 const syscall: Syscall = try .start();
9515 while (true) switch (windows.ntdll.NtLockFile(
9516 file.handle,
9517 null,
9518 null,
9519 null,
9520 &io_status_block,
9521 &windows_lock_range_off,
9522 &windows_lock_range_len,
9523 null,
9524 .TRUE,
9525 .fromBool(exclusive),
9526 )) {
9527 .SUCCESS => {
9528 syscall.finish();
9529 return true;
9530 },
9531 .LOCK_NOT_GRANTED => {
9532 syscall.finish();
9533 return false;
9534 },
9535 .CANCELLED => {
9536 try syscall.checkCancel();
9537 continue;
9538 },
9539 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
9540 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
9541 else => |status| return syscall.unexpectedNtstatus(status),
9542 };
9543 }
9544
9545 const operation: i32 = switch (lock) {
9546 .none => posix.LOCK.UN,
9547 .shared => posix.LOCK.SH | posix.LOCK.NB,
9548 .exclusive => posix.LOCK.EX | posix.LOCK.NB,
9549 };
9550 const syscall: Syscall = try .start();
9551 while (true) {
9552 switch (posix.errno(posix.system.flock(file.handle, operation))) {
9553 .SUCCESS => {
9554 syscall.finish();
9555 return true;
9556 },
9557 .INTR => {
9558 try syscall.checkCancel();
9559 continue;
9560 },
9561 .AGAIN => {
9562 syscall.finish();
9563 return false;
9564 },
9565 else => |e| {
9566 syscall.finish();
9567 switch (e) {
9568 .BADF => |err| return errnoBug(err),
9569 .INVAL => |err| return errnoBug(err), // invalid parameters
9570 .NOLCK => return error.SystemResources,
9571 .OPNOTSUPP => return error.FileLocksUnsupported,
9572 else => |err| return posix.unexpectedErrno(err),
9573 }
9574 },
9575 }
9576 }
9577}
9578
9579fn fileUnlock(userdata: ?*anyopaque, file: File) void {
9580 if (native_os == .wasi) return;
9581 const t: *Threaded = @ptrCast(@alignCast(userdata));
9582 _ = t;
9583
9584 if (is_windows) {
9585 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9586 while (true) switch (windows.ntdll.NtUnlockFile(
9587 file.handle,
9588 &io_status_block,
9589 &windows_lock_range_off,
9590 &windows_lock_range_len,
9591 0,
9592 )) {
9593 .SUCCESS => return,
9594 .CANCELLED => continue,
9595 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked.
9596 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
9597 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
9598 };
9599 }
9600
9601 while (true) {
9602 switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) {
9603 .SUCCESS => return,
9604 .CANCELED, .INTR => continue,
9605 .AGAIN => return assert(!is_debug), // unlocking can't block
9606 .BADF => return assert(!is_debug), // File descriptor used after closed.
9607 .INVAL => return assert(!is_debug), // invalid parameters
9608 .NOLCK => return assert(!is_debug), // Resource deallocation.
9609 .OPNOTSUPP => return assert(!is_debug), // We already got the lock.
9610 else => return assert(!is_debug), // Resource deallocation must succeed.
9611 }
9612 }
9613}
9614
9615fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {
9616 if (native_os == .wasi) return;
9617 const t: *Threaded = @ptrCast(@alignCast(userdata));
9618 _ = t;
9619
9620 if (is_windows) {
9621 // On Windows it works like a semaphore + exclusivity flag. To
9622 // implement this function, we first obtain another lock in shared
9623 // mode. This changes the exclusivity flag, but increments the
9624 // semaphore to 2. So we follow up with an NtUnlockFile which
9625 // decrements the semaphore but does not modify the exclusivity flag.
9626 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
9627 const syscall: Syscall = try .start();
9628 while (true) switch (windows.ntdll.NtLockFile(
9629 file.handle,
9630 null,
9631 null,
9632 null,
9633 &io_status_block,
9634 &windows_lock_range_off,
9635 &windows_lock_range_len,
9636 null,
9637 .TRUE,
9638 .FALSE,
9639 )) {
9640 .SUCCESS => break syscall.finish(),
9641 .CANCELLED => {
9642 try syscall.checkCancel();
9643 continue;
9644 },
9645 .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err),
9646 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // File was not locked in exclusive mode.
9647 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
9648 else => |status| return syscall.unexpectedNtstatus(status),
9649 };
9650 while (true) switch (windows.ntdll.NtUnlockFile(
9651 file.handle,
9652 &io_status_block,
9653 &windows_lock_range_off,
9654 &windows_lock_range_len,
9655 0,
9656 )) {
9657 .SUCCESS => return,
9658 .CANCELLED => continue,
9659 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked.
9660 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer
9661 else => if (is_debug) unreachable else return, // Resource deallocation must succeed.
9662 };
9663 }
9664
9665 const operation = posix.LOCK.SH | posix.LOCK.NB;
9666
9667 const syscall: Syscall = try .start();
9668 while (true) {
9669 switch (posix.errno(posix.system.flock(file.handle, operation))) {
9670 .SUCCESS => {
9671 syscall.finish();
9672 return;
9673 },
9674 .INTR => {
9675 try syscall.checkCancel();
9676 continue;
9677 },
9678 else => |e| {
9679 syscall.finish();
9680 switch (e) {
9681 .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode.
9682 .BADF => |err| return errnoBug(err),
9683 .INVAL => |err| return errnoBug(err), // invalid parameters
9684 .NOLCK => |err| return errnoBug(err), // Lock already obtained.
9685 .OPNOTSUPP => |err| return errnoBug(err), // Lock already obtained.
9686 else => |err| return posix.unexpectedErrno(err),
9687 }
9688 },
9689 }
9690 }
9691}
9692
9693fn dirOpenDirWasi(
9694 userdata: ?*anyopaque,
9695 dir: Dir,
9696 sub_path: []const u8,
9697 options: Dir.OpenOptions,
9698) Dir.OpenError!Dir {
9699 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
9700 const t: *Threaded = @ptrCast(@alignCast(userdata));
9701 _ = t;
9702 const wasi = std.os.wasi;
9703
9704 var base: std.os.wasi.rights_t = .{
9705 .FD_FILESTAT_GET = true,
9706 .FD_FDSTAT_SET_FLAGS = true,
9707 .FD_FILESTAT_SET_TIMES = true,
9708 };
9709 if (options.access_sub_paths) {
9710 base.FD_READDIR = true;
9711 base.PATH_CREATE_DIRECTORY = true;
9712 base.PATH_CREATE_FILE = true;
9713 base.PATH_LINK_SOURCE = true;
9714 base.PATH_LINK_TARGET = true;
9715 base.PATH_OPEN = true;
9716 base.PATH_READLINK = true;
9717 base.PATH_RENAME_SOURCE = true;
9718 base.PATH_RENAME_TARGET = true;
9719 base.PATH_FILESTAT_GET = true;
9720 base.PATH_FILESTAT_SET_SIZE = true;
9721 base.PATH_FILESTAT_SET_TIMES = true;
9722 base.PATH_SYMLINK = true;
9723 base.PATH_REMOVE_DIRECTORY = true;
9724 base.PATH_UNLINK_FILE = true;
9725 }
9726
9727 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks };
9728 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
9729 const fdflags: wasi.fdflags_t = .{};
9730 var fd: posix.fd_t = undefined;
9731 const syscall: Syscall = try .start();
9732 while (true) {
9733 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
9734 .SUCCESS => {
9735 syscall.finish();
9736 return .{ .handle = fd };
9737 },
9738 .INTR => {
9739 try syscall.checkCancel();
9740 continue;
9741 },
9742 else => |e| {
9743 syscall.finish();
9744 switch (e) {
9745 .FAULT => |err| return errnoBug(err),
9746 .INVAL => return error.BadPathName,
9747 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
9748 .ACCES => return error.AccessDenied,
9749 .LOOP => return error.SymLinkLoop,
9750 .MFILE => return error.ProcessFdQuotaExceeded,
9751 .NAMETOOLONG => return error.NameTooLong,
9752 .NFILE => return error.SystemFdQuotaExceeded,
9753 .NODEV => return error.NoDevice,
9754 .NOENT => return error.FileNotFound,
9755 .NOMEM => return error.SystemResources,
9756 .NOTDIR => return error.NotDir,
9757 .PERM => return error.PermissionDenied,
9758 .NOTCAPABLE => return error.AccessDenied,
9759 .ILSEQ => return error.BadPathName,
9760 else => |err| return posix.unexpectedErrno(err),
9761 }
9762 },
9763 }
9764 }
9765}
9766
9767fn dirHardLink(
9768 userdata: ?*anyopaque,
9769 old_dir: Dir,
9770 old_sub_path: []const u8,
9771 new_dir: Dir,
9772 new_sub_path: []const u8,
9773 options: Dir.HardLinkOptions,
9774) Dir.HardLinkError!void {
9775 if (is_windows) return error.OperationUnsupported;
9776 const t: *Threaded = @ptrCast(@alignCast(userdata));
9777 _ = t;
9778
9779 if (native_os == .wasi and !builtin.link_libc) {
9780 const flags: std.os.wasi.lookupflags_t = .{
9781 .SYMLINK_FOLLOW = options.follow_symlinks,
9782 };
9783 const syscall: Syscall = try .start();
9784 while (true) {
9785 switch (std.os.wasi.path_link(
9786 old_dir.handle,
9787 flags,
9788 old_sub_path.ptr,
9789 old_sub_path.len,
9790 new_dir.handle,
9791 new_sub_path.ptr,
9792 new_sub_path.len,
9793 )) {
9794 .SUCCESS => return syscall.finish(),
9795 .INTR => {
9796 try syscall.checkCancel();
9797 continue;
9798 },
9799 else => |e| {
9800 syscall.finish();
9801 switch (e) {
9802 .ACCES => return error.AccessDenied,
9803 .DQUOT => return error.DiskQuota,
9804 .EXIST => return error.PathAlreadyExists,
9805 .FAULT => |err| return errnoBug(err),
9806 .IO => return error.HardwareFailure,
9807 .LOOP => return error.SymLinkLoop,
9808 .MLINK => return error.LinkQuotaExceeded,
9809 .NAMETOOLONG => return error.NameTooLong,
9810 .NOENT => return error.FileNotFound,
9811 .NOMEM => return error.SystemResources,
9812 .NOSPC => return error.NoSpaceLeft,
9813 .NOTDIR => return error.NotDir,
9814 .PERM => return error.PermissionDenied,
9815 .ROFS => return error.ReadOnlyFileSystem,
9816 .XDEV => return error.CrossDevice,
9817 .INVAL => |err| return errnoBug(err),
9818 .ILSEQ => return error.BadPathName,
9819 else => |err| return posix.unexpectedErrno(err),
9820 }
9821 },
9822 }
9823 }
9824 }
9825
9826 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
9827 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
9828
9829 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
9830 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
9831
9832 const flags: u32 = if (options.follow_symlinks) posix.AT.SYMLINK_FOLLOW else 0;
9833 return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags);
9834}
9835
9836fn fileClose(userdata: ?*anyopaque, files: []const File) void {
9837 const t: *Threaded = @ptrCast(@alignCast(userdata));
9838 _ = t;
9839 for (files) |file| {
9840 if (is_windows) {
9841 windows.CloseHandle(file.handle);
9842 } else {
9843 closeFd(file.handle);
9844 }
9845 }
9846}
9847
9848fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize {
9849 const t: *Threaded = @ptrCast(@alignCast(userdata));
9850 _ = t;
9851 if (is_windows) return fileReadStreamingWindows(file, data);
9852 return fileReadStreamingPosix(file, data);
9853}
9854
9855fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize {
9856 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
9857 var i: usize = 0;
9858 for (data) |buf| {
9859 if (iovecs_buffer.len - i == 0) break;
9860 if (buf.len != 0) {
9861 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
9862 i += 1;
9863 }
9864 }
9865 if (i == 0) return 0;
9866 const dest = iovecs_buffer[0..i];
9867 assert(dest[0].len > 0);
9868
9869 if (native_os == .wasi and !builtin.link_libc) {
9870 const syscall: Syscall = try .start();
9871 while (true) {
9872 var nread: usize = undefined;
9873 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
9874 .SUCCESS => {
9875 syscall.finish();
9876 if (nread == 0) return error.EndOfStream;
9877 return nread;
9878 },
9879 .INTR, .TIMEDOUT => {
9880 try syscall.checkCancel();
9881 continue;
9882 },
9883 .BADF => return syscall.fail(error.IsDir), // File operation on directory.
9884 .IO => return syscall.fail(error.InputOutput),
9885 .ISDIR => return syscall.fail(error.IsDir),
9886 .NOBUFS => return syscall.fail(error.SystemResources),
9887 .NOMEM => return syscall.fail(error.SystemResources),
9888 .NOTCONN => return syscall.fail(error.SocketUnconnected),
9889 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
9890 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
9891 .INVAL => |err| return syscall.errnoBug(err),
9892 .FAULT => |err| return syscall.errnoBug(err),
9893 else => |err| return syscall.unexpectedErrno(err),
9894 }
9895 }
9896 }
9897
9898 const syscall: Syscall = try .start();
9899 while (true) {
9900 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
9901 switch (posix.errno(rc)) {
9902 .SUCCESS => {
9903 syscall.finish();
9904 if (rc == 0) return error.EndOfStream;
9905 return @intCast(rc);
9906 },
9907 .INTR, .TIMEDOUT => {
9908 try syscall.checkCancel();
9909 continue;
9910 },
9911 .BADF => {
9912 syscall.finish();
9913 if (native_os == .wasi) return error.IsDir; // File operation on directory.
9914 return error.NotOpenForReading;
9915 },
9916 .AGAIN => return syscall.fail(error.WouldBlock),
9917 .IO => return syscall.fail(error.InputOutput),
9918 .ISDIR => return syscall.fail(error.IsDir),
9919 .NOBUFS => return syscall.fail(error.SystemResources),
9920 .NOMEM => return syscall.fail(error.SystemResources),
9921 .NOTCONN => return syscall.fail(error.SocketUnconnected),
9922 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
9923 .INVAL => |err| return syscall.errnoBug(err),
9924 .FAULT => |err| return syscall.errnoBug(err),
9925 else => |err| return syscall.unexpectedErrno(err),
9926 }
9927 }
9928}
9929
9930fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize {
9931 var iosb: windows.IO_STATUS_BLOCK = undefined;
9932 var index: usize = 0;
9933 while (data.len - index != 0 and data[index].len == 0) index += 1;
9934 if (data.len - index == 0) return 0;
9935 const buffer = data[index];
9936 const short_buffer_len = std.math.lossyCast(u32, buffer.len);
9937 if (file.flags.nonblocking) {
9938 var done: bool = false;
9939 switch (windows.ntdll.NtReadFile(
9940 file.handle,
9941 null, // event
9942 flagApc,
9943 &done, // APC context
9944 &iosb,
9945 buffer.ptr,
9946 short_buffer_len,
9947 null, // byte offset
9948 null, // key
9949 )) {
9950 // We must wait for the APC routine.
9951 .PENDING, .SUCCESS => while (!done) {
9952 // Once we get here we must not return from the function until the
9953 // operation completes, thereby releasing reference to the iosb.
9954 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
9955 error.Canceled => |e| {
9956 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
9957 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb);
9958 while (!done) waitForApcOrAlert();
9959 return e;
9960 },
9961 };
9962 waitForApcOrAlert();
9963 alertable_syscall.finish();
9964 },
9965 else => |status| iosb.u.Status = status,
9966 }
9967 } else {
9968 const syscall: Syscall = try .start();
9969 while (true) switch (windows.ntdll.NtReadFile(
9970 file.handle,
9971 null, // event
9972 null, // APC routine
9973 null, // APC context
9974 &iosb,
9975 buffer.ptr,
9976 short_buffer_len,
9977 null, // byte offset
9978 null, // key
9979 )) {
9980 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
9981 .CANCELLED => {
9982 try syscall.checkCancel();
9983 continue;
9984 },
9985 else => |status| {
9986 syscall.finish();
9987 iosb.u.Status = status;
9988 break;
9989 },
9990 };
9991 }
9992 return ntReadFileResult(&iosb);
9993}
9994
9995fn flagApc(userdata: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(apc_align) callconv(.winapi) void {
9996 const flag: *bool = @ptrCast(userdata);
9997 flag.* = true;
9998}
9999
10000fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
10001 switch (io_status_block.u.Status) {
10002 .PENDING => unreachable,
10003 .CANCELLED => unreachable,
10004 .SUCCESS => return io_status_block.Information,
10005 .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream,
10006 .INVALID_HANDLE => return error.NotOpenForReading,
10007 .INVALID_DEVICE_REQUEST => return error.IsDir,
10008 .FILE_LOCK_CONFLICT => return error.LockViolation,
10009 .ACCESS_DENIED => return error.AccessDenied,
10010 else => |status| return windows.unexpectedStatus(status),
10011 }
10012}
10013
10014fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
10015 switch (io_status_block.u.Status) {
10016 .PENDING => unreachable,
10017 .CANCELLED => unreachable,
10018 .SUCCESS => return io_status_block.Information,
10019 .INVALID_USER_BUFFER => return error.SystemResources,
10020 .NO_MEMORY => return error.SystemResources,
10021 .QUOTA_EXCEEDED => return error.SystemResources,
10022 .PIPE_BROKEN => return error.BrokenPipe,
10023 .INVALID_HANDLE => return error.NotOpenForWriting,
10024 .FILE_LOCK_CONFLICT => return error.LockViolation,
10025 .ACCESS_DENIED => return error.AccessDenied,
10026 .WORKING_SET_QUOTA => return error.SystemResources,
10027 .DISK_FULL => return error.NoSpaceLeft,
10028 else => |status| return windows.unexpectedStatus(status),
10029 }
10030}
10031
10032fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
10033 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
10034 var i: usize = 0;
10035 for (data) |buf| {
10036 if (iovecs_buffer.len - i == 0) break;
10037 if (buf.len != 0) {
10038 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
10039 i += 1;
10040 }
10041 }
10042 if (i == 0) return 0;
10043 const dest = iovecs_buffer[0..i];
10044 assert(dest[0].len > 0);
10045
10046 if (native_os == .wasi and !builtin.link_libc) {
10047 const syscall: Syscall = try .start();
10048 while (true) {
10049 var nread: usize = undefined;
10050 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
10051 .SUCCESS => {
10052 syscall.finish();
10053 return nread;
10054 },
10055 .INTR, .TIMEDOUT => {
10056 try syscall.checkCancel();
10057 continue;
10058 },
10059 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
10060 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
10061 .INVAL => |err| return syscall.errnoBug(err),
10062 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault
10063 .AGAIN => |err| return syscall.errnoBug(err),
10064 .IO => return syscall.fail(error.InputOutput),
10065 .ISDIR => return syscall.fail(error.IsDir),
10066 .BADF => return syscall.fail(error.IsDir),
10067 .NOBUFS => return syscall.fail(error.SystemResources),
10068 .NOMEM => return syscall.fail(error.SystemResources),
10069 .NXIO => return syscall.fail(error.Unseekable),
10070 .SPIPE => return syscall.fail(error.Unseekable),
10071 .OVERFLOW => return syscall.fail(error.Unseekable),
10072 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
10073 else => |err| return syscall.unexpectedErrno(err),
10074 }
10075 }
10076 }
10077
10078 if (have_preadv) {
10079 const syscall: Syscall = try .start();
10080 while (true) {
10081 const rc = if (native_os == .haiku)
10082 posix.system.readv_pos(file.handle, @bitCast(offset), dest.ptr, @intCast(dest.len))
10083 else
10084 preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));
10085 switch (posix.errno(rc)) {
10086 .SUCCESS => {
10087 syscall.finish();
10088 return @bitCast(rc);
10089 },
10090 .INTR, .TIMEDOUT => {
10091 try syscall.checkCancel();
10092 continue;
10093 },
10094 .NXIO => return syscall.fail(error.Unseekable),
10095 .SPIPE => return syscall.fail(error.Unseekable),
10096 .OVERFLOW => return syscall.fail(error.Unseekable),
10097 .NOBUFS => return syscall.fail(error.SystemResources),
10098 .NOMEM => return syscall.fail(error.SystemResources),
10099 .AGAIN => return syscall.fail(error.WouldBlock),
10100 .IO => return syscall.fail(error.InputOutput),
10101 .ISDIR => return syscall.fail(error.IsDir),
10102 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
10103 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
10104 .INVAL => |err| return syscall.errnoBug(err),
10105 .FAULT => |err| return syscall.errnoBug(err),
10106 .BADF => {
10107 syscall.finish();
10108 if (native_os == .wasi) return error.IsDir; // File operation on directory.
10109 return error.NotOpenForReading;
10110 },
10111 else => |err| return syscall.unexpectedErrno(err),
10112 }
10113 }
10114 }
10115
10116 const syscall: Syscall = try .start();
10117 while (true) {
10118 const rc = pread_sym(file.handle, dest[0].base, @intCast(dest[0].len), @bitCast(offset));
10119 switch (posix.errno(rc)) {
10120 .SUCCESS => {
10121 syscall.finish();
10122 return @bitCast(rc);
10123 },
10124 .INTR, .TIMEDOUT => {
10125 try syscall.checkCancel();
10126 continue;
10127 },
10128 .NXIO => return syscall.fail(error.Unseekable),
10129 .SPIPE => return syscall.fail(error.Unseekable),
10130 .OVERFLOW => return syscall.fail(error.Unseekable),
10131 .NOBUFS => return syscall.fail(error.SystemResources),
10132 .NOMEM => return syscall.fail(error.SystemResources),
10133 .AGAIN => return syscall.fail(error.WouldBlock),
10134 .IO => return syscall.fail(error.InputOutput),
10135 .ISDIR => return syscall.fail(error.IsDir),
10136 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
10137 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
10138 .INVAL => |err| return syscall.errnoBug(err),
10139 .FAULT => |err| return syscall.errnoBug(err),
10140 .BADF => {
10141 syscall.finish();
10142 if (native_os == .wasi) return error.IsDir; // File operation on directory.
10143 return error.NotOpenForReading;
10144 },
10145 else => |err| return syscall.unexpectedErrno(err),
10146 }
10147 }
10148}
10149
10150fn fileReadPositional(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
10151 const t: *Threaded = @ptrCast(@alignCast(userdata));
10152 _ = t;
10153 if (is_windows) return fileReadPositionalWindows(file, data, offset);
10154 return fileReadPositionalPosix(file, data, offset);
10155}
10156
10157fn fileReadPositionalWindows(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
10158 var index: usize = 0;
10159 while (index < data.len and data[index].len == 0) index += 1;
10160 if (index == data.len) return 0;
10161 const buffer = data[index];
10162
10163 return readFilePositionalWindows(file, buffer, offset);
10164}
10165
10166fn readFilePositionalWindows(file: File, buffer: []u8, offset: u64) File.ReadPositionalError!usize {
10167 var iosb: windows.IO_STATUS_BLOCK = undefined;
10168 const short_buffer_len = std.math.lossyCast(u32, buffer.len);
10169 const signed_offset: windows.LARGE_INTEGER = @intCast(offset);
10170 if (file.flags.nonblocking) {
10171 var done: bool = false;
10172 switch (windows.ntdll.NtReadFile(
10173 file.handle,
10174 null, // event
10175 flagApc,
10176 &done, // APC context
10177 &iosb,
10178 buffer.ptr,
10179 short_buffer_len,
10180 &signed_offset,
10181 null, // key
10182 )) {
10183 // We must wait for the APC routine.
10184 .PENDING, .SUCCESS => while (!done) {
10185 // Once we get here we must not return from the function until the
10186 // operation completes, thereby releasing reference to the iosb.
10187 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
10188 error.Canceled => |e| {
10189 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
10190 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb);
10191 while (!done) waitForApcOrAlert();
10192 return e;
10193 },
10194 };
10195 waitForApcOrAlert();
10196 alertable_syscall.finish();
10197 },
10198 else => |status| iosb.u.Status = status,
10199 }
10200 } else {
10201 const syscall: Syscall = try .start();
10202 while (true) switch (windows.ntdll.NtReadFile(
10203 file.handle,
10204 null, // event
10205 null, // APC routine
10206 null, // APC context
10207 &iosb,
10208 buffer.ptr,
10209 short_buffer_len,
10210 &signed_offset,
10211 null, // key
10212 )) {
10213 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
10214 .CANCELLED => try syscall.checkCancel(),
10215 else => |status| {
10216 syscall.finish();
10217 iosb.u.Status = status;
10218 break;
10219 },
10220 };
10221 }
10222 return ntReadFileResult(&iosb) catch |err| switch (err) {
10223 error.EndOfStream => 0,
10224 else => |e| e,
10225 };
10226}
10227
10228fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {
10229 const t: *Threaded = @ptrCast(@alignCast(userdata));
10230 _ = t;
10231
10232 if (is_windows) {
10233 var iosb: windows.IO_STATUS_BLOCK = undefined;
10234 var info: windows.FILE.POSITION_INFORMATION = undefined;
10235 const syscall: Syscall = try .start();
10236 while (true) switch (windows.ntdll.NtQueryInformationFile(
10237 file.handle,
10238 &iosb,
10239 &info,
10240 @sizeOf(windows.FILE.POSITION_INFORMATION),
10241 .Position,
10242 )) {
10243 .SUCCESS => break,
10244 .CANCELLED => try syscall.checkCancel(),
10245 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
10246 .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable),
10247 else => |status| return syscall.unexpectedNtstatus(status),
10248 };
10249 info.CurrentByteOffset = @bitCast((if (offset >= 0) std.math.add(
10250 u64,
10251 @bitCast(info.CurrentByteOffset),
10252 @intCast(offset),
10253 ) else std.math.sub(
10254 u64,
10255 @bitCast(info.CurrentByteOffset),
10256 @intCast(-offset),
10257 )) catch |err| switch (err) {
10258 error.Overflow => return syscall.fail(error.Unseekable),
10259 });
10260 while (true) switch (windows.ntdll.NtSetInformationFile(
10261 file.handle,
10262 &iosb,
10263 &info,
10264 @sizeOf(windows.FILE.POSITION_INFORMATION),
10265 .Position,
10266 )) {
10267 .SUCCESS => return syscall.finish(),
10268 .CANCELLED => try syscall.checkCancel(),
10269 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
10270 .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable),
10271 else => |status| return syscall.unexpectedNtstatus(status),
10272 };
10273 }
10274
10275 if (native_os == .wasi and !builtin.link_libc) {
10276 var new_offset: std.os.wasi.filesize_t = undefined;
10277 const syscall: Syscall = try .start();
10278 while (true) {
10279 switch (std.os.wasi.fd_seek(file.handle, offset, .CUR, &new_offset)) {
10280 .SUCCESS => {
10281 syscall.finish();
10282 return;
10283 },
10284 .INTR => {
10285 try syscall.checkCancel();
10286 continue;
10287 },
10288 else => |e| {
10289 syscall.finish();
10290 switch (e) {
10291 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
10292 .INVAL => return error.Unseekable,
10293 .OVERFLOW => return error.Unseekable,
10294 .SPIPE => return error.Unseekable,
10295 .NXIO => return error.Unseekable,
10296 .NOTCAPABLE => return error.AccessDenied,
10297 else => |err| return posix.unexpectedErrno(err),
10298 }
10299 },
10300 }
10301 }
10302 }
10303
10304 if (posix.SEEK == void) return error.Unseekable;
10305
10306 if (native_os == .linux and !builtin.link_libc and @sizeOf(posix.system.syscall_arg_t) == 4) {
10307 var result: i64 = undefined;
10308 const syscall: Syscall = try .start();
10309 while (true) {
10310 switch (posix.errno(posix.system.llseek(file.handle, offset, &result, posix.SEEK.CUR))) {
10311 .SUCCESS => {
10312 syscall.finish();
10313 return;
10314 },
10315 .INTR => {
10316 try syscall.checkCancel();
10317 continue;
10318 },
10319 else => |e| {
10320 syscall.finish();
10321 switch (e) {
10322 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
10323 .INVAL => return error.Unseekable,
10324 .OVERFLOW => return error.Unseekable,
10325 .SPIPE => return error.Unseekable,
10326 .NXIO => return error.Unseekable,
10327 else => |err| return posix.unexpectedErrno(err),
10328 }
10329 },
10330 }
10331 }
10332 }
10333
10334 const syscall: Syscall = try .start();
10335 while (true) {
10336 switch (posix.errno(lseek_sym(file.handle, offset, posix.SEEK.CUR))) {
10337 .SUCCESS => {
10338 syscall.finish();
10339 return;
10340 },
10341 .INTR => {
10342 try syscall.checkCancel();
10343 continue;
10344 },
10345 else => |e| {
10346 syscall.finish();
10347 switch (e) {
10348 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
10349 .INVAL => return error.Unseekable,
10350 .OVERFLOW => return error.Unseekable,
10351 .SPIPE => return error.Unseekable,
10352 .NXIO => return error.Unseekable,
10353 else => |err| return posix.unexpectedErrno(err),
10354 }
10355 },
10356 }
10357 }
10358}
10359
10360fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {
10361 const t: *Threaded = @ptrCast(@alignCast(userdata));
10362 _ = t;
10363
10364 if (is_windows) {
10365 var iosb: windows.IO_STATUS_BLOCK = undefined;
10366 var info: windows.FILE.POSITION_INFORMATION = .{ .CurrentByteOffset = @bitCast(offset) };
10367 const syscall: Syscall = try .start();
10368 while (true) switch (windows.ntdll.NtSetInformationFile(
10369 file.handle,
10370 &iosb,
10371 &info,
10372 @sizeOf(windows.FILE.POSITION_INFORMATION),
10373 .Position,
10374 )) {
10375 .SUCCESS => return syscall.finish(),
10376 .CANCELLED => try syscall.checkCancel(),
10377 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
10378 .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable),
10379 else => |status| return syscall.unexpectedNtstatus(status),
10380 };
10381 }
10382
10383 if (native_os == .wasi and !builtin.link_libc) {
10384 const syscall: Syscall = try .start();
10385 while (true) {
10386 var new_offset: std.os.wasi.filesize_t = undefined;
10387 switch (std.os.wasi.fd_seek(file.handle, @bitCast(offset), .SET, &new_offset)) {
10388 .SUCCESS => {
10389 syscall.finish();
10390 return;
10391 },
10392 .INTR => {
10393 try syscall.checkCancel();
10394 continue;
10395 },
10396 else => |e| {
10397 syscall.finish();
10398 switch (e) {
10399 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
10400 .INVAL => return error.Unseekable,
10401 .OVERFLOW => return error.Unseekable,
10402 .SPIPE => return error.Unseekable,
10403 .NXIO => return error.Unseekable,
10404 .NOTCAPABLE => return error.AccessDenied,
10405 else => |err| return posix.unexpectedErrno(err),
10406 }
10407 },
10408 }
10409 }
10410 }
10411
10412 if (posix.SEEK == void) return error.Unseekable;
10413
10414 return posixSeekTo(file.handle, offset);
10415}
10416
10417fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void {
10418 if (native_os == .linux and !builtin.link_libc and @sizeOf(posix.system.syscall_arg_t) == 4) {
10419 const syscall: Syscall = try .start();
10420 while (true) {
10421 var result: i64 = undefined;
10422 switch (posix.errno(posix.system.llseek(fd, @bitCast(offset), &result, posix.SEEK.SET))) {
10423 .SUCCESS => {
10424 syscall.finish();
10425 return;
10426 },
10427 .INTR => {
10428 try syscall.checkCancel();
10429 continue;
10430 },
10431 else => |e| {
10432 syscall.finish();
10433 switch (e) {
10434 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
10435 .INVAL => return error.Unseekable,
10436 .OVERFLOW => return error.Unseekable,
10437 .SPIPE => return error.Unseekable,
10438 .NXIO => return error.Unseekable,
10439 else => |err| return posix.unexpectedErrno(err),
10440 }
10441 },
10442 }
10443 }
10444 }
10445
10446 const syscall: Syscall = try .start();
10447 while (true) {
10448 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
10449 .SUCCESS => {
10450 syscall.finish();
10451 return;
10452 },
10453 .INTR => {
10454 try syscall.checkCancel();
10455 continue;
10456 },
10457 else => |e| {
10458 syscall.finish();
10459 switch (e) {
10460 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
10461 .INVAL => return error.Unseekable,
10462 .OVERFLOW => return error.Unseekable,
10463 .SPIPE => return error.Unseekable,
10464 .NXIO => return error.Unseekable,
10465 else => |err| return posix.unexpectedErrno(err),
10466 }
10467 },
10468 }
10469 }
10470}
10471
10472fn processExecutableOpen(userdata: ?*anyopaque, flags: Dir.OpenFileOptions) process.OpenExecutableError!File {
10473 const t: *Threaded = @ptrCast(@alignCast(userdata));
10474 switch (native_os) {
10475 .wasi => return error.OperationUnsupported,
10476 .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags),
10477 .windows => {
10478 // If ImagePathName is a symlink, then it will contain the path of the symlink,
10479 // not the path that the symlink points to. However, because we are opening
10480 // the file, we can let the openFileW call follow the symlink for us.
10481 const image_path_name = windows.peb().ProcessParameters.ImagePathName.sliceZ();
10482 const prefixed_path_w = try wToPrefixedFileW(null, image_path_name, .{});
10483 return dirOpenFileWtf16(null, prefixed_path_w.span(), flags);
10484 },
10485 .driverkit,
10486 .ios,
10487 .maccatalyst,
10488 .macos,
10489 .tvos,
10490 .visionos,
10491 .watchos,
10492 => {
10493 // _NSGetExecutablePath() returns a path that might be a symlink to
10494 // the executable. Here it does not matter since we open it.
10495 var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined;
10496 var n: u32 = symlink_path_buf.len;
10497 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n);
10498 if (rc != 0) return error.NameTooLong;
10499 const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0);
10500 return dirOpenFilePosix(t, .cwd(), symlink_path, flags);
10501 },
10502 else => {
10503 var buffer: [Dir.max_path_bytes]u8 = undefined;
10504 const n = try processExecutablePath(t, &buffer);
10505 buffer[n] = 0;
10506 const executable_path = buffer[0..n :0];
10507 return dirOpenFilePosix(t, .cwd(), executable_path, flags);
10508 },
10509 }
10510}
10511
10512fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.ExecutablePathError!usize {
10513 const t: *Threaded = @ptrCast(@alignCast(userdata));
10514
10515 switch (native_os) {
10516 .driverkit,
10517 .ios,
10518 .maccatalyst,
10519 .macos,
10520 .tvos,
10521 .visionos,
10522 .watchos,
10523 => {
10524 // _NSGetExecutablePath() returns a path that might be a symlink to
10525 // the executable.
10526 var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined;
10527 var n: u32 = symlink_path_buf.len;
10528 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n);
10529 if (rc != 0) return error.NameTooLong;
10530 const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0);
10531 return Io.Dir.realPathFileAbsolute(io(t), symlink_path, out_buffer) catch |err| switch (err) {
10532 error.NetworkNotFound => unreachable, // Windows-only
10533 error.FileBusy => unreachable, // Windows-only
10534 else => |e| return e,
10535 };
10536 },
10537 .linux, .serenity => return Io.Dir.readLinkAbsolute(io(t), "/proc/self/exe", out_buffer) catch |err| switch (err) {
10538 error.UnsupportedReparsePointType => unreachable, // Windows-only
10539 error.NetworkNotFound => unreachable, // Windows-only
10540 error.FileBusy => unreachable, // Windows-only
10541 else => |e| return e,
10542 },
10543 .illumos => return Io.Dir.readLinkAbsolute(io(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
10544 error.UnsupportedReparsePointType => unreachable, // Windows-only
10545 error.NetworkNotFound => unreachable, // Windows-only
10546 error.FileBusy => unreachable, // Windows-only
10547 else => |e| return e,
10548 },
10549 .freebsd, .dragonfly => {
10550 var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 };
10551 var out_len: usize = out_buffer.len;
10552 const syscall: Syscall = try .start();
10553 while (true) switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
10554 .SUCCESS => {
10555 syscall.finish();
10556 return out_len - 1; // discard terminating NUL
10557 },
10558 .INTR => {
10559 try syscall.checkCancel();
10560 continue;
10561 },
10562 .PERM => return syscall.fail(error.PermissionDenied),
10563 .NOMEM => return syscall.fail(error.SystemResources),
10564 .FAULT => |err| return syscall.errnoBug(err),
10565 .NOENT => |err| return syscall.errnoBug(err),
10566 else => |err| return syscall.unexpectedErrno(err),
10567 };
10568 },
10569 .netbsd => {
10570 var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME };
10571 var out_len: usize = out_buffer.len;
10572 const syscall: Syscall = try .start();
10573 while (true) {
10574 switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
10575 .SUCCESS => {
10576 syscall.finish();
10577 return out_len - 1; // discard terminating NUL
10578 },
10579 .INTR => {
10580 try syscall.checkCancel();
10581 continue;
10582 },
10583 .PERM => return syscall.fail(error.PermissionDenied),
10584 .NOMEM => return syscall.fail(error.SystemResources),
10585 .FAULT => |err| return syscall.errnoBug(err),
10586 .NOENT => |err| return syscall.errnoBug(err),
10587 else => |err| return syscall.unexpectedErrno(err),
10588 }
10589 }
10590 },
10591 .openbsd, .haiku => {
10592 // The best we can do on these operating systems is check based on
10593 // the first process argument.
10594 const argv0 = std.mem.span(t.argv0.value orelse return error.OperationUnsupported);
10595 if (std.mem.findScalar(u8, argv0, '/') != null) {
10596 // argv[0] is a path (relative or absolute): use realpath(3) directly
10597 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
10598 const syscall: Syscall = try .start();
10599 while (true) {
10600 if (std.c.realpath(argv0, &resolved_buf)) |p| {
10601 assert(p == &resolved_buf);
10602 break syscall.finish();
10603 } else switch (@as(std.c.E, @fromBackingInt(@intCast(std.c._errno().*)))) {
10604 .INTR => {
10605 try syscall.checkCancel();
10606 continue;
10607 },
10608 else => |e| {
10609 syscall.finish();
10610 switch (e) {
10611 .ACCES => return error.AccessDenied,
10612 .INVAL => |err| return errnoBug(err), // the pathname argument is a null pointer
10613 .IO => return error.InputOutput,
10614 .LOOP => return error.SymLinkLoop,
10615 .NAMETOOLONG => return error.NameTooLong,
10616 .NOENT => return error.FileNotFound,
10617 .NOTDIR => return error.NotDir,
10618 .NOMEM => |err| return errnoBug(err), // sufficient storage space is unavailable for allocation
10619 else => |err| return posix.unexpectedErrno(err),
10620 }
10621 },
10622 }
10623 }
10624 const resolved = std.mem.sliceTo(&resolved_buf, 0);
10625 if (resolved.len > out_buffer.len)
10626 return error.NameTooLong;
10627 @memcpy(out_buffer[0..resolved.len], resolved);
10628 return resolved.len;
10629 } else if (argv0.len != 0) {
10630 // argv[0] is not empty (and not a path): search PATH
10631 t.scanEnviron();
10632 const PATH = t.environ.string.PATH orelse return error.FileNotFound;
10633 var it = std.mem.tokenizeScalar(u8, PATH, ':');
10634 it: while (it.next()) |dir| {
10635 var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined;
10636 const resolved_path = std.mem.printSentinel(&resolved_path_buf, "{s}/{s}", .{
10637 dir, argv0,
10638 }, 0) catch continue;
10639
10640 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
10641 const syscall: Syscall = try .start();
10642 while (true) {
10643 if (std.c.realpath(resolved_path, &resolved_buf)) |p| {
10644 assert(p == &resolved_buf);
10645 break syscall.finish();
10646 } else switch (@as(std.c.E, @fromBackingInt(@intCast(std.c._errno().*)))) {
10647 .INTR => {
10648 try syscall.checkCancel();
10649 continue;
10650 },
10651 .NAMETOOLONG => {
10652 syscall.finish();
10653 return error.NameTooLong;
10654 },
10655 .NOMEM => {
10656 syscall.finish();
10657 return error.SystemResources;
10658 },
10659 .IO => {
10660 syscall.finish();
10661 return error.InputOutput;
10662 },
10663 .ACCES, .LOOP, .NOENT, .NOTDIR => {
10664 syscall.finish();
10665 continue :it;
10666 },
10667 else => |err| {
10668 syscall.finish();
10669 return posix.unexpectedErrno(err);
10670 },
10671 }
10672 }
10673 const resolved = std.mem.sliceTo(&resolved_buf, 0);
10674 if (resolved.len > out_buffer.len)
10675 return error.NameTooLong;
10676 @memcpy(out_buffer[0..resolved.len], resolved);
10677 return resolved.len;
10678 }
10679 }
10680 return error.FileNotFound;
10681 },
10682 .windows => {
10683 // If ImagePathName is a symlink, then it will contain the path of the
10684 // symlink, not the path that the symlink points to. We want the path
10685 // that the symlink points to, though, so we need to get the realpath.
10686 var path_name_w_buf = try wToPrefixedFileW(
10687 null,
10688 windows.peb().ProcessParameters.ImagePathName.sliceZ(),
10689 .{},
10690 );
10691
10692 const h_file = handle: {
10693 if (OpenFile(path_name_w_buf.span(), .{
10694 .dir = null,
10695 .access_mask = .{
10696 .GENERIC = .{ .READ = true },
10697 .STANDARD = .{ .SYNCHRONIZE = true },
10698 },
10699 .creation = .OPEN,
10700 .filter = .any,
10701 })) |handle| {
10702 break :handle handle;
10703 } else |err| switch (err) {
10704 error.WouldBlock => unreachable,
10705 error.FileBusy => unreachable,
10706 else => |e| return e,
10707 }
10708 };
10709 defer windows.CloseHandle(h_file);
10710
10711 const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
10712
10713 const len = std.unicode.calcWtf8Len(wide_slice);
10714 if (len > out_buffer.len)
10715 return error.NameTooLong;
10716
10717 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
10718 return end_index;
10719 },
10720 else => return error.OperationUnsupported,
10721 }
10722}
10723
10724fn fileWritePositional(
10725 userdata: ?*anyopaque,
10726 file: File,
10727 header: []const u8,
10728 data: []const []const u8,
10729 splat: usize,
10730 offset: u64,
10731) File.WritePositionalError!usize {
10732 const t: *Threaded = @ptrCast(@alignCast(userdata));
10733 _ = t;
10734
10735 if (is_windows) {
10736 if (header.len != 0) {
10737 return writeFilePositionalWindows(file, header, offset);
10738 }
10739 for (data[0 .. data.len - 1]) |buf| {
10740 if (buf.len == 0) continue;
10741 return writeFilePositionalWindows(file, buf, offset);
10742 }
10743 const pattern = data[data.len - 1];
10744 if (pattern.len == 0 or splat == 0) return 0;
10745 return writeFilePositionalWindows(file, pattern, offset);
10746 }
10747
10748 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
10749 var iovlen: iovlen_t = 0;
10750 addBuf(&iovecs, &iovlen, header);
10751 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes);
10752 const pattern = data[data.len - 1];
10753
10754 var splat_backup_buffer: [splat_buffer_size]u8 = undefined;
10755 if (iovecs.len - iovlen != 0) switch (splat) {
10756 0 => {},
10757 1 => addBuf(&iovecs, &iovlen, pattern),
10758 else => switch (pattern.len) {
10759 0 => {},
10760 1 => {
10761 const splat_buffer = &splat_backup_buffer;
10762 const memset_len = @min(splat_buffer.len, splat);
10763 const buf = splat_buffer[0..memset_len];
10764 @memset(buf, pattern[0]);
10765 addBuf(&iovecs, &iovlen, buf);
10766 var remaining_splat = splat - buf.len;
10767 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) {
10768 assert(buf.len == splat_buffer.len);
10769 addBuf(&iovecs, &iovlen, splat_buffer);
10770 remaining_splat -= splat_buffer.len;
10771 }
10772 addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
10773 },
10774 else => for (0..@min(splat, iovecs.len - iovlen)) |_| {
10775 addBuf(&iovecs, &iovlen, pattern);
10776 },
10777 },
10778 };
10779
10780 if (iovlen == 0) return 0;
10781
10782 if (native_os == .wasi and !builtin.link_libc) {
10783 var n_written: usize = undefined;
10784 const syscall: Syscall = try .start();
10785 while (true) {
10786 switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) {
10787 .SUCCESS => {
10788 syscall.finish();
10789 return n_written;
10790 },
10791 .INTR => {
10792 try syscall.checkCancel();
10793 continue;
10794 },
10795 else => |e| {
10796 syscall.finish();
10797 switch (e) {
10798 .INVAL => |err| return errnoBug(err),
10799 .FAULT => |err| return errnoBug(err),
10800 .AGAIN => |err| return errnoBug(err),
10801 .BADF => return error.NotOpenForWriting,
10802 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
10803 .DQUOT => return error.DiskQuota,
10804 .FBIG => return error.FileTooBig,
10805 .IO => return error.InputOutput,
10806 .NOSPC => return error.NoSpaceLeft,
10807 .PERM => return error.PermissionDenied,
10808 .PIPE => return error.BrokenPipe,
10809 .NOTCAPABLE => return error.AccessDenied,
10810 .NXIO => return error.Unseekable,
10811 .SPIPE => return error.Unseekable,
10812 .OVERFLOW => return error.Unseekable,
10813 else => |err| return posix.unexpectedErrno(err),
10814 }
10815 },
10816 }
10817 }
10818 }
10819
10820 const syscall: Syscall = try .start();
10821 while (true) {
10822 const rc = if (native_os == .haiku)
10823 posix.system.writev_pos(file.handle, @bitCast(offset), &iovecs, @intCast(iovlen))
10824 else
10825 pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset));
10826 switch (posix.errno(rc)) {
10827 .SUCCESS => {
10828 syscall.finish();
10829 return @intCast(rc);
10830 },
10831 .INTR => {
10832 try syscall.checkCancel();
10833 continue;
10834 },
10835 .INVAL => |err| return syscall.errnoBug(err),
10836 .FAULT => |err| return syscall.errnoBug(err),
10837 .DESTADDRREQ => |err| return syscall.errnoBug(err), // `connect` was never called.
10838 .CONNRESET => |err| return syscall.errnoBug(err), // Not a socket handle.
10839 .BADF => return syscall.fail(error.NotOpenForWriting),
10840 .AGAIN => return syscall.fail(error.WouldBlock),
10841 .DQUOT => return syscall.fail(error.DiskQuota),
10842 .FBIG => return syscall.fail(error.FileTooBig),
10843 .IO => return syscall.fail(error.InputOutput),
10844 .NOSPC => return syscall.fail(error.NoSpaceLeft),
10845 .PERM => return syscall.fail(error.PermissionDenied),
10846 .PIPE => return syscall.fail(error.BrokenPipe),
10847 .BUSY => return syscall.fail(error.DeviceBusy),
10848 .TXTBSY => return syscall.fail(error.FileBusy),
10849 .NXIO => return syscall.fail(error.Unseekable),
10850 .SPIPE => return syscall.fail(error.Unseekable),
10851 .OVERFLOW => return syscall.fail(error.Unseekable),
10852 else => |err| return syscall.unexpectedErrno(err),
10853 }
10854 }
10855}
10856
10857fn writeFilePositionalWindows(file: File, buffer: []const u8, offset: u64) File.WritePositionalError!usize {
10858 assert(buffer.len != 0);
10859 var iosb: windows.IO_STATUS_BLOCK = undefined;
10860 const short_buffer_len = std.math.lossyCast(u32, buffer.len);
10861 const signed_offset: windows.LARGE_INTEGER = @intCast(offset);
10862 if (file.flags.nonblocking) {
10863 var done: bool = false;
10864 switch (windows.ntdll.NtWriteFile(
10865 file.handle,
10866 null, // event
10867 flagApc,
10868 &done, // APC context
10869 &iosb,
10870 buffer.ptr,
10871 short_buffer_len,
10872 &signed_offset,
10873 null, // key
10874 )) {
10875 // We must wait for the APC routine.
10876 .PENDING, .SUCCESS => while (!done) {
10877 // Once we get here we must not return from the function until the
10878 // operation completes, thereby releasing reference to the iosb.
10879 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
10880 error.Canceled => |e| {
10881 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
10882 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb);
10883 while (!done) waitForApcOrAlert();
10884 return e;
10885 },
10886 };
10887 waitForApcOrAlert();
10888 alertable_syscall.finish();
10889 },
10890 else => |status| iosb.u.Status = status,
10891 }
10892 } else {
10893 const syscall: Syscall = try .start();
10894 while (true) switch (windows.ntdll.NtWriteFile(
10895 file.handle,
10896 null, // event
10897 null, // APC routine
10898 null, // APC context
10899 &iosb,
10900 buffer.ptr,
10901 short_buffer_len,
10902 &signed_offset,
10903 null, // key
10904 )) {
10905 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
10906 .CANCELLED => try syscall.checkCancel(),
10907 else => |status| {
10908 syscall.finish();
10909 iosb.u.Status = status;
10910 return ntWriteFileResult(&iosb);
10911 },
10912 };
10913 }
10914 return ntWriteFileResult(&iosb);
10915}
10916
10917fn fileWriteStreaming(
10918 userdata: ?*anyopaque,
10919 file: File,
10920 header: []const u8,
10921 data: []const []const u8,
10922 splat: usize,
10923) File.Writer.Error!usize {
10924 const t: *Threaded = @ptrCast(@alignCast(userdata));
10925 _ = t;
10926
10927 if (is_windows) {
10928 const buffer = windowsWriteBuffer(header, data, splat);
10929 if (buffer.len == 0) return 0;
10930 return fileWriteStreamingWindows(file, buffer);
10931 }
10932
10933 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
10934 var iovlen: iovlen_t = 0;
10935 addBuf(&iovecs, &iovlen, header);
10936 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes);
10937 const pattern = data[data.len - 1];
10938
10939 var splat_backup_buffer: [splat_buffer_size]u8 = undefined;
10940 if (iovecs.len - iovlen != 0) switch (splat) {
10941 0 => {},
10942 1 => addBuf(&iovecs, &iovlen, pattern),
10943 else => switch (pattern.len) {
10944 0 => {},
10945 1 => {
10946 const splat_buffer = &splat_backup_buffer;
10947 const memset_len = @min(splat_buffer.len, splat);
10948 const buf = splat_buffer[0..memset_len];
10949 @memset(buf, pattern[0]);
10950 addBuf(&iovecs, &iovlen, buf);
10951 var remaining_splat = splat - buf.len;
10952 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) {
10953 assert(buf.len == splat_buffer.len);
10954 addBuf(&iovecs, &iovlen, splat_buffer);
10955 remaining_splat -= splat_buffer.len;
10956 }
10957 addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
10958 },
10959 else => for (0..@min(splat, iovecs.len - iovlen)) |_| {
10960 addBuf(&iovecs, &iovlen, pattern);
10961 },
10962 },
10963 };
10964
10965 if (iovlen == 0) return 0;
10966
10967 if (native_os == .wasi and !builtin.link_libc) {
10968 var n_written: usize = undefined;
10969 const syscall: Syscall = try .start();
10970 while (true) {
10971 switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) {
10972 .SUCCESS => {
10973 syscall.finish();
10974 return n_written;
10975 },
10976 .INTR => {
10977 try syscall.checkCancel();
10978 continue;
10979 },
10980 else => |e| {
10981 syscall.finish();
10982 switch (e) {
10983 .INVAL => |err| return errnoBug(err),
10984 .FAULT => |err| return errnoBug(err),
10985 .AGAIN => |err| return errnoBug(err),
10986 .BADF => return error.NotOpenForWriting, // can be a race condition.
10987 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
10988 .DQUOT => return error.DiskQuota,
10989 .FBIG => return error.FileTooBig,
10990 .IO => return error.InputOutput,
10991 .NOSPC => return error.NoSpaceLeft,
10992 .PERM => return error.PermissionDenied,
10993 .PIPE => return error.BrokenPipe,
10994 .NOTCAPABLE => return error.AccessDenied,
10995 else => |err| return posix.unexpectedErrno(err),
10996 }
10997 },
10998 }
10999 }
11000 }
11001
11002 const syscall: Syscall = try .start();
11003 while (true) {
11004 const rc = posix.system.writev(file.handle, &iovecs, @intCast(iovlen));
11005 switch (posix.errno(rc)) {
11006 .SUCCESS => {
11007 syscall.finish();
11008 return @intCast(rc);
11009 },
11010 .INTR => {
11011 try syscall.checkCancel();
11012 continue;
11013 },
11014 else => |e| {
11015 syscall.finish();
11016 switch (e) {
11017 .INVAL => |err| return errnoBug(err),
11018 .FAULT => |err| return errnoBug(err),
11019 .AGAIN => return error.WouldBlock,
11020 .BADF => return error.NotOpenForWriting, // Can be a race condition.
11021 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
11022 .DQUOT => return error.DiskQuota,
11023 .FBIG => return error.FileTooBig,
11024 .IO => return error.InputOutput,
11025 .NOSPC => return error.NoSpaceLeft,
11026 .PERM => return error.PermissionDenied,
11027 .PIPE => return error.BrokenPipe,
11028 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.
11029 .BUSY => return error.DeviceBusy,
11030 .ACCES => return error.AccessDenied,
11031 else => |err| return posix.unexpectedErrno(err),
11032 }
11033 },
11034 }
11035 }
11036}
11037
11038fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!usize {
11039 assert(buffer.len != 0);
11040 var iosb: windows.IO_STATUS_BLOCK = undefined;
11041 if (file.flags.nonblocking) {
11042 var done: bool = false;
11043 switch (windows.ntdll.NtWriteFile(
11044 file.handle,
11045 null, // event
11046 flagApc,
11047 &done, // APC context
11048 &iosb,
11049 buffer.ptr,
11050 @intCast(buffer.len),
11051 null, // byte offset
11052 null, // key
11053 )) {
11054 // We must wait for the APC routine.
11055 .PENDING, .SUCCESS => while (!done) {
11056 // Once we get here we must not return from the function until the
11057 // operation completes, thereby releasing reference to io_status_block.
11058 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
11059 error.Canceled => |e| {
11060 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
11061 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb);
11062 while (!done) waitForApcOrAlert();
11063 return e;
11064 },
11065 };
11066 waitForApcOrAlert();
11067 alertable_syscall.finish();
11068 },
11069 else => |status| iosb.u.Status = status,
11070 }
11071 } else {
11072 const syscall: Syscall = try .start();
11073 while (true) switch (windows.ntdll.NtWriteFile(
11074 file.handle,
11075 null, // event
11076 null, // APC routine
11077 null, // APC context
11078 &iosb,
11079 buffer.ptr,
11080 @intCast(buffer.len),
11081 null, // byte offset
11082 null, // key
11083 )) {
11084 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
11085 .CANCELLED => try syscall.checkCancel(),
11086 else => |status| {
11087 syscall.finish();
11088 iosb.u.Status = status;
11089 break;
11090 },
11091 };
11092 }
11093 return ntWriteFileResult(&iosb);
11094}
11095
11096fn fileWriteFileStreaming(
11097 userdata: ?*anyopaque,
11098 file: File,
11099 header: []const u8,
11100 file_reader: *File.Reader,
11101 limit: Io.Limit,
11102) File.Writer.WriteFileError!usize {
11103 const t: *Threaded = @ptrCast(@alignCast(userdata));
11104 const reader_buffered = file_reader.interface.buffered();
11105 if (reader_buffered.len >= @backingInt(limit)) {
11106 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
11107 file_reader.interface.toss(n -| header.len);
11108 return n;
11109 }
11110 const file_limit = @backingInt(limit) - reader_buffered.len;
11111 const out_fd = file.handle;
11112 const in_fd = file_reader.file.handle;
11113
11114 if (file_reader.size) |size| {
11115 if (size - file_reader.pos == 0) {
11116 if (reader_buffered.len != 0) {
11117 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
11118 file_reader.interface.toss(n -| header.len);
11119 return n;
11120 } else {
11121 return error.EndOfStream;
11122 }
11123 }
11124 }
11125
11126 if (native_os == .freebsd) sf: {
11127 // Try using sendfile on FreeBSD.
11128 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
11129 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
11130 var hdtr_data: std.c.sf_hdtr = undefined;
11131 var headers: [2]posix.iovec_const = undefined;
11132 var headers_i: u8 = 0;
11133 if (header.len != 0) {
11134 headers[headers_i] = .{ .base = header.ptr, .len = header.len };
11135 headers_i += 1;
11136 }
11137 if (reader_buffered.len != 0) {
11138 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
11139 headers_i += 1;
11140 }
11141 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
11142 hdtr_data = .{
11143 .headers = &headers,
11144 .hdr_cnt = headers_i,
11145 .trailers = null,
11146 .trl_cnt = 0,
11147 };
11148 break :b &hdtr_data;
11149 };
11150 var sbytes: std.c.off_t = 0;
11151 const nbytes: usize = @min(file_limit, std.math.maxInt(usize));
11152 const flags = 0;
11153
11154 const syscall: Syscall = try .start();
11155 while (true) {
11156 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
11157 .SUCCESS => {
11158 syscall.finish();
11159 break;
11160 },
11161 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => {
11162 // Give calling code chance to observe before trying
11163 // something else.
11164 syscall.finish();
11165 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
11166 return 0;
11167 },
11168 .INTR, .BUSY => {
11169 if (sbytes == 0) {
11170 try syscall.checkCancel();
11171 continue;
11172 } else {
11173 // Even if we are being canceled, there have been side
11174 // effects, so it is better to report those side
11175 // effects to the caller.
11176 syscall.finish();
11177 break;
11178 }
11179 },
11180 .AGAIN => {
11181 syscall.finish();
11182 if (sbytes == 0) return error.WouldBlock;
11183 break;
11184 },
11185 else => |e| {
11186 syscall.finish();
11187 assert(error.Unexpected == switch (e) {
11188 .NOTCONN => return error.BrokenPipe,
11189 .IO => return error.InputOutput,
11190 .PIPE => return error.BrokenPipe,
11191 .NOBUFS => return error.SystemResources,
11192 .BADF => |err| errnoBug(err),
11193 .FAULT => |err| errnoBug(err),
11194 else => |err| posix.unexpectedErrno(err),
11195 });
11196 // Give calling code chance to observe the error before trying
11197 // something else.
11198 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
11199 return 0;
11200 },
11201 }
11202 }
11203 if (sbytes == 0) {
11204 file_reader.size = file_reader.pos;
11205 return error.EndOfStream;
11206 }
11207 const ubytes: usize = @intCast(sbytes);
11208 file_reader.interface.toss(ubytes -| header.len);
11209 return ubytes;
11210 }
11211
11212 if (is_darwin) sf: {
11213 // Try using sendfile on macOS.
11214 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
11215 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
11216 var hdtr_data: std.c.sf_hdtr = undefined;
11217 var headers: [2]posix.iovec_const = undefined;
11218 var headers_i: u8 = 0;
11219 if (header.len != 0) {
11220 headers[headers_i] = .{ .base = header.ptr, .len = header.len };
11221 headers_i += 1;
11222 }
11223 if (reader_buffered.len != 0) {
11224 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
11225 headers_i += 1;
11226 }
11227 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
11228 hdtr_data = .{
11229 .headers = &headers,
11230 .hdr_cnt = headers_i,
11231 .trailers = null,
11232 .trl_cnt = 0,
11233 };
11234 break :b &hdtr_data;
11235 };
11236 const max_count = std.math.maxInt(i32); // Avoid EINVAL.
11237 var len: std.c.off_t = @min(file_limit, max_count);
11238 const flags = 0;
11239 const syscall: Syscall = try .start();
11240 while (true) {
11241 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
11242 .SUCCESS => {
11243 syscall.finish();
11244 break;
11245 },
11246 .OPNOTSUPP, .NOTSOCK, .NOSYS => {
11247 // Give calling code chance to observe before trying
11248 // something else.
11249 syscall.finish();
11250 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
11251 return 0;
11252 },
11253 .INTR => {
11254 if (len == 0) {
11255 try syscall.checkCancel();
11256 continue;
11257 } else {
11258 // Even if we are being canceled, there have been side
11259 // effects, so it is better to report those side
11260 // effects to the caller.
11261 syscall.finish();
11262 break;
11263 }
11264 },
11265 .AGAIN => {
11266 syscall.finish();
11267 if (len == 0) return error.WouldBlock;
11268 break;
11269 },
11270 else => |e| {
11271 syscall.finish();
11272 assert(error.Unexpected == switch (e) {
11273 .NOTCONN => return error.BrokenPipe,
11274 .IO => return error.InputOutput,
11275 .PIPE => return error.BrokenPipe,
11276 .BADF => |err| errnoBug(err),
11277 .FAULT => |err| errnoBug(err),
11278 .INVAL => |err| errnoBug(err),
11279 else => |err| posix.unexpectedErrno(err),
11280 });
11281 // Give calling code chance to observe the error before trying
11282 // something else.
11283 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
11284 return 0;
11285 },
11286 }
11287 }
11288 if (len == 0) {
11289 file_reader.size = file_reader.pos;
11290 return error.EndOfStream;
11291 }
11292 const u_len: usize = @bitCast(len);
11293 file_reader.interface.toss(u_len -| header.len);
11294 return u_len;
11295 }
11296
11297 if (native_os == .linux) sf: {
11298 // Try using sendfile on Linux.
11299 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
11300 // Linux sendfile does not support headers.
11301 if (header.len != 0 or reader_buffered.len != 0) {
11302 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
11303 file_reader.interface.toss(n -| header.len);
11304 return n;
11305 }
11306 const max_count = 0x7ffff000; // Avoid EINVAL.
11307 var off: std.os.linux.off_t = undefined;
11308 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
11309 .positional => o: {
11310 const size = file_reader.getSize() catch |err| switch (err) {
11311 error.Canceled => |e| return e,
11312 else => break :sf,
11313 };
11314 off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed;
11315 break :o .{ &off, @min(@backingInt(limit), size - file_reader.pos, max_count) };
11316 },
11317 .streaming => .{ null, limit.minInt(max_count) },
11318 .streaming_simple, .positional_simple => break :sf,
11319 .failure => return error.ReadFailed,
11320 };
11321 const syscall: Syscall = try .start();
11322 const n: usize = while (true) {
11323 const rc = sendfile_sym(out_fd, in_fd, off_ptr, count);
11324 switch (posix.errno(rc)) {
11325 .SUCCESS => {
11326 syscall.finish();
11327 break @intCast(rc);
11328 },
11329 .NOSYS, .INVAL => {
11330 // Give calling code chance to observe before trying
11331 // something else.
11332 syscall.finish();
11333 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
11334 return 0;
11335 },
11336 .INTR => {
11337 try syscall.checkCancel();
11338 continue;
11339 },
11340 else => |e| {
11341 syscall.finish();
11342 assert(error.Unexpected == switch (e) {
11343 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
11344 .AGAIN => return error.WouldBlock,
11345 .IO => return error.InputOutput,
11346 .PIPE => return error.BrokenPipe,
11347 .NOMEM => return error.SystemResources,
11348 .NXIO, .SPIPE => {
11349 file_reader.mode = file_reader.mode.toStreaming();
11350 const pos = file_reader.pos;
11351 if (pos != 0) {
11352 file_reader.pos = 0;
11353 file_reader.seekBy(@intCast(pos)) catch {
11354 file_reader.mode = .failure;
11355 return error.ReadFailed;
11356 };
11357 }
11358 return 0;
11359 },
11360 .BADF => |err| errnoBug(err), // Always a race condition.
11361 .FAULT => |err| errnoBug(err), // Segmentation fault.
11362 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`.
11363 else => |err| posix.unexpectedErrno(err),
11364 });
11365 // Give calling code chance to observe the error before trying
11366 // something else.
11367 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
11368 return 0;
11369 },
11370 }
11371 };
11372 if (n == 0) {
11373 file_reader.size = file_reader.pos;
11374 return error.EndOfStream;
11375 }
11376 file_reader.pos += n;
11377 return n;
11378 }
11379
11380 if (have_copy_file_range) cfr: {
11381 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
11382 if (header.len != 0 or reader_buffered.len != 0) {
11383 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
11384 file_reader.interface.toss(n -| header.len);
11385 return n;
11386 }
11387 var len: usize = @backingInt(limit);
11388 var off_in: i64 = undefined;
11389 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
11390 .positional_simple, .streaming_simple => return error.Unimplemented,
11391 .positional => p: {
11392 len = @min(len, std.math.maxInt(usize) - file_reader.pos);
11393 off_in = @intCast(file_reader.pos);
11394 break :p &off_in;
11395 },
11396 .streaming => null,
11397 .failure => return error.ReadFailed,
11398 };
11399 const n: usize = switch (native_os) {
11400 .linux => n: {
11401 const syscall: Syscall = try .start();
11402 while (true) {
11403 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, len, 0);
11404 switch (linux_copy_file_range_sys.errno(rc)) {
11405 .SUCCESS => {
11406 syscall.finish();
11407 break :n @intCast(rc);
11408 },
11409 .INTR => {
11410 try syscall.checkCancel();
11411 continue;
11412 },
11413 .OPNOTSUPP, .INVAL, .NOSYS => {
11414 // Give calling code chance to observe before trying
11415 // something else.
11416 syscall.finish();
11417 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11418 return 0;
11419 },
11420 else => |e| {
11421 syscall.finish();
11422 assert(error.Unexpected == switch (e) {
11423 .FBIG => return error.FileTooBig,
11424 .IO => return error.InputOutput,
11425 .NOMEM => return error.SystemResources,
11426 .NOSPC => return error.NoSpaceLeft,
11427 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count.
11428 .PERM => return error.PermissionDenied,
11429 .BUSY => return error.DeviceBusy,
11430 .TXTBSY => return error.FileBusy,
11431 // copy_file_range can still work but not on
11432 // this pair of file descriptors.
11433 .XDEV => return error.Unimplemented,
11434 .ISDIR => |err| errnoBug(err),
11435 .BADF => |err| errnoBug(err),
11436 else => |err| posix.unexpectedErrno(err),
11437 });
11438 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11439 return 0;
11440 },
11441 }
11442 }
11443 },
11444 .freebsd => n: {
11445 const syscall: Syscall = try .start();
11446 while (true) {
11447 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @backingInt(limit), 0);
11448 switch (std.c.errno(rc)) {
11449 .SUCCESS => {
11450 syscall.finish();
11451 break :n @intCast(rc);
11452 },
11453 .INTR => {
11454 try syscall.checkCancel();
11455 continue;
11456 },
11457 .OPNOTSUPP, .INVAL, .NOSYS => {
11458 // Give calling code chance to observe before trying
11459 // something else.
11460 syscall.finish();
11461 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11462 return 0;
11463 },
11464 else => |e| {
11465 syscall.finish();
11466 assert(error.Unexpected == switch (e) {
11467 .FBIG => return error.FileTooBig,
11468 .IO => return error.InputOutput,
11469 .INTEGRITY => return error.CorruptedData,
11470 .NOSPC => return error.NoSpaceLeft,
11471 .ISDIR => |err| errnoBug(err),
11472 .BADF => |err| errnoBug(err),
11473 else => |err| posix.unexpectedErrno(err),
11474 });
11475 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11476 return 0;
11477 },
11478 }
11479 }
11480 },
11481 else => comptime unreachable,
11482 };
11483 if (n == 0) {
11484 file_reader.size = file_reader.pos;
11485 return error.EndOfStream;
11486 }
11487 file_reader.pos += n;
11488 return n;
11489 }
11490
11491 return error.Unimplemented;
11492}
11493
11494fn netWriteFile(
11495 userdata: ?*anyopaque,
11496 socket_handle: net.Socket.Handle,
11497 header: []const u8,
11498 file_reader: *File.Reader,
11499 limit: Io.Limit,
11500) net.Stream.Writer.WriteFileError!usize {
11501 const t: *Threaded = @ptrCast(@alignCast(userdata));
11502 _ = t;
11503 _ = socket_handle;
11504 _ = header;
11505 _ = file_reader;
11506 _ = limit;
11507 // TODO implement netWriteFile
11508 return error.Unimplemented;
11509}
11510
11511fn fileWriteFilePositional(
11512 userdata: ?*anyopaque,
11513 file: File,
11514 header: []const u8,
11515 file_reader: *File.Reader,
11516 limit: Io.Limit,
11517 offset: u64,
11518) File.WriteFilePositionalError!usize {
11519 const t: *Threaded = @ptrCast(@alignCast(userdata));
11520 const reader_buffered = file_reader.interface.buffered();
11521 if (reader_buffered.len >= @backingInt(limit)) {
11522 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
11523 file_reader.interface.toss(n -| header.len);
11524 return n;
11525 }
11526 const out_fd = file.handle;
11527 const in_fd = file_reader.file.handle;
11528
11529 if (file_reader.size) |size| {
11530 if (size - file_reader.pos == 0) {
11531 if (reader_buffered.len != 0) {
11532 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
11533 file_reader.interface.toss(n -| header.len);
11534 return n;
11535 } else {
11536 return error.EndOfStream;
11537 }
11538 }
11539 }
11540
11541 if (have_copy_file_range) cfr: {
11542 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
11543 if (header.len != 0 or reader_buffered.len != 0) {
11544 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
11545 file_reader.interface.toss(n -| header.len);
11546 return n;
11547 }
11548 var len: usize = @min(@backingInt(limit), std.math.maxInt(usize) - offset);
11549 var off_in: i64 = undefined;
11550 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
11551 .positional_simple, .streaming_simple => return error.Unimplemented,
11552 .positional => p: {
11553 len = @min(len, std.math.maxInt(usize) - file_reader.pos);
11554 off_in = @intCast(file_reader.pos);
11555 break :p &off_in;
11556 },
11557 .streaming => null,
11558 .failure => return error.ReadFailed,
11559 };
11560 var off_out: i64 = @intCast(offset);
11561 const n: usize = switch (native_os) {
11562 .linux => n: {
11563 const syscall: Syscall = try .start();
11564 while (true) {
11565 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, len, 0);
11566 switch (linux_copy_file_range_sys.errno(rc)) {
11567 .SUCCESS => {
11568 syscall.finish();
11569 break :n @intCast(rc);
11570 },
11571 .INTR => {
11572 try syscall.checkCancel();
11573 continue;
11574 },
11575 .OPNOTSUPP, .INVAL, .NOSYS => {
11576 // Give calling code chance to observe before trying
11577 // something else.
11578 syscall.finish();
11579 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11580 return 0;
11581 },
11582 else => |e| {
11583 syscall.finish();
11584 assert(error.Unexpected == switch (e) {
11585 .FBIG => return error.FileTooBig,
11586 .IO => return error.InputOutput,
11587 .NOMEM => return error.SystemResources,
11588 .NOSPC => return error.NoSpaceLeft,
11589 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count.
11590 .NXIO => return error.Unseekable,
11591 .SPIPE => return error.Unseekable,
11592 .PERM => return error.PermissionDenied,
11593 .TXTBSY => return error.FileBusy,
11594 // copy_file_range can still work but not on
11595 // this pair of file descriptors.
11596 .XDEV => return error.Unimplemented,
11597 .ISDIR => |err| errnoBug(err),
11598 .BADF => |err| errnoBug(err),
11599 else => |err| posix.unexpectedErrno(err),
11600 });
11601 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11602 return 0;
11603 },
11604 }
11605 }
11606 },
11607 .freebsd => n: {
11608 const syscall: Syscall = try .start();
11609 while (true) {
11610 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @backingInt(limit), 0);
11611 switch (std.c.errno(rc)) {
11612 .SUCCESS => {
11613 syscall.finish();
11614 break :n @intCast(rc);
11615 },
11616 .INTR => {
11617 try syscall.checkCancel();
11618 continue;
11619 },
11620 .OPNOTSUPP, .INVAL, .NOSYS => {
11621 // Give calling code chance to observe before trying
11622 // something else.
11623 syscall.finish();
11624 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11625 return 0;
11626 },
11627 else => |e| {
11628 syscall.finish();
11629 assert(error.Unexpected == switch (e) {
11630 .FBIG => return error.FileTooBig,
11631 .IO => return error.InputOutput,
11632 .INTEGRITY => return error.CorruptedData,
11633 .NOSPC => return error.NoSpaceLeft,
11634 .OVERFLOW => return error.Unseekable,
11635 .NXIO => return error.Unseekable,
11636 .SPIPE => return error.Unseekable,
11637 .ISDIR => |err| errnoBug(err),
11638 .BADF => |err| errnoBug(err),
11639 else => |err| posix.unexpectedErrno(err),
11640 });
11641 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
11642 return 0;
11643 },
11644 }
11645 }
11646 },
11647 else => comptime unreachable,
11648 };
11649 if (n == 0) {
11650 file_reader.size = file_reader.pos;
11651 return error.EndOfStream;
11652 }
11653 file_reader.pos += n;
11654 return n;
11655 }
11656
11657 if (is_darwin) fcf: {
11658 if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf;
11659 if (file_reader.pos != 0) break :fcf;
11660 if (offset != 0) break :fcf;
11661 if (limit != .unlimited) break :fcf;
11662 const size = file_reader.getSize() catch |err| switch (err) {
11663 error.Canceled => |e| return e,
11664 else => break :fcf,
11665 };
11666 if (header.len != 0 or reader_buffered.len != 0) {
11667 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
11668 file_reader.interface.toss(n -| header.len);
11669 return n;
11670 }
11671 const syscall: Syscall = try .start();
11672 while (true) {
11673 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
11674 switch (posix.errno(rc)) {
11675 .SUCCESS => {
11676 syscall.finish();
11677 break;
11678 },
11679 .INTR => {
11680 try syscall.checkCancel();
11681 continue;
11682 },
11683 .OPNOTSUPP => {
11684 // Give calling code chance to observe before trying
11685 // something else.
11686 syscall.finish();
11687 @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic);
11688 return 0;
11689 },
11690 else => |e| {
11691 syscall.finish();
11692 assert(error.Unexpected == switch (e) {
11693 .NOMEM => return error.SystemResources,
11694 .INVAL => |err| errnoBug(err),
11695 else => |err| posix.unexpectedErrno(err),
11696 });
11697 return 0;
11698 },
11699 }
11700 }
11701 file_reader.pos = size;
11702 return size;
11703 }
11704
11705 return error.Unimplemented;
11706}
11707
11708fn nowPosix(clock: Io.Clock) Io.Timestamp {
11709 const clock_id: posix.clockid_t = clockToPosix(clock);
11710 var timespec: posix.timespec = undefined;
11711 switch (posix.errno(posix.system.clock_gettime(clock_id, &timespec))) {
11712 .SUCCESS => return timestampFromPosix(&timespec),
11713 else => return .zero,
11714 }
11715}
11716
11717fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp {
11718 const t: *Threaded = @ptrCast(@alignCast(userdata));
11719 _ = t;
11720 return switch (native_os) {
11721 .windows => nowWindows(clock),
11722 .wasi => nowWasi(clock),
11723 else => nowPosix(clock),
11724 };
11725}
11726
11727fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration {
11728 const t: *Threaded = @ptrCast(@alignCast(userdata));
11729 _ = t;
11730 return switch (native_os) {
11731 .windows => switch (clock) {
11732 .awake, .boot, .real => {
11733 // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA
11734 // (a read-only page of info updated and mapped by the kernel to all processes):
11735 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data
11736 // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
11737 var qpf: windows.LARGE_INTEGER = undefined;
11738 if (!windows.ntdll.RtlQueryPerformanceFrequency(&qpf).toBool()) {
11739 recoverableOsBugDetected();
11740 return .zero;
11741 }
11742 // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it.
11743 // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701
11744 const common_qpf = 10_000_000;
11745 if (qpf == common_qpf) return .fromNanoseconds(std.time.ns_per_s / common_qpf);
11746
11747 // Convert to ns using fixed point.
11748 const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf));
11749 const result = scale >> 32;
11750 return .fromNanoseconds(result);
11751 },
11752 .cpu_process, .cpu_thread => return error.ClockUnavailable,
11753 },
11754 .wasi => {
11755 if (builtin.link_libc) return clockResolutionPosix(clock);
11756 var ns: std.os.wasi.timestamp_t = undefined;
11757 return switch (std.os.wasi.clock_res_get(clockToWasi(clock), &ns)) {
11758 .SUCCESS => .fromNanoseconds(ns),
11759 .INVAL => return error.ClockUnavailable,
11760 else => |err| return posix.unexpectedErrno(err),
11761 };
11762 },
11763 else => return clockResolutionPosix(clock),
11764 };
11765}
11766
11767fn clockResolutionPosix(clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration {
11768 const clock_id: posix.clockid_t = clockToPosix(clock);
11769 var timespec: posix.timespec = undefined;
11770 return switch (posix.errno(posix.system.clock_getres(clock_id, &timespec))) {
11771 .SUCCESS => .fromNanoseconds(nanosecondsFromPosix(&timespec)),
11772 .INVAL => return error.ClockUnavailable,
11773 else => |err| return posix.unexpectedErrno(err),
11774 };
11775}
11776
11777fn nowWindows(clock: Io.Clock) Io.Timestamp {
11778 switch (clock) {
11779 .real => {
11780 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
11781 // and uses the NTFS/Windows epoch, which is 1601-01-01.
11782 const epoch_ns = std.time.epoch.windows * std.time.ns_per_s;
11783 return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 + epoch_ns };
11784 },
11785 .awake, .boot => {
11786 // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA
11787 // (a read-only page of info updated and mapped by the kernel to all processes):
11788 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data
11789 // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
11790 const qpf: u64 = qpf: {
11791 var qpf: windows.LARGE_INTEGER = undefined;
11792 assert(windows.ntdll.RtlQueryPerformanceFrequency(&qpf).toBool());
11793 break :qpf @bitCast(qpf);
11794 };
11795
11796 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
11797 const qpc: u64 = qpc: {
11798 var qpc: windows.LARGE_INTEGER = undefined;
11799 assert(windows.ntdll.RtlQueryPerformanceCounter(&qpc).toBool());
11800 break :qpc @bitCast(qpc);
11801 };
11802
11803 // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it.
11804 // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701
11805 const common_qpf = 10_000_000;
11806 if (qpf == common_qpf) return .{ .nanoseconds = qpc * (std.time.ns_per_s / common_qpf) };
11807
11808 // Convert to ns using fixed point.
11809 const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf));
11810 const result = (@as(u96, qpc) * scale) >> 32;
11811 return .{ .nanoseconds = @intCast(result) };
11812 },
11813 .cpu_process => {
11814 const handle = windows.GetCurrentProcess();
11815 var times: windows.KERNEL_USER_TIMES = undefined;
11816
11817 // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L442-L485
11818 if (windows.ntdll.NtQueryInformationProcess(
11819 handle,
11820 .Times,
11821 &times,
11822 @sizeOf(windows.KERNEL_USER_TIMES),
11823 null,
11824 ) != .SUCCESS) return .zero;
11825
11826 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);
11827 return .{ .nanoseconds = sum * 100 };
11828 },
11829 .cpu_thread => {
11830 const handle = windows.GetCurrentThread();
11831 var times: windows.KERNEL_USER_TIMES = undefined;
11832
11833 // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L2971-L3019
11834 if (windows.ntdll.NtQueryInformationThread(
11835 handle,
11836 .Times,
11837 &times,
11838 @sizeOf(windows.KERNEL_USER_TIMES),
11839 null,
11840 ) != .SUCCESS) return .zero;
11841
11842 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);
11843 return .{ .nanoseconds = sum * 100 };
11844 },
11845 }
11846}
11847
11848fn nowWasi(clock: Io.Clock) Io.Timestamp {
11849 var ns: std.os.wasi.timestamp_t = undefined;
11850 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
11851 if (err != .SUCCESS) return .zero;
11852 return .fromNanoseconds(ns);
11853}
11854
11855fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {
11856 const t: *Threaded = @ptrCast(@alignCast(userdata));
11857 if (use_parking_sleep) return parking_sleep.sleep(timeout);
11858 if (native_os == .wasi) return sleepWasi(t, timeout);
11859 if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout);
11860 return sleepNanosleep(t, timeout);
11861}
11862
11863fn sleepPosix(timeout: Io.Timeout) Io.Cancelable!void {
11864 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
11865 .none => .awake,
11866 .duration => |d| d.clock,
11867 .deadline => |d| d.clock,
11868 });
11869 const deadline_nanoseconds: i96 = switch (timeout) {
11870 .none => std.math.maxInt(i96),
11871 .duration => |duration| duration.raw.nanoseconds,
11872 .deadline => |deadline| deadline.raw.nanoseconds,
11873 };
11874 var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds);
11875 const syscall: Syscall = try .start();
11876 while (true) {
11877 const rc = posix.system.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) {
11878 .none, .duration => false,
11879 .deadline => true,
11880 } }, &timespec, &timespec);
11881 // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly
11882 switch (if (builtin.link_libc) @as(posix.E, @fromBackingInt(@intCast(rc))) else posix.errno(rc)) {
11883 .INTR => {
11884 try syscall.checkCancel();
11885 continue;
11886 },
11887 // Handles SUCCESS as well as clock not available and unexpected
11888 // errors. The user had a chance to check clock resolution before
11889 // getting here, which would have reported 0, making this a legal
11890 // amount of time to sleep.
11891 else => {
11892 syscall.finish();
11893 return;
11894 },
11895 }
11896 }
11897}
11898
11899fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
11900 const t_io = io(t);
11901 const w = std.os.wasi;
11902
11903 const clock: w.subscription_clock_t = if (timeout.toDurationFromNow(t_io)) |d| .{
11904 .id = clockToWasi(d.clock),
11905 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds),
11906 .precision = 0,
11907 .flags = 0,
11908 } else .{
11909 .id = .MONOTONIC,
11910 .timeout = std.math.maxInt(u64),
11911 .precision = 0,
11912 .flags = 0,
11913 };
11914 const in: w.subscription_t = .{
11915 .userdata = 0,
11916 .u = .{
11917 .tag = .CLOCK,
11918 .u = .{ .clock = clock },
11919 },
11920 };
11921 var event: w.event_t = undefined;
11922 var nevents: usize = undefined;
11923 const syscall: Syscall = try .start();
11924 _ = w.poll_oneoff(&in, &event, 1, &nevents);
11925 syscall.finish();
11926}
11927
11928fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
11929 const t_io = io(t);
11930 const sec_type = @typeInfo(posix.timespec).@"struct".field_types[0];
11931 const nsec_type = @typeInfo(posix.timespec).@"struct".field_types[1];
11932
11933 var timespec: posix.timespec = t: {
11934 const d = timeout.toDurationFromNow(t_io) orelse break :t .{
11935 .sec = std.math.maxInt(sec_type),
11936 .nsec = std.math.maxInt(nsec_type),
11937 };
11938 break :t timestampToPosix(d.raw.toNanoseconds());
11939 };
11940 const syscall: Syscall = try .start();
11941 while (true) {
11942 switch (posix.errno(posix.system.nanosleep(&timespec, &timespec))) {
11943 .INTR => {
11944 try syscall.checkCancel();
11945 continue;
11946 },
11947 // This prong handles success as well as unexpected errors.
11948 else => return syscall.finish(),
11949 }
11950 }
11951}
11952
11953fn netListenIpPosix(
11954 userdata: ?*anyopaque,
11955 address: *const IpAddress,
11956 options: IpAddress.ListenOptions,
11957) IpAddress.ListenError!net.Socket {
11958 if (!have_networking) return error.NetworkDown;
11959 const t: *Threaded = @ptrCast(@alignCast(userdata));
11960 _ = t;
11961 const family = posixAddressFamily(address);
11962 const socket_fd = try openSocketPosix(family, .{ .mode = options.mode, .protocol = options.protocol });
11963 errdefer closeFd(socket_fd);
11964
11965 if (options.reuse_address) {
11966 try setSocketOptionPosix(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
11967 if (@hasDecl(posix.SO, "REUSEPORT"))
11968 try setSocketOptionPosix(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
11969 }
11970
11971 var storage: PosixAddress = undefined;
11972 var addr_len = addressToPosix(address, &storage);
11973 try posixBind(socket_fd, &storage.any, addr_len);
11974
11975 const syscall: Syscall = try .start();
11976 while (true) {
11977 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
11978 .SUCCESS => {
11979 syscall.finish();
11980 break;
11981 },
11982 .INTR => {
11983 try syscall.checkCancel();
11984 continue;
11985 },
11986 .ADDRINUSE => return syscall.fail(error.AddressInUse),
11987 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
11988 else => |err| return syscall.unexpectedErrno(err),
11989 }
11990 }
11991
11992 try posixGetSockName(socket_fd, &storage.any, &addr_len);
11993 return .{ .handle = socket_fd, .address = addressFromPosix(&storage) };
11994}
11995
11996fn netListenIpWindows(
11997 userdata: ?*anyopaque,
11998 address: *const IpAddress,
11999 options: IpAddress.ListenOptions,
12000) IpAddress.ListenError!net.Socket {
12001 if (!have_networking) return error.NetworkDown;
12002 const t: *Threaded = @ptrCast(@alignCast(userdata));
12003 _ = t;
12004 const family = posixAddressFamily(address);
12005 const socket_handle = try openSocketAfd(family, .{ .mode = options.mode, .protocol = options.protocol });
12006 errdefer windows.CloseHandle(socket_handle);
12007 if (options.reuse_address) try setSocketOptionAfd(socket_handle, ws2_32.SOL.SOCKET, ws2_32.SO.REUSEADDR, true);
12008 const bound_address = try bindSocketIpAfd(socket_handle, address, .Passive);
12009 switch ((try deviceIoControl(&.{
12010 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
12011 .code = windows.IOCTL.AFD.START_LISTEN,
12012 .in = @ptrCast(&windows.AFD.LISTEN_INFO{
12013 .UseSAN = .FALSE,
12014 .MaximumConnectionQueue = options.kernel_backlog,
12015 .UseDelayedAcceptance = .FALSE,
12016 }),
12017 })).u.Status) {
12018 .SUCCESS => {},
12019 .CANCELLED => unreachable,
12020 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12021 else => |status| return windows.unexpectedStatus(status),
12022 }
12023 return .{ .handle = socket_handle, .address = bound_address };
12024}
12025
12026fn netListenUnixPosix(
12027 userdata: ?*anyopaque,
12028 address: *const net.UnixAddress,
12029 options: net.UnixAddress.ListenOptions,
12030) net.UnixAddress.ListenError!net.Socket.Handle {
12031 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
12032 const t: *Threaded = @ptrCast(@alignCast(userdata));
12033 _ = t;
12034 const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
12035 error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported,
12036 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
12037 error.SocketModeUnsupported => return error.AddressFamilyUnsupported,
12038 error.OptionUnsupported => return error.Unexpected,
12039 else => |e| return e,
12040 };
12041 errdefer closeFd(socket_fd);
12042
12043 var storage: UnixAddress = undefined;
12044 const addr_len = addressUnixToPosix(address, &storage);
12045 try posixBindUnix(socket_fd, &storage.any, addr_len);
12046
12047 const syscall: Syscall = try .start();
12048 while (true) {
12049 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
12050 .SUCCESS => {
12051 syscall.finish();
12052 break;
12053 },
12054 .INTR => {
12055 try syscall.checkCancel();
12056 continue;
12057 },
12058 .ADDRINUSE => return syscall.fail(error.AddressInUse),
12059 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
12060 else => |err| return syscall.unexpectedErrno(err),
12061 }
12062 }
12063
12064 return socket_fd;
12065}
12066
12067fn netListenUnixWindows(
12068 userdata: ?*anyopaque,
12069 address: *const net.UnixAddress,
12070 options: net.UnixAddress.ListenOptions,
12071) net.UnixAddress.ListenError!net.Socket.Handle {
12072 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
12073 if (!have_networking) return error.NetworkDown;
12074 const t: *Threaded = @ptrCast(@alignCast(userdata));
12075 _ = t;
12076 const is_abstract = address.isAbstract();
12077 const wps = if (!is_abstract) sliceToPrefixedFileW(null, address.path, .{
12078 .allow_relative = false,
12079 }) catch |err| switch (err) {
12080 error.NameTooLong, error.BadPathName => return error.AddressUnavailable,
12081 else => |e| return e,
12082 } else undefined;
12083 const socket_handle = openSocketAfd(ws2_32.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
12084 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
12085 else => |e| return e,
12086 };
12087 errdefer windows.CloseHandle(socket_handle);
12088 if (!is_abstract) try socketOptionAfd(socket_handle, .special, 0, ws2_32.SO.UNIX_PATH, @constCast(
12089 @as([]const u8, @ptrCast(&windows.AFD.SOCKOPT_INFO.UNIX_PATH{
12090 .Path = wps.data,
12091 }))[0 .. @offsetOf(windows.AFD.SOCKOPT_INFO.UNIX_PATH, "Path") + @sizeOf(windows.WCHAR) * wps.len],
12092 ));
12093 try bindSocketUnixAfd(socket_handle, address);
12094 switch ((try deviceIoControl(&.{
12095 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
12096 .code = windows.IOCTL.AFD.START_LISTEN,
12097 .in = @ptrCast(&windows.AFD.LISTEN_INFO{
12098 .UseSAN = .FALSE,
12099 .MaximumConnectionQueue = options.kernel_backlog,
12100 .UseDelayedAcceptance = .FALSE,
12101 }),
12102 })).u.Status) {
12103 .SUCCESS => {},
12104 .CANCELLED => unreachable,
12105 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12106 else => |status| return windows.unexpectedStatus(status),
12107 }
12108 return socket_handle;
12109}
12110
12111fn posixBindUnix(
12112 fd: posix.socket_t,
12113 addr: *const posix.sockaddr,
12114 addr_len: posix.socklen_t,
12115) !void {
12116 const syscall: Syscall = try .start();
12117 while (true) {
12118 switch (posix.errno(posix.system.bind(fd, addr, addr_len))) {
12119 .SUCCESS => {
12120 syscall.finish();
12121 break;
12122 },
12123 .INTR => {
12124 try syscall.checkCancel();
12125 continue;
12126 },
12127 else => |e| {
12128 syscall.finish();
12129 switch (e) {
12130 .ACCES => return error.AccessDenied,
12131 .ADDRINUSE => return error.AddressInUse,
12132 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
12133 .ADDRNOTAVAIL => return error.AddressUnavailable,
12134 .NOMEM => return error.SystemResources,
12135
12136 .LOOP => return error.SymLinkLoop,
12137 .NOENT => return error.FileNotFound,
12138 .NOTDIR => return error.NotDir,
12139 .ROFS => return error.ReadOnlyFileSystem,
12140 .PERM => return error.PermissionDenied,
12141
12142 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12143 .INVAL => |err| return errnoBug(err), // invalid parameters
12144 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
12145 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer
12146 .NAMETOOLONG => |err| return errnoBug(err),
12147 else => |err| return posix.unexpectedErrno(err),
12148 }
12149 },
12150 }
12151 }
12152}
12153
12154fn posixBind(
12155 socket_fd: posix.socket_t,
12156 addr: *const posix.sockaddr,
12157 addr_len: posix.socklen_t,
12158) !void {
12159 const syscall: Syscall = try .start();
12160 while (true) {
12161 switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) {
12162 .SUCCESS => {
12163 syscall.finish();
12164 break;
12165 },
12166 .INTR => {
12167 try syscall.checkCancel();
12168 continue;
12169 },
12170 else => |e| {
12171 syscall.finish();
12172 switch (e) {
12173 .ACCES => return error.AccessDenied,
12174 .ADDRINUSE => return error.AddressInUse,
12175 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12176 .INVAL => |err| return errnoBug(err), // invalid parameters
12177 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
12178 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
12179 .ADDRNOTAVAIL => return error.AddressUnavailable,
12180 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer
12181 .NOMEM => return error.SystemResources,
12182 else => |err| return posix.unexpectedErrno(err),
12183 }
12184 },
12185 }
12186 }
12187}
12188
12189fn posixConnect(
12190 socket_fd: posix.socket_t,
12191 addr: *const posix.sockaddr,
12192 addr_len: posix.socklen_t,
12193) !void {
12194 const syscall: Syscall = try .start();
12195 while (true) switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) {
12196 .SUCCESS => {
12197 syscall.finish();
12198 return;
12199 },
12200 .INTR => {
12201 try syscall.checkCancel();
12202 continue;
12203 },
12204 .ADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable),
12205 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
12206 .AGAIN, .INPROGRESS => return syscall.fail(error.WouldBlock),
12207 .ALREADY => return syscall.fail(error.ConnectionPending),
12208 .CONNREFUSED => return syscall.fail(error.ConnectionRefused),
12209 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
12210 .HOSTUNREACH => return syscall.fail(error.HostUnreachable),
12211 .NETUNREACH => return syscall.fail(error.NetworkUnreachable),
12212 .TIMEDOUT => return syscall.fail(error.Timeout),
12213 .ACCES => return syscall.fail(error.AccessDenied),
12214 .NETDOWN => return syscall.fail(error.NetworkDown),
12215 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
12216 .CONNABORTED => |err| return syscall.errnoBug(err),
12217 .FAULT => |err| return syscall.errnoBug(err),
12218 .ISCONN => |err| return syscall.errnoBug(err),
12219 .NOENT => |err| return syscall.errnoBug(err),
12220 .NOTSOCK => |err| return syscall.errnoBug(err),
12221 .PERM => |err| return syscall.errnoBug(err),
12222 .PROTOTYPE => |err| return syscall.errnoBug(err),
12223 else => |err| return syscall.unexpectedErrno(err),
12224 };
12225}
12226
12227fn posixConnectUnix(
12228 fd: posix.socket_t,
12229 addr: *const posix.sockaddr,
12230 addr_len: posix.socklen_t,
12231) !void {
12232 const syscall: Syscall = try .start();
12233 while (true) {
12234 switch (posix.errno(posix.system.connect(fd, addr, addr_len))) {
12235 .SUCCESS => {
12236 syscall.finish();
12237 return;
12238 },
12239 .INTR => {
12240 try syscall.checkCancel();
12241 continue;
12242 },
12243 else => |e| {
12244 syscall.finish();
12245 switch (e) {
12246 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
12247 .AGAIN => return error.WouldBlock,
12248 .INPROGRESS => return error.WouldBlock,
12249 .ACCES => return error.AccessDenied,
12250
12251 .LOOP => return error.SymLinkLoop,
12252 .NOENT => return error.FileNotFound,
12253 .NOTDIR => return error.NotDir,
12254 .ROFS => return error.ReadOnlyFileSystem,
12255 .PERM => return error.PermissionDenied,
12256 .CONNREFUSED => return error.ConnectionRefused,
12257
12258 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12259 .CONNABORTED => |err| return errnoBug(err),
12260 .FAULT => |err| return errnoBug(err),
12261 .ISCONN => |err| return errnoBug(err),
12262 .NOTSOCK => |err| return errnoBug(err),
12263 .PROTOTYPE => |err| return errnoBug(err),
12264 else => |err| return posix.unexpectedErrno(err),
12265 }
12266 },
12267 }
12268 }
12269}
12270
12271fn posixGetSockName(
12272 socket_fd: posix.fd_t,
12273 addr: *posix.sockaddr,
12274 addr_len: *posix.socklen_t,
12275) !void {
12276 const syscall: Syscall = try .start();
12277 while (true) {
12278 switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) {
12279 .SUCCESS => {
12280 syscall.finish();
12281 break;
12282 },
12283 .INTR => {
12284 try syscall.checkCancel();
12285 continue;
12286 },
12287 else => |e| {
12288 syscall.finish();
12289 switch (e) {
12290 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12291 .FAULT => |err| return errnoBug(err),
12292 .INVAL => |err| return errnoBug(err), // invalid parameters
12293 .NOTSOCK => |err| return errnoBug(err), // always a race condition
12294 .NOBUFS => return error.SystemResources,
12295 else => |err| return posix.unexpectedErrno(err),
12296 }
12297 },
12298 }
12299 }
12300}
12301
12302fn setSocketOptionPosix(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
12303 const o: []const u8 = @ptrCast(&option);
12304 const syscall: Syscall = try .start();
12305 while (true) {
12306 switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) {
12307 .SUCCESS => {
12308 syscall.finish();
12309 return;
12310 },
12311 .INTR => {
12312 try syscall.checkCancel();
12313 continue;
12314 },
12315 else => |e| {
12316 syscall.finish();
12317 switch (e) {
12318 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12319 .NOTSOCK => |err| return errnoBug(err),
12320 .INVAL => |err| return errnoBug(err),
12321 .FAULT => |err| return errnoBug(err),
12322 else => |err| return posix.unexpectedErrno(err),
12323 }
12324 },
12325 }
12326 }
12327}
12328
12329fn setSocketOptionAfd(socket: net.Socket.Handle, level: i32, opt_name: u32, opt_val: anytype) !void {
12330 try socketOptionAfd(socket, .set, level, opt_name, @ptrCast(@constCast(&opt_val)));
12331}
12332
12333fn socketOptionAfd(socket: net.Socket.Handle, mode: windows.AFD.SOCKOPT_INFO.Mode, level: i32, opt_name: u32, opt_val: []u8) !void {
12334 switch ((try deviceIoControl(&.{
12335 .file = .{ .handle = socket, .flags = .{ .nonblocking = true } },
12336 .code = windows.IOCTL.AFD.SOCKOPT,
12337 .in = @ptrCast(&windows.AFD.SOCKOPT_INFO{
12338 .mode = mode,
12339 .level = level,
12340 .optname = opt_name,
12341 .optval = opt_val.ptr,
12342 .optlen = opt_val.len,
12343 }),
12344 })).u.Status) {
12345 .SUCCESS => return,
12346 .CANCELLED => unreachable,
12347 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12348 else => |status| return windows.unexpectedStatus(status),
12349 }
12350}
12351
12352fn netConnectIpPosix(
12353 userdata: ?*anyopaque,
12354 address: *const IpAddress,
12355 options: IpAddress.ConnectOptions,
12356) IpAddress.ConnectError!net.Socket {
12357 if (!have_networking) return error.NetworkDown;
12358 if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout");
12359 const t: *Threaded = @ptrCast(@alignCast(userdata));
12360 _ = t;
12361 const family = posixAddressFamily(address);
12362 const socket_fd = try openSocketPosix(family, .{ .mode = options.mode, .protocol = options.protocol });
12363 errdefer closeFd(socket_fd);
12364 var storage: PosixAddress = undefined;
12365 var addr_len = addressToPosix(address, &storage);
12366 try posixConnect(socket_fd, &storage.any, addr_len);
12367 try posixGetSockName(socket_fd, &storage.any, &addr_len);
12368 return .{ .handle = socket_fd, .address = addressFromPosix(&storage) };
12369}
12370
12371fn netConnectIpWindows(
12372 userdata: ?*anyopaque,
12373 address: *const IpAddress,
12374 options: IpAddress.ConnectOptions,
12375) IpAddress.ConnectError!net.Socket {
12376 if (!have_networking) return error.NetworkDown;
12377 if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout");
12378 const t: *Threaded = @ptrCast(@alignCast(userdata));
12379 _ = t;
12380 const family = posixAddressFamily(address);
12381 const socket_handle = try openSocketAfd(family, .{ .mode = options.mode, .protocol = options.protocol });
12382 errdefer windows.CloseHandle(socket_handle);
12383 try setSocketOptionAfd(socket_handle, ws2_32.SOL.SOCKET, ws2_32.SO.REUSE_UNICASTPORT, true);
12384 const bound_address = bindSocketIpAfd(socket_handle, &switch (address.*) {
12385 .ip4 => .{ .ip4 = .unspecified(0) },
12386 .ip6 => .{ .ip6 = .unspecified(0) },
12387 }, .Active) catch |err| switch (err) {
12388 error.AddressInUse => return error.Unexpected,
12389 else => |e| return e,
12390 };
12391 const Storage = extern struct { Reserved0: [3]usize = @splat(0), Address: PosixAddress };
12392 var storage: Storage = .{ .Address = undefined };
12393 const addr_len = addressToPosix(address, &storage.Address);
12394 switch ((try deviceIoControl(&.{
12395 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
12396 .code = windows.IOCTL.AFD.CONNECT,
12397 .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len],
12398 })).u.Status) {
12399 .SUCCESS => {},
12400 .CANCELLED => unreachable,
12401 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12402 .CONNECTION_REFUSED => return error.ConnectionRefused,
12403 else => |status| return windows.unexpectedStatus(status),
12404 }
12405 return .{ .handle = socket_handle, .address = bound_address };
12406}
12407
12408fn netConnectUnixPosix(
12409 userdata: ?*anyopaque,
12410 address: *const net.UnixAddress,
12411) net.UnixAddress.ConnectError!net.Socket.Handle {
12412 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
12413 const t: *Threaded = @ptrCast(@alignCast(userdata));
12414 _ = t;
12415 const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
12416 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
12417 error.OptionUnsupported => return error.Unexpected,
12418 else => |e| return e,
12419 };
12420 errdefer closeFd(socket_fd);
12421 var storage: UnixAddress = undefined;
12422 const addr_len = addressUnixToPosix(address, &storage);
12423 try posixConnectUnix(socket_fd, &storage.any, addr_len);
12424 return socket_fd;
12425}
12426
12427fn netConnectUnixWindows(
12428 userdata: ?*anyopaque,
12429 address: *const net.UnixAddress,
12430) net.UnixAddress.ConnectError!net.Socket.Handle {
12431 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
12432 if (!have_networking) return error.NetworkDown;
12433 const t: *Threaded = @ptrCast(@alignCast(userdata));
12434 _ = t;
12435 const is_abstract = address.isAbstract();
12436 const wps = if (!is_abstract) sliceToPrefixedFileW(null, address.path, .{
12437 .allow_relative = false,
12438 }) catch |err| switch (err) {
12439 error.NameTooLong, error.BadPathName => return error.FileNotFound,
12440 else => |e| return e,
12441 } else undefined;
12442 const socket_handle = openSocketAfd(ws2_32.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
12443 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
12444 else => |e| return e,
12445 };
12446 errdefer windows.CloseHandle(socket_handle);
12447 if (!is_abstract) try socketOptionAfd(socket_handle, .special, 0, ws2_32.SO.UNIX_PATH, @constCast(
12448 @as([]const u8, @ptrCast(&windows.AFD.SOCKOPT_INFO.UNIX_PATH{
12449 .Path = wps.data,
12450 }))[0 .. @offsetOf(windows.AFD.SOCKOPT_INFO.UNIX_PATH, "Path") + @sizeOf(windows.WCHAR) * wps.len],
12451 ));
12452 bindSocketUnixAfd(socket_handle, &(net.UnixAddress.init("") catch |err| switch (err) {
12453 error.NameTooLong => unreachable,
12454 })) catch |err| switch (err) {
12455 error.AddressInUse => return error.Unexpected,
12456 else => |e| return e,
12457 };
12458 const Storage = extern struct { Reserved0: [3]usize = @splat(0), Address: UnixAddress };
12459 var storage: Storage = .{ .Address = undefined };
12460 const addr_len = addressUnixToPosix(address, &storage.Address);
12461 switch ((try deviceIoControl(&.{
12462 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
12463 .code = windows.IOCTL.AFD.CONNECT,
12464 .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len],
12465 })).u.Status) {
12466 .SUCCESS => {},
12467 .CANCELLED => unreachable,
12468 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12469 else => |status| return windows.unexpectedStatus(status),
12470 }
12471 return socket_handle;
12472}
12473
12474fn netBindIpPosix(
12475 userdata: ?*anyopaque,
12476 address: *const IpAddress,
12477 options: IpAddress.BindOptions,
12478) IpAddress.BindError!net.Socket {
12479 if (!have_networking) return error.NetworkDown;
12480 const t: *Threaded = @ptrCast(@alignCast(userdata));
12481 _ = t;
12482 const family = posixAddressFamily(address);
12483 const socket_fd = try openSocketPosix(family, options);
12484 errdefer closeFd(socket_fd);
12485 var storage: PosixAddress = undefined;
12486 var addr_len = addressToPosix(address, &storage);
12487 try posixBind(socket_fd, &storage.any, addr_len);
12488 if (options.allow_broadcast) try setSocketOptionPosix(socket_fd, std.posix.SOL.SOCKET, std.posix.SO.BROADCAST, 1);
12489 try posixGetSockName(socket_fd, &storage.any, &addr_len);
12490 return .{ .handle = socket_fd, .address = addressFromPosix(&storage) };
12491}
12492
12493fn netBindIpWindows(
12494 userdata: ?*anyopaque,
12495 address: *const IpAddress,
12496 options: IpAddress.BindOptions,
12497) IpAddress.BindError!net.Socket {
12498 if (!have_networking) return error.NetworkDown;
12499 const t: *Threaded = @ptrCast(@alignCast(userdata));
12500 _ = t;
12501 const family = posixAddressFamily(address);
12502 const socket_handle = try openSocketAfd(family, options);
12503 errdefer windows.CloseHandle(socket_handle);
12504 const bound_address = try bindSocketIpAfd(socket_handle, address, .Active);
12505 if (options.allow_broadcast) try setSocketOptionAfd(socket_handle, ws2_32.SOL.SOCKET, ws2_32.SO.BROADCAST, true);
12506 return .{ .handle = socket_handle, .address = bound_address };
12507}
12508
12509fn openSocketPosix(
12510 family: posix.sa_family_t,
12511 options: IpAddress.BindOptions,
12512) error{
12513 AddressFamilyUnsupported,
12514 ProtocolUnsupportedBySystem,
12515 ProcessFdQuotaExceeded,
12516 SystemFdQuotaExceeded,
12517 SystemResources,
12518 ProtocolUnsupportedByAddressFamily,
12519 SocketModeUnsupported,
12520 OptionUnsupported,
12521 Unexpected,
12522 Canceled,
12523}!posix.socket_t {
12524 const mode, const protocol = try posixSocketModeProtocol(family, options.mode, options.protocol);
12525 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
12526 const syscall: Syscall = try .start();
12527 const socket_fd = while (true) {
12528 const rc = posix.system.socket(family, flags, protocol);
12529 switch (posix.errno(rc)) {
12530 .SUCCESS => {
12531 syscall.finish();
12532 const fd: posix.fd_t = @intCast(rc);
12533 errdefer closeFd(fd);
12534 if (socket_flags_unsupported) try setCloexec(fd);
12535 break fd;
12536 },
12537 .INTR => {
12538 try syscall.checkCancel();
12539 continue;
12540 },
12541 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
12542 .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem),
12543 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
12544 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
12545 .NOBUFS => return syscall.fail(error.SystemResources),
12546 .NOMEM => return syscall.fail(error.SystemResources),
12547 .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily),
12548 .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported),
12549 else => |err| return syscall.unexpectedErrno(err),
12550 }
12551 };
12552 errdefer closeFd(socket_fd);
12553
12554 if (options.ip6_only) |ip6_only| {
12555 if (posix.IPV6 == void) return error.OptionUnsupported;
12556 try setSocketOptionPosix(socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, @intFromBool(ip6_only));
12557 }
12558
12559 return socket_fd;
12560}
12561
12562fn setCloexec(fd: posix.fd_t) error{ Canceled, Unexpected }!void {
12563 const syscall: Syscall = try .start();
12564 while (true) switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
12565 .SUCCESS => return syscall.finish(),
12566 .INTR => {
12567 try syscall.checkCancel();
12568 continue;
12569 },
12570 else => |err| return syscall.unexpectedErrno(err),
12571 };
12572}
12573
12574fn netSocketCreatePair(
12575 userdata: ?*anyopaque,
12576 options: net.Socket.CreatePairOptions,
12577) net.Socket.CreatePairError![2]net.Socket {
12578 const t: *Threaded = @ptrCast(@alignCast(userdata));
12579 _ = t;
12580 if (!have_networking) return error.OperationUnsupported;
12581 if (@TypeOf(posix.system.socketpair) == void) return error.OperationUnsupported;
12582 if (native_os == .haiku) @panic("TODO");
12583
12584 const family: posix.sa_family_t = switch (options.family) {
12585 .ip4 => posix.AF.INET,
12586 .ip6 => posix.AF.INET6,
12587 };
12588 const mode, const protocol = try posixSocketModeProtocol(family, options.mode, options.protocol);
12589 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
12590
12591 var sockets: [2]posix.socket_t = undefined;
12592 const syscall: Syscall = try .start();
12593 while (true) switch (posix.errno(posix.system.socketpair(family, flags, protocol, &sockets))) {
12594 .SUCCESS => {
12595 syscall.finish();
12596 errdefer {
12597 closeFd(sockets[0]);
12598 closeFd(sockets[1]);
12599 }
12600 if (socket_flags_unsupported) {
12601 try setCloexec(sockets[0]);
12602 try setCloexec(sockets[1]);
12603 }
12604 var storages: [2]PosixAddress = undefined;
12605 var addr_lens: [2]posix.socklen_t = .{ @sizeOf(PosixAddress), @sizeOf(PosixAddress) };
12606 try posixGetSockName(sockets[0], &storages[0].any, &addr_lens[0]);
12607 try posixGetSockName(sockets[1], &storages[1].any, &addr_lens[1]);
12608 return .{
12609 .{ .handle = sockets[0], .address = addressFromPosix(&storages[0]) },
12610 .{ .handle = sockets[1], .address = addressFromPosix(&storages[1]) },
12611 };
12612 },
12613 .INTR => {
12614 try syscall.checkCancel();
12615 continue;
12616 },
12617 .ACCES => return syscall.fail(error.AccessDenied),
12618 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
12619 .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem),
12620 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
12621 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
12622 .NOBUFS => return syscall.fail(error.SystemResources),
12623 .NOMEM => return syscall.fail(error.SystemResources),
12624 .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily),
12625 .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported),
12626 else => |err| return syscall.unexpectedErrno(err),
12627 };
12628}
12629
12630fn openSocketAfd(family: ws2_32.ADDRESS_FAMILY, options: IpAddress.BindOptions) !net.Socket.Handle {
12631 const mode, const protocol = try posixSocketModeProtocol(family, options.mode, options.protocol);
12632 var handle: windows.HANDLE = undefined;
12633 var iosb: windows.IO_STATUS_BLOCK = undefined;
12634 var syscall: Syscall = try .start();
12635 while (true) switch (windows.ntdll.NtCreateFile(
12636 &handle,
12637 .{
12638 .STANDARD = .{ .RIGHTS = .{ .WRITE_DAC = true }, .SYNCHRONIZE = true },
12639 .GENERIC = .{ .WRITE = true, .READ = true },
12640 },
12641 &.{
12642 .ObjectName = @constCast(&windows.UNICODE_STRING.init(
12643 windows.AFD.DEVICE_NAME ++ .{ '\\', 'E', 'n', 'd', 'p', 'o', 'i', 'n', 't' },
12644 )),
12645 },
12646 &iosb,
12647 null,
12648 .{},
12649 .{ .READ = true, .WRITE = true },
12650 .OPEN_IF,
12651 .{ .IO = .ASYNCHRONOUS },
12652 &windows.AFD.OPEN_PACKET.FULL_EA_INFORMATION{ .Value = .{
12653 .EndpointType = .{
12654 .CONNECTIONLESS = switch (options.mode) {
12655 .stream, .seqpacket, .rdm => false,
12656 .dgram, .raw => true,
12657 },
12658 .MESSAGEMODE = options.mode != .stream,
12659 .RAW = options.mode == .raw,
12660 },
12661 .GroupID = 0,
12662 .AddressFamily = family,
12663 .SocketType = @bitCast(mode),
12664 .Protocol = @bitCast(protocol),
12665 .TransportDeviceNameLength = 0,
12666 .TransportDeviceName = undefined,
12667 } },
12668 @sizeOf(windows.AFD.OPEN_PACKET.FULL_EA_INFORMATION),
12669 )) {
12670 .SUCCESS => {
12671 syscall.finish();
12672 return handle;
12673 },
12674 .CANCELLED => {
12675 try syscall.checkCancel();
12676 continue;
12677 },
12678 .PROTOCOL_NOT_SUPPORTED => return syscall.fail(error.AddressFamilyUnsupported),
12679 .NO_SUCH_FILE => return syscall.fail(error.ProtocolUnsupportedByAddressFamily),
12680 else => |status| return syscall.unexpectedNtstatus(status),
12681 };
12682}
12683
12684fn bindSocketIpAfd(socket_handle: net.Socket.Handle, address: *const IpAddress, mode: windows.AFD.BIND_INFO.MODE) !IpAddress {
12685 const Storage = extern struct { Info: windows.AFD.BIND_INFO, Address: PosixAddress };
12686 var storage: Storage = .{ .Info = .{ .Mode = mode }, .Address = undefined };
12687 const addr_len = addressToPosix(address, &storage.Address);
12688 switch ((try deviceIoControl(&.{
12689 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
12690 .code = windows.IOCTL.AFD.BIND,
12691 .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len],
12692 .out = @as([]u8, @ptrCast(&storage.Address))[0..addr_len],
12693 })).u.Status) {
12694 .SUCCESS => {},
12695 .CANCELLED => unreachable,
12696 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12697 .SHARING_VIOLATION => return error.AddressInUse,
12698 else => |status| return windows.unexpectedStatus(status),
12699 }
12700 return addressFromPosix(&storage.Address);
12701}
12702
12703fn bindSocketUnixAfd(socket_handle: net.Socket.Handle, address: *const net.UnixAddress) !void {
12704 const Storage = extern struct { Info: windows.AFD.BIND_INFO, Address: UnixAddress };
12705 var storage: Storage = .{ .Info = .{ .Mode = .Unix }, .Address = undefined };
12706 const addr_len = addressUnixToPosix(address, &storage.Address);
12707 switch ((try deviceIoControl(&.{
12708 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
12709 .code = windows.IOCTL.AFD.BIND,
12710 .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len],
12711 .out = @ptrCast(&storage.Address),
12712 })).u.Status) {
12713 .SUCCESS => {},
12714 .CANCELLED => unreachable,
12715 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12716 .ADDRESS_ALREADY_EXISTS => return error.AddressInUse,
12717 else => |status| return windows.unexpectedStatus(status),
12718 }
12719}
12720
12721fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle, options: net.Server.AcceptOptions) net.Server.AcceptError!net.Socket {
12722 if (!have_networking) return error.NetworkDown;
12723 const t: *Threaded = @ptrCast(@alignCast(userdata));
12724 _ = t;
12725 options;
12726 var storage: PosixAddress = undefined;
12727 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
12728 const syscall: Syscall = try .start();
12729 const fd = while (true) {
12730 const rc = if (have_accept4)
12731 posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC)
12732 else
12733 posix.system.accept(listen_fd, &storage.any, &addr_len);
12734 switch (posix.errno(rc)) {
12735 .SUCCESS => {
12736 syscall.finish();
12737 const fd: posix.fd_t = @intCast(rc);
12738 errdefer closeFd(fd);
12739 if (!have_accept4) try setCloexec(fd);
12740 break fd;
12741 },
12742 .INTR => {
12743 try syscall.checkCancel();
12744 continue;
12745 },
12746 else => |e| {
12747 syscall.finish();
12748 switch (e) {
12749 .AGAIN => |err| return errnoBug(err),
12750 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12751 .CONNABORTED => return error.ConnectionAborted,
12752 .FAULT => |err| return errnoBug(err),
12753 .INVAL => return error.SocketNotListening,
12754 .NOTSOCK => |err| return errnoBug(err),
12755 .MFILE => return error.ProcessFdQuotaExceeded,
12756 .NFILE => return error.SystemFdQuotaExceeded,
12757 .NOBUFS => return error.SystemResources,
12758 .NOMEM => return error.SystemResources,
12759 .OPNOTSUPP => |err| return errnoBug(err),
12760 .PROTO => return error.ProtocolFailure,
12761 .PERM => return error.BlockedByFirewall,
12762 else => |err| return posix.unexpectedErrno(err),
12763 }
12764 },
12765 }
12766 };
12767 return .{ .handle = fd, .address = addressFromPosix(&storage) };
12768}
12769
12770fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle, options: net.Server.AcceptOptions) net.Server.AcceptError!net.Socket {
12771 if (!have_networking) return error.NetworkDown;
12772 const t: *Threaded = @ptrCast(@alignCast(userdata));
12773 const Storage = extern struct {
12774 Info: windows.AFD.LISTEN_RESPONSE_INFO,
12775 RemoteAddress: extern union { posix: PosixAddress, unix: UnixAddress },
12776 };
12777 var storage: Storage = undefined;
12778 switch ((try deviceIoControl(&.{
12779 .file = .{ .handle = listen_handle, .flags = .{ .nonblocking = true } },
12780 .code = windows.IOCTL.AFD.WAIT_FOR_LISTEN,
12781 .out = @ptrCast(&storage),
12782 })).u.Status) {
12783 .SUCCESS => {},
12784 .CANCELLED => unreachable,
12785 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12786 else => |status| return windows.unexpectedStatus(status),
12787 }
12788 errdefer t.deferAcceptAfd(listen_handle, storage.Info);
12789 const accept_handle = openSocketAfd(
12790 storage.RemoteAddress.posix.any.family,
12791 .{ .mode = options.mode, .protocol = options.protocol },
12792 ) catch |err| switch (err) {
12793 error.AddressFamilyUnsupported => return error.Unexpected,
12794 error.ProtocolUnsupportedByAddressFamily => return error.Unexpected,
12795 else => |e| return e,
12796 };
12797 errdefer windows.CloseHandle(accept_handle);
12798 switch ((try deviceIoControl(&.{
12799 .file = .{ .handle = listen_handle, .flags = .{ .nonblocking = true } },
12800 .code = windows.IOCTL.AFD.ACCEPT,
12801 .in = @ptrCast(&windows.AFD.ACCEPT_INFO{
12802 .UseSAN = .FALSE,
12803 .Sequence = storage.Info.Sequence,
12804 .AcceptHandle = accept_handle,
12805 }),
12806 })).u.Status) {
12807 .SUCCESS => {},
12808 .CANCELLED => unreachable,
12809 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12810 else => |status| return windows.unexpectedStatus(status),
12811 }
12812 return .{ .handle = accept_handle, .address = addressFromPosix(&storage.RemoteAddress.posix) };
12813}
12814
12815fn deferAcceptAfd(t: *Threaded, listen_handle: net.Socket.Handle, info: windows.AFD.LISTEN_RESPONSE_INFO) void {
12816 const cancel_protection = swapCancelProtection(t, .blocked);
12817 defer _ = swapCancelProtection(t, cancel_protection);
12818 switch ((deviceIoControl(&.{
12819 .file = .{ .handle = listen_handle, .flags = .{ .nonblocking = true } },
12820 .code = windows.IOCTL.AFD.DEFER_ACCEPT,
12821 .in = @ptrCast(&windows.AFD.DEFER_ACCEPT_INFO{
12822 .Sequence = info.Sequence,
12823 .Reject = .FALSE,
12824 }),
12825 }) catch |err| switch (err) {
12826 error.Canceled => unreachable, // blocked
12827 }).u.Status) {
12828 .SUCCESS => {},
12829 .CANCELLED => unreachable,
12830 else => |status| windows.unexpectedStatus(status) catch {},
12831 }
12832}
12833
12834fn netRead(socket_handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
12835 if (!have_networking) return error.NetworkDown;
12836
12837 if (is_windows) return netReadWindows(socket_handle, data);
12838 return netReadPosix(socket_handle, data);
12839}
12840
12841fn netReadPosix(fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
12842 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
12843 var i: usize = 0;
12844 for (data) |buf| {
12845 if (iovecs_buffer.len - i == 0) break;
12846 if (buf.len != 0) {
12847 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
12848 i += 1;
12849 }
12850 }
12851 const dest = iovecs_buffer[0..i];
12852 assert(dest[0].len > 0);
12853
12854 if (native_os == .wasi and !builtin.link_libc) {
12855 const syscall: Syscall = try .start();
12856 while (true) {
12857 var n: usize = undefined;
12858 switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) {
12859 .SUCCESS => {
12860 syscall.finish();
12861 return n;
12862 },
12863 .INTR => {
12864 try syscall.checkCancel();
12865 continue;
12866 },
12867 else => |e| {
12868 syscall.finish();
12869 switch (e) {
12870 .INVAL => |err| return errnoBug(err),
12871 .FAULT => |err| return errnoBug(err),
12872 .AGAIN => |err| return errnoBug(err),
12873 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12874 .NOBUFS => return error.SystemResources,
12875 .NOMEM => return error.SystemResources,
12876 .NOTCONN => return error.SocketUnconnected,
12877 .CONNRESET => return error.ConnectionResetByPeer,
12878 .TIMEDOUT => return error.ConnectionTimedOut,
12879 .NOTCAPABLE => return error.AccessDenied,
12880 else => |err| return posix.unexpectedErrno(err),
12881 }
12882 },
12883 }
12884 }
12885 }
12886
12887 const syscall: Syscall = try .start();
12888 while (true) {
12889 const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len));
12890 switch (posix.errno(rc)) {
12891 .SUCCESS => {
12892 syscall.finish();
12893 return @intCast(rc);
12894 },
12895 .INTR => {
12896 try syscall.checkCancel();
12897 continue;
12898 },
12899 else => |e| {
12900 syscall.finish();
12901 switch (e) {
12902 .INVAL => |err| return errnoBug(err),
12903 .FAULT => |err| return errnoBug(err),
12904 .AGAIN => |err| return errnoBug(err),
12905 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
12906 .NOBUFS => return error.SystemResources,
12907 .NOMEM => return error.SystemResources,
12908 .NOTCONN => return error.SocketUnconnected,
12909 .CONNRESET => return error.ConnectionResetByPeer,
12910 .TIMEDOUT => return error.ConnectionTimedOut,
12911 .PIPE => return error.SocketUnconnected,
12912 .NETDOWN => return error.NetworkDown,
12913 else => |err| return posix.unexpectedErrno(err),
12914 }
12915 },
12916 }
12917 }
12918}
12919
12920fn netReadWindows(socket_handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
12921 var iovecs: [max_iovecs_len]windows.AFD.WSABUF(.@"var") = undefined;
12922 var len: u32 = 0;
12923 for (data) |buf| {
12924 if (iovecs.len - len == 0) break;
12925 addAfdBuf(.@"var", &iovecs, &len, buf);
12926 }
12927
12928 const iosb = try deviceIoControl(&.{
12929 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
12930 .code = windows.IOCTL.AFD.RECEIVE,
12931 .in = @ptrCast(&windows.AFD.RECV_INFO{
12932 .BufferArray = &iovecs,
12933 .BufferCount = len,
12934 .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true },
12935 .TdiFlags = .{ .NORMAL = true },
12936 }),
12937 });
12938 switch (iosb.u.Status) {
12939 .SUCCESS => return iosb.Information,
12940 .CANCELLED => unreachable,
12941 .INSUFFICIENT_RESOURCES => return error.SystemResources,
12942 .CONNECTION_RESET, .REMOTE_DISCONNECT => return error.ConnectionResetByPeer,
12943 .IO_TIMEOUT => return error.ConnectionTimedOut,
12944 else => |status| return windows.unexpectedStatus(status),
12945 }
12946}
12947
12948fn netSendPosix(
12949 t: *Threaded,
12950 socket_handle: net.Socket.Handle,
12951 messages: []net.OutgoingMessage,
12952 flags: net.SendFlags,
12953 nonblocking: bool,
12954) struct { ?(net.Socket.SendError || error{WouldBlock}), usize } {
12955 if (!have_networking) return .{ error.NetworkDown, 0 };
12956
12957 const posix_flags: u32 =
12958 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) |
12959 @as(u32, if (@hasDecl(posix.MSG, "DONTROUTE") and flags.dont_route) posix.MSG.DONTROUTE else 0) |
12960 @as(u32, if (@hasDecl(posix.MSG, "EOR") and flags.eor) posix.MSG.EOR else 0) |
12961 @as(u32, if (@hasDecl(posix.MSG, "OOB") and flags.oob) posix.MSG.OOB else 0) |
12962 @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) |
12963 @as(u32, if (@hasDecl(posix.MSG, "DONTWAIT") and nonblocking) posix.MSG.DONTWAIT else 0) |
12964 posix.MSG.NOSIGNAL;
12965
12966 var i: usize = 0;
12967 while (messages.len - i != 0) {
12968 if (have_sendmmsg) {
12969 i += netSendManyPosix(socket_handle, messages[i..], posix_flags) catch |err| return .{ err, i };
12970 continue;
12971 }
12972 t.netSendOnePosix(socket_handle, &messages[i], posix_flags) catch |err| return .{ err, i };
12973 i += 1;
12974 }
12975 return .{ null, i };
12976}
12977
12978fn netSendWindows(
12979 t: *Threaded,
12980 socket_handle: net.Socket.Handle,
12981 messages: []net.OutgoingMessage,
12982 flags: net.SendFlags,
12983) struct { ?net.Socket.SendError, usize } {
12984 if (!have_networking) return .{ error.NetworkDown, 0 };
12985 for (messages, 0..) |*m, i| {
12986 t.netSendOneWindows(socket_handle, m, flags) catch |err| return .{ err, i };
12987 }
12988 return .{ null, messages.len };
12989}
12990
12991fn netSendOneWindows(
12992 t: *Threaded,
12993 socket_handle: net.Socket.Handle,
12994 message: *net.OutgoingMessage,
12995 flags: net.SendFlags,
12996) net.Socket.SendError!void {
12997 _ = t;
12998 _ = flags;
12999 const iovecs: [1]windows.AFD.WSABUF(.@"const") = .{.{
13000 .buf = message.data_ptr,
13001 .len = std.math.cast(std.os.windows.ULONG, message.data_len) orelse
13002 return error.MessageOversize,
13003 }};
13004 var storage: PosixAddress = undefined;
13005 const addr_len = addressToPosix(message.address, &storage);
13006 switch ((try deviceIoControl(&.{
13007 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
13008 .code = windows.IOCTL.AFD.SEND_DATAGRAM,
13009 .in = @ptrCast(&windows.AFD.SEND_DATAGRAM_INFO{
13010 .BufferArray = &iovecs,
13011 .BufferCount = iovecs.len,
13012 .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true },
13013 .TdiRequest = undefined,
13014 .TdiConnInfo = .{
13015 .UserDataLength = undefined,
13016 .UserData = undefined,
13017 .OptionsLength = undefined,
13018 .Options = undefined,
13019 .RemoteAddressLength = @bitCast(addr_len),
13020 .RemoteAddress = &storage,
13021 },
13022 }),
13023 })).u.Status) {
13024 .SUCCESS => return,
13025 .CANCELLED => unreachable,
13026 .INSUFFICIENT_RESOURCES => return error.SystemResources,
13027 else => |status| return windows.unexpectedStatus(status),
13028 }
13029}
13030
13031fn netSendOnePosix(
13032 t: *Threaded,
13033 socket_handle: net.Socket.Handle,
13034 message: *net.OutgoingMessage,
13035 flags: u32,
13036) (net.Socket.SendError || error{WouldBlock})!void {
13037 _ = t;
13038 var addr: PosixAddress = undefined;
13039 var iovec: posix.iovec_const = .{ .base = @constCast(message.data_ptr), .len = message.data_len };
13040 const msg: posix.msghdr_const = .{
13041 .name = &addr.any,
13042 .namelen = addressToPosix(message.address, &addr),
13043 .iov = (&iovec)[0..1],
13044 .iovlen = 1,
13045 // OS returns EINVAL if this pointer is invalid even if controllen is zero.
13046 .control = if (message.control.len == 0) null else @constCast(message.control.ptr),
13047 .controllen = @intCast(message.control.len),
13048 .flags = 0,
13049 };
13050 var syscall: if (is_windows) AlertableSyscall else Syscall = try .start();
13051 while (true) {
13052 const rc = posix.system.sendmsg(socket_handle, &msg, flags);
13053 switch (posix.errno(rc)) {
13054 .SUCCESS => {
13055 syscall.finish();
13056 message.data_len = @intCast(rc);
13057 return;
13058 },
13059 .INTR => {
13060 try syscall.checkCancel();
13061 continue;
13062 },
13063 .ACCES => return syscall.fail(error.AccessDenied),
13064 .AGAIN => return syscall.fail(error.WouldBlock),
13065 .ALREADY => return syscall.fail(error.FastOpenAlreadyInProgress),
13066 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
13067 .MSGSIZE => return syscall.fail(error.MessageOversize),
13068 .NOBUFS => return syscall.fail(error.SystemResources),
13069 .NOMEM => return syscall.fail(error.SystemResources),
13070 .PIPE => return syscall.fail(error.SocketUnconnected),
13071 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
13072 .HOSTUNREACH => return syscall.fail(error.HostUnreachable),
13073 .NETUNREACH => return syscall.fail(error.NetworkUnreachable),
13074 .NOTCONN => return syscall.fail(error.SocketUnconnected),
13075 .TIMEDOUT => return syscall.fail(error.ConnectionTimedOut),
13076 .NETDOWN => return syscall.fail(error.NetworkDown),
13077 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
13078 .DESTADDRREQ => |err| return syscall.errnoBug(err),
13079 .FAULT => |err| return syscall.errnoBug(err),
13080 .INVAL => |err| return syscall.errnoBug(err),
13081 .ISCONN => |err| return syscall.errnoBug(err),
13082 .NOTSOCK => |err| return syscall.errnoBug(err),
13083 .OPNOTSUPP => |err| return syscall.errnoBug(err),
13084 else => |err| return syscall.unexpectedErrno(err),
13085 }
13086 }
13087}
13088
13089fn netSendManyPosix(
13090 socket_handle: net.Socket.Handle,
13091 messages: []net.OutgoingMessage,
13092 flags: u32,
13093) (net.Socket.SendError || error{WouldBlock})!usize {
13094 var msg_buffer: [64]posix.system.mmsghdr = undefined;
13095 var addr_buffer: [msg_buffer.len]PosixAddress = undefined;
13096 var iovecs_buffer: [msg_buffer.len]posix.iovec = undefined;
13097 const min_len: usize = @min(messages.len, msg_buffer.len);
13098 const clamped_messages = messages[0..min_len];
13099 const clamped_msgs = (&msg_buffer)[0..min_len];
13100 const clamped_addrs = (&addr_buffer)[0..min_len];
13101 const clamped_iovecs = (&iovecs_buffer)[0..min_len];
13102
13103 for (clamped_messages, clamped_msgs, clamped_addrs, clamped_iovecs) |*message, *msg, *addr, *iovec| {
13104 iovec.* = .{ .base = @constCast(message.data_ptr), .len = message.data_len };
13105 msg.* = .{
13106 .hdr = .{
13107 .name = &addr.any,
13108 .namelen = addressToPosix(message.address, addr),
13109 .iov = iovec[0..1],
13110 .iovlen = 1,
13111 .control = @constCast(message.control.ptr),
13112 .controllen = message.control.len,
13113 .flags = 0,
13114 },
13115 .len = undefined, // Populated by calling sendmmsg below.
13116 };
13117 }
13118
13119 const syscall: Syscall = try .start();
13120 while (true) {
13121 const rc = posix.system.sendmmsg(socket_handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags);
13122 switch (posix.errno(rc)) {
13123 .SUCCESS => {
13124 syscall.finish();
13125 const n: usize = @intCast(rc);
13126 for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| {
13127 message.data_len = msg.len;
13128 }
13129 return n;
13130 },
13131 .INTR => {
13132 try syscall.checkCancel();
13133 continue;
13134 },
13135 .ACCES => return syscall.fail(error.AccessDenied),
13136 .AGAIN => return syscall.fail(error.WouldBlock),
13137 .ALREADY => return syscall.fail(error.FastOpenAlreadyInProgress),
13138 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
13139 .MSGSIZE => return syscall.fail(error.MessageOversize),
13140 .NOBUFS => return syscall.fail(error.SystemResources),
13141 .NOMEM => return syscall.fail(error.SystemResources),
13142 .PIPE => return syscall.fail(error.SocketUnconnected),
13143 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
13144 .HOSTUNREACH => return syscall.fail(error.HostUnreachable),
13145 .NETUNREACH => return syscall.fail(error.NetworkUnreachable),
13146 .NOTCONN => return syscall.fail(error.SocketUnconnected),
13147 .TIMEDOUT => return syscall.fail(error.ConnectionTimedOut),
13148 .NETDOWN => return syscall.fail(error.NetworkDown),
13149
13150 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed.
13151 .DESTADDRREQ => |err| return syscall.errnoBug(err), // The socket is not connection-mode, and no peer address is set.
13152 .FAULT => |err| return syscall.errnoBug(err), // An invalid user space address was specified for an argument.
13153 .INVAL => |err| return syscall.errnoBug(err), // Invalid argument passed.
13154 .ISCONN => |err| return syscall.errnoBug(err), // connection-mode socket was connected already but a recipient was specified
13155 .NOTSOCK => |err| return syscall.errnoBug(err), // The file descriptor sockfd does not refer to a socket.
13156 .OPNOTSUPP => |err| return syscall.errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type.
13157
13158 else => |err| return syscall.unexpectedErrno(err),
13159 }
13160 }
13161}
13162
13163fn netReceivePosix(
13164 socket_handle: net.Socket.Handle,
13165 message: *net.IncomingMessage,
13166 data_buffer: []u8,
13167 flags: net.ReceiveFlags,
13168 nonblocking: bool,
13169) (net.Socket.ReceiveError || error{WouldBlock})!void {
13170 // recvmmsg is useless, here's why:
13171 // * [timeout bug](https://bugzilla.kernel.org/show_bug.cgi?id=75371)
13172 // * it wants iovecs for each message but we have a better API: one data
13173 // buffer to handle all the messages. The better API cannot be lowered to
13174 // the split vectors though because reducing the buffer size might make
13175 // some messages unreceivable.
13176 const posix_flags: u32 =
13177 @as(u32, if (flags.oob) posix.MSG.OOB else 0) |
13178 @as(u32, if (flags.peek) posix.MSG.PEEK else 0) |
13179 @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) |
13180 posix.MSG.NOSIGNAL |
13181 @as(u32, if (nonblocking) posix.MSG.DONTWAIT else 0);
13182
13183 var storage: PosixAddress = undefined;
13184 var iov: posix.iovec = .{ .base = data_buffer.ptr, .len = data_buffer.len };
13185 var msg: posix.msghdr = .{
13186 .name = &storage.any,
13187 .namelen = @sizeOf(PosixAddress),
13188 .iov = (&iov)[0..1],
13189 .iovlen = 1,
13190 .control = message.control.ptr,
13191 .controllen = @intCast(message.control.len),
13192 .flags = undefined,
13193 };
13194
13195 const syscall = try Syscall.start();
13196 while (true) {
13197 const rc = posix.system.recvmsg(socket_handle, &msg, posix_flags);
13198 switch (posix.errno(rc)) {
13199 .SUCCESS => {
13200 syscall.finish();
13201 const data = data_buffer[0..@intCast(rc)];
13202 message.* = .{
13203 .from = addressFromPosix(&storage),
13204 .data = data,
13205 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control,
13206 .flags = .{
13207 .eor = (msg.flags & posix.MSG.EOR) != 0,
13208 .trunc = (msg.flags & posix.MSG.TRUNC) != 0,
13209 .ctrunc = (msg.flags & posix.MSG.CTRUNC) != 0,
13210 .oob = (msg.flags & posix.MSG.OOB) != 0,
13211 .errqueue = if (@hasDecl(posix.MSG, "ERRQUEUE")) (msg.flags & posix.MSG.ERRQUEUE) != 0 else false,
13212 },
13213 };
13214 return;
13215 },
13216 .INTR => {
13217 try syscall.checkCancel();
13218 continue;
13219 },
13220 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
13221 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
13222 .NOBUFS => return syscall.fail(error.SystemResources),
13223 .NOMEM => return syscall.fail(error.SystemResources),
13224 .NOTCONN => return syscall.fail(error.SocketUnconnected),
13225 .MSGSIZE => return syscall.fail(error.MessageOversize),
13226 .PIPE => return syscall.fail(error.SocketUnconnected),
13227 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
13228 .TIMEDOUT => return syscall.fail(error.ConnectionTimedOut),
13229 .NETDOWN => return syscall.fail(error.NetworkDown),
13230 .AGAIN => return syscall.fail(error.WouldBlock),
13231 .BADF => |err| return syscall.errnoBug(err),
13232 .FAULT => |err| return syscall.errnoBug(err),
13233 .INVAL => |err| return syscall.errnoBug(err),
13234 .NOTSOCK => |err| return syscall.errnoBug(err),
13235 .OPNOTSUPP => |err| return syscall.errnoBug(err),
13236 else => |err| return syscall.unexpectedErrno(err),
13237 }
13238 }
13239}
13240
13241fn netReceiveWindows(
13242 t: *Threaded,
13243 socket_handle: net.Socket.Handle,
13244 message_buffer: []net.IncomingMessage,
13245 data_buffer: []u8,
13246 flags: net.ReceiveFlags,
13247) struct { ?net.Socket.ReceiveError, usize } {
13248 t.netReceiveOneWindows(socket_handle, &message_buffer[0], data_buffer, flags) catch |err|
13249 return .{ err, 0 };
13250 return .{ null, 1 };
13251}
13252
13253fn netReceiveOneWindows(
13254 t: *Threaded,
13255 socket_handle: net.Socket.Handle,
13256 message: *net.IncomingMessage,
13257 data_buffer: []u8,
13258 flags: net.ReceiveFlags,
13259) net.Socket.ReceiveError!void {
13260 if (!have_networking) return error.NetworkDown;
13261 _ = t;
13262 const iovecs: [1]windows.AFD.WSABUF(.@"var") = .{.{
13263 .buf = data_buffer.ptr,
13264 .len = std.math.cast(std.os.windows.ULONG, data_buffer.len) orelse return error.MessageOversize,
13265 }};
13266 var storage: PosixAddress = undefined;
13267 var addr_len: windows.ULONG = @sizeOf(PosixAddress);
13268 const iosb = try deviceIoControl(&.{
13269 .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } },
13270 .code = windows.IOCTL.AFD.RECEIVE_DATAGRAM,
13271 .in = @ptrCast(&windows.AFD.RECV_DATAGRAM_INFO{
13272 .BufferArray = &iovecs,
13273 .BufferCount = iovecs.len,
13274 .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true },
13275 .TdiFlags = .{ .NORMAL = !flags.oob, .EXPEDITED = flags.oob, .PEEK = flags.peek },
13276 .Address = &storage,
13277 .AddressLength = &addr_len,
13278 }),
13279 });
13280 switch (iosb.u.Status) {
13281 .SUCCESS, .RECEIVE_EXPEDITED => |status| message.* = .{
13282 .from = addressFromPosix(&storage),
13283 .data = data_buffer[0..iosb.Information],
13284 .control = &.{},
13285 .flags = .{
13286 .eor = false,
13287 .trunc = false,
13288 .ctrunc = false,
13289 .oob = switch (status) {
13290 else => unreachable,
13291 .SUCCESS, .RECEIVE_PARTIAL, .BUFFER_OVERFLOW => false,
13292 .RECEIVE_EXPEDITED, .RECEIVE_PARTIAL_EXPEDITED => true,
13293 },
13294 .errqueue = false,
13295 },
13296 },
13297 .RECEIVE_PARTIAL,
13298 .RECEIVE_PARTIAL_EXPEDITED,
13299 => |status| return windows.unexpectedStatus(status), // TdiFlags.PARTIAL = false
13300 .CANCELLED => unreachable,
13301 .INSUFFICIENT_RESOURCES => return error.SystemResources,
13302 .BUFFER_OVERFLOW => return error.MessageOversize,
13303 .PORT_UNREACHABLE => return error.PortUnreachable,
13304 else => |status| return windows.unexpectedStatus(status),
13305 }
13306}
13307
13308fn netWritePosix(
13309 fd: net.Socket.Handle,
13310 header: []const u8,
13311 data: []const []const u8,
13312 splat: usize,
13313) net.Stream.Writer.Error!usize {
13314 if (!have_networking) return error.NetworkDown;
13315
13316 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
13317 var msg: posix.msghdr_const = .{
13318 .name = null,
13319 .namelen = 0,
13320 .iov = &iovecs,
13321 .iovlen = 0,
13322 .control = null,
13323 .controllen = 0,
13324 .flags = 0,
13325 };
13326 addBuf(&iovecs, &msg.iovlen, header);
13327 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes);
13328 const pattern = data[data.len - 1];
13329
13330 var splat_backup_buffer: [splat_buffer_size]u8 = undefined;
13331 if (iovecs.len - msg.iovlen != 0) switch (splat) {
13332 0 => {},
13333 1 => addBuf(&iovecs, &msg.iovlen, pattern),
13334 else => switch (pattern.len) {
13335 0 => {},
13336 1 => {
13337 const splat_buffer = &splat_backup_buffer;
13338 const memset_len = @min(splat_buffer.len, splat);
13339 const buf = splat_buffer[0..memset_len];
13340 @memset(buf, pattern[0]);
13341 addBuf(&iovecs, &msg.iovlen, buf);
13342 var remaining_splat = splat - buf.len;
13343 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
13344 assert(buf.len == splat_buffer.len);
13345 addBuf(&iovecs, &msg.iovlen, splat_buffer);
13346 remaining_splat -= splat_buffer.len;
13347 }
13348 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
13349 },
13350 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
13351 addBuf(&iovecs, &msg.iovlen, pattern);
13352 },
13353 },
13354 };
13355 const flags = posix.MSG.NOSIGNAL;
13356
13357 const syscall: Syscall = try .start();
13358 while (true) {
13359 const rc = posix.system.sendmsg(fd, &msg, flags);
13360 switch (posix.errno(rc)) {
13361 .SUCCESS => {
13362 syscall.finish();
13363 return @intCast(rc);
13364 },
13365 .INTR => {
13366 try syscall.checkCancel();
13367 continue;
13368 },
13369 else => |e| {
13370 syscall.finish();
13371 switch (e) {
13372 .ACCES => |err| return errnoBug(err),
13373 .AGAIN => |err| return errnoBug(err),
13374 .ALREADY => return error.FastOpenAlreadyInProgress,
13375 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
13376 .CONNRESET => return error.ConnectionResetByPeer,
13377 .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set.
13378 .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument.
13379 .INVAL => |err| return errnoBug(err), // Invalid argument passed.
13380 .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified
13381 .MSGSIZE => |err| return errnoBug(err),
13382 .NOBUFS => return error.SystemResources,
13383 .NOMEM => return error.SystemResources,
13384 .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket.
13385 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type.
13386 .PIPE => return error.SocketUnconnected,
13387 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
13388 .HOSTUNREACH => return error.HostUnreachable,
13389 .NETUNREACH => return error.NetworkUnreachable,
13390 .NOTCONN => return error.SocketUnconnected,
13391 .TIMEDOUT => return error.ConnectionTimedOut,
13392 .NETDOWN => return error.NetworkDown,
13393 else => |err| return posix.unexpectedErrno(err),
13394 }
13395 },
13396 }
13397 }
13398}
13399
13400fn netWriteWindows(
13401 handle: net.Socket.Handle,
13402 header: []const u8,
13403 data: []const []const u8,
13404 splat: usize,
13405) net.Stream.Writer.Error!usize {
13406 if (!have_networking) return error.NetworkDown;
13407
13408 var iovecs: [max_iovecs_len]windows.AFD.WSABUF(.@"const") = undefined;
13409 var len: u32 = 0;
13410 addAfdBuf(.@"const", &iovecs, &len, header);
13411 for (data[0 .. data.len - 1]) |bytes| addAfdBuf(.@"const", &iovecs, &len, bytes);
13412 const pattern = data[data.len - 1];
13413 var backup_buffer: [64]u8 = undefined;
13414 if (iovecs.len - len != 0) switch (splat) {
13415 0 => {},
13416 1 => addAfdBuf(.@"const", &iovecs, &len, pattern),
13417 else => switch (pattern.len) {
13418 0 => {},
13419 1 => {
13420 const splat_buffer = &backup_buffer;
13421 const memset_len = @min(splat_buffer.len, splat);
13422 const buf = splat_buffer[0..memset_len];
13423 @memset(buf, pattern[0]);
13424 addAfdBuf(.@"const", &iovecs, &len, buf);
13425 var remaining_splat = splat - buf.len;
13426 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
13427 addAfdBuf(.@"const", &iovecs, &len, splat_buffer);
13428 remaining_splat -= splat_buffer.len;
13429 }
13430 addAfdBuf(.@"const", &iovecs, &len, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
13431 },
13432 else => for (0..@min(splat, iovecs.len - len)) |_| {
13433 addAfdBuf(.@"const", &iovecs, &len, pattern);
13434 },
13435 },
13436 };
13437
13438 const iosb = try deviceIoControl(&.{
13439 .file = .{ .handle = handle, .flags = .{ .nonblocking = true } },
13440 .code = windows.IOCTL.AFD.SEND,
13441 .in = @ptrCast(&windows.AFD.SEND_INFO{
13442 .BufferArray = &iovecs,
13443 .BufferCount = len,
13444 .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true },
13445 .TdiFlags = .{},
13446 }),
13447 });
13448 switch (iosb.u.Status) {
13449 .SUCCESS => return iosb.Information,
13450 .CANCELLED => unreachable,
13451 .INSUFFICIENT_RESOURCES => return error.SystemResources,
13452 .CONNECTION_RESET, .REMOTE_DISCONNECT => return error.ConnectionResetByPeer,
13453 .IO_TIMEOUT => return error.ConnectionTimedOut,
13454 else => |status| return windows.unexpectedStatus(status),
13455 }
13456}
13457
13458fn addAfdBuf(
13459 comptime mutability: windows.AFD.Mutability,
13460 iovecs: []windows.AFD.WSABUF(mutability),
13461 len: *u32,
13462 bytes: switch (mutability) {
13463 .@"const" => []const u8,
13464 .@"var" => []u8,
13465 },
13466) void {
13467 if (bytes.len == 0) return;
13468 const cap = std.math.maxInt(u32);
13469 var remaining = bytes;
13470 while (remaining.len > cap) {
13471 if (iovecs.len - len.* == 0) return;
13472 iovecs[len.*] = .{ .buf = remaining.ptr, .len = cap };
13473 len.* += 1;
13474 remaining = remaining[cap..];
13475 } else {
13476 @branchHint(.likely);
13477 if (iovecs.len - len.* == 0) return;
13478 iovecs[len.*] = .{ .buf = remaining.ptr, .len = @intCast(remaining.len) };
13479 len.* += 1;
13480 }
13481}
13482
13483/// This is either usize or u32. Since, either is fine, let's use the same
13484/// `addBuf` function for both writing to a file and sending network messages.
13485const iovlen_t = switch (native_os) {
13486 .wasi => u32,
13487 else => @FieldType(posix.msghdr_const, "iovlen"),
13488};
13489
13490fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void {
13491 // OS checks ptr addr before length so zero length vectors must be omitted.
13492 if (bytes.len == 0) return;
13493 if (v.len - i.* == 0) return;
13494 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len };
13495 i.* += 1;
13496}
13497
13498fn netClose(userdata: ?*anyopaque, sockets: []const net.Socket) void {
13499 if (!have_networking) unreachable;
13500 const t: *Threaded = @ptrCast(@alignCast(userdata));
13501 _ = t;
13502 for (sockets) |socket| switch (native_os) {
13503 .windows => windows.CloseHandle(socket.handle),
13504 else => closeFd(socket.handle),
13505 };
13506}
13507
13508fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
13509 if (!have_networking) return error.NetworkDown;
13510 const t: *Threaded = @ptrCast(@alignCast(userdata));
13511 _ = t;
13512
13513 const posix_how: i32 = switch (how) {
13514 .recv => posix.SHUT.RD,
13515 .send => posix.SHUT.WR,
13516 .both => posix.SHUT.RDWR,
13517 };
13518
13519 const syscall: Syscall = try .start();
13520 while (true) {
13521 switch (posix.errno(posix.system.shutdown(handle, posix_how))) {
13522 .SUCCESS => return syscall.finish(),
13523 .INTR => {
13524 try syscall.checkCancel();
13525 continue;
13526 },
13527 else => |e| {
13528 syscall.finish();
13529 switch (e) {
13530 .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),
13531 .NOTCONN => return error.SocketUnconnected,
13532 .NOBUFS => return error.SystemResources,
13533 else => |err| return posix.unexpectedErrno(err),
13534 }
13535 },
13536 }
13537 }
13538}
13539
13540fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void {
13541 if (!have_networking) return error.NetworkDown;
13542 const t: *Threaded = @ptrCast(@alignCast(userdata));
13543 _ = t;
13544
13545 // shutdown does not support apcs at all
13546 switch ((try deviceIoControl(&.{
13547 .file = .{ .handle = handle, .flags = .{ .nonblocking = false } },
13548 .code = windows.IOCTL.AFD.PARTIAL_DISCONNECT,
13549 .in = @ptrCast(&windows.AFD.PARTIAL_DISCONNECT_INFO{
13550 .DisconnectMode = .{ .SEND = how != .recv, .RECEIVE = how != .send },
13551 .Timeout = -1,
13552 }),
13553 })).u.Status) {
13554 .SUCCESS => {},
13555 .CANCELLED => unreachable,
13556 .INSUFFICIENT_RESOURCES => return error.SystemResources,
13557 else => |status| return windows.unexpectedStatus(status),
13558 }
13559}
13560
13561fn netInterfaceNameResolve(
13562 userdata: ?*anyopaque,
13563 name: *const net.Interface.Name,
13564) net.Interface.Name.ResolveError!net.Interface {
13565 if (!have_networking) return error.InterfaceNotFound;
13566 const t: *Threaded = @ptrCast(@alignCast(userdata));
13567
13568 if (native_os == .linux) {
13569 const sock_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) {
13570 error.ProcessFdQuotaExceeded => return error.SystemResources,
13571 error.SystemFdQuotaExceeded => return error.SystemResources,
13572 error.AddressFamilyUnsupported => return error.Unexpected,
13573 error.ProtocolUnsupportedBySystem => return error.Unexpected,
13574 error.ProtocolUnsupportedByAddressFamily => return error.Unexpected,
13575 error.SocketModeUnsupported => return error.Unexpected,
13576 error.OptionUnsupported => return error.Unexpected,
13577 else => |e| return e,
13578 };
13579 defer closeFd(sock_fd);
13580
13581 var ifr: posix.ifreq = .{
13582 .ifrn = .{ .name = @bitCast(name.bytes) },
13583 .ifru = undefined,
13584 };
13585
13586 const syscall: Syscall = try .start();
13587 while (true) switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
13588 .SUCCESS => {
13589 syscall.finish();
13590 return .{ .index = @bitCast(ifr.ifru.ivalue) };
13591 },
13592 .INTR => {
13593 try syscall.checkCancel();
13594 continue;
13595 },
13596 .NODEV => return syscall.fail(error.InterfaceNotFound),
13597 else => |err| return syscall.unexpectedErrno(err),
13598 };
13599 }
13600
13601 if (is_windows) {
13602 var ConvertInterfaceNameToLuidW = t.dl.ConvertInterfaceNameToLuidW.load(.acquire);
13603 var ConvertInterfaceLuidToIndex = t.dl.ConvertInterfaceLuidToIndex.load(.acquire);
13604 if (ConvertInterfaceNameToLuidW == null or ConvertInterfaceLuidToIndex == null) {
13605 const iphlpapi_dll = t.dl.iphlpapi_dll.load(.acquire) orelse iphlpapi_dll: {
13606 try Thread.checkCancel();
13607 var iphlpapi_dll: *anyopaque = undefined;
13608 switch (windows.ntdll.LdrLoadDll(null, null, &.init(
13609 &.{ 'I', 'P', 'H', 'L', 'P', 'A', 'P', 'I', '.', 'D', 'L', 'L' },
13610 ), &iphlpapi_dll)) {
13611 .SUCCESS => {},
13612 .DLL_NOT_FOUND => return error.Unexpected,
13613 else => |status| return windows.unexpectedStatus(status),
13614 }
13615 const handle = t.dl.iphlpapi_dll.cmpxchgStrong(null, iphlpapi_dll, .release, .monotonic) orelse
13616 break :iphlpapi_dll iphlpapi_dll;
13617 switch (windows.ntdll.LdrUnloadDll(iphlpapi_dll)) {
13618 .SUCCESS => break :iphlpapi_dll handle.?,
13619 else => |status| return windows.unexpectedStatus(status),
13620 }
13621 };
13622 switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init(
13623 &.{
13624 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e',
13625 'N', 'a', 'm', 'e', 'T', 'o', 'L', 'u', 'i', 'd', 'W',
13626 },
13627 ), 0, @ptrCast(&ConvertInterfaceNameToLuidW))) {
13628 .SUCCESS => t.dl.ConvertInterfaceNameToLuidW.store(ConvertInterfaceNameToLuidW, .release),
13629 else => |status| return windows.unexpectedStatus(status),
13630 }
13631 switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init(
13632 &.{
13633 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e',
13634 'L', 'u', 'i', 'd', 'T', 'o', 'I', 'n', 'd', 'e', 'x',
13635 },
13636 ), 0, @ptrCast(&ConvertInterfaceLuidToIndex))) {
13637 .SUCCESS => t.dl.ConvertInterfaceLuidToIndex.store(ConvertInterfaceLuidToIndex, .release),
13638 else => |status| return windows.unexpectedStatus(status),
13639 }
13640 }
13641 try Thread.checkCancel();
13642 var name_w: [net.Interface.Name.max_len:0]windows.WCHAR = undefined;
13643 name_w[
13644 std.unicode.wtf8ToWtf16Le(&name_w, name.toSlice()) catch |err| switch (err) {
13645 error.InvalidWtf8 => return error.InterfaceNotFound,
13646 }
13647 ] = 0;
13648 var luid: windows.NET.LUID = undefined;
13649 switch (ConvertInterfaceNameToLuidW.?(&name_w, &luid)) {
13650 .SUCCESS => {},
13651 .INVALID_NAME => return error.InterfaceNotFound,
13652 .INVALID_PARAMETER => unreachable,
13653 else => |err| return windows.unexpectedError(err),
13654 }
13655 var index: windows.NET.IFINDEX = undefined;
13656 switch (ConvertInterfaceLuidToIndex.?(&luid, &index)) {
13657 .SUCCESS => {},
13658 .INVALID_PARAMETER => unreachable,
13659 else => |err| return windows.unexpectedError(err),
13660 }
13661 return .{ .index = @backingInt(index) };
13662 }
13663
13664 if (builtin.link_libc) {
13665 try Thread.checkCancel();
13666 const index = std.c.if_nametoindex(&name.bytes);
13667 if (index == 0) return error.InterfaceNotFound;
13668 return .{ .index = @bitCast(index) };
13669 }
13670
13671 @panic("unimplemented");
13672}
13673
13674fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
13675 const t: *Threaded = @ptrCast(@alignCast(userdata));
13676
13677 if (native_os == .linux) {
13678 try Thread.checkCancel();
13679 @panic("TODO implement netInterfaceName for linux");
13680 }
13681
13682 if (is_windows) {
13683 var ConvertInterfaceIndexToLuid = t.dl.ConvertInterfaceIndexToLuid.load(.acquire);
13684 var ConvertInterfaceLuidToNameW = t.dl.ConvertInterfaceLuidToNameW.load(.acquire);
13685 if (ConvertInterfaceIndexToLuid == null or ConvertInterfaceLuidToNameW == null) {
13686 const iphlpapi_dll = t.dl.iphlpapi_dll.load(.acquire) orelse iphlpapi_dll: {
13687 try Thread.checkCancel();
13688 var iphlpapi_dll: *anyopaque = undefined;
13689 switch (windows.ntdll.LdrLoadDll(null, null, &.init(
13690 &.{ 'I', 'P', 'H', 'L', 'P', 'A', 'P', 'I', '.', 'D', 'L', 'L' },
13691 ), &iphlpapi_dll)) {
13692 .SUCCESS => {},
13693 .DLL_NOT_FOUND => return error.Unexpected,
13694 else => |status| return windows.unexpectedStatus(status),
13695 }
13696 const handle = t.dl.iphlpapi_dll.cmpxchgStrong(null, iphlpapi_dll, .release, .monotonic) orelse
13697 break :iphlpapi_dll iphlpapi_dll;
13698 switch (windows.ntdll.LdrUnloadDll(iphlpapi_dll)) {
13699 .SUCCESS => break :iphlpapi_dll handle.?,
13700 else => |status| return windows.unexpectedStatus(status),
13701 }
13702 };
13703 switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init(
13704 &.{
13705 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e',
13706 'I', 'n', 'd', 'e', 'x', 'T', 'o', 'L', 'u', 'i', 'd',
13707 },
13708 ), 0, @ptrCast(&ConvertInterfaceIndexToLuid))) {
13709 .SUCCESS => t.dl.ConvertInterfaceIndexToLuid.store(ConvertInterfaceIndexToLuid, .release),
13710 else => |status| return windows.unexpectedStatus(status),
13711 }
13712 switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init(
13713 &.{
13714 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e',
13715 'L', 'u', 'i', 'd', 'T', 'o', 'N', 'a', 'm', 'e', 'W',
13716 },
13717 ), 0, @ptrCast(&ConvertInterfaceLuidToNameW))) {
13718 .SUCCESS => t.dl.ConvertInterfaceLuidToNameW.store(ConvertInterfaceLuidToNameW, .release),
13719 else => |status| return windows.unexpectedStatus(status),
13720 }
13721 }
13722 try Thread.checkCancel();
13723 var luid: windows.NET.LUID = undefined;
13724 switch (ConvertInterfaceIndexToLuid.?(@fromBackingInt(@intCast(interface.index)), &luid)) {
13725 .SUCCESS => {},
13726 .FILE_NOT_FOUND => return error.InterfaceNotFound,
13727 .INVALID_PARAMETER => unreachable,
13728 else => |err| return windows.unexpectedError(err),
13729 }
13730 var name_w: [net.Interface.Name.max_len:0]windows.WCHAR = undefined;
13731 switch (ConvertInterfaceLuidToNameW.?(&luid, &name_w, name_w.len)) {
13732 .SUCCESS => {},
13733 .INVALID_PARAMETER => unreachable,
13734 .NOT_ENOUGH_MEMORY => return error.NameTooLong,
13735 else => |err| return windows.unexpectedError(err),
13736 }
13737 var name: [3 * net.Interface.Name.max_len]u8 = undefined;
13738 return .fromSlice(name[0..std.unicode.wtf16LeToWtf8(&name, std.mem.sliceTo(&name_w, 0))]);
13739 }
13740
13741 if (builtin.link_libc) {
13742 try Thread.checkCancel();
13743 @panic("TODO implement netInterfaceName for libc");
13744 }
13745
13746 @panic("unimplemented");
13747}
13748
13749fn netLookup(
13750 userdata: ?*anyopaque,
13751 host_name: HostName,
13752 resolved: *Io.Queue(HostName.LookupResult),
13753 options: HostName.LookupOptions,
13754) net.HostName.LookupError!void {
13755 const t: *Threaded = @ptrCast(@alignCast(userdata));
13756 defer resolved.close(io(t));
13757 t.netLookupFallible(host_name, resolved, options) catch |err| switch (err) {
13758 error.Closed => unreachable, // `resolved` must not be closed until `netLookup` returns
13759 else => |e| return e,
13760 };
13761}
13762
13763fn netLookupFallible(
13764 t: *Threaded,
13765 host_name: HostName,
13766 resolved: *Io.Queue(HostName.LookupResult),
13767 options: HostName.LookupOptions,
13768) (net.HostName.LookupError || Io.QueueClosedError)!void {
13769 if (!have_networking) return error.NetworkDown;
13770
13771 const t_io = t.io();
13772 const name = host_name.bytes;
13773 assert(name.len <= HostName.max_len);
13774
13775 // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics.
13776 // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it.
13777 const is_glibc = builtin.link_libc and builtin.target.isGnuLibC();
13778
13779 if (is_glibc) {
13780 // TODO use getaddrinfo_a / gai_cancel
13781 }
13782
13783 // On Linux, we have to go through glibc because of the Name Service Switch feature.
13784 const non_glibc_linux = native_os == .linux and !is_glibc;
13785 if (non_glibc_linux or is_windows) {
13786 if (IpAddress.parseIp6(name, options.port)) |addr| {
13787 if (options.family == .ip4) return error.UnknownHostName;
13788 if (copyCanon(options.canonical_name_buffer, name)) |canon| {
13789 try resolved.putAll(t_io, &.{
13790 .{ .address = addr },
13791 .{ .canonical_name = canon },
13792 });
13793 } else {
13794 try resolved.putOne(t_io, .{ .address = addr });
13795 }
13796 return;
13797 } else |_| {}
13798
13799 if (IpAddress.parseIp4(name, options.port)) |addr| {
13800 if (options.family == .ip6) return error.UnknownHostName;
13801 if (copyCanon(options.canonical_name_buffer, name)) |canon| {
13802 try resolved.putAll(t_io, &.{
13803 .{ .address = addr },
13804 .{ .canonical_name = canon },
13805 });
13806 } else {
13807 try resolved.putOne(t_io, .{ .address = addr });
13808 }
13809 return;
13810 } else |_| {}
13811
13812 if (t.lookupHosts(host_name, resolved, options)) return else |err| switch (err) {
13813 error.UnknownHostName => {},
13814 else => |e| return e,
13815 }
13816
13817 // RFC 6761 Section 6.3.3
13818 // Name resolution APIs and libraries SHOULD recognize
13819 // localhost names as special and SHOULD always return the IP
13820 // loopback address for address queries and negative responses
13821 // for all other query types.
13822
13823 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
13824 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
13825 if (std.mem.endsWith(u8, name, localhost) and
13826 (name.len == localhost.len or name[name.len - localhost.len - 1] == '.'))
13827 {
13828 var results_buffer: [3]HostName.LookupResult = undefined;
13829 var results_index: usize = 0;
13830 if (options.family != .ip4) {
13831 results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } };
13832 results_index += 1;
13833 }
13834 if (options.family != .ip6) {
13835 results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } };
13836 results_index += 1;
13837 }
13838 if (options.canonical_name_buffer) |buf| {
13839 const canon_name = "localhost";
13840 const canon_name_dest = buf[0..canon_name.len];
13841 canon_name_dest.* = canon_name.*;
13842 results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } };
13843 results_index += 1;
13844 }
13845 try resolved.putAll(t_io, results_buffer[0..results_index]);
13846 return;
13847 }
13848
13849 if (native_os == .linux) return t.lookupDnsSearch(host_name, resolved, options);
13850
13851 comptime assert(is_windows);
13852 var DnsQueryEx = t.dl.DnsQueryEx.load(.acquire);
13853 //var DnsCancelQuery = t.dl.DnsCancelQuery.load(.acquire);
13854 var DnsFree = t.dl.DnsFree.load(.acquire);
13855 if (DnsQueryEx == null or
13856 //DnsCancelQuery == null or
13857 DnsFree == null)
13858 {
13859 const dnsapi_dll = t.dl.dnsapi_dll.load(.acquire) orelse dnsapi_dll: {
13860 try Thread.checkCancel();
13861 var dnsapi_dll: *anyopaque = undefined;
13862 switch (windows.ntdll.LdrLoadDll(null, null, &.init(
13863 &.{ 'd', 'n', 's', 'a', 'p', 'i', '.', 'd', 'l', 'l' },
13864 ), &dnsapi_dll)) {
13865 .SUCCESS => {},
13866 .DLL_NOT_FOUND => return error.Unexpected,
13867 else => |status| return windows.unexpectedStatus(status),
13868 }
13869 const handle = t.dl.dnsapi_dll.cmpxchgStrong(null, dnsapi_dll, .release, .monotonic) orelse
13870 break :dnsapi_dll dnsapi_dll;
13871 switch (windows.ntdll.LdrUnloadDll(dnsapi_dll)) {
13872 .SUCCESS => break :dnsapi_dll handle.?,
13873 else => |status| return windows.unexpectedStatus(status),
13874 }
13875 };
13876 switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init(
13877 &.{ 'D', 'n', 's', 'Q', 'u', 'e', 'r', 'y', 'E', 'x' },
13878 ), 0, @ptrCast(&DnsQueryEx))) {
13879 .SUCCESS => t.dl.DnsQueryEx.store(DnsQueryEx, .release),
13880 else => |status| return windows.unexpectedStatus(status),
13881 }
13882 //switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init(
13883 // &.{ 'D', 'n', 's', 'C', 'a', 'n', 'c', 'e', 'l', 'Q', 'u', 'e', 'r', 'y' },
13884 //), 0, @ptrCast(&DnsCancelQuery))) {
13885 // .SUCCESS => t.dl.DnsCancelQuery.store(DnsCancelQuery, .release),
13886 // else => |status| return windows.unexpectedStatus(status),
13887 //}
13888 switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init(
13889 &.{ 'D', 'n', 's', 'F', 'r', 'e', 'e' },
13890 ), 0, @ptrCast(&DnsFree))) {
13891 .SUCCESS => t.dl.DnsFree.store(DnsFree, .release),
13892 else => |status| return windows.unexpectedStatus(status),
13893 }
13894 }
13895 try Thread.checkCancel();
13896 const current_thread = Thread.current;
13897 var lookup_dns: LookupDnsWindows = .{
13898 .threaded = t,
13899 .thread = if (current_thread) |thread| thread.handle else undefined,
13900 .resolved = resolved,
13901 .options = options,
13902 .results = .{
13903 .Version = 1,
13904 .QueryStatus = undefined,
13905 .QueryOptions = undefined,
13906 .pQueryRecords = undefined,
13907 .Reserved = undefined,
13908 },
13909 .done = false,
13910 };
13911 var host_name_w: [HostName.max_len:0]windows.WCHAR = undefined;
13912 host_name_w[
13913 std.unicode.wtf8ToWtf16Le(&host_name_w, name) catch |err| switch (err) {
13914 error.InvalidWtf8 => return error.UnknownHostName,
13915 }
13916 ] = 0;
13917 //var cancel_token: windows.DNS.QUERY.CANCEL = undefined;
13918 // Workaround various bugs by attempting a synchronous non-wire query first
13919 switch (DnsQueryEx.?(&.{
13920 .Version = 1,
13921 .QueryName = &host_name_w,
13922 .QueryType = if (options.family == .ip4) .A else .AAAA,
13923 .QueryOptions = .{
13924 .NO_WIRE_QUERY = true,
13925 .NO_HOSTS_FILE = true, // handled above
13926 .ADDRCONFIG = true,
13927 .DUAL_ADDR = options.family == null,
13928 },
13929 }, &lookup_dns.results, null)) {
13930 .SUCCESS => try lookup_dns.completedFallible(),
13931 // We must wait for the APC routine.
13932 .DNS_REQUEST_PENDING => unreachable, // `pQueryCompletionCallback` was `null`
13933 .DNS_ERROR_RECORD_DOES_NOT_EXIST => switch (DnsQueryEx.?(&.{
13934 .Version = 1,
13935 .QueryName = &host_name_w,
13936 .QueryType = if (options.family == .ip4) .A else .AAAA,
13937 .QueryOptions = .{
13938 .NO_HOSTS_FILE = true, // handled above
13939 .ADDRCONFIG = true,
13940 .DUAL_ADDR = options.family == null,
13941 .MULTICAST_WAIT = true,
13942 },
13943 .pQueryCompletionCallback = if (current_thread) |_| &LookupDnsWindows.completed else null,
13944 }, &lookup_dns.results,
13945 //&cancel_token,
13946 null)) {
13947 .SUCCESS => try lookup_dns.completedFallible(),
13948 // We must wait for the APC routine.
13949 .DNS_REQUEST_PENDING => {
13950 assert(current_thread != null); // `pQueryCompletionCallback` was `null`
13951 while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) {
13952 // Once we get here we must not return from the function until the
13953 // operation completes, thereby releasing references to `host_name_w`,
13954 // `lookup_dns.results`, and `cancel_token`.
13955 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
13956 error.Canceled => |e| {
13957 //_ = DnsCancelQuery.?(&cancel_token);
13958 while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) waitForApcOrAlert();
13959 return e;
13960 },
13961 };
13962 waitForApcOrAlert();
13963 alertable_syscall.finish();
13964 }
13965 },
13966 else => |status| lookup_dns.results.QueryStatus = status,
13967 },
13968 else => |status| lookup_dns.results.QueryStatus = status,
13969 }
13970 switch (lookup_dns.results.QueryStatus) {
13971 .SUCCESS => return,
13972 .DNS_REQUEST_PENDING => unreachable, // already handled
13973 .INVALID_NAME,
13974 .DNS_ERROR_RCODE_NAME_ERROR,
13975 .DNS_INFO_NO_RECORDS,
13976 .DNS_ERROR_INVALID_NAME_CHAR,
13977 .DNS_ERROR_RECORD_DOES_NOT_EXIST,
13978 => return error.UnknownHostName,
13979 .TIMEOUT => return error.NameServerFailure,
13980 else => |err| return windows.unexpectedError(err),
13981 }
13982 }
13983
13984 if (native_os == .openbsd) {
13985 // TODO use getaddrinfo_async / asr_abort
13986 }
13987
13988 if (native_os == .freebsd) {
13989 // TODO use dnsres_getaddrinfo
13990 }
13991
13992 if (is_darwin) {
13993 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
13994 }
13995
13996 if (builtin.link_libc) {
13997 // This operating system lacks a way to resolve asynchronously. We are
13998 // stuck with getaddrinfo.
13999 var name_buffer: [HostName.max_len:0]u8 = undefined;
14000 @memcpy(name_buffer[0..name.len], name);
14001 name_buffer[name.len] = 0;
14002 const name_c = name_buffer[0..name.len :0];
14003
14004 var port_buffer: [8]u8 = undefined;
14005 const port_c = std.mem.printSentinel(&port_buffer, "{d}", .{options.port}, 0) catch unreachable;
14006
14007 const family: i32 = if (options.family) |f| switch (f) {
14008 .ip4 => posix.AF.INET,
14009 .ip6 => posix.AF.INET6,
14010 } else posix.AF.UNSPEC;
14011
14012 const hints: posix.addrinfo = .{
14013 .flags = .{ .CANONNAME = options.canonical_name_buffer != null, .NUMERICSERV = true },
14014 .family = family,
14015 .socktype = posix.SOCK.STREAM,
14016 .protocol = posix.IPPROTO.TCP,
14017 .canonname = null,
14018 .addr = null,
14019 .addrlen = 0,
14020 .next = null,
14021 };
14022 var res: ?*posix.addrinfo = null;
14023 const syscall: Syscall = try .start();
14024 while (true) {
14025 switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
14026 @as(posix.system.EAI, @fromBackingInt(@intCast(0))) => {
14027 syscall.finish();
14028 break;
14029 },
14030 .SYSTEM => switch (posix.errno(-1)) {
14031 .INTR => {
14032 try syscall.checkCancel();
14033 continue;
14034 },
14035 else => |e| {
14036 syscall.finish();
14037 return posix.unexpectedErrno(e);
14038 },
14039 },
14040 else => |e| {
14041 syscall.finish();
14042 switch (e) {
14043 .ADDRFAMILY => return error.AddressFamilyUnsupported,
14044 .AGAIN => return error.NameServerFailure,
14045 .FAIL => return error.NameServerFailure,
14046 .FAMILY => return error.AddressFamilyUnsupported,
14047 .MEMORY => return error.SystemResources,
14048 .NODATA => return error.UnknownHostName,
14049 .NONAME => return error.UnknownHostName,
14050 else => return error.Unexpected,
14051 }
14052 },
14053 }
14054 }
14055 defer if (res) |some| posix.system.freeaddrinfo(some);
14056
14057 var it = res;
14058 var canon_name: ?[*:0]const u8 = null;
14059 while (it) |info| : (it = info.next) {
14060 const addr = info.addr orelse continue;
14061 try resolved.putOne(t_io, .{ .address = addressFromPosix(@alignCast(@fieldParentPtr("any", addr))) });
14062
14063 if (info.canonname) |n| {
14064 if (canon_name == null) {
14065 canon_name = n;
14066 }
14067 }
14068 }
14069 if (canon_name) |n| {
14070 if (copyCanon(options.canonical_name_buffer, std.mem.sliceTo(n, 0))) |canon| {
14071 try resolved.putOne(t_io, .{ .canonical_name = canon });
14072 }
14073 }
14074 return;
14075 }
14076
14077 return error.OptionUnsupported;
14078}
14079
14080fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr {
14081 const t: *Threaded = @ptrCast(@alignCast(userdata));
14082 const current_thread_id = Thread.currentId();
14083
14084 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {
14085 mutexLock(&t.stderr_mutex);
14086 assert(t.stderr_mutex_lock_count == 0);
14087 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);
14088 }
14089 t.stderr_mutex_lock_count += 1;
14090
14091 return initLockedStderr(t, terminal_mode);
14092}
14093
14094fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr {
14095 const t: *Threaded = @ptrCast(@alignCast(userdata));
14096 const current_thread_id = Thread.currentId();
14097
14098 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) {
14099 if (!t.stderr_mutex.tryLock()) return null;
14100 assert(t.stderr_mutex_lock_count == 0);
14101 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered);
14102 }
14103 t.stderr_mutex_lock_count += 1;
14104
14105 return try initLockedStderr(t, terminal_mode);
14106}
14107
14108fn initLockedStderr(t: *Threaded, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr {
14109 if (!t.stderr_writer_initialized) {
14110 const io_t = io(t);
14111 if (is_windows) t.stderr_writer.file = .stderr();
14112 t.stderr_writer.io = io_t;
14113 t.stderr_writer_initialized = true;
14114 t.scanEnviron();
14115 const NO_COLOR = t.environ.exist.NO_COLOR;
14116 const CLICOLOR_FORCE = t.environ.exist.CLICOLOR_FORCE;
14117 t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file, NO_COLOR, CLICOLOR_FORCE);
14118 }
14119 return .{
14120 .file_writer = &t.stderr_writer,
14121 .terminal_mode = terminal_mode orelse t.stderr_mode,
14122 };
14123}
14124
14125fn unlockStderr(userdata: ?*anyopaque) void {
14126 const t: *Threaded = @ptrCast(@alignCast(userdata));
14127 if (t.stderr_writer.err == null) t.stderr_writer.interface.flush() catch {};
14128 if (t.stderr_writer.err) |err| {
14129 switch (err) {
14130 error.Canceled => recancelInner(),
14131 else => {},
14132 }
14133 t.stderr_writer.err = null;
14134 }
14135 t.stderr_writer.interface.end = 0;
14136 t.stderr_writer.interface.buffer = &.{};
14137
14138 t.stderr_mutex_lock_count -= 1;
14139 if (t.stderr_mutex_lock_count == 0) {
14140 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, Thread.invalid_id, .unordered);
14141 mutexUnlock(&t.stderr_mutex);
14142 }
14143}
14144
14145fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {
14146 const t: *Threaded = @ptrCast(@alignCast(userdata));
14147 _ = t;
14148 if (is_windows) {
14149 var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;
14150 const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len * 2 + 2, &wtf16le_buf) / 2;
14151 if (n == 0) return error.Unexpected;
14152 assert(n <= wtf16le_buf.len);
14153 const wtf16le_slice = wtf16le_buf[0..n];
14154 var end_index: usize = 0;
14155 var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice);
14156 while (it.nextCodepoint()) |codepoint| {
14157 const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable;
14158 if (end_index + seq_len >= buffer.len)
14159 return error.NameTooLong;
14160 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
14161 }
14162 return end_index;
14163 } else if (native_os == .wasi and !builtin.link_libc) {
14164 if (buffer.len == 0) return error.NameTooLong;
14165 buffer[0] = '.';
14166 return 1;
14167 }
14168
14169 const err: posix.E = if (builtin.link_libc) err: {
14170 const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*;
14171 break :err @fromBackingInt(@intCast(c_err));
14172 } else err: {
14173 break :err posix.errno(posix.system.getcwd(buffer.ptr, buffer.len));
14174 };
14175 switch (err) {
14176 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
14177 .NOENT => return error.CurrentDirUnlinked,
14178 .RANGE => return error.NameTooLong,
14179 .FAULT => |e| return errnoBug(e),
14180 .INVAL => |e| return errnoBug(e),
14181 else => return posix.unexpectedErrno(err),
14182 }
14183}
14184
14185fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void {
14186 const t: *Threaded = @ptrCast(@alignCast(userdata));
14187 _ = t;
14188
14189 if (native_os == .wasi) return error.OperationUnsupported;
14190
14191 if (is_windows) {
14192 var dir_path_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
14193 const dir_path = try GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buf);
14194 const syscall: Syscall = try .start();
14195 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(dir_path))) {
14196 .SUCCESS => return syscall.finish(),
14197 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
14198 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
14199 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
14200 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
14201 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
14202 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
14203 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
14204 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
14205 .CANCELLED => {
14206 try syscall.checkCancel();
14207 continue;
14208 },
14209 else => |status| return syscall.unexpectedNtstatus(status),
14210 };
14211 }
14212
14213 return fchdir(dir.handle);
14214}
14215
14216fn processSetCurrentPath(userdata: ?*anyopaque, path: []const u8) process.SetCurrentPathError!void {
14217 const t: *Threaded = @ptrCast(@alignCast(userdata));
14218 _ = t;
14219
14220 if (native_os == .wasi) return error.OperationUnsupported;
14221
14222 if (is_windows) {
14223 var path_w_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
14224 const len = std.unicode.calcWtf16LeLen(path) catch return error.InvalidWtf8;
14225 if (len > path_w_buf.len) return error.NameTooLong;
14226 const path_w_len = std.unicode.wtf8ToWtf16Le(&path_w_buf, path) catch |err| switch (err) {
14227 error.InvalidWtf8 => unreachable, // already validated
14228 };
14229 const path_w = path_w_buf[0..path_w_len];
14230
14231 const syscall: Syscall = try .start();
14232 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(path_w))) {
14233 .SUCCESS => return syscall.finish(),
14234 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
14235 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
14236 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
14237 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
14238 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
14239 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
14240 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
14241 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
14242 .CANCELLED => {
14243 try syscall.checkCancel();
14244 continue;
14245 },
14246 else => |status| return syscall.unexpectedNtstatus(status),
14247 };
14248 }
14249
14250 return chdir(path);
14251}
14252
14253pub const PosixAddress = extern union {
14254 any: posix.sockaddr,
14255 in: posix.sockaddr.in,
14256 in6: posix.sockaddr.in6,
14257};
14258
14259const UnixAddress = extern union {
14260 any: posix.sockaddr,
14261 un: posix.sockaddr.un,
14262};
14263
14264pub fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t {
14265 return switch (a.*) {
14266 .ip4 => posix.AF.INET,
14267 .ip6 => posix.AF.INET6,
14268 };
14269}
14270
14271pub fn addressFromPosix(posix_address: *const PosixAddress) IpAddress {
14272 return switch (posix_address.any.family) {
14273 posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) },
14274 posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) },
14275 else => .{ .ip4 = .loopback(0) },
14276 };
14277}
14278
14279pub fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t {
14280 return switch (a.*) {
14281 .ip4 => |ip4| {
14282 storage.in = address4ToPosix(ip4);
14283 return @sizeOf(posix.sockaddr.in);
14284 },
14285 .ip6 => |*ip6| {
14286 storage.in6 = address6ToPosix(ip6);
14287 return @sizeOf(posix.sockaddr.in6);
14288 },
14289 };
14290}
14291
14292fn addressUnixToPosix(a: *const net.UnixAddress, storage: *UnixAddress) posix.socklen_t {
14293 storage.un.family = posix.AF.UNIX;
14294 var path_len = switch (native_os) {
14295 .windows => @min(a.path.len, storage.un.path.len),
14296 else => a.path.len,
14297 };
14298 // With the AFD API, `sockaddr.un` is purely informational, so
14299 // use a suffix which is usually the most relevant part of a path.
14300 @memcpy(storage.un.path[0..path_len], a.path[a.path.len - path_len ..]);
14301 if (storage.un.path.len - path_len > 0) {
14302 @branchHint(.likely);
14303 storage.un.path[path_len] = 0;
14304 path_len += 1;
14305 }
14306 switch (native_os) {
14307 .windows => {
14308 if (storage.un.path[0] == 0) @memset(storage.un.path[path_len..], 0);
14309 return @sizeOf(posix.sockaddr.un);
14310 },
14311 else => return @intCast(@offsetOf(posix.sockaddr.un, "path") + path_len),
14312 }
14313}
14314
14315fn address4FromPosix(in: *const posix.sockaddr.in) net.Ip4Address {
14316 // The network byte order address in `in.addr` is already the byte order we want.
14317 const addr_bytes: *const [4]u8 = @ptrCast(&in.addr);
14318 return .{
14319 .port = std.mem.bigToNative(u16, in.port),
14320 .bytes = addr_bytes.*,
14321 };
14322}
14323
14324fn address6FromPosix(in6: *const posix.sockaddr.in6) net.Ip6Address {
14325 return .{
14326 .port = std.mem.bigToNative(u16, in6.port),
14327 .bytes = in6.addr,
14328 .flow = in6.flowinfo,
14329 .interface = .{ .index = in6.scope_id },
14330 };
14331}
14332
14333fn address4ToPosix(a: net.Ip4Address) posix.sockaddr.in {
14334 // The byte order of `a.bytes` is already equivalent to a network byte order address.
14335 const addr_raw: *align(1) const u32 = @ptrCast(&a.bytes);
14336 return .{
14337 .port = std.mem.nativeToBig(u16, a.port),
14338 .addr = addr_raw.*,
14339 };
14340}
14341
14342fn address6ToPosix(a: *const net.Ip6Address) posix.sockaddr.in6 {
14343 return .{
14344 .port = std.mem.nativeToBig(u16, a.port),
14345 .flowinfo = a.flow,
14346 .addr = a.bytes,
14347 .scope_id = a.interface.index,
14348 };
14349}
14350
14351pub fn errnoBug(err: posix.E) Io.UnexpectedError {
14352 if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err});
14353 return error.Unexpected;
14354}
14355
14356pub fn posixSocketModeProtocol(family: posix.sa_family_t, mode: net.Socket.Mode, protocol: ?net.Protocol) !struct { u32, u32 } {
14357 return .{
14358 switch (mode) {
14359 .stream => posix.SOCK.STREAM,
14360 .dgram => posix.SOCK.DGRAM,
14361 .seqpacket => posix.SOCK.SEQPACKET,
14362 .raw => posix.SOCK.RAW,
14363 .rdm => if (@hasDecl(posix.SOCK, "RDM")) posix.SOCK.RDM else return error.OptionUnsupported,
14364 },
14365 if (protocol) |p| @backingInt(p) else if (is_windows) switch (family) {
14366 posix.AF.UNIX => switch (mode) {
14367 .stream => 0,
14368 else => return error.ProtocolUnsupportedByAddressFamily,
14369 },
14370 posix.AF.INET, posix.AF.INET6 => @backingInt(@as(net.Protocol, switch (mode) {
14371 .stream => .tcp,
14372 .dgram => .udp,
14373 else => return error.ProtocolUnsupportedByAddressFamily,
14374 })),
14375 else => return error.ProtocolUnsupportedByAddressFamily,
14376 } else 0,
14377 };
14378}
14379
14380pub fn recoverableOsBugDetected() void {
14381 if (is_debug) unreachable;
14382}
14383
14384pub fn clockToPosix(clock: Io.Clock) posix.clockid_t {
14385 return switch (clock) {
14386 .real => posix.CLOCK.REALTIME,
14387 .awake => switch (native_os) {
14388 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW,
14389 else => posix.CLOCK.MONOTONIC,
14390 },
14391 .boot => switch (native_os) {
14392 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.MONOTONIC_RAW,
14393 // On freebsd derivatives, use MONOTONIC_FAST as currently there's
14394 // no precision tradeoff.
14395 .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST,
14396 // On linux, use BOOTTIME instead of MONOTONIC as it ticks while
14397 // suspended.
14398 .linux => posix.CLOCK.BOOTTIME,
14399 // On other posix systems, MONOTONIC is generally the fastest and
14400 // ticks while suspended.
14401 else => posix.CLOCK.MONOTONIC,
14402 },
14403 .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID,
14404 .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID,
14405 };
14406}
14407
14408fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {
14409 return switch (clock) {
14410 .real => .REALTIME,
14411 .awake => .MONOTONIC,
14412 .boot => .MONOTONIC,
14413 .cpu_process => .PROCESS_CPUTIME_ID,
14414 .cpu_thread => .THREAD_CPUTIME_ID,
14415 };
14416}
14417
14418pub const linux_statx_request: std.os.linux.STATX = .{
14419 .TYPE = true,
14420 .MODE = true,
14421 .ATIME = true,
14422 .MTIME = true,
14423 .CTIME = true,
14424 .INO = true,
14425 .SIZE = true,
14426 .NLINK = true,
14427 .BLOCKS = true,
14428};
14429
14430pub const linux_statx_check: std.os.linux.STATX = .{
14431 .TYPE = true,
14432 .MODE = true,
14433 .ATIME = false,
14434 .MTIME = true,
14435 .CTIME = true,
14436 .INO = true,
14437 .SIZE = true,
14438 .NLINK = true,
14439 .BLOCKS = false,
14440};
14441
14442pub fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
14443 const actual_mask_int: u32 = @bitCast(stx.mask);
14444 const wanted_mask_int: u32 = @bitCast(linux_statx_check);
14445 if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected;
14446
14447 return .{
14448 .inode = stx.ino,
14449 .nlink = stx.nlink,
14450 .size = stx.size,
14451 .permissions = .fromMode(stx.mode),
14452 .kind = statxKind(stx.mode),
14453 .atime = if (!stx.mask.ATIME) null else .{
14454 .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec),
14455 },
14456 .mtime = .{ .nanoseconds = @intCast(@as(i128, stx.mtime.sec) * std.time.ns_per_s + stx.mtime.nsec) },
14457 .ctime = .{ .nanoseconds = @intCast(@as(i128, stx.ctime.sec) * std.time.ns_per_s + stx.ctime.nsec) },
14458 .block_size = if (stx.mask.BLOCKS) stx.blksize else 1,
14459 };
14460}
14461
14462pub fn statxKind(stx_mode: u16) File.Kind {
14463 return switch (stx_mode & std.os.linux.S.IFMT) {
14464 std.os.linux.S.IFDIR => .directory,
14465 std.os.linux.S.IFCHR => .character_device,
14466 std.os.linux.S.IFBLK => .block_device,
14467 std.os.linux.S.IFREG => .file,
14468 std.os.linux.S.IFIFO => .named_pipe,
14469 std.os.linux.S.IFLNK => .sym_link,
14470 std.os.linux.S.IFSOCK => .unix_domain_socket,
14471 else => .unknown,
14472 };
14473}
14474
14475pub fn statFromPosix(st: *const posix.Stat) File.Stat {
14476 const atime = st.atime();
14477 const mtime = st.mtime();
14478 const ctime = st.ctime();
14479 return .{
14480 .inode = st.ino,
14481 .nlink = st.nlink,
14482 .size = @bitCast(st.size),
14483 .permissions = .fromMode(st.mode),
14484 .kind = k: {
14485 const m = st.mode & posix.S.IFMT;
14486 switch (m) {
14487 posix.S.IFBLK => break :k .block_device,
14488 posix.S.IFCHR => break :k .character_device,
14489 posix.S.IFDIR => break :k .directory,
14490 posix.S.IFIFO => break :k .named_pipe,
14491 posix.S.IFLNK => break :k .sym_link,
14492 posix.S.IFREG => break :k .file,
14493 posix.S.IFSOCK => break :k .unix_domain_socket,
14494 else => {},
14495 }
14496 if (native_os == .illumos) switch (m) {
14497 posix.S.IFDOOR => break :k .door,
14498 posix.S.IFPORT => break :k .event_port,
14499 else => {},
14500 };
14501
14502 break :k .unknown;
14503 },
14504 .atime = timestampFromPosix(&atime),
14505 .mtime = timestampFromPosix(&mtime),
14506 .ctime = timestampFromPosix(&ctime),
14507 .block_size = @intCast(st.blksize),
14508 };
14509}
14510
14511fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {
14512 return .{
14513 .inode = st.ino,
14514 .nlink = st.nlink,
14515 .size = @bitCast(st.size),
14516 .permissions = .default_file,
14517 .kind = switch (st.filetype) {
14518 .BLOCK_DEVICE => .block_device,
14519 .CHARACTER_DEVICE => .character_device,
14520 .DIRECTORY => .directory,
14521 .SYMBOLIC_LINK => .sym_link,
14522 .REGULAR_FILE => .file,
14523 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
14524 else => .unknown,
14525 },
14526 .atime = .fromNanoseconds(st.atim),
14527 .mtime = .fromNanoseconds(st.mtim),
14528 .ctime = .fromNanoseconds(st.ctim),
14529 .block_size = 1,
14530 };
14531}
14532
14533pub fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp {
14534 return .{ .nanoseconds = nanosecondsFromPosix(timespec) };
14535}
14536
14537pub fn nanosecondsFromPosix(timespec: *const posix.timespec) i96 {
14538 return @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
14539}
14540
14541fn timestampToPosix(nanoseconds: i96) posix.timespec {
14542 return .{
14543 .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)),
14544 .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)),
14545 };
14546}
14547
14548pub fn setTimestampToPosix(set_ts: File.SetTimestamp) posix.timespec {
14549 return switch (set_ts) {
14550 .unchanged => posix.UTIME.OMIT,
14551 .now => posix.UTIME.NOW,
14552 .new => |t| timestampToPosix(t.nanoseconds),
14553 };
14554}
14555
14556pub fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 {
14557 if (std.mem.containsAtLeastScalar(u8, file_path, 0, 1)) return error.BadPathName;
14558 // >= rather than > to make room for the null byte
14559 if (file_path.len >= buffer.len) return error.NameTooLong;
14560 @memcpy(buffer[0..file_path.len], file_path);
14561 buffer[file_path.len] = 0;
14562 return buffer[0..file_path.len :0];
14563}
14564
14565fn lookupDnsSearch(
14566 t: *Threaded,
14567 host_name: HostName,
14568 resolved: *Io.Queue(HostName.LookupResult),
14569 options: HostName.LookupOptions,
14570) (HostName.LookupError || Io.QueueClosedError)!void {
14571 const t_io = io(t);
14572 const rc = HostName.ResolvConf.init(t_io) catch return error.ResolvConfParseFailed;
14573
14574 // Count dots, suppress search when >=ndots or name ends in
14575 // a dot, which is an explicit request for global scope.
14576 const dots = std.mem.countScalar(u8, host_name.bytes, '.');
14577 const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len;
14578 const search = rc.search_buffer[0..search_len];
14579
14580 var canon_name = host_name.bytes;
14581
14582 // Strip final dot for canon, fail if multiple trailing dots.
14583 if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
14584 if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
14585
14586 // Name with search domain appended is set up in `canon_name`. This
14587 // both provides the desired default canonical name (if the requested
14588 // name is not a CNAME record) and serves as a buffer for passing the
14589 // full requested name to `lookupDns`.
14590 var local_buf: [HostName.max_len]u8 = undefined;
14591 const canon_buf = options.canonical_name_buffer orelse &local_buf;
14592 @memcpy(canon_buf[0..canon_name.len], canon_name);
14593 canon_buf[canon_name.len] = '.';
14594 var it = std.mem.tokenizeAny(u8, search, " \t");
14595 while (it.next()) |token| {
14596 @memcpy(canon_buf[canon_name.len + 1 ..][0..token.len], token);
14597 const lookup_canon_name = canon_buf[0 .. canon_name.len + 1 + token.len];
14598 if (t.lookupDns(lookup_canon_name, &rc, resolved, options)) |result| {
14599 return result;
14600 } else |err| switch (err) {
14601 error.UnknownHostName, error.NoAddressReturned => continue,
14602 else => |e| return e,
14603 }
14604 }
14605
14606 const lookup_canon_name = canon_buf[0..canon_name.len];
14607 return t.lookupDns(lookup_canon_name, &rc, resolved, options);
14608}
14609
14610fn lookupDns(
14611 t: *Threaded,
14612 lookup_canon_name: []const u8,
14613 rc: *const HostName.ResolvConf,
14614 resolved: *Io.Queue(HostName.LookupResult),
14615 options: HostName.LookupOptions,
14616) (HostName.LookupError || Io.QueueClosedError)!void {
14617 const t_io = io(t);
14618 const family_records: [2]struct { af: IpAddress.Family, rr: HostName.DnsRecord } = .{
14619 .{ .af = .ip6, .rr = .A },
14620 .{ .af = .ip4, .rr = .AAAA },
14621 };
14622 var query_buffers: [2][280]u8 = undefined;
14623 var answer_buffer: [2 * 512]u8 = undefined;
14624 var queries_buffer: [2][]const u8 = undefined;
14625 var answers_buffer: [2][]const u8 = undefined;
14626 var nq: usize = 0;
14627 var answer_buffer_i: usize = 0;
14628
14629 for (family_records) |fr| {
14630 if (options.family != fr.af) {
14631 var entropy: [2]u8 = undefined;
14632 random(t, &entropy);
14633 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
14634 queries_buffer[nq] = query_buffers[nq][0..len];
14635 nq += 1;
14636 }
14637 }
14638
14639 var ip4_mapped_buffer: [HostName.ResolvConf.max_nameservers]IpAddress = undefined;
14640 const ip4_mapped = ip4_mapped_buffer[0..rc.nameservers_len];
14641 var any_ip6 = false;
14642 for (rc.nameservers(), ip4_mapped) |*ns, *m| {
14643 m.* = .{ .ip6 = .fromAny(ns.*) };
14644 any_ip6 = any_ip6 or ns.* == .ip6;
14645 }
14646 var socket = s: {
14647 if (any_ip6) ip6: {
14648 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
14649 const socket = ip6_addr.bind(t_io, .{ .ip6_only = false, .mode = .dgram }) catch |err| switch (err) {
14650 error.AddressFamilyUnsupported => break :ip6,
14651 else => |e| return e,
14652 };
14653 break :s socket;
14654 }
14655 any_ip6 = false;
14656 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
14657 const socket = try ip4_addr.bind(t_io, .{ .mode = .dgram });
14658 break :s socket;
14659 };
14660 defer socket.close(t_io);
14661
14662 const mapped_nameservers = if (any_ip6) ip4_mapped else rc.nameservers();
14663 const queries = queries_buffer[0..nq];
14664 const answers = answers_buffer[0..queries.len];
14665 var answers_remaining = answers.len;
14666 for (answers) |*answer| answer.len = 0;
14667
14668 // boot clock is chosen because time the computer is suspended should count
14669 // against time spent waiting for external messages to arrive.
14670 const clock: Io.Clock = .boot;
14671 var now_ts = clock.now(t_io);
14672 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
14673 const attempt_duration: Io.Duration = .{
14674 .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds),
14675 };
14676
14677 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = clock.now(t_io)) {
14678 const timeout: Io.Timeout = .{ .deadline = .{
14679 .raw = now_ts.addDuration(attempt_duration),
14680 .clock = clock,
14681 } };
14682
14683 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers;
14684 {
14685 var message_buffer: [max_messages]net.OutgoingMessage = undefined;
14686 var message_i: usize = 0;
14687 for (queries, answers) |query, *answer| {
14688 if (answer.len != 0) continue;
14689 for (mapped_nameservers) |*ns| {
14690 message_buffer[message_i] = .{
14691 .address = ns,
14692 .data_ptr = query.ptr,
14693 .data_len = query.len,
14694 };
14695 message_i += 1;
14696 }
14697 }
14698 const send_err, _ = socket.sendManyTimeout(t_io, message_buffer[0..message_i], .{}, timeout);
14699 if (send_err) |err| switch (err) {
14700 error.Canceled => |e| return e,
14701 error.Timeout => continue :send,
14702 else => {},
14703 };
14704 }
14705
14706 while (true) {
14707 var message_buffer: [max_messages]net.IncomingMessage = @splat(.init);
14708 const buf = answer_buffer[answer_buffer_i..];
14709 const recv_err, const recv_n = socket.receiveManyTimeout(t_io, &message_buffer, buf, .{}, timeout);
14710 for (message_buffer[0..recv_n]) |*received_message| {
14711 const reply = received_message.data;
14712 // Ignore non-identifiable packets.
14713 if (reply.len < 4) continue;
14714
14715 // Ignore replies from addresses we didn't send to.
14716 const ns = for (mapped_nameservers) |*ns| {
14717 if (received_message.from.eql(ns)) break ns;
14718 } else {
14719 continue;
14720 };
14721
14722 // Find which query this answer goes with, if any.
14723 const query, const answer = for (queries, answers) |query, *answer| {
14724 if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer };
14725 } else {
14726 continue;
14727 };
14728 if (answer.len != 0) continue;
14729
14730 // Only accept positive or negative responses; retry immediately on
14731 // server failure, and ignore all other codes such as refusal.
14732 switch (reply[3] & 15) {
14733 0, 3 => {
14734 answer.* = reply;
14735 answer_buffer_i += reply.len;
14736 answers_remaining -= 1;
14737 if (answer_buffer.len - answer_buffer_i == 0) break :send;
14738 if (answers_remaining == 0) break :send;
14739 },
14740 2 => {
14741 socket.sendTimeout(t_io, ns, query, timeout) catch |err| switch (err) {
14742 error.Canceled => |e| return e,
14743 error.Timeout => continue :send,
14744 else => {},
14745 };
14746 continue;
14747 },
14748 else => continue,
14749 }
14750 }
14751 if (recv_err) |err| switch (err) {
14752 error.Canceled => |e| return e,
14753 error.Timeout => continue :send,
14754 else => continue,
14755 };
14756 }
14757 } else {
14758 return error.NameServerFailure;
14759 }
14760
14761 var addresses_len: usize = 0;
14762 var canonical_name: ?HostName = null;
14763
14764 for (answers) |answer| {
14765 var it = HostName.DnsResponse.init(answer) catch {
14766 // Here we could potentially add diagnostics to the results queue.
14767 continue;
14768 };
14769 while (it.next() catch {
14770 // Here we could potentially add diagnostics to the results queue.
14771 continue;
14772 }) |record| switch (record.rr) {
14773 .A => {
14774 const data = record.packet[record.data_off..][0..record.data_len];
14775 if (data.len != 4) return error.InvalidDnsARecord;
14776 try resolved.putOne(t_io, .{ .address = .{ .ip4 = .{
14777 .bytes = data[0..4].*,
14778 .port = options.port,
14779 } } });
14780 addresses_len += 1;
14781 },
14782 .AAAA => {
14783 const data = record.packet[record.data_off..][0..record.data_len];
14784 if (data.len != 16) return error.InvalidDnsAAAARecord;
14785 try resolved.putOne(t_io, .{ .address = .{ .ip6 = .{
14786 .bytes = data[0..16].*,
14787 .port = options.port,
14788 } } });
14789 addresses_len += 1;
14790 },
14791 .CNAME => {
14792 if (options.canonical_name_buffer) |buf| {
14793 _, canonical_name = HostName.expand(
14794 record.packet,
14795 record.data_off,
14796 buf,
14797 ) catch return error.InvalidDnsCnameRecord;
14798 }
14799 },
14800 _ => continue,
14801 };
14802 }
14803
14804 if (options.canonical_name_buffer != null) {
14805 try resolved.putOne(t_io, .{
14806 .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name },
14807 });
14808 }
14809 if (addresses_len == 0) return error.NoAddressReturned;
14810}
14811
14812fn lookupHosts(
14813 t: *Threaded,
14814 host_name: HostName,
14815 resolved: *Io.Queue(HostName.LookupResult),
14816 options: HostName.LookupOptions,
14817) !void {
14818 const path_w = if (is_windows) path_w: {
14819 var path_w_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;
14820 const system_dir = windows.getSystemDirectoryWtf16Le();
14821 const suffix = [_]u16{
14822 '\\', 'd', 'r', 'i', 'v', 'e', 'r', 's', '\\', 'e', 't', 'c', '\\', 'h', 'o', 's', 't', 's',
14823 };
14824 @memcpy(path_w_buf[0..system_dir.len], system_dir);
14825 @memcpy(path_w_buf[system_dir.len..][0..suffix.len], &suffix);
14826 path_w_buf[system_dir.len + suffix.len] = 0;
14827 break :path_w wToPrefixedFileW(null, &path_w_buf, .{}) catch |err| switch (err) {
14828 error.FileNotFound,
14829 error.AccessDenied,
14830 => return error.UnknownHostName,
14831
14832 error.Canceled => |e| return e,
14833
14834 else => {
14835 // Here we could add more detailed diagnostics to the results queue.
14836 return error.DetectingNetworkConfigurationFailed;
14837 },
14838 };
14839 };
14840 const file = (if (is_windows)
14841 dirOpenFileWtf16(null, path_w.span(), .{})
14842 else
14843 dirOpenFile(t, .cwd(), "/etc/hosts", .{})) catch |err| switch (err) {
14844 error.FileNotFound,
14845 error.NotDir,
14846 error.AccessDenied,
14847 => return error.UnknownHostName,
14848
14849 error.Canceled => |e| return e,
14850
14851 else => {
14852 // Here we could add more detailed diagnostics to the results queue.
14853 return error.DetectingNetworkConfigurationFailed;
14854 },
14855 };
14856 defer fileClose(t, &.{file});
14857
14858 var line_buf: [512]u8 = undefined;
14859 var file_reader = file.reader(t.io(), &line_buf);
14860 return t.lookupHostsReader(host_name, resolved, options, &file_reader.interface) catch |err| switch (err) {
14861 error.ReadFailed => switch (file_reader.err.?) {
14862 error.Canceled => |e| return e,
14863 else => {
14864 // Here we could add more detailed diagnostics to the results queue.
14865 return error.DetectingNetworkConfigurationFailed;
14866 },
14867 },
14868 error.Canceled,
14869 error.Closed,
14870 error.UnknownHostName,
14871 => |e| return e,
14872 };
14873}
14874
14875fn lookupHostsReader(
14876 t: *Threaded,
14877 host_name: HostName,
14878 resolved: *Io.Queue(HostName.LookupResult),
14879 options: HostName.LookupOptions,
14880 reader: *Io.Reader,
14881) error{ ReadFailed, Canceled, UnknownHostName, Closed }!void {
14882 const t_io = io(t);
14883 var addresses_len: usize = 0;
14884 var canonical_name: ?HostName = null;
14885 while (true) {
14886 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
14887 error.StreamTooLong => {
14888 // Skip lines that are too long.
14889 _ = reader.discardDelimiterInclusive('\n') catch |er| switch (er) {
14890 error.EndOfStream => break,
14891 error.ReadFailed => |e| return e,
14892 };
14893 continue;
14894 },
14895 error.ReadFailed => |e| return e,
14896 error.EndOfStream => break,
14897 };
14898 reader.toss(@min(1, reader.bufferedLen()));
14899 var split_it = std.mem.splitScalar(u8, if (is_windows and std.mem.endsWith(u8, line, "\r"))
14900 line[0 .. line.len - 1]
14901 else
14902 line, '#');
14903 const no_comment_line = split_it.first();
14904
14905 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t");
14906 const ip_text = line_it.next() orelse continue;
14907 var first_name_text: ?[]const u8 = null;
14908 while (line_it.next()) |name_text| {
14909 if (std.ascii.eqlIgnoreCase(name_text, host_name.bytes)) {
14910 if (first_name_text == null) first_name_text = name_text;
14911 break;
14912 }
14913 } else continue;
14914
14915 if (canonical_name == null) {
14916 if (options.canonical_name_buffer) |buf| {
14917 if (HostName.init(first_name_text.?)) |name_text| {
14918 if (name_text.bytes.len <= buf.len) {
14919 const canonical_name_dest = buf[0..name_text.bytes.len];
14920 @memcpy(canonical_name_dest, name_text.bytes);
14921 canonical_name = .{ .bytes = canonical_name_dest };
14922 }
14923 } else |_| {}
14924 }
14925 }
14926
14927 if (options.family != .ip6) {
14928 if (IpAddress.parseIp4(ip_text, options.port)) |addr| {
14929 try resolved.putOne(t_io, .{ .address = addr });
14930 addresses_len += 1;
14931 } else |_| {}
14932 }
14933 if (options.family != .ip4) {
14934 if (IpAddress.parseIp6(ip_text, options.port)) |addr| {
14935 try resolved.putOne(t_io, .{ .address = addr });
14936 addresses_len += 1;
14937 } else |_| {}
14938 }
14939 }
14940
14941 if (canonical_name) |canon_name| try resolved.putOne(t_io, .{ .canonical_name = canon_name });
14942 if (addresses_len == 0) return error.UnknownHostName;
14943}
14944
14945/// Writes DNS resolution query packet data to `w`; at most 280 bytes.
14946fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: HostName.DnsRecord, entropy: [2]u8) usize {
14947 // This implementation is ported from musl libc.
14948 // A more idiomatic "ziggy" implementation would be welcome.
14949 var name = dname;
14950 if (std.mem.endsWith(u8, name, ".")) name.len -= 1;
14951 assert(name.len <= 253);
14952 const n = 17 + name.len + @intFromBool(name.len != 0);
14953
14954 // Construct query template - ID will be filled later
14955 q[0..2].* = entropy;
14956 @memset(q[2..n], 0);
14957 q[2] = @as(u8, op) * 8 + 1;
14958 q[5] = 1;
14959 @memcpy(q[13..][0..name.len], name);
14960 var i: usize = 13;
14961 var j: usize = undefined;
14962 while (q[i] != 0) : (i = j + 1) {
14963 j = i;
14964 while (q[j] != 0 and q[j] != '.') : (j += 1) {}
14965 // TODO determine the circumstances for this and whether or
14966 // not this should be an error.
14967 if (j - i - 1 > 62) unreachable;
14968 q[i - 1] = @intCast(j - i);
14969 }
14970 q[i + 1] = @backingInt(ty);
14971 q[i + 3] = class;
14972 return n;
14973}
14974
14975const LookupDnsWindows = struct {
14976 threaded: *Threaded,
14977 thread: Thread.Handle,
14978 resolved: *Io.Queue(HostName.LookupResult),
14979 options: HostName.LookupOptions,
14980 results: windows.DNS.QUERY.RESULT,
14981 done: bool,
14982
14983 fn completed(
14984 pQueryContext: ?*anyopaque,
14985 pQueryResults: *windows.DNS.QUERY.RESULT,
14986 ) callconv(.winapi) void {
14987 _ = pQueryContext;
14988 const lookup_dns: *LookupDnsWindows = @fieldParentPtr("results", pQueryResults);
14989 lookup_dns.completedFallible() catch |err| switch (err) {
14990 error.Closed => unreachable, // `resolved` must not be closed until `netLookup` returns
14991 error.Canceled => unreachable, // called from an uncancelable thread
14992 };
14993 @atomicStore(bool, &lookup_dns.done, true, .release);
14994 _ = windows.ntdll.NtAlertThread(lookup_dns.thread);
14995 }
14996 fn completedFallible(lookup_dns: *LookupDnsWindows) (Io.QueueClosedError || Io.Cancelable)!void {
14997 assert(!lookup_dns.done);
14998 const t = lookup_dns.threaded;
14999 defer t.dl.DnsFree.raw.?(lookup_dns.results.pQueryRecords, .RecordList);
15000 if (lookup_dns.results.QueryStatus != .SUCCESS) return;
15001 const t_io = t.io();
15002 var record_it = lookup_dns.results.pQueryRecords;
15003 while (record_it) |record| : (record_it = record.pNext) switch (record.wType) {
15004 else => {},
15005 .A => try lookup_dns.resolved.putOne(t_io, .{
15006 .address = .{ .ip4 = .{ .bytes = record.Data.A, .port = lookup_dns.options.port } },
15007 }),
15008 .AAAA => {
15009 const ip6: net.Ip6Address = .{
15010 .bytes = record.Data.AAAA,
15011 .port = lookup_dns.options.port,
15012 };
15013 try lookup_dns.resolved.putOne(t_io, .{
15014 .address = if (lookup_dns.options.family) |_| .{ .ip6 = ip6 } else .fromIp6(ip6),
15015 });
15016 },
15017 };
15018 if (lookup_dns.results.pQueryRecords) |record| {
15019 if (lookup_dns.options.canonical_name_buffer) |buf| {
15020 const name_wtf16 = std.mem.span(
15021 @as([*:0]const windows.WCHAR, @ptrCast(@alignCast(record.pName))),
15022 );
15023 const len = std.unicode.wtf16LeToWtf8(buf, name_wtf16);
15024 try lookup_dns.resolved.putOne(t_io, .{
15025 .canonical_name = .{ .bytes = buf[0..len] },
15026 });
15027 }
15028 }
15029 }
15030};
15031
15032fn copyCanon(canonical_name_buffer: ?*[HostName.max_len]u8, name: []const u8) ?HostName {
15033 const buf = canonical_name_buffer orelse return null;
15034 const dest = buf[0..name.len];
15035 @memcpy(dest, name);
15036 return .{ .bytes = dest };
15037}
15038
15039/// Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it:
15040/// https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6
15041///
15042/// This XNU version appears to correspond to 11.0.1:
15043/// https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html
15044///
15045/// ulock_wait() uses 32-bit micro-second timeouts where 0 = INFINITE or no-timeout
15046/// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention)
15047const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11;
15048
15049fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
15050
15051const WindowsEnvironStrings = struct {
15052 PATH: ?[:0]const u16 = null,
15053 PATHEXT: ?[:0]const u16 = null,
15054
15055 fn scan() WindowsEnvironStrings {
15056 const peb = windows.peb();
15057 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
15058 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
15059 const ptr = peb.ProcessParameters.Environment;
15060
15061 var result: WindowsEnvironStrings = .{};
15062 var i: usize = 0;
15063 while (ptr[i] != 0) {
15064 const key_start = i;
15065
15066 // There are some special environment variables that start with =,
15067 // so we need a special case to not treat = as a key/value separator
15068 // if it's the first character.
15069 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
15070 if (ptr[key_start] == '=') i += 1;
15071
15072 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
15073 const key_w = ptr[key_start..i];
15074
15075 if (ptr[i] == '=') i += 1;
15076
15077 const value_start = i;
15078 while (ptr[i] != 0) : (i += 1) {}
15079 const value_w = ptr[value_start..i :0];
15080
15081 i += 1; // skip over null byte
15082
15083 inline for (@typeInfo(WindowsEnvironStrings).@"struct".field_names) |field_name| {
15084 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field_name);
15085 if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field_name) = value_w;
15086 }
15087 }
15088
15089 return result;
15090 }
15091};
15092
15093fn scanEnviron(t: *Threaded) void {
15094 mutexLock(&t.mutex);
15095 defer mutexUnlock(&t.mutex);
15096 if (t.environ_initialized) return;
15097 t.environ.scan(t.allocator);
15098 t.environ_initialized = true;
15099}
15100
15101fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError {
15102 const t: *Threaded = @ptrCast(@alignCast(userdata));
15103
15104 if (!process.can_replace) return error.OperationUnsupported;
15105
15106 t.scanEnviron(); // for PATH
15107 const PATH = t.environ.string.PATH orelse default_PATH;
15108
15109 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15110 defer arena_allocator.deinit();
15111 const arena = arena_allocator.allocator();
15112
15113 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
15114 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
15115
15116 const env_block = env_block: {
15117 const prog_fd: i32 = -1;
15118 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
15119 .zig_progress_fd = prog_fd,
15120 });
15121 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
15122 .zig_progress_fd = prog_fd,
15123 });
15124 };
15125
15126 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
15127}
15128
15129fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
15130 if (!process.can_replace) return error.OperationUnsupported;
15131 _ = userdata;
15132 _ = dir;
15133 _ = options;
15134 @panic("TODO processReplacePath");
15135}
15136
15137fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child {
15138 if (!process.can_spawn) return error.OperationUnsupported;
15139 _ = userdata;
15140 _ = dir;
15141 _ = options;
15142 @panic("TODO processSpawnPath");
15143}
15144
15145const processSpawn = switch (native_os) {
15146 .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported,
15147 .windows => processSpawnWindows,
15148 else => processSpawnPosix,
15149};
15150
15151fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15152 _ = userdata;
15153 _ = options;
15154 return error.OperationUnsupported;
15155}
15156
15157const Spawned = struct {
15158 pid: posix.pid_t,
15159 err_fd: posix.fd_t,
15160 stdin: ?File,
15161 stdout: ?File,
15162 stderr: ?File,
15163};
15164
15165fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Spawned {
15166 // The child process does need to access (one end of) these pipes. However,
15167 // we must initially set CLOEXEC to avoid a race condition. If another thread
15168 // is racing to spawn a different child process, we don't want it to inherit
15169 // these FDs in any scenario; that would mean that, for instance, calls to
15170 // `poll` from the parent would not report the child's stdout as closing when
15171 // expected, since the other child may retain a reference to the write end of
15172 // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we
15173 // need to do something in the new child to make sure we preserve the reference
15174 // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it
15175 // turns out, we `dup2` everything anyway, so there's no need!
15176 const pipe_flags: posix.O = .{ .CLOEXEC = true };
15177
15178 const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined;
15179 errdefer if (options.stdin == .pipe) {
15180 destroyPipe(stdin_pipe);
15181 };
15182
15183 const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined;
15184 errdefer if (options.stdout == .pipe) {
15185 destroyPipe(stdout_pipe);
15186 };
15187
15188 const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined;
15189 errdefer if (options.stderr == .pipe) {
15190 destroyPipe(stderr_pipe);
15191 };
15192
15193 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
15194 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
15195
15196 const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none) pipe: {
15197 // We use CLOEXEC for the same reason as in `pipe_flags`.
15198 const pipe = try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
15199 switch (native_os) {
15200 .linux => _ = posix.system.fcntl(pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2)),
15201 else => {},
15202 }
15203 break :pipe pipe;
15204 } else .{ -1, -1 };
15205 errdefer destroyPipe(prog_pipe);
15206
15207 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15208 defer arena_allocator.deinit();
15209 const arena = arena_allocator.allocator();
15210
15211 // The POSIX standard does not allow malloc() between fork() and execve(),
15212 // and this allocator may be a libc allocator.
15213 // I have personally observed the child process deadlocking when it tries
15214 // to call malloc() due to a heap allocation between fork() and execve(),
15215 // in musl v1.1.24.
15216 // Additionally, we want to reduce the number of possible ways things
15217 // can fail between fork() and execve().
15218 // Therefore, we do all the allocation for the execve() before the fork().
15219 // This means we must do the null-termination of argv and env vars here.
15220 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
15221 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
15222
15223 const prog_fileno = 3;
15224 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
15225
15226 const env_block = env_block: {
15227 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
15228 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
15229 .zig_progress_fd = prog_fd,
15230 });
15231 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
15232 .zig_progress_fd = prog_fd,
15233 });
15234 };
15235
15236 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
15237 // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.
15238 const err_pipe = try pipe2(.{ .CLOEXEC = true });
15239 errdefer destroyPipe(err_pipe);
15240
15241 t.scanEnviron(); // for PATH
15242 const PATH = t.environ.string.PATH orelse default_PATH;
15243
15244 const pid_result: posix.pid_t = fork: {
15245 const rc = posix.system.fork();
15246 switch (posix.errno(rc)) {
15247 .SUCCESS => break :fork @intCast(rc),
15248 .AGAIN => return error.SystemResources,
15249 .NOMEM => return error.SystemResources,
15250 .NOSYS => return error.OperationUnsupported,
15251 else => |err| return posix.unexpectedErrno(err),
15252 }
15253 };
15254
15255 if (pid_result == 0) {
15256 defer comptime unreachable; // We are the child.
15257 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;
15258 const ep1 = err_pipe[1];
15259
15260 setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15261 setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15262 setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
15263
15264 switch (options.cwd) {
15265 .inherit => {},
15266 .dir => |cwd| {
15267 fchdir(cwd.handle) catch |err| forkBail(ep1, err);
15268 },
15269 .path => |cwd| {
15270 chdir(cwd) catch |err| forkBail(ep1, err);
15271 },
15272 }
15273
15274 // Must happen after fchdir above, the cwd file descriptor might be
15275 // equal to prog_fileno and be clobbered by this dup2 call.
15276 if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err);
15277
15278 if (options.gid) |gid| {
15279 switch (posix.errno(posix.system.setregid(gid, gid))) {
15280 .SUCCESS => {},
15281 .AGAIN => forkBail(ep1, error.ResourceLimitReached),
15282 .INVAL => forkBail(ep1, error.InvalidUserId),
15283 .PERM => forkBail(ep1, error.PermissionDenied),
15284 else => forkBail(ep1, error.Unexpected),
15285 }
15286 }
15287
15288 if (options.uid) |uid| {
15289 switch (posix.errno(posix.system.setreuid(uid, uid))) {
15290 .SUCCESS => {},
15291 .AGAIN => forkBail(ep1, error.ResourceLimitReached),
15292 .INVAL => forkBail(ep1, error.InvalidUserId),
15293 .PERM => forkBail(ep1, error.PermissionDenied),
15294 else => forkBail(ep1, error.Unexpected),
15295 }
15296 }
15297
15298 if (options.pgid) |pid| {
15299 switch (posix.errno(posix.system.setpgid(0, pid))) {
15300 .SUCCESS => {},
15301 .ACCES => forkBail(ep1, error.ProcessAlreadyExec),
15302 .INVAL => forkBail(ep1, error.InvalidProcessGroupId),
15303 .PERM => forkBail(ep1, error.PermissionDenied),
15304 else => forkBail(ep1, error.Unexpected),
15305 }
15306 }
15307
15308 if (options.start_suspended) {
15309 switch (posix.errno(posix.system.kill(0, .STOP))) {
15310 .SUCCESS => {},
15311 .PERM => forkBail(ep1, error.PermissionDenied),
15312 else => forkBail(ep1, error.Unexpected),
15313 }
15314 }
15315
15316 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
15317 forkBail(ep1, err);
15318 }
15319
15320 const pid: posix.pid_t = @intCast(pid_result); // We are the parent.
15321 errdefer comptime unreachable; // The child is forked; we must not error from now on
15322
15323 closeFd(err_pipe[1]); // make sure only the child holds the write end open
15324
15325 if (options.stdin == .pipe) closeFd(stdin_pipe[0]);
15326 if (options.stdout == .pipe) closeFd(stdout_pipe[1]);
15327 if (options.stderr == .pipe) closeFd(stderr_pipe[1]);
15328
15329 if (prog_pipe[1] != -1) closeFd(prog_pipe[1]);
15330 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
15331
15332 return .{
15333 .pid = pid,
15334 .err_fd = err_pipe[0],
15335 .stdin = switch (options.stdin) {
15336 .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } },
15337 else => null,
15338 },
15339 .stdout = switch (options.stdout) {
15340 .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } },
15341 else => null,
15342 },
15343 .stderr = switch (options.stderr) {
15344 .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } },
15345 else => null,
15346 },
15347 };
15348}
15349
15350fn getDevNullFd(t: *Threaded) !posix.fd_t {
15351 {
15352 mutexLock(&t.mutex);
15353 defer mutexUnlock(&t.mutex);
15354 if (t.null_file.fd != -1) return t.null_file.fd;
15355 }
15356 const mode: u32 = 0;
15357 const syscall: Syscall = try .start();
15358 while (true) {
15359 const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, mode);
15360 switch (posix.errno(rc)) {
15361 .SUCCESS => {
15362 syscall.finish();
15363 const fresh_fd: posix.fd_t = @intCast(rc);
15364 mutexLock(&t.mutex); // Another thread might have won the race.
15365 defer mutexUnlock(&t.mutex);
15366 if (t.null_file.fd != -1) {
15367 closeFd(fresh_fd);
15368 return t.null_file.fd;
15369 } else {
15370 t.null_file.fd = fresh_fd;
15371 return fresh_fd;
15372 }
15373 },
15374 .INTR => {
15375 try syscall.checkCancel();
15376 continue;
15377 },
15378 .ACCES => return syscall.fail(error.AccessDenied),
15379 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
15380 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
15381 .NODEV => return syscall.fail(error.NoDevice),
15382 .NOENT => return syscall.fail(error.FileNotFound),
15383 .NOMEM => return syscall.fail(error.SystemResources),
15384 .PERM => return syscall.fail(error.PermissionDenied),
15385 else => |err| return syscall.unexpectedErrno(err),
15386 }
15387 }
15388}
15389
15390fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15391 const t: *Threaded = @ptrCast(@alignCast(userdata));
15392 const spawned = try spawnPosix(t, options);
15393 defer closeFd(spawned.err_fd);
15394
15395 // Wait for the child to report any errors in or before `execvpe`.
15396 if (readIntFd(spawned.err_fd)) |child_err_int| {
15397 const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int));
15398 return child_err;
15399 } else |read_err| switch (read_err) {
15400 error.EndOfStream => {
15401 // Write end closed by CLOEXEC at the time of the `execvpe` call,
15402 // indicating success.
15403 },
15404 else => {
15405 // Problem reading the error from the error reporting pipe. We
15406 // don't know if the child is alive or dead. Better to assume it is
15407 // alive so the resource does not risk being leaked.
15408 },
15409 }
15410
15411 return .{
15412 .id = spawned.pid,
15413 .thread_handle = {},
15414 .stdin = spawned.stdin,
15415 .stdout = spawned.stdout,
15416 .stderr = spawned.stderr,
15417 .request_resource_usage_statistics = options.request_resource_usage_statistics,
15418 };
15419}
15420
15421fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term {
15422 if (native_os == .wasi) unreachable;
15423 const t: *Threaded = @ptrCast(@alignCast(userdata));
15424 _ = t;
15425 switch (native_os) {
15426 .windows => return childWaitWindows(child),
15427 else => return childWaitPosix(child),
15428 }
15429}
15430
15431fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
15432 if (native_os == .wasi) unreachable;
15433 const t: *Threaded = @ptrCast(@alignCast(userdata));
15434 if (is_windows) {
15435 childKillWindows(t, child, 1) catch childCleanupWindows(child);
15436 } else {
15437 childKillPosix(child) catch {};
15438 childCleanupPosix(child);
15439 }
15440}
15441
15442fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void {
15443 _ = t; // TODO cancelation
15444 const handle = child.id.?;
15445 _ = windows.ntdll.RtlReportSilentProcessExit(handle, @fromBackingInt(@intCast(exit_code)));
15446 switch (windows.ntdll.NtTerminateProcess(handle, @fromBackingInt(@intCast(exit_code)))) {
15447 .SUCCESS, .PROCESS_IS_TERMINATING => {
15448 _ = windows.ntdll.NtWaitForSingleObject(handle, .FALSE, null);
15449 childCleanupWindows(child);
15450 },
15451 .ACCESS_DENIED => {
15452 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it
15453 // indicates that the process has already exited, but there may be
15454 // some rare edge cases where our process handle no longer has the
15455 // PROCESS_TERMINATE access right, so let's do another check to make
15456 // sure the process is really no longer running:
15457 const minimal_timeout: windows.LARGE_INTEGER = -1;
15458 return switch (windows.ntdll.NtWaitForSingleObject(handle, .FALSE, &minimal_timeout)) {
15459 windows.NTSTATUS.WAIT_0 => error.AlreadyTerminated,
15460 else => error.AccessDenied,
15461 };
15462 },
15463 else => |status| return windows.unexpectedStatus(status),
15464 }
15465}
15466
15467fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
15468 const handle = child.id.?;
15469
15470 const alertable_syscall: AlertableSyscall = try .start();
15471 while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, .TRUE, null)) {
15472 windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(),
15473 .USER_APC, .ALERTED, .TIMEOUT => {
15474 try alertable_syscall.checkCancel();
15475 continue;
15476 },
15477 else => |status| return alertable_syscall.unexpectedNtstatus(status),
15478 };
15479
15480 var info: windows.PROCESS.BASIC_INFORMATION = undefined;
15481 const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess(
15482 handle,
15483 .BasicInformation,
15484 &info,
15485 @sizeOf(windows.PROCESS.BASIC_INFORMATION),
15486 null,
15487 )) {
15488 .SUCCESS => .{ .exited = @as(u8, @truncate(@backingInt(info.ExitStatus))) },
15489 else => .{ .unknown = 0 },
15490 };
15491
15492 childCleanupWindows(child);
15493 return term;
15494}
15495
15496fn childCleanupWindows(child: *process.Child) void {
15497 const handle = child.id orelse return;
15498
15499 if (child.request_resource_usage_statistics) {
15500 var vmc: windows.PROCESS.VM_COUNTERS = undefined;
15501 switch (windows.ntdll.NtQueryInformationProcess(
15502 handle,
15503 .VmCounters,
15504 &vmc,
15505 @sizeOf(windows.PROCESS.VM_COUNTERS),
15506 null,
15507 )) {
15508 .SUCCESS => child.resource_usage_statistics.rusage = vmc,
15509 else => child.resource_usage_statistics.rusage = null,
15510 }
15511 }
15512
15513 windows.CloseHandle(handle);
15514 child.id = null;
15515
15516 windows.CloseHandle(child.thread_handle);
15517 child.thread_handle = undefined;
15518
15519 if (child.stdin) |stdin| {
15520 windows.CloseHandle(stdin.handle);
15521 child.stdin = null;
15522 }
15523 if (child.stdout) |stdout| {
15524 windows.CloseHandle(stdout.handle);
15525 child.stdout = null;
15526 }
15527 if (child.stderr) |stderr| {
15528 windows.CloseHandle(stderr.handle);
15529 child.stderr = null;
15530 }
15531}
15532
15533fn childWaitPosix(child: *process.Child) process.Child.WaitError!process.Child.Term {
15534 defer childCleanupPosix(child);
15535
15536 const pid = child.id.?;
15537
15538 var ru: posix.rusage = undefined;
15539 const ru_ptr = if (child.request_resource_usage_statistics) &ru else null;
15540
15541 if (have_wait4) {
15542 var status: if (builtin.link_libc) c_int else i32 = undefined;
15543 const syscall: Syscall = try .start();
15544 while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) {
15545 .SUCCESS => {
15546 syscall.finish();
15547 if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*;
15548 return statusToTerm(@bitCast(status));
15549 },
15550 .INTR => {
15551 try syscall.checkCancel();
15552 continue;
15553 },
15554 .CHILD => |err| return syscall.errnoBug(err), // Double-free.
15555 else => |err| return syscall.unexpectedErrno(err),
15556 };
15557 }
15558
15559 if (have_waitid) {
15560 const linux = std.os.linux; // Bypass libc which has the wrong signature.
15561 var info: linux.siginfo_t = undefined;
15562 const syscall: Syscall = try .start();
15563 while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) {
15564 .SUCCESS => {
15565 syscall.finish();
15566 if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*;
15567 const status: u32 = @bitCast(info.fields.common.second.sigchld.status);
15568 const code: linux.CLD = @fromBackingInt(@intCast(info.code));
15569 return switch (code) {
15570 .EXITED => .{ .exited = @truncate(status) },
15571 .KILLED, .DUMPED => .{ .signal = @fromBackingInt(@intCast(status)) },
15572 .TRAPPED, .STOPPED => .{ .stopped = @fromBackingInt(@intCast(status)) },
15573 _, .CONTINUED => .{ .unknown = status },
15574 };
15575 },
15576 .INTR => {
15577 try syscall.checkCancel();
15578 continue;
15579 },
15580 .CHILD => |err| return syscall.errnoBug(err), // Double-free.
15581 else => |err| return syscall.unexpectedErrno(err),
15582 };
15583 }
15584
15585 var status: if (builtin.link_libc) c_int else i32 = undefined;
15586 const syscall: Syscall = try .start();
15587 while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) {
15588 .SUCCESS => {
15589 syscall.finish();
15590 return statusToTerm(@bitCast(status));
15591 },
15592 .INTR => {
15593 try syscall.checkCancel();
15594 continue;
15595 },
15596 .CHILD => |err| return syscall.errnoBug(err), // Double-free.
15597 else => |err| return syscall.unexpectedErrno(err),
15598 };
15599}
15600
15601pub fn statusToTerm(status: u32) process.Child.Term {
15602 return if (posix.W.IFEXITED(status))
15603 .{ .exited = posix.W.EXITSTATUS(status) }
15604 else if (posix.W.IFSIGNALED(status))
15605 .{ .signal = posix.W.TERMSIG(status) }
15606 else if (posix.W.IFSTOPPED(status))
15607 .{ .stopped = posix.W.STOPSIG(status) }
15608 else
15609 .{ .unknown = status };
15610}
15611
15612fn childKillPosix(child: *process.Child) !void {
15613 // Entire function body is intentionally uncancelable.
15614
15615 const pid = child.id.?;
15616
15617 while (true) switch (posix.errno(posix.system.kill(pid, .TERM))) {
15618 .SUCCESS => break,
15619 .INTR => continue,
15620 .PERM => return error.PermissionDenied,
15621 .INVAL => |err| return errnoBug(err),
15622 .SRCH => |err| return errnoBug(err),
15623 else => |err| return posix.unexpectedErrno(err),
15624 };
15625
15626 if (have_wait4) {
15627 var status: if (builtin.link_libc) c_int else i32 = undefined;
15628 while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, null))) {
15629 .SUCCESS => return,
15630 .INTR => continue,
15631 .CHILD => |err| return errnoBug(err), // Double-free.
15632 else => |err| return posix.unexpectedErrno(err),
15633 };
15634 }
15635
15636 if (have_waitid) {
15637 const linux = std.os.linux; // Bypass libc which has the wrong signature.
15638 var info: linux.siginfo_t = undefined;
15639 while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, null))) {
15640 .SUCCESS => return,
15641 .INTR => continue,
15642 .CHILD => |err| return errnoBug(err), // Double-free.
15643 else => |err| return posix.unexpectedErrno(err),
15644 };
15645 }
15646
15647 var status: if (builtin.link_libc) c_int else i32 = undefined;
15648 while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) {
15649 .SUCCESS => return,
15650 .INTR => continue,
15651 .CHILD => |err| return errnoBug(err), // Double-free.
15652 else => |err| return posix.unexpectedErrno(err),
15653 };
15654}
15655
15656fn childCleanupPosix(child: *process.Child) void {
15657 if (child.stdin) |stdin| {
15658 closeFd(stdin.handle);
15659 child.stdin = null;
15660 }
15661 if (child.stdout) |stdout| {
15662 closeFd(stdout.handle);
15663 child.stdout = null;
15664 }
15665 if (child.stderr) |stderr| {
15666 closeFd(stderr.handle);
15667 child.stderr = null;
15668 }
15669 child.id = null;
15670}
15671
15672/// Errors that can occur between fork() and execv()
15673const ForkBailError = process.SpawnError || process.ReplaceError;
15674
15675/// Child of fork calls this to report an error to the fork parent. Then the
15676/// child exits.
15677fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn {
15678 writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {};
15679 // If we're linking libc, some naughty applications may have registered atexit handlers
15680 // which we really do not want to run in the fork child. I caught LLVM doing this and
15681 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
15682 // "Why'd you have to go and make things so complicated?"
15683 if (builtin.link_libc) {
15684 // The `_exit` function does nothing but make the exit syscall, unlike `exit`.
15685 std.c._exit(1);
15686 } else if (native_os == .linux and !builtin.single_threaded) {
15687 std.os.linux.exit_group(1);
15688 } else {
15689 posix.system.exit(1);
15690 }
15691}
15692
15693fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void {
15694 var buffer: [8]u8 = undefined;
15695 std.mem.writeInt(u64, &buffer, value, .little);
15696 // Skip the cancel mechanism.
15697 var i: usize = 0;
15698 while (true) {
15699 const rc = posix.system.write(fd, buffer[i..].ptr, buffer.len - i);
15700 switch (posix.errno(rc)) {
15701 .SUCCESS => {
15702 const n: usize = @intCast(rc);
15703 i += n;
15704 if (buffer.len - i == 0) return;
15705 },
15706 .INTR => continue,
15707 else => return error.SystemResources,
15708 }
15709 }
15710}
15711
15712fn readIntFd(fd: posix.fd_t) !ErrInt {
15713 var buffer: [8]u8 = undefined;
15714 var i: usize = 0;
15715 while (true) {
15716 const rc = posix.system.read(fd, buffer[i..].ptr, buffer.len - i);
15717 switch (posix.errno(rc)) {
15718 .SUCCESS => {
15719 const n: usize = @intCast(rc);
15720 if (n == 0) break;
15721 i += n;
15722 continue;
15723 },
15724 .INTR => continue,
15725 else => |err| return posix.unexpectedErrno(err),
15726 }
15727 }
15728 if (buffer.len - i != 0) return error.EndOfStream;
15729 return @intCast(std.mem.readInt(u64, &buffer, .little));
15730}
15731
15732const ErrInt = @Int(.unsigned, @sizeOf(anyerror) * 8);
15733
15734fn destroyPipe(pipe: [2]posix.fd_t) void {
15735 if (pipe[0] != -1) closeFd(pipe[0]);
15736 if (pipe[0] != pipe[1]) closeFd(pipe[1]);
15737}
15738
15739fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
15740 switch (stdio) {
15741 .pipe => try dup2(pipe_fd, std_fileno),
15742 .close => closeFd(std_fileno),
15743 .inherit => {},
15744 .ignore => try dup2(dev_null_fd, std_fileno),
15745 .file => |file| try dup2(file.handle, std_fileno),
15746 }
15747}
15748
15749fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15750 const t: *Threaded = @ptrCast(@alignCast(userdata));
15751
15752 const any_ignore =
15753 options.stdin == .ignore or
15754 options.stdout == .ignore or
15755 options.stderr == .ignore;
15756 const nul_handle = if (any_ignore) try getNulDevice(t) else undefined;
15757
15758 const any_inherit =
15759 options.stdin == .inherit or
15760 options.stdout == .inherit or
15761 options.stderr == .inherit;
15762 const peb = if (any_inherit) windows.peb() else undefined;
15763
15764 const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{
15765 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15766 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15767 .outbound = true,
15768 }) else undefined;
15769 errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle);
15770
15771 const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{
15772 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15773 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15774 .inbound = true,
15775 }) else undefined;
15776 errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle);
15777
15778 const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{
15779 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15780 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15781 .inbound = true,
15782 }) else undefined;
15783 errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle);
15784
15785 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
15786 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15787 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } },
15788 .inbound = true,
15789 .quota = std.Progress.max_packet_len * 2,
15790 }) else undefined;
15791 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
15792
15793 var siStartInfo: windows.STARTUPINFOW = .{
15794 .cb = @sizeOf(windows.STARTUPINFOW),
15795 .dwFlags = windows.STARTF_USESTDHANDLES,
15796 .hStdInput = switch (options.stdin) {
15797 .inherit => peb.ProcessParameters.hStdInput,
15798 .file => |file| try OpenFile(&.{}, .{
15799 .access_mask = .{
15800 .STANDARD = .{ .SYNCHRONIZE = true },
15801 .GENERIC = .{ .READ = true },
15802 },
15803 .dir = file.handle,
15804 .sa = &.{
15805 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
15806 .lpSecurityDescriptor = null,
15807 .bInheritHandle = .TRUE,
15808 },
15809 .creation = .OPEN,
15810 }),
15811 .ignore => nul_handle,
15812 .pipe => stdin_pipe[1],
15813 .close => null,
15814 },
15815 .hStdOutput = switch (options.stdout) {
15816 .inherit => peb.ProcessParameters.hStdOutput,
15817 .file => |file| try OpenFile(&.{}, .{
15818 .access_mask = .{
15819 .STANDARD = .{ .SYNCHRONIZE = true },
15820 .GENERIC = .{ .WRITE = true },
15821 },
15822 .dir = file.handle,
15823 .sa = &.{
15824 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
15825 .lpSecurityDescriptor = null,
15826 .bInheritHandle = .TRUE,
15827 },
15828 .creation = .OPEN,
15829 }),
15830 .ignore => nul_handle,
15831 .pipe => stdout_pipe[1],
15832 .close => null,
15833 },
15834 .hStdError = switch (options.stderr) {
15835 .inherit => peb.ProcessParameters.hStdError,
15836 .file => |file| try OpenFile(&.{}, .{
15837 .access_mask = .{
15838 .STANDARD = .{ .SYNCHRONIZE = true },
15839 .GENERIC = .{ .WRITE = true },
15840 },
15841 .dir = file.handle,
15842 .sa = &.{
15843 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
15844 .lpSecurityDescriptor = null,
15845 .bInheritHandle = .TRUE,
15846 },
15847 .creation = .OPEN,
15848 }),
15849 .ignore => nul_handle,
15850 .pipe => stderr_pipe[1],
15851 .close => null,
15852 },
15853
15854 .lpReserved = null,
15855 .lpDesktop = null,
15856 .lpTitle = null,
15857 .dwX = 0,
15858 .dwY = 0,
15859 .dwXSize = 0,
15860 .dwYSize = 0,
15861 .dwXCountChars = 0,
15862 .dwYCountChars = 0,
15863 .dwFillAttribute = 0,
15864 .wShowWindow = 0,
15865 .cbReserved2 = 0,
15866 .lpReserved2 = null,
15867 };
15868 var piProcInfo: windows.PROCESS.INFORMATION = undefined;
15869
15870 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15871 defer arena_allocator.deinit();
15872 const arena = arena_allocator.allocator();
15873
15874 const cwd_w = cwd_w: {
15875 switch (options.cwd) {
15876 .inherit => break :cwd_w null,
15877 .dir => |cwd_dir| {
15878 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);
15879 const dir_path = try GetFinalPathNameByHandle(
15880 cwd_dir.handle,
15881 .{},
15882 dir_path_buffer[0..windows.PATH_MAX_WIDE],
15883 );
15884 dir_path_buffer[dir_path.len] = 0;
15885 // Shrink the allocation down to just the path buffer + sentinel
15886 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);
15887 break :cwd_w dir_path_buffer[0..dir_path.len :0];
15888 },
15889 .path => |cwd| {
15890 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15891 },
15892 }
15893 };
15894 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
15895
15896 const env_block = env_block: {
15897 const prog_handle = if (options.progress_node.index != .none)
15898 prog_pipe[1]
15899 else
15900 windows.INVALID_HANDLE_VALUE;
15901 if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{
15902 .zig_progress_handle = prog_handle,
15903 });
15904 break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{
15905 .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE,
15906 });
15907 };
15908
15909 const app_name_wtf8 = options.argv[0];
15910 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
15911
15912 // The cwd provided by options is in effect when choosing the executable
15913 // path to match POSIX semantics.
15914 const cwd_path_w = x: {
15915 // If the app name is absolute, then we need to use its dirname as the cwd
15916 if (app_name_is_absolute) {
15917 const dir = Dir.path.dirname(app_name_wtf8).?;
15918 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir);
15919 } else if (cwd_w) |cwd| {
15920 break :x cwd;
15921 } else {
15922 break :x &[_:0]u16{}; // empty for cwd
15923 }
15924 };
15925
15926 // If the app name has more than just a filename, then we need to separate
15927 // that into the basename and dirname and use the dirname as an addition to
15928 // the cwd path. This is because NtQueryDirectoryFile cannot accept
15929 // FileName params with path separators.
15930 const app_basename_wtf8 = Dir.path.basename(app_name_wtf8);
15931 // If the app name is absolute, then the cwd will already have the app's dirname in it,
15932 // so only populate app_dirname if app name is a relative path with > 0 path separators.
15933 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null;
15934 const app_dirname_w: ?[:0]u16 = x: {
15935 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
15936 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8);
15937 }
15938 break :x null;
15939 };
15940 const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8);
15941
15942 const flags: windows.CreateProcessFlags = .{
15943 .create_suspended = options.start_suspended,
15944 .create_unicode_environment = true,
15945 .create_no_window = options.create_no_window,
15946 };
15947
15948 run: {
15949 // We have to scan each time because the PEB environment pointer is not stable.
15950 const env_strings: WindowsEnvironStrings = .scan();
15951 const PATH = env_strings.PATH orelse &[_:0]u16{};
15952 const PATHEXT = env_strings.PATHEXT orelse &[_:0]u16{};
15953
15954 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules
15955 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously
15956 // constructed arguments.
15957 //
15958 // We'll need to wait until we're actually trying to run the command to know for sure
15959 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually
15960 // serializing the command line until we determine how it should be serialized.
15961 var cmd_line_cache = WindowsCommandLineCache.init(arena, options.argv);
15962
15963 var app_buf: std.ArrayList(u16) = .empty;
15964 try app_buf.appendSlice(arena, app_name_w);
15965
15966 var dir_buf: std.ArrayList(u16) = .empty;
15967
15968 if (cwd_path_w.len > 0) {
15969 try dir_buf.appendSlice(arena, cwd_path_w);
15970 }
15971 if (app_dirname_w) |app_dir| {
15972 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep);
15973 try dir_buf.appendSlice(arena, app_dir);
15974 }
15975
15976 windowsCreateProcessPathExt(
15977 arena,
15978 &dir_buf,
15979 &app_buf,
15980 PATHEXT,
15981 &cmd_line_cache,
15982 env_block,
15983 cwd_w_ptr,
15984 flags,
15985 &siStartInfo,
15986 &piProcInfo,
15987 ) catch |no_path_err| {
15988 const original_err = switch (no_path_err) {
15989 // argv[0] contains unsupported characters that will never resolve to a valid exe.
15990 error.InvalidArg0 => return error.FileNotFound,
15991 error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e,
15992 error.UnrecoverableInvalidExe => return error.InvalidExe,
15993 else => |e| return e,
15994 };
15995
15996 // If the app name had path separators, that disallows PATH searching,
15997 // and there's no need to search the PATH if the app name is absolute.
15998 // We still search the path if the cwd is absolute because of the
15999 // "cwd provided by options is in effect when choosing the executable path
16000 // to match posix semantics" behavior--we don't want to skip searching
16001 // the PATH just because we were trying to set the cwd of the child process.
16002 if (app_dirname_w != null or app_name_is_absolute) {
16003 return original_err;
16004 }
16005
16006 var it = std.mem.tokenizeScalar(u16, PATH, ';');
16007 while (it.next()) |search_path| {
16008 dir_buf.clearRetainingCapacity();
16009 try dir_buf.appendSlice(arena, search_path);
16010
16011 if (windowsCreateProcessPathExt(
16012 arena,
16013 &dir_buf,
16014 &app_buf,
16015 PATHEXT,
16016 &cmd_line_cache,
16017 env_block,
16018 cwd_w_ptr,
16019 flags,
16020 &siStartInfo,
16021 &piProcInfo,
16022 )) {
16023 break :run;
16024 } else |err| switch (err) {
16025 // argv[0] contains unsupported characters that will never resolve to a valid exe.
16026 error.InvalidArg0 => return error.FileNotFound,
16027 error.FileNotFound, error.AccessDenied, error.InvalidExe => continue,
16028 error.UnrecoverableInvalidExe => return error.InvalidExe,
16029 else => |e| return e,
16030 }
16031 } else {
16032 return original_err;
16033 }
16034 };
16035 }
16036
16037 if (options.progress_node.index != .none) {
16038 windows.CloseHandle(prog_pipe[1]);
16039 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
16040 }
16041
16042 return .{
16043 .id = piProcInfo.hProcess,
16044 .thread_handle = piProcInfo.hThread,
16045 .stdin = stdin: switch (options.stdin) {
16046 .file => {
16047 windows.CloseHandle(siStartInfo.hStdInput.?);
16048 break :stdin null;
16049 },
16050 .pipe => {
16051 windows.CloseHandle(stdin_pipe[1]);
16052 break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } };
16053 },
16054 else => null,
16055 },
16056 .stdout = stdout: switch (options.stdout) {
16057 .file => {
16058 windows.CloseHandle(siStartInfo.hStdOutput.?);
16059 break :stdout null;
16060 },
16061 .pipe => {
16062 windows.CloseHandle(stdout_pipe[1]);
16063 break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } };
16064 },
16065 else => null,
16066 },
16067 .stderr = stderr: switch (options.stderr) {
16068 .file => {
16069 windows.CloseHandle(siStartInfo.hStdError.?);
16070 break :stderr null;
16071 },
16072 .pipe => {
16073 windows.CloseHandle(stderr_pipe[1]);
16074 break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } };
16075 },
16076 else => null,
16077 },
16078 .request_resource_usage_statistics = options.request_resource_usage_statistics,
16079 };
16080}
16081
16082fn inheritFile() windows.HANDLE {}
16083
16084fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
16085 {
16086 mutexLock(&t.mutex);
16087 defer mutexUnlock(&t.mutex);
16088 if (t.random_file.handle) |handle| return handle;
16089 }
16090
16091 var fresh_handle: windows.HANDLE = undefined;
16092 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16093 var syscall: Syscall = try .start();
16094 while (true) switch (windows.ntdll.NtOpenFile(
16095 &fresh_handle,
16096 .{
16097 .STANDARD = .{ .SYNCHRONIZE = true },
16098 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
16099 },
16100 &.{ .ObjectName = @constCast(&windows.UNICODE_STRING.init(
16101 &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' },
16102 )) },
16103 &io_status_block,
16104 .VALID_FLAGS,
16105 .{ .IO = .SYNCHRONOUS_NONALERT },
16106 )) {
16107 .SUCCESS => {
16108 syscall.finish();
16109 mutexLock(&t.mutex); // Another thread might have won the race.
16110 defer mutexUnlock(&t.mutex);
16111 if (t.random_file.handle) |prev_handle| {
16112 windows.CloseHandle(fresh_handle);
16113 return prev_handle;
16114 } else {
16115 t.random_file.handle = fresh_handle;
16116 return fresh_handle;
16117 }
16118 },
16119 .CANCELLED => {
16120 try syscall.checkCancel();
16121 continue;
16122 },
16123 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.EntropyUnavailable), // Observed on wine 10.0
16124 else => return syscall.fail(error.EntropyUnavailable),
16125 };
16126}
16127
16128fn getNulDevice(t: *Threaded) !windows.HANDLE {
16129 {
16130 mutexLock(&t.mutex);
16131 defer mutexUnlock(&t.mutex);
16132 if (t.null_file.handle) |handle| return handle;
16133 }
16134
16135 var fresh_handle: windows.HANDLE = undefined;
16136 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16137 var syscall: Syscall = try .start();
16138 while (true) switch (windows.ntdll.NtOpenFile(
16139 &fresh_handle,
16140 .{
16141 .STANDARD = .{ .SYNCHRONIZE = true },
16142 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } },
16143 },
16144 &.{
16145 .Attributes = .{ .INHERIT = true },
16146 .ObjectName = @constCast(&windows.UNICODE_STRING.init(
16147 &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' },
16148 )),
16149 },
16150 &io_status_block,
16151 .VALID_FLAGS,
16152 .{ .IO = .SYNCHRONOUS_NONALERT },
16153 )) {
16154 .SUCCESS => {
16155 syscall.finish();
16156 mutexLock(&t.mutex); // Another thread might have won the race.
16157 defer mutexUnlock(&t.mutex);
16158 if (t.null_file.handle) |prev_handle| {
16159 windows.CloseHandle(fresh_handle);
16160 return prev_handle;
16161 } else {
16162 t.null_file.handle = fresh_handle;
16163 return fresh_handle;
16164 }
16165 },
16166 .CANCELLED => {
16167 try syscall.checkCancel();
16168 continue;
16169 },
16170 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16171 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
16172 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
16173 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
16174 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
16175 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
16176 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
16177 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
16178 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
16179 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
16180 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
16181 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
16182 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
16183 else => |status| return syscall.unexpectedNtstatus(status),
16184 };
16185}
16186
16187fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE {
16188 {
16189 mutexLock(&t.mutex);
16190 defer mutexUnlock(&t.mutex);
16191 if (t.pipe_file.handle) |handle| return handle;
16192 }
16193
16194 var fresh_handle: windows.HANDLE = undefined;
16195 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
16196 var syscall: Syscall = try .start();
16197 while (true) switch (windows.ntdll.NtOpenFile(
16198 &fresh_handle,
16199 .{ .STANDARD = .{ .SYNCHRONIZE = true } },
16200 &.{
16201 .ObjectName = @constCast(&windows.UNICODE_STRING.init(
16202 &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' },
16203 )),
16204 },
16205 &io_status_block,
16206 .VALID_FLAGS,
16207 .{ .IO = .SYNCHRONOUS_NONALERT },
16208 )) {
16209 .SUCCESS => {
16210 syscall.finish();
16211 mutexLock(&t.mutex); // Another thread might have won the race.
16212 defer mutexUnlock(&t.mutex);
16213 if (t.pipe_file.handle) |prev_handle| {
16214 windows.CloseHandle(fresh_handle);
16215 return prev_handle;
16216 } else {
16217 t.pipe_file.handle = fresh_handle;
16218 return fresh_handle;
16219 }
16220 },
16221 .DELETE_PENDING => {
16222 // This error means that there *was* a file in this location on
16223 // the file system, but it was deleted. However, the OS is not
16224 // finished with the deletion operation, and so this CreateFile
16225 // call has failed. There is not really a sane way to handle
16226 // this other than retrying the creation after the OS finishes
16227 // the deletion.
16228 syscall.finish();
16229 try parking_sleep.sleep(.{ .duration = .{
16230 .raw = .fromMilliseconds(1),
16231 .clock = .awake,
16232 } });
16233 syscall = try .start();
16234 continue;
16235 },
16236 .CANCELLED => {
16237 try syscall.checkCancel();
16238 continue;
16239 },
16240 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
16241 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
16242 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
16243 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
16244 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
16245 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
16246 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
16247 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
16248 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
16249 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
16250 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
16251 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
16252 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
16253 else => |status| return syscall.unexpectedNtstatus(status),
16254 };
16255}
16256
16257/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.
16258/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path.
16259/// Note: `app_buf` should not contain any leading path separators.
16260/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
16261fn windowsCreateProcessPathExt(
16262 arena: Allocator,
16263 dir_buf: *std.ArrayList(u16),
16264 app_buf: *std.ArrayList(u16),
16265 pathext: [:0]const u16,
16266 cmd_line_cache: *WindowsCommandLineCache,
16267 env_block: ?process.Environ.WindowsBlock,
16268 cwd_ptr: ?[*:0]u16,
16269 flags: windows.CreateProcessFlags,
16270 lpStartupInfo: *windows.STARTUPINFOW,
16271 lpProcessInformation: *windows.PROCESS.INFORMATION,
16272) !void {
16273 const app_name_len = app_buf.items.len;
16274 const dir_path_len = dir_buf.items.len;
16275
16276 if (app_name_len == 0) return error.FileNotFound;
16277
16278 defer app_buf.shrinkRetainingCapacity(app_name_len);
16279 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
16280
16281 // The name of the game here is to avoid CreateProcessW calls at all costs,
16282 // and only ever try calling it when we have a real candidate for execution.
16283 // Secondarily, we want to minimize the number of syscalls used when checking
16284 // for each PATHEXT-appended version of the app name.
16285 //
16286 // An overview of the technique used:
16287 // - Open the search directory for iteration (either cwd or a path from PATH)
16288 // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to
16289 // check if anything that could possibly match either the unappended version
16290 // of the app name or any of the versions with a PATHEXT value appended exists.
16291 // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early
16292 // without needing to use PATHEXT at all.
16293 //
16294 // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence
16295 // for any directory that doesn't contain any possible matches, instead of having
16296 // to use a separate look up for each individual filename combination (unappended +
16297 // each PATHEXT appended). For directories where the wildcard *does* match something,
16298 // we iterate the matches and take note of any that are either the unappended version,
16299 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW
16300 // with the found versions in the appropriate order.
16301 const dir = dir: {
16302 // needs to be null-terminated
16303 try dir_buf.append(arena, 0);
16304 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
16305 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
16306 const prefixed_path = try wToPrefixedFileW(null, dir_path_z, .{});
16307 break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
16308 .iterate = true,
16309 }) catch |err| switch (err) {
16310 // These errors must not be ignored because they should not be able
16311 // to affect which file is chosen to execute. Also `error.Canceled`
16312 // must never be swallowed.
16313 error.Canceled,
16314 error.SystemResources,
16315 error.Unexpected,
16316 error.ProcessFdQuotaExceeded,
16317 error.SystemFdQuotaExceeded,
16318 => |e| return e,
16319
16320 error.AccessDenied,
16321 error.PermissionDenied,
16322 error.SymLinkLoop,
16323 error.FileNotFound,
16324 error.NotDir,
16325 error.NoDevice,
16326 error.NetworkNotFound,
16327 error.NameTooLong,
16328 error.BadPathName,
16329 => return error.FileNotFound,
16330 };
16331 };
16332 defer windows.CloseHandle(dir.handle);
16333
16334 // Add wildcard and null-terminator
16335 try app_buf.append(arena, '*');
16336 try app_buf.append(arena, 0);
16337 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];
16338
16339 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries
16340 // returned per NtQueryDirectoryFile call.
16341 var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined;
16342 const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2);
16343 if (file_information_buf.len < file_info_maximum_single_entry_size) {
16344 @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry");
16345 }
16346 var io_status: windows.IO_STATUS_BLOCK = undefined;
16347
16348 const num_supported_pathext = @typeInfo(process.WindowsExtension).@"enum".field_names.len;
16349 var pathext_seen: [num_supported_pathext]bool = @splat(false);
16350 var any_pathext_seen = false;
16351 var unappended_exists = false;
16352
16353 // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions
16354 // of the app_name we should try to spawn.
16355 // Note: This is necessary because the order of the files returned is filesystem-dependent:
16356 // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists.
16357 // On FAT32, it's possible for something like `blah.exe.obj` to be returned first.
16358 while (true) {
16359 // If we get nothing with the wildcard, then we can just bail out
16360 // as we know appending PATHEXT will not yield anything.
16361 switch (windows.ntdll.NtQueryDirectoryFile(
16362 dir.handle,
16363 null,
16364 null,
16365 null,
16366 &io_status,
16367 &file_information_buf,
16368 file_information_buf.len,
16369 .Directory,
16370 .FALSE, // single result
16371 &.init(app_name_wildcard),
16372 .FALSE, // restart iteration
16373 )) {
16374 .SUCCESS => {},
16375 .NO_SUCH_FILE => return error.FileNotFound,
16376 .NO_MORE_FILES => break,
16377 .ACCESS_DENIED => return error.AccessDenied,
16378 else => |status| return windows.unexpectedStatus(status),
16379 }
16380
16381 // According to the docs, this can only happen if there is not enough room in the
16382 // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry.
16383 // Therefore, this condition should not be possible to hit with the buffer size we use.
16384 std.debug.assert(io_status.Information != 0);
16385
16386 var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf };
16387 while (it.next()) |info| {
16388 // Skip directories
16389 if (info.FileAttributes.DIRECTORY) continue;
16390 const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2];
16391 // Because all results start with the app_name since we're using the wildcard `app_name*`,
16392 // if the length is equal to app_name then this is an exact match
16393 if (filename.len == app_name_len) {
16394 // Note: We can't break early here because it's possible that the unappended version
16395 // fails to spawn, in which case we still want to try the PATHEXT appended versions.
16396 unappended_exists = true;
16397 } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| {
16398 pathext_seen[@backingInt(pathext_ext)] = true;
16399 any_pathext_seen = true;
16400 }
16401 }
16402 }
16403
16404 const unappended_err = unappended: {
16405 if (unappended_exists) {
16406 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
16407 '/', '\\' => {},
16408 else => try dir_buf.append(arena, Dir.path.sep),
16409 };
16410 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]);
16411 try dir_buf.append(arena, 0);
16412 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
16413
16414 const is_bat_or_cmd = bat_or_cmd: {
16415 const app_name = app_buf.items[0..app_name_len];
16416 const ext_start = std.mem.findScalarLast(u16, app_name, '.') orelse break :bat_or_cmd false;
16417 const ext = app_name[ext_start..];
16418 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false;
16419 switch (ext_enum) {
16420 .cmd, .bat => break :bat_or_cmd true,
16421 else => break :bat_or_cmd false,
16422 }
16423 };
16424 const cmd_line_w = if (is_bat_or_cmd)
16425 try cmd_line_cache.scriptCommandLine(full_app_name)
16426 else
16427 try cmd_line_cache.commandLine();
16428 const app_name_w = if (is_bat_or_cmd)
16429 try cmd_line_cache.cmdExePath()
16430 else
16431 full_app_name;
16432
16433 if (windowsCreateProcess(
16434 app_name_w.ptr,
16435 cmd_line_w.ptr,
16436 env_block,
16437 cwd_ptr,
16438 flags,
16439 lpStartupInfo,
16440 lpProcessInformation,
16441 )) |_| {
16442 return;
16443 } else |err| switch (err) {
16444 error.FileNotFound,
16445 error.AccessDenied,
16446 => break :unappended err,
16447 error.InvalidExe => {
16448 // On InvalidExe, if the extension of the app name is .exe then
16449 // it's treated as an unrecoverable error. Otherwise, it'll be
16450 // skipped as normal.
16451 const app_name = app_buf.items[0..app_name_len];
16452 const ext_start = std.mem.findScalarLast(u16, app_name, '.') orelse break :unappended err;
16453 const ext = app_name[ext_start..];
16454 if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
16455 return error.UnrecoverableInvalidExe;
16456 }
16457 break :unappended err;
16458 },
16459 else => return err,
16460 }
16461 }
16462 break :unappended error.FileNotFound;
16463 };
16464
16465 if (!any_pathext_seen) return unappended_err;
16466
16467 // Now try any PATHEXT appended versions that we've seen
16468 var ext_it = std.mem.tokenizeScalar(u16, pathext, ';');
16469 while (ext_it.next()) |ext| {
16470 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue;
16471 if (!pathext_seen[@backingInt(ext_enum)]) continue;
16472
16473 dir_buf.shrinkRetainingCapacity(dir_path_len);
16474 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
16475 '/', '\\' => {},
16476 else => try dir_buf.append(arena, Dir.path.sep),
16477 };
16478 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]);
16479 try dir_buf.appendSlice(arena, ext);
16480 try dir_buf.append(arena, 0);
16481 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
16482
16483 const is_bat_or_cmd = switch (ext_enum) {
16484 .cmd, .bat => true,
16485 else => false,
16486 };
16487 const cmd_line_w = if (is_bat_or_cmd)
16488 try cmd_line_cache.scriptCommandLine(full_app_name)
16489 else
16490 try cmd_line_cache.commandLine();
16491 const app_name_w = if (is_bat_or_cmd)
16492 try cmd_line_cache.cmdExePath()
16493 else
16494 full_app_name;
16495
16496 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
16497 return;
16498 } else |err| switch (err) {
16499 error.FileNotFound => continue,
16500 error.AccessDenied => continue,
16501 error.InvalidExe => {
16502 // On InvalidExe, if the extension of the app name is .exe then
16503 // it's treated as an unrecoverable error. Otherwise, it'll be
16504 // skipped as normal.
16505 if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
16506 return error.UnrecoverableInvalidExe;
16507 }
16508 continue;
16509 },
16510 else => return err,
16511 }
16512 }
16513
16514 return unappended_err;
16515}
16516
16517fn windowsCreateProcess(
16518 app_name: [*:0]u16,
16519 cmd_line: [*:0]u16,
16520 env_block: ?process.Environ.WindowsBlock,
16521 cwd_ptr: ?[*:0]u16,
16522 flags: windows.CreateProcessFlags,
16523 lpStartupInfo: *windows.STARTUPINFOW,
16524 lpProcessInformation: *windows.PROCESS.INFORMATION,
16525) !void {
16526 const syscall: Syscall = try .start();
16527 while (true) {
16528 if (windows.kernel32.CreateProcessW(
16529 app_name,
16530 cmd_line,
16531 null,
16532 null,
16533 .TRUE,
16534 flags,
16535 if (env_block) |block| block.slice.ptr else null,
16536 cwd_ptr,
16537 lpStartupInfo,
16538 lpProcessInformation,
16539 ).toBool()) {
16540 return syscall.finish();
16541 } else switch (windows.GetLastError()) {
16542 .INVALID_PARAMETER => unreachable,
16543 .OPERATION_ABORTED => {
16544 try syscall.checkCancel();
16545 continue;
16546 },
16547 .FILE_NOT_FOUND => return syscall.fail(error.FileNotFound),
16548 .PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
16549 .DIRECTORY => return syscall.fail(error.FileNotFound),
16550 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
16551 .INVALID_NAME => return syscall.fail(error.InvalidName),
16552 .FILENAME_EXCED_RANGE => return syscall.fail(error.NameTooLong),
16553 .SHARING_VIOLATION => return syscall.fail(error.FileBusy),
16554 .COMMITMENT_LIMIT => return syscall.fail(error.SystemResources),
16555
16556 // These are all the system errors that are mapped to ENOEXEC by
16557 // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error
16558 // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK)
16559 // or urt/misc/errno.cpp (newer SDK) in the Windows SDK.
16560 .BAD_FORMAT,
16561 .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp
16562 .INVALID_STACKSEG,
16563 .INVALID_MODULETYPE,
16564 .INVALID_EXE_SIGNATURE,
16565 .EXE_MARKED_INVALID,
16566 .BAD_EXE_FORMAT,
16567 .ITERATED_DATA_EXCEEDS_64k,
16568 .INVALID_MINALLOCSIZE,
16569 .DYNLINK_FROM_INVALID_RING,
16570 .IOPL_NOT_ENABLED,
16571 .INVALID_SEGDPL,
16572 .AUTODATASEG_EXCEEDS_64k,
16573 .RING2SEG_MUST_BE_MOVABLE,
16574 .RELOC_CHAIN_XEEDS_SEGLIM,
16575 .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp
16576 // This one is not mapped to ENOEXEC but it is possible, for example
16577 // when calling CreateProcessW on a plain text file with a .exe extension
16578 .EXE_MACHINE_TYPE_MISMATCH,
16579 => return syscall.fail(error.InvalidExe),
16580
16581 else => |err| {
16582 syscall.finish();
16583 return windows.unexpectedError(err);
16584 },
16585 }
16586 }
16587}
16588
16589/// Case-insensitive WTF-16 lookup
16590fn windowsCreateProcessSupportsExtension(ext: []const u16) ?process.WindowsExtension {
16591 comptime {
16592 // Ensures keeping this function in sync with the enum.
16593 const field_names = @typeInfo(process.WindowsExtension).@"enum".field_names;
16594 assert(field_names.len == 4);
16595 assert(@backingInt(process.WindowsExtension.bat) == 0);
16596 assert(@backingInt(process.WindowsExtension.cmd) == 1);
16597 assert(@backingInt(process.WindowsExtension.com) == 2);
16598 assert(@backingInt(process.WindowsExtension.exe) == 3);
16599 }
16600
16601 if (ext.len != 4) return null;
16602 const State = enum {
16603 start,
16604 dot,
16605 b,
16606 ba,
16607 c,
16608 cm,
16609 co,
16610 e,
16611 ex,
16612 };
16613 var state: State = .start;
16614 for (ext) |c| switch (state) {
16615 .start => switch (c) {
16616 '.' => state = .dot,
16617 else => return null,
16618 },
16619 .dot => switch (c) {
16620 'b', 'B' => state = .b,
16621 'c', 'C' => state = .c,
16622 'e', 'E' => state = .e,
16623 else => return null,
16624 },
16625 .b => switch (c) {
16626 'a', 'A' => state = .ba,
16627 else => return null,
16628 },
16629 .c => switch (c) {
16630 'm', 'M' => state = .cm,
16631 'o', 'O' => state = .co,
16632 else => return null,
16633 },
16634 .e => switch (c) {
16635 'x', 'X' => state = .ex,
16636 else => return null,
16637 },
16638 .ba => switch (c) {
16639 't', 'T' => return .bat,
16640 else => return null,
16641 },
16642 .cm => switch (c) {
16643 'd', 'D' => return .cmd,
16644 else => return null,
16645 },
16646 .co => switch (c) {
16647 'm', 'M' => return .com,
16648 else => return null,
16649 },
16650 .ex => switch (c) {
16651 'e', 'E' => return .exe,
16652 else => return null,
16653 },
16654 };
16655 return null;
16656}
16657
16658test windowsCreateProcessSupportsExtension {
16659 try std.testing.expectEqual(process.WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?);
16660 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
16661}
16662
16663/// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW.
16664///
16665/// Serialization is done on-demand and the result is cached in order to allow for:
16666/// - Only serializing the particular type of command line needed (`.bat`/`.cmd`
16667/// command line serialization is different from `.exe`/etc)
16668/// - Reusing the serialized command lines if necessary (i.e. if the execution
16669/// of a command fails and the PATH is going to be continued to be searched
16670/// for more candidates)
16671const WindowsCommandLineCache = struct {
16672 cmd_line: ?[:0]u16 = null,
16673 script_cmd_line: ?[:0]u16 = null,
16674 cmd_exe_path: ?[:0]u16 = null,
16675 argv: []const []const u8,
16676 allocator: Allocator,
16677
16678 fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache {
16679 return .{
16680 .allocator = allocator,
16681 .argv = argv,
16682 };
16683 }
16684
16685 fn deinit(self: *WindowsCommandLineCache) void {
16686 if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line);
16687 if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line);
16688 if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path);
16689 }
16690
16691 fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 {
16692 if (self.cmd_line == null) {
16693 self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv);
16694 }
16695 return self.cmd_line.?;
16696 }
16697
16698 /// Not cached, since the path to the batch script will change during PATH searching.
16699 /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched,
16700 /// then script_path should include both the search path and the script filename
16701 /// (this allows avoiding cmd.exe having to search the PATH again).
16702 fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 {
16703 if (self.script_cmd_line) |v| self.allocator.free(v);
16704 self.script_cmd_line = try argvToScriptCommandLineWindows(
16705 self.allocator,
16706 script_path,
16707 self.argv[1..],
16708 );
16709 return self.script_cmd_line.?;
16710 }
16711
16712 fn cmdExePath(self: *WindowsCommandLineCache) Allocator.Error![:0]u16 {
16713 if (self.cmd_exe_path == null) {
16714 // Remove trailing slash from system directory path; we'll re-add it below
16715 const system_dir = std.mem.trimEnd(u16, windows.getSystemDirectoryWtf16Le(), &.{ '/', '\\' });
16716 const suffix = std.unicode.utf8ToUtf16LeStringLiteral("\\cmd.exe");
16717 const buf = try self.allocator.allocSentinel(u16, system_dir.len + suffix.len, 0);
16718 errdefer comptime unreachable;
16719 @memcpy(buf[0..system_dir.len], system_dir);
16720 @memcpy(buf[system_dir.len..], suffix);
16721 self.cmd_exe_path = buf;
16722 }
16723 return self.cmd_exe_path.?;
16724 }
16725};
16726
16727const ArgvToScriptCommandLineError = error{
16728 OutOfMemory,
16729 InvalidWtf8,
16730 /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
16731 /// within arguments when executing a `.bat`/`.cmd` script.
16732 /// - NUL/LF signifiies end of arguments, so anything afterwards
16733 /// would be lost after execution.
16734 /// - CR is stripped by `cmd.exe`, so any CR codepoints
16735 /// would be lost after execution.
16736 InvalidBatchScriptArg,
16737};
16738
16739/// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific
16740/// escaping rules. The caller owns the returned slice.
16741///
16742/// Escapes `argv` using the suggested mitigation against arbitrary command execution from:
16743/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
16744///
16745/// The return of this function will look like
16746/// `cmd.exe /d /e:ON /v:OFF /c "<escaped command line>"`
16747/// and should be used as the `lpCommandLine` of `CreateProcessW`, while the return of
16748/// `WindowsCommandLineCache.cmdExePath` should be used as `lpApplicationName`.
16749///
16750/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise.
16751/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem.
16752fn argvToScriptCommandLineWindows(
16753 allocator: Allocator,
16754 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.
16755 /// The script must have been verified to exist at this path before calling this function.
16756 script_path: []const u16,
16757 /// Arguments, not including the script name itself. Expected to be encoded as WTF-8.
16758 script_args: []const []const u8,
16759) ArgvToScriptCommandLineError![:0]u16 {
16760 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 64);
16761 defer buf.deinit();
16762
16763 // `/d` disables execution of AutoRun commands.
16764 // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation:
16765 // > If delayed expansion is enabled via the registry value DelayedExpansion,
16766 // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option.
16767 // > Escaping for % requires the command extension to be enabled.
16768 // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option.
16769 // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
16770 buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \"");
16771
16772 // Always quote the path to the script arg
16773 buf.appendAssumeCapacity('"');
16774 // We always want the path to the batch script to include a path separator in order to
16775 // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary
16776 // command execution mitigation, we just know exactly what script we want to execute
16777 // at this point, and potentially making cmd.exe re-find it is unnecessary.
16778 //
16779 // If the script path does not have a path separator, then we know its relative to CWD and
16780 // we can just put `.\` in the front.
16781 if (std.mem.findAny(u16, script_path, &[_]u16{
16782 std.mem.nativeToLittle(u16, '\\'), std.mem.nativeToLittle(u16, '/'),
16783 }) == null) {
16784 try buf.appendSlice(".\\");
16785 }
16786 // Note that we don't do any escaping/mitigations for this argument, since the relevant
16787 // characters (", %, etc) are illegal in file paths and this function should only be called
16788 // with script paths that have been verified to exist.
16789 try std.unicode.wtf16LeToWtf8ArrayList(&buf, script_path);
16790 buf.appendAssumeCapacity('"');
16791
16792 for (script_args) |arg| {
16793 // Literal carriage returns get stripped when run through cmd.exe
16794 // and NUL/newlines act as 'end of command.' Because of this, it's basically
16795 // always a mistake to include these characters in argv, so it's
16796 // an error condition in order to ensure that the return of this
16797 // function can always roundtrip through cmd.exe.
16798 if (std.mem.findAny(u8, arg, "\x00\r\n") != null) {
16799 return error.InvalidBatchScriptArg;
16800 }
16801
16802 // Separate args with a space.
16803 try buf.append(' ');
16804
16805 // Need to quote if the argument is empty (otherwise the arg would just be lost)
16806 // or if the last character is a `\`, since then something like "%~2" in a .bat
16807 // script would cause the closing " to be escaped which we don't want.
16808 var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\';
16809 if (!needs_quotes) {
16810 for (arg) |c| {
16811 switch (c) {
16812 // Known good characters that don't need to be quoted
16813 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {},
16814 // When in doubt, quote
16815 else => {
16816 needs_quotes = true;
16817 break;
16818 },
16819 }
16820 }
16821 }
16822 if (needs_quotes) {
16823 try buf.append('"');
16824 }
16825 var backslashes: usize = 0;
16826 for (arg) |c| {
16827 switch (c) {
16828 '\\' => {
16829 backslashes += 1;
16830 },
16831 '"' => {
16832 try buf.appendNTimes('\\', backslashes);
16833 try buf.append('"');
16834 backslashes = 0;
16835 },
16836 // Replace `%` with `%%cd:~,%`.
16837 //
16838 // cmd.exe allows extracting a substring from an environment
16839 // variable with the syntax: `%foo:~<start_index>,<end_index>%`.
16840 // Therefore, `%cd:~,%` will always expand to an empty string
16841 // since both the start and end index are blank, and it is assumed
16842 // that `%cd%` is always available since it is a built-in variable
16843 // that corresponds to the current directory.
16844 //
16845 // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%`
16846 // will stop `%foo%` from being expanded and *after* expansion
16847 // we'll still be left with `%foo%` (the literal string).
16848 '%' => {
16849 // the trailing `%` is appended outside the switch
16850 try buf.appendSlice("%%cd:~,");
16851 backslashes = 0;
16852 },
16853 else => {
16854 backslashes = 0;
16855 },
16856 }
16857 try buf.append(c);
16858 }
16859 if (needs_quotes) {
16860 try buf.appendNTimes('\\', backslashes);
16861 try buf.append('"');
16862 }
16863 }
16864
16865 try buf.append('"');
16866
16867 return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
16868}
16869
16870const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
16871
16872/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
16873/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
16874///
16875/// To avoid arbitrary command execution, this function should not be used when spawning `.bat`/`.cmd` scripts.
16876/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
16877///
16878/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead.
16879fn argvToCommandLineWindows(
16880 allocator: Allocator,
16881 argv: []const []const u8,
16882) ArgvToCommandLineError![:0]u16 {
16883 var buf = std.array_list.Managed(u8).init(allocator);
16884 defer buf.deinit();
16885
16886 if (argv.len != 0) {
16887 const arg0 = argv[0];
16888
16889 // The first argument must be quoted if it contains spaces or ASCII control characters
16890 // (excluding DEL). It also follows special quoting rules where backslashes have no special
16891 // interpretation, which makes it impossible to pass certain first arguments containing
16892 // double quotes to a child process without characters from the first argument leaking into
16893 // subsequent ones (which could have security implications).
16894 //
16895 // Empty arguments technically don't need quotes, but we quote them anyway for maximum
16896 // compatibility with different implementations of the 'CommandLineToArgvW' algorithm.
16897 //
16898 // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject
16899 // all first arguments containing double quotes, even ones that we could theoretically
16900 // serialize in unquoted form.
16901 var needs_quotes = arg0.len == 0;
16902 for (arg0) |c| {
16903 if (c <= ' ') {
16904 needs_quotes = true;
16905 } else if (c == '"') {
16906 return error.InvalidArg0;
16907 }
16908 }
16909 if (needs_quotes) {
16910 try buf.append('"');
16911 try buf.appendSlice(arg0);
16912 try buf.append('"');
16913 } else {
16914 try buf.appendSlice(arg0);
16915 }
16916
16917 for (argv[1..]) |arg| {
16918 try buf.append(' ');
16919
16920 // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes,
16921 // or if they are empty. For simplicity and for maximum compatibility with different
16922 // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII
16923 // control characters (again, excluding DEL).
16924 needs_quotes = for (arg) |c| {
16925 if (c <= ' ' or c == '"') {
16926 break true;
16927 }
16928 } else arg.len == 0;
16929 if (!needs_quotes) {
16930 try buf.appendSlice(arg);
16931 continue;
16932 }
16933
16934 try buf.append('"');
16935 var backslash_count: usize = 0;
16936 for (arg) |byte| {
16937 switch (byte) {
16938 '\\' => {
16939 backslash_count += 1;
16940 },
16941 '"' => {
16942 try buf.appendNTimes('\\', backslash_count * 2 + 1);
16943 try buf.append('"');
16944 backslash_count = 0;
16945 },
16946 else => {
16947 try buf.appendNTimes('\\', backslash_count);
16948 try buf.append(byte);
16949 backslash_count = 0;
16950 },
16951 }
16952 }
16953 try buf.appendNTimes('\\', backslash_count * 2);
16954 try buf.append('"');
16955 }
16956 }
16957
16958 return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
16959}
16960
16961test argvToCommandLineWindows {
16962 const t = testArgvToCommandLineWindows;
16963
16964 try t(&.{
16965 \\C:\Program Files\zig\zig.exe
16966 ,
16967 \\run
16968 ,
16969 \\.\src\main.zig
16970 ,
16971 \\-target
16972 ,
16973 \\x86_64-windows-gnu
16974 ,
16975 \\-O
16976 ,
16977 \\safe
16978 ,
16979 \\--
16980 ,
16981 \\--emoji=🗿
16982 ,
16983 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
16984 ,
16985 },
16986 \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O safe -- --emoji=🗿 "--eval=new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")"
16987 );
16988
16989 try t(&.{}, "");
16990 try t(&.{""}, "\"\"");
16991 try t(&.{" "}, "\" \"");
16992 try t(&.{"\t"}, "\"\t\"");
16993 try t(&.{"\x07"}, "\"\x07\"");
16994 try t(&.{"🦎"}, "🦎");
16995
16996 try t(
16997 &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" },
16998 "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee",
16999 );
17000
17001 try t(
17002 &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" },
17003 "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"",
17004 );
17005
17006 try std.testing.expectError(
17007 error.InvalidArg0,
17008 argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}),
17009 );
17010 try std.testing.expectError(
17011 error.InvalidArg0,
17012 argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}),
17013 );
17014 try std.testing.expectError(
17015 error.InvalidArg0,
17016 argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}),
17017 );
17018}
17019
17020fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void {
17021 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
17022 defer std.testing.allocator.free(cmd_line_w);
17023
17024 const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
17025 defer std.testing.allocator.free(cmd_line);
17026
17027 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
17028}
17029
17030fn posixExecv(
17031 arg0_expand: process.ArgExpansion,
17032 file: [*:0]const u8,
17033 child_argv: [*:null]?[*:0]const u8,
17034 env_block: process.Environ.PosixBlock,
17035 PATH: []const u8,
17036) process.ReplaceError {
17037 const file_slice = std.mem.sliceTo(file, 0);
17038 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block);
17039
17040 // Use of PATH_MAX here is valid as the path_buf will be passed
17041 // directly to the operating system in posixExecvPath.
17042 var path_buf: [posix.PATH_MAX]u8 = undefined;
17043 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17044 var seen_eacces = false;
17045 var err: process.ReplaceError = error.FileNotFound;
17046
17047 // In case of expanding arg0 we must put it back if we return with an error.
17048 const prev_arg0 = child_argv[0];
17049 defer switch (arg0_expand) {
17050 .expand => child_argv[0] = prev_arg0,
17051 .no_expand => {},
17052 };
17053
17054 while (it.next()) |search_path| {
17055 const path_len = search_path.len + file_slice.len + 1;
17056 if (path_buf.len < path_len + 1) return error.NameTooLong;
17057 @memcpy(path_buf[0..search_path.len], search_path);
17058 path_buf[search_path.len] = '/';
17059 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
17060 path_buf[path_len] = 0;
17061 const full_path = path_buf[0..path_len :0].ptr;
17062 switch (arg0_expand) {
17063 .expand => child_argv[0] = full_path,
17064 .no_expand => {},
17065 }
17066 err = posixExecvPath(full_path, child_argv, env_block);
17067 switch (err) {
17068 error.AccessDenied => seen_eacces = true,
17069 error.FileNotFound, error.NotDir => {},
17070 else => |e| return e,
17071 }
17072 }
17073 if (seen_eacces) return error.AccessDenied;
17074 return err;
17075}
17076
17077/// This function ignores PATH environment variable.
17078pub fn posixExecvPath(
17079 path: [*:0]const u8,
17080 child_argv: [*:null]const ?[*:0]const u8,
17081 env_block: process.Environ.PosixBlock,
17082) process.ReplaceError {
17083 try Thread.checkCancel();
17084 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) {
17085 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
17086 .@"2BIG" => return error.SystemResources,
17087 .MFILE => return error.ProcessFdQuotaExceeded,
17088 .NAMETOOLONG => return error.NameTooLong,
17089 .NFILE => return error.SystemFdQuotaExceeded,
17090 .NOMEM => return error.SystemResources,
17091 .ACCES => return error.AccessDenied,
17092 .PERM => return error.PermissionDenied,
17093 .INVAL => return error.InvalidExe,
17094 .NOEXEC => return error.InvalidExe,
17095 .IO => return error.FileSystem,
17096 .LOOP => return error.FileSystem,
17097 .ISDIR => return error.IsDir,
17098 .NOENT => return error.FileNotFound,
17099 .NOTDIR => return error.NotDir,
17100 .TXTBSY => return error.FileBusy,
17101 else => |err| switch (native_os) {
17102 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) {
17103 .BADEXEC => return error.InvalidExe,
17104 .BADARCH => return error.InvalidExe,
17105 else => return posix.unexpectedErrno(err),
17106 },
17107 .linux => switch (err) {
17108 .LIBBAD => return error.InvalidExe,
17109 else => return posix.unexpectedErrno(err),
17110 },
17111 else => return posix.unexpectedErrno(err),
17112 },
17113 }
17114}
17115
17116pub const CreatePipeOptions = struct {
17117 server: End,
17118 client: End,
17119 inbound: bool = false,
17120 outbound: bool = false,
17121 maximum_instances: u32 = 1,
17122 quota: u32 = 4096,
17123 default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100,
17124
17125 pub const End = struct {
17126 attributes: windows.OBJECT.ATTRIBUTES.Flags = .{},
17127 mode: windows.FILE.MODE,
17128 };
17129};
17130pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE {
17131 const named_pipe_device = try t.getNamedPipeDevice();
17132 const server_handle = server_handle: {
17133 var handle: windows.HANDLE = undefined;
17134 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
17135 const syscall: Syscall = try .start();
17136 while (true) switch (windows.ntdll.NtCreateNamedPipeFile(
17137 &handle,
17138 .{
17139 .SPECIFIC = .{ .FILE_PIPE = .{
17140 .READ_DATA = options.inbound,
17141 .WRITE_DATA = options.outbound,
17142 .WRITE_ATTRIBUTES = true,
17143 } },
17144 .STANDARD = .{ .SYNCHRONIZE = true },
17145 },
17146 &.{
17147 .RootDirectory = named_pipe_device,
17148 .Attributes = options.server.attributes,
17149 },
17150 &io_status_block,
17151 .{ .READ = true, .WRITE = true },
17152 .CREATE,
17153 options.server.mode,
17154 .{ .TYPE = .BYTE_STREAM },
17155 .{ .MODE = .BYTE_STREAM },
17156 .{ .OPERATION = .QUEUE },
17157 options.maximum_instances,
17158 if (options.inbound) options.quota else 0,
17159 if (options.outbound) options.quota else 0,
17160 &options.default_timeout,
17161 )) {
17162 .SUCCESS => break syscall.finish(),
17163 .CANCELLED => {
17164 try syscall.checkCancel();
17165 continue;
17166 },
17167 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
17168 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
17169 else => |status| return syscall.unexpectedNtstatus(status),
17170 };
17171 break :server_handle handle;
17172 };
17173 errdefer windows.CloseHandle(server_handle);
17174 const client_handle = client_handle: {
17175 var handle: windows.HANDLE = undefined;
17176 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
17177 const syscall: Syscall = try .start();
17178 while (true) switch (windows.ntdll.NtOpenFile(
17179 &handle,
17180 .{
17181 .SPECIFIC = .{ .FILE_PIPE = .{
17182 .READ_DATA = options.outbound,
17183 .WRITE_DATA = options.inbound,
17184 .WRITE_ATTRIBUTES = true,
17185 } },
17186 .STANDARD = .{ .SYNCHRONIZE = true },
17187 },
17188 &.{
17189 .RootDirectory = server_handle,
17190 .Attributes = options.client.attributes,
17191 },
17192 &io_status_block,
17193 .{ .READ = true, .WRITE = true },
17194 options.client.mode,
17195 )) {
17196 .SUCCESS => break syscall.finish(),
17197 .CANCELLED => {
17198 try syscall.checkCancel();
17199 continue;
17200 },
17201 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
17202 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
17203 else => |status| return syscall.unexpectedNtstatus(status),
17204 };
17205 break :client_handle handle;
17206 };
17207 errdefer windows.CloseHandle(client_handle);
17208 return .{ server_handle, client_handle };
17209}
17210
17211fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
17212 const t: *Threaded = @ptrCast(@alignCast(userdata));
17213 t.scanEnviron();
17214 return t.environ.zig_progress_file;
17215}
17216
17217pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
17218 t.scanEnviron();
17219 return @field(t.environ.string, name);
17220}
17221
17222fn random(userdata: ?*anyopaque, buffer: []u8) void {
17223 const t: *Threaded = @ptrCast(@alignCast(userdata));
17224 const thread = Thread.current orelse return randomMainThread(t, buffer);
17225 if (!thread.csprng.isInitialized()) {
17226 @branchHint(.unlikely);
17227 var seed: [Csprng.seed_len]u8 = undefined;
17228 randomMainThread(t, &seed);
17229 thread.csprng.rng = .init(seed);
17230 }
17231 thread.csprng.rng.fill(buffer);
17232}
17233
17234fn randomMainThread(t: *Threaded, buffer: []u8) void {
17235 mutexLock(&t.mutex);
17236 defer mutexUnlock(&t.mutex);
17237
17238 if (!t.csprng.isInitialized()) {
17239 @branchHint(.unlikely);
17240 var seed: [Csprng.seed_len]u8 = undefined;
17241 {
17242 mutexUnlock(&t.mutex);
17243 defer mutexLock(&t.mutex);
17244
17245 const prev = swapCancelProtection(t, .blocked);
17246 defer _ = swapCancelProtection(t, prev);
17247
17248 randomSecure(t, &seed) catch |err| switch (err) {
17249 error.Canceled => unreachable,
17250 error.EntropyUnavailable => fallbackSeed(t, &seed),
17251 };
17252 }
17253 t.csprng.rng = .init(seed);
17254 }
17255
17256 t.csprng.rng.fill(buffer);
17257}
17258
17259pub fn fallbackSeed(aslr_addr: ?*anyopaque, seed: *[Csprng.seed_len]u8) void {
17260 @memset(seed, 0);
17261 std.mem.writeInt(usize, seed[seed.len - @sizeOf(usize) ..][0..@sizeOf(usize)], @intFromPtr(aslr_addr), .native);
17262 const fallbackSeedImpl = switch (native_os) {
17263 .windows => fallbackSeedWindows,
17264 .wasi => if (builtin.link_libc) fallbackSeedPosix else fallbackSeedWasi,
17265 else => fallbackSeedPosix,
17266 };
17267 fallbackSeedImpl(seed);
17268}
17269
17270fn fallbackSeedPosix(seed: *[Csprng.seed_len]u8) void {
17271 std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native);
17272 const i_1 = @sizeOf(posix.pid_t);
17273
17274 var ts: posix.timespec = undefined;
17275 const Sec = @TypeOf(ts.sec);
17276 const Nsec = @TypeOf(ts.nsec);
17277 const i_2 = i_1 + @sizeOf(Sec);
17278 switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) {
17279 .SUCCESS => {
17280 std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native);
17281 std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native);
17282 },
17283 else => {},
17284 }
17285}
17286
17287fn fallbackSeedWindows(seed: *[Csprng.seed_len]u8) void {
17288 var pc: windows.LARGE_INTEGER = undefined;
17289 _ = windows.ntdll.RtlQueryPerformanceCounter(&pc);
17290 std.mem.writeInt(windows.LARGE_INTEGER, seed[0..@sizeOf(windows.LARGE_INTEGER)], pc, .native);
17291}
17292
17293fn fallbackSeedWasi(seed: *[Csprng.seed_len]u8) void {
17294 var ts: std.os.wasi.timestamp_t = undefined;
17295 if (std.os.wasi.clock_time_get(.REALTIME, 1, &ts) == .SUCCESS) {
17296 std.mem.writeInt(std.os.wasi.timestamp_t, seed[0..@sizeOf(std.os.wasi.timestamp_t)], ts, .native);
17297 }
17298}
17299
17300fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
17301 const t: *Threaded = @ptrCast(@alignCast(userdata));
17302
17303 if (is_windows) {
17304 if (buffer.len == 0) return;
17305 // ProcessPrng from bcryptprimitives.dll has the following properties:
17306 // * introduces a dependency on bcryptprimitives.dll, which apparently
17307 // runs a test suite every time it is loaded
17308 // * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL
17309 // despite the function being documented to always return TRUE
17310 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
17311 // Therefore, that function is avoided in favor of using the device directly.
17312 const cng_device = try getCngDevice(t);
17313 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
17314 var i: usize = 0;
17315 const syscall: Syscall = try .start();
17316 while (true) {
17317 const remaining_len = std.math.lossyCast(u32, buffer.len - i);
17318 switch (windows.ntdll.NtDeviceIoControlFile(
17319 cng_device,
17320 null,
17321 null,
17322 null,
17323 &io_status_block,
17324 windows.IOCTL.KSEC.GEN_RANDOM,
17325 null,
17326 0,
17327 buffer[i..].ptr,
17328 remaining_len,
17329 )) {
17330 .SUCCESS => {
17331 i += remaining_len;
17332 if (buffer.len - i == 0) {
17333 return syscall.finish();
17334 } else {
17335 try syscall.checkCancel();
17336 continue;
17337 }
17338 },
17339 .CANCELLED => {
17340 try syscall.checkCancel();
17341 continue;
17342 },
17343 else => return syscall.fail(error.EntropyUnavailable),
17344 }
17345 }
17346 }
17347
17348 if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {
17349 if (buffer.len == 0) return;
17350 posix.system.arc4random_buf(buffer.ptr, buffer.len);
17351 return;
17352 }
17353
17354 if (native_os == .wasi) {
17355 if (buffer.len == 0) return;
17356 const syscall: Syscall = try .start();
17357 while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) {
17358 .SUCCESS => return syscall.finish(),
17359 .INTR => {
17360 try syscall.checkCancel();
17361 continue;
17362 },
17363 else => return syscall.fail(error.EntropyUnavailable),
17364 };
17365 }
17366
17367 if (@TypeOf(posix.system.getrandom) != void) {
17368 const getrandom = if (use_libc_getrandom) std.c.getrandom else std.os.linux.getrandom;
17369 var i: usize = 0;
17370 const syscall: Syscall = try .start();
17371 while (buffer.len - i != 0) {
17372 const buf = buffer[i..];
17373 const rc = getrandom(buf.ptr, buf.len, 0);
17374 switch (posix.errno(rc)) {
17375 .SUCCESS => {
17376 syscall.finish();
17377 const n: usize = @intCast(rc);
17378 i += n;
17379 continue;
17380 },
17381 .INTR => {
17382 try syscall.checkCancel();
17383 continue;
17384 },
17385 else => return syscall.fail(error.EntropyUnavailable),
17386 }
17387 }
17388 return;
17389 }
17390
17391 if (native_os == .emscripten) {
17392 if (buffer.len == 0) return;
17393 const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len));
17394 switch (err) {
17395 .SUCCESS => return,
17396 else => return error.EntropyUnavailable,
17397 }
17398 }
17399
17400 if (native_os == .linux) {
17401 comptime assert(use_dev_urandom);
17402 const urandom_fd = try getRandomFd(t);
17403
17404 var i: usize = 0;
17405 while (buffer.len - i != 0) {
17406 const syscall: Syscall = try .start();
17407 const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i);
17408 switch (posix.errno(rc)) {
17409 .SUCCESS => {
17410 syscall.finish();
17411 const n: usize = @intCast(rc);
17412 if (n == 0) return error.EntropyUnavailable;
17413 i += n;
17414 continue;
17415 },
17416 .INTR => {
17417 try syscall.checkCancel();
17418 continue;
17419 },
17420 else => return syscall.fail(error.EntropyUnavailable),
17421 }
17422 }
17423 }
17424
17425 return error.EntropyUnavailable;
17426}
17427
17428fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
17429 {
17430 mutexLock(&t.mutex);
17431 defer mutexUnlock(&t.mutex);
17432
17433 if (t.random_file.fd == -2) return error.EntropyUnavailable;
17434 if (t.random_file.fd != -1) return t.random_file.fd;
17435 }
17436
17437 const mode: posix.mode_t = 0;
17438
17439 const fd: posix.fd_t = fd: {
17440 const syscall: Syscall = try .start();
17441 while (true) {
17442 const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{
17443 .ACCMODE = .RDONLY,
17444 .CLOEXEC = true,
17445 }, mode);
17446 switch (posix.errno(rc)) {
17447 .SUCCESS => {
17448 syscall.finish();
17449 break :fd @intCast(rc);
17450 },
17451 .INTR => {
17452 try syscall.checkCancel();
17453 continue;
17454 },
17455 else => return syscall.fail(error.EntropyUnavailable),
17456 }
17457 }
17458 };
17459 errdefer closeFd(fd);
17460
17461 switch (native_os) {
17462 .linux => {
17463 const sys = if (statx_use_c) std.c else std.os.linux;
17464 const syscall: Syscall = try .start();
17465 while (true) {
17466 var statx = std.mem.zeroes(std.os.linux.Statx);
17467 switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
17468 .SUCCESS => {
17469 syscall.finish();
17470 if (!statx.mask.TYPE) return error.EntropyUnavailable;
17471 mutexLock(&t.mutex); // Another thread might have won the race.
17472 defer mutexUnlock(&t.mutex);
17473 if (t.random_file.fd >= 0) {
17474 closeFd(fd);
17475 return t.random_file.fd;
17476 } else if (!posix.S.ISCHR(statx.mode)) {
17477 t.random_file.fd = -2;
17478 return error.EntropyUnavailable;
17479 } else {
17480 t.random_file.fd = fd;
17481 return fd;
17482 }
17483 },
17484 .INTR => {
17485 try syscall.checkCancel();
17486 continue;
17487 },
17488 else => return syscall.fail(error.EntropyUnavailable),
17489 }
17490 }
17491 },
17492 else => {
17493 const syscall: Syscall = try .start();
17494 while (true) {
17495 var stat = std.mem.zeroes(posix.Stat);
17496 switch (posix.errno(fstat_sym(fd, &stat))) {
17497 .SUCCESS => {
17498 syscall.finish();
17499 mutexLock(&t.mutex); // Another thread might have won the race.
17500 defer mutexUnlock(&t.mutex);
17501 if (t.random_file.fd >= 0) {
17502 closeFd(fd);
17503 return t.random_file.fd;
17504 } else if (!posix.S.ISCHR(stat.mode)) {
17505 t.random_file.fd = -2;
17506 return error.EntropyUnavailable;
17507 } else {
17508 t.random_file.fd = fd;
17509 return fd;
17510 }
17511 },
17512 .INTR => {
17513 try syscall.checkCancel();
17514 continue;
17515 },
17516 else => return syscall.fail(error.EntropyUnavailable),
17517 }
17518 }
17519 },
17520 }
17521}
17522
17523test {
17524 _ = @import("Threaded/test.zig");
17525}
17526
17527const use_parking_futex = switch (native_os) {
17528 .windows => true, // RtlWaitOnAddress is a userland implementation anyway
17529 .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now.
17530 .illumos => true, // Illumos has no futex mechanism
17531 .haiku => true, // Haiku has no futex mechanism
17532 else => false,
17533};
17534const use_parking_sleep = switch (native_os) {
17535 // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in
17536 // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the
17537 // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm
17538 // also more confident that it will always correctly handle the cancelation race (so "unpark"
17539 // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired
17540 // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might
17541 // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger
17542 // this behavior because `RtlWaitOnAddress` relies on it.
17543 .windows => true,
17544
17545 // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better
17546 // cancelation mechanism.
17547 .netbsd,
17548 .illumos,
17549 => true,
17550
17551 else => false,
17552};
17553
17554const parking_futex = struct {
17555 comptime {
17556 assert(use_parking_futex);
17557 }
17558
17559 const Bucket = struct {
17560 /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no
17561 /// waiters. It is important for `wait` to increment this *before* checking the futex value to
17562 /// avoid a race.
17563 num_waiters: std.atomic.Value(u32),
17564 /// Protects `waiters`.
17565 mutex: ParkingMutex,
17566 waiters: std.DoublyLinkedList,
17567
17568 /// Prevent false sharing between buckets.
17569 _: void align(std.atomic.cache_line) = {},
17570
17571 const init: Bucket = .{ .num_waiters = .init(0), .mutex = .init, .waiters = .{} };
17572 };
17573
17574 const Waiter = struct {
17575 node: std.DoublyLinkedList.Node,
17576 address: usize,
17577 tid: ParkTid,
17578 /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread
17579 /// which atomically updates it (to `.none` or `.canceling`) is responsible for:
17580 ///
17581 /// * Removing the `Waiter` from `Bucket.waiters`
17582 /// * Decrementing `Bucket.num_waiters`
17583 /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope
17584 /// while it is still in the `Bucket`).
17585 thread_status: *std.atomic.Value(Thread.Status),
17586 unpark_flag: if (need_unpark_flag) *UnparkFlag else void,
17587 };
17588
17589 fn bucketForAddress(address: usize) *Bucket {
17590 const global = struct {
17591 /// Length must be a power of two. The longer this array, the less likely contention is
17592 /// between different futexes. This length seems like it'll provide a reasonable balance
17593 /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line
17594 /// alignment), this uses 32 KiB of memory.
17595 var buckets: [256]Bucket = @splat(.init);
17596 };
17597
17598 // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input
17599 // values across a range, giving a poor, but extremely quick to compute, hash.
17600
17601 // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The
17602 // shift then converts it to '2^b / phi', where 'b' is the pointer bit width.
17603 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize));
17604 const hashed = address *% fibonacci_multiplier;
17605
17606 comptime assert(std.math.isPowerOfTwo(global.buckets.len));
17607 // The high bits of `hashed` have better entropy than the low bits.
17608 const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len));
17609
17610 return &global.buckets[index];
17611 }
17612
17613 fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void {
17614 const bucket = bucketForAddress(@intFromPtr(ptr));
17615
17616 // Put the threadlocal access outside of the critical section.
17617 const opt_thread = Thread.current;
17618 const self_tid = getParkTid();
17619
17620 var waiter: Waiter = .{
17621 .node = undefined, // populated by list append
17622 .address = @intFromPtr(ptr),
17623 .tid = self_tid,
17624 .thread_status = undefined, // populated in critical section
17625 .unpark_flag = undefined, // populated in critical section
17626 };
17627
17628 var status_buf: std.atomic.Value(Thread.Status) = undefined;
17629 var unpark_flag_buf: UnparkFlag = unpark_flag_init;
17630
17631 {
17632 bucket.mutex.lock();
17633 defer bucket.mutex.unlock();
17634
17635 _ = bucket.num_waiters.fetchAdd(1, .acquire);
17636
17637 if (@atomicLoad(u32, ptr, .monotonic) != expect) {
17638 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
17639 return;
17640 }
17641
17642 // This is in the critical section to avoid marking the thread as parked until we're
17643 // certain that we're actually going to park.
17644 waiter.thread_status, waiter.unpark_flag = status: {
17645 cancelable: {
17646 if (uncancelable) break :cancelable;
17647 const thread = opt_thread orelse break :cancelable;
17648 switch (thread.cancel_protection) {
17649 .blocked => break :cancelable,
17650 .unblocked => {},
17651 }
17652 thread.futex_waiter = &waiter;
17653 const old_status = thread.status.fetchOr(
17654 .{ .cancelation = @fromBackingInt(@intCast(0b001)), .awaitable = .null },
17655 .release, // release `thread.futex_waiter`
17656 );
17657 switch (old_status.cancelation) {
17658 .none => {}, // status is now `.parked`
17659 .canceling => {
17660 // status is now `.canceled`
17661 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
17662 return error.Canceled;
17663 },
17664 .canceled => break :cancelable, // status is still `.canceled`
17665 .parked => unreachable,
17666 .blocked => unreachable,
17667 .blocked_alertable => unreachable,
17668 .blocked_alertable_canceling => unreachable,
17669 .blocked_canceling => unreachable,
17670 }
17671 // We could now be unparked for a cancelation at any time!
17672 break :status .{ &thread.status, if (need_unpark_flag) &thread.unpark_flag };
17673 }
17674 // This is an uncancelable wait, so just use `status_buf`. Note that the value of
17675 // `status_buf.awaitable` is irrelevant because this is only visible to futex code,
17676 // while only cancelation cares about `awaitable`.
17677 status_buf.raw = .{ .cancelation = .parked, .awaitable = .null };
17678 break :status .{ &status_buf, if (need_unpark_flag) &unpark_flag_buf };
17679 };
17680
17681 bucket.waiters.append(&waiter.node);
17682 }
17683
17684 if (park(timeout, ptr, waiter.unpark_flag)) {
17685 // We were unparked by either `wake` or cancelation, so our current status is either
17686 // `.none` or `.canceling`. In either case, they've already removed `waiter` from
17687 // `bucket`, so we have nothing more to do!
17688 } else |err| switch (err) {
17689 error.Timeout => {
17690 // We're not out of the woods yet: an unpark could race with the timeout.
17691 const old_status = waiter.thread_status.fetchAnd(
17692 .{ .cancelation = @fromBackingInt(@intCast(0b110)), .awaitable = .all_ones },
17693 .monotonic,
17694 );
17695 switch (old_status.cancelation) {
17696 .parked => {
17697 // No race. It is our responsibility to remove `waiter` from `bucket`.
17698 // New status is `.none`.
17699 bucket.mutex.lock();
17700 defer bucket.mutex.unlock();
17701 bucket.waiters.remove(&waiter.node);
17702 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
17703 },
17704 .none, .canceling => {
17705 // Race condition: the timeout was reached, then `wake` or a canceler tried
17706 // to unpark us. Whoever did that will remove us from `bucket`. Wait for
17707 // that (and drop the unpark request in doing so).
17708 // New status is `.none` or `.canceling` respectively.
17709 park(.none, ptr, waiter.unpark_flag) catch |e| switch (e) {
17710 error.Timeout => unreachable,
17711 };
17712 },
17713 .canceled => unreachable,
17714 .blocked => unreachable,
17715 .blocked_alertable => unreachable,
17716 .blocked_canceling => unreachable,
17717 .blocked_alertable_canceling => unreachable,
17718 }
17719 },
17720 }
17721 }
17722
17723 fn wake(ptr: *const u32, max_waiters: u32) void {
17724 if (max_waiters == 0) return;
17725
17726 const bucket = bucketForAddress(@intFromPtr(ptr));
17727
17728 // To ensure the store to `ptr` is ordered before this check, we effectively want a `.release`
17729 // load, but that doesn't exist in the C11 memory model, so emulate it with a non-mutating rmw.
17730 if (bucket.num_waiters.fetchAdd(0, .release) == 0) {
17731 @branchHint(.likely);
17732 return; // no waiters
17733 }
17734
17735 // Waiters removed from the linked list under the mutex so we can unpark their threads outside
17736 // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`.
17737 var waking_head: ?*std.DoublyLinkedList.Node = null;
17738 {
17739 bucket.mutex.lock();
17740 defer bucket.mutex.unlock();
17741
17742 var num_removed: u32 = 0;
17743 var it = bucket.waiters.first;
17744 while (num_removed < max_waiters) {
17745 const waiter: *Waiter = @fieldParentPtr("node", it orelse break);
17746 it = waiter.node.next;
17747 if (waiter.address != @intFromPtr(ptr)) continue;
17748 const old_status = waiter.thread_status.fetchAnd(
17749 .{ .cancelation = @fromBackingInt(@intCast(0b110)), .awaitable = .all_ones },
17750 .monotonic,
17751 );
17752 switch (old_status.cancelation) {
17753 .parked => {}, // state updated to `.none`
17754 .none => continue, // race with timeout; they are about to lock `bucket.mutex` and remove themselves from the bucket
17755 .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet
17756 .canceled => unreachable,
17757 .blocked => unreachable,
17758 .blocked_alertable => unreachable,
17759 .blocked_alertable_canceling => unreachable,
17760 .blocked_canceling => unreachable,
17761 }
17762 // We're waking this waiter. Remove them from the bucket and add them to our local list.
17763 bucket.waiters.remove(&waiter.node);
17764 waiter.node.next = waking_head;
17765 waking_head = &waiter.node;
17766 num_removed += 1;
17767 }
17768 _ = bucket.num_waiters.fetchSub(num_removed, .monotonic);
17769 }
17770
17771 var unpark_buf: [128]UnparkTid = undefined;
17772 var unpark_len: usize = 0;
17773
17774 // Finally, unpark the threads.
17775 while (waking_head) |node| {
17776 waking_head = node.next;
17777 const waiter: *Waiter = @fieldParentPtr("node", node);
17778 unpark_buf[unpark_len] = waiter.tid;
17779 if (need_unpark_flag) setUnparkFlag(waiter.unpark_flag);
17780 unpark_len += 1;
17781 if (unpark_len == unpark_buf.len) {
17782 unpark(&unpark_buf, ptr);
17783 unpark_len = 0;
17784 }
17785 }
17786 if (unpark_len > 0) {
17787 unpark(unpark_buf[0..unpark_len], ptr);
17788 }
17789 }
17790
17791 fn removeCanceledWaiter(waiter: *Waiter) void {
17792 const bucket = bucketForAddress(waiter.address);
17793 bucket.mutex.lock();
17794 defer bucket.mutex.unlock();
17795 bucket.waiters.remove(&waiter.node);
17796 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
17797 }
17798};
17799const parking_sleep = struct {
17800 comptime {
17801 assert(use_parking_sleep);
17802 }
17803 fn sleep(timeout: Io.Timeout) Io.Cancelable!void {
17804 const opt_thread = Thread.current;
17805 cancelable: {
17806 const thread = opt_thread orelse break :cancelable;
17807 switch (thread.cancel_protection) {
17808 .blocked => break :cancelable,
17809 .unblocked => {},
17810 }
17811 thread.futex_waiter = null;
17812 {
17813 const old_status = thread.status.fetchOr(
17814 .{ .cancelation = @fromBackingInt(@intCast(0b001)), .awaitable = .null },
17815 .release, // release `thread.futex_waiter`
17816 );
17817 switch (old_status.cancelation) {
17818 .none => {}, // status is now `.parked`
17819 .canceling => return error.Canceled, // status is now `.canceled`
17820 .canceled => break :cancelable, // status is still `.canceled`
17821 .parked => unreachable,
17822 .blocked => unreachable,
17823 .blocked_alertable => unreachable,
17824 .blocked_alertable_canceling => unreachable,
17825 .blocked_canceling => unreachable,
17826 }
17827 }
17828 if (park(timeout, null, if (need_unpark_flag) &thread.unpark_flag)) {
17829 // The only reason this could possibly happen is cancelation.
17830 const old_status = thread.status.load(.monotonic);
17831 assert(old_status.cancelation == .canceling);
17832 thread.status.store(
17833 .{ .cancelation = .canceled, .awaitable = old_status.awaitable },
17834 .monotonic,
17835 );
17836 return error.Canceled;
17837 } else |err| switch (err) {
17838 error.Timeout => {
17839 // We're not out of the woods yet: an unpark could race with the timeout.
17840 const old_status = thread.status.fetchAnd(
17841 .{ .cancelation = @fromBackingInt(@intCast(0b110)), .awaitable = .all_ones },
17842 .monotonic,
17843 );
17844 switch (old_status.cancelation) {
17845 .parked => return, // No race; new status is `.none`
17846 .canceling => {
17847 // Race condition: the timeout was reached, then someone tried to unpark
17848 // us for a cancelation. Whoever did that will have called `unpark`, so
17849 // drop that unpark request by waiting for it.
17850 // Status is still `.canceling`.
17851 park(.none, null, if (need_unpark_flag) &thread.unpark_flag) catch |e| switch (e) {
17852 error.Timeout => unreachable,
17853 };
17854 return;
17855 },
17856 .none => unreachable,
17857 .canceled => unreachable,
17858 .blocked => unreachable,
17859 .blocked_alertable => unreachable,
17860 .blocked_canceling => unreachable,
17861 .blocked_alertable_canceling => unreachable,
17862 }
17863 },
17864 }
17865 }
17866
17867 // Uncancelable sleep; we expect not to be manually unparked.
17868
17869 // On systems where parking the thread requires a one-time setup operation (e.g. creating a
17870 // semaphore), we need to ensure that setup is done before we call `park`.
17871 _ = getParkTid();
17872 var dummy_flag: UnparkFlag = unpark_flag_init;
17873 if (park(timeout, null, if (need_unpark_flag) &dummy_flag)) {
17874 unreachable; // unexpected unpark
17875 } else |err| switch (err) {
17876 error.Timeout => return,
17877 }
17878 }
17879};
17880const ParkingMutex = struct {
17881 state: std.atomic.Value(State),
17882
17883 const init: ParkingMutex = .{ .state = .init(.unlocked) };
17884
17885 comptime {
17886 assert(use_parking_futex);
17887 }
17888
17889 const State = enum(usize) {
17890 unlocked = 1,
17891 /// This value is intentionally 0 so that `waiter` returns `null`.
17892 locked_once = 0,
17893 /// Contended; value is a `*Waiter`.
17894 _,
17895 /// Returns the head of the waiter list. Illegal to call if `s == .unlocked`.
17896 fn waiter(s: State) ?*Waiter {
17897 return @ptrFromInt(@backingInt(s));
17898 }
17899 /// Returns a locked state where `w` is contending the lock.
17900 /// If `w` is `null`, returns `.locked_once`.
17901 fn fromWaiter(w: ?*Waiter) State {
17902 return @fromBackingInt(@intCast(@intFromPtr(w)));
17903 }
17904 };
17905 const Waiter = struct {
17906 unpark_flag: UnparkFlag,
17907 /// Never modified once the `Waiter` is in the linked list.
17908 next: ?*Waiter,
17909 /// Never modified once the `Waiter` is in the linked list.
17910 tid: ParkTid,
17911 };
17912 fn lock(m: *ParkingMutex) void {
17913 state: switch (State.unlocked) { // assume 'unlocked' to optimize for uncontended case
17914 .unlocked => continue :state m.state.cmpxchgWeak(
17915 .unlocked,
17916 .locked_once,
17917 .acquire, // acquire lock
17918 .monotonic,
17919 ) orelse {
17920 @branchHint(.likely);
17921 return;
17922 },
17923
17924 .locked_once, _ => |last_state| {
17925 const old_waiter = last_state.waiter();
17926 const self_tid = getParkTid();
17927 var waiter: Waiter = .{
17928 .next = old_waiter,
17929 .unpark_flag = unpark_flag_init,
17930 .tid = self_tid,
17931 };
17932 if (m.state.cmpxchgWeak(
17933 .fromWaiter(old_waiter),
17934 .fromWaiter(&waiter),
17935 .release, // release `waiter`
17936 .monotonic,
17937 )) |new_state| {
17938 continue :state new_state;
17939 }
17940 // We're now in the list of waiters---park until we're given the lock.
17941 park(.none, m, if (need_unpark_flag) &waiter.unpark_flag) catch |err| switch (err) {
17942 error.Timeout => unreachable,
17943 };
17944 return;
17945 },
17946 }
17947 }
17948 fn unlock(m: *ParkingMutex) void {
17949 state: switch (State.locked_once) { // assume 'locked_once' to optimize for uncontended case
17950 .unlocked => unreachable, // we hold the lock
17951
17952 .locked_once => continue :state m.state.cmpxchgWeak(
17953 .locked_once,
17954 .unlocked,
17955 .release, // release lock
17956 .acquire, // acquire any `Waiter` memory
17957 ) orelse {
17958 @branchHint(.likely);
17959 return;
17960 },
17961
17962 _ => |last_state| {
17963 // The logic here does not have ABA problems, and does some accesses non-atomically,
17964 // because `Waiter.next` is owned by the lock holder (that's us!) once the waiter is
17965 // in the linked list, up until we unpark the waiter.
17966
17967 // Run through the waiter list to the end to ensure fairness. This is obviously not
17968 // ideal, but it shouldn't be a big deal in practice provided the critical section
17969 // is fairly small (so we won't get too many threads contending the mutex at once).
17970 // There's a *chance* we could get away with a LIFO queue for our use case, but I
17971 // don't wanna risk that.
17972 var parent: ?*Waiter = null;
17973 var waiter: *Waiter = last_state.waiter().?;
17974 while (waiter.next) |next| {
17975 parent = waiter;
17976 waiter = next;
17977 }
17978 // `waiter` is next in line for the lock. Remove them from the list.
17979 if (parent) |p| {
17980 assert(p.next == waiter);
17981 p.next = null;
17982 } else {
17983 // We're waking the last waiter, so clear the list head.
17984 if (m.state.cmpxchgWeak(
17985 .fromWaiter(last_state.waiter().?),
17986 .locked_once,
17987 .acquire,
17988 .acquire, // acquire any new `Waiter` memory
17989 )) |new_state| {
17990 continue :state new_state;
17991 }
17992 }
17993 // Now we're ready to actually hand the lock over to them.
17994 const tid = waiter.tid; // load before the unpark below potentially invalidates `waiter`
17995 if (need_unpark_flag) setUnparkFlag(&waiter.unpark_flag);
17996 unpark(&.{tid}, m);
17997 return;
17998 },
17999 }
18000 }
18001};
18002
18003fn timeoutToWindowsInterval(timeout: Io.Timeout) ?windows.LARGE_INTEGER {
18004 // ntdll only supports two combinations:
18005 // * real-time (`.real`) sleeps with absolute deadlines
18006 // * monotonic (`.awake`/`.boot`) sleeps with relative durations
18007 const clock = switch (timeout) {
18008 .none => return null,
18009 .duration => |d| d.clock,
18010 .deadline => |d| d.clock,
18011 };
18012 switch (clock) {
18013 .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time
18014 .real => {
18015 const deadline = switch (timeout) {
18016 .none => unreachable,
18017 .duration => |d| nowWindows(clock).addDuration(d.raw),
18018 .deadline => |d| d.raw,
18019 };
18020 const epoch_ns = std.time.epoch.windows * std.time.ns_per_s;
18021 return @intCast(@max(@divTrunc(deadline.nanoseconds - epoch_ns, 100), 0));
18022 },
18023 .awake, .boot => {
18024 const duration = switch (timeout) {
18025 .none => unreachable,
18026 .duration => |d| d.raw,
18027 .deadline => |d| nowWindows(clock).durationTo(d.raw),
18028 };
18029 return @intCast(@min(@divTrunc(-duration.nanoseconds, 100), -1));
18030 },
18031 }
18032}
18033
18034/// The API on NetBSD and Illumos sucks and can unpark spuriously (well, it *can't*, but signals
18035/// cause an indistinguishable unblock, and libpthread really likes to leave unparks pending).
18036/// As such, on these targets only, we need to pass around a flag to track whether a thread is
18037/// "actually" being unparked.
18038const need_unpark_flag = switch (native_os) {
18039 .netbsd, .illumos => true,
18040 else => false,
18041};
18042const UnparkFlag = if (need_unpark_flag) std.atomic.Value(bool) else void;
18043const unpark_flag_init: UnparkFlag = if (need_unpark_flag) .init(false);
18044/// Must be called before `unpark`. After this function is called, the thread may be unparked at any
18045/// time, so the caller must not reference values on its stack.
18046fn setUnparkFlag(f: *UnparkFlag) void {
18047 f.store(true, .release);
18048}
18049
18050/// The type passed into `unpark` for the thread ID. You'd think this was just a `std.Thread.Id`,
18051/// but it seems that someone at Microsoft forgot how big their TIDs are supposed to be.
18052const UnparkTid = switch (native_os) {
18053 .windows => usize,
18054 else => ParkTid,
18055};
18056
18057const ParkTid = switch (native_os) {
18058 .haiku => std.c.sem_id,
18059 else => std.Thread.Id,
18060};
18061
18062threadlocal var park_sem: std.c.sem_id = -1;
18063
18064fn getParkTid() ParkTid {
18065 switch (native_os) {
18066 .haiku => {
18067 if (park_sem == -1) {
18068 park_sem = std.c._kern_create_sem(0, null);
18069 if (park_sem < 0) @panic("_kern_create_sem failed");
18070 _ = std.c.on_exit_thread(destroyParkSem, null);
18071 }
18072 return park_sem;
18073 },
18074 else => {
18075 return if (Thread.current) |thread| thread.id else std.Thread.getCurrentId();
18076 },
18077 }
18078}
18079
18080fn destroyParkSem(_: ?*anyopaque) callconv(.c) void {
18081 _ = std.c._kern_delete_sem(park_sem);
18082}
18083
18084fn park(
18085 timeout: Io.Timeout,
18086 /// This value has no semantic effect, but may allow the OS to optimize the operation.
18087 addr_hint: ?*const anyopaque,
18088 unpark_flag: if (need_unpark_flag) *UnparkFlag else void,
18089) error{Timeout}!void {
18090 comptime assert(use_parking_futex or use_parking_sleep);
18091 switch (native_os) {
18092 .windows => {
18093 const raw_timeout = timeoutToWindowsInterval(timeout);
18094 // `RtlWaitOnAddress` passes the futex address in as the first argument to this call,
18095 // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId`
18096 // does *not* accept the address so the kernel can't really be using it as a hint. An
18097 // old Microsoft blog post discusses a more traditional futex-like mechanism in the
18098 // kernel which definitely isn't how `RtlWaitOnAddress` works today:
18099 //
18100 // https://devblogs.microsoft.com/oldnewthing/20160826-00/?p=94185
18101 //
18102 // ...so it's possible this argument is simply a remnant which no longer does anything
18103 // (perhaps the implementation changed during development but someone forgot to remove
18104 // this parameter). However, to err on the side of caution, let's match the behavior of
18105 // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something
18106 // stupid such as trying to dereference it.
18107 switch (windows.ntdll.NtWaitForAlertByThreadId(
18108 addr_hint,
18109 if (raw_timeout) |*t| t else null,
18110 )) {
18111 .ALERTED => return,
18112 .TIMEOUT => return error.Timeout,
18113 else => unreachable,
18114 }
18115 },
18116 .netbsd => {
18117 var ts_buf: posix.timespec = undefined;
18118 const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) {
18119 .none => .{ null, false, false },
18120 .deadline => |timestamp| timeout: {
18121 ts_buf = timestampToPosix(timestamp.raw.nanoseconds);
18122 break :timeout .{ &ts_buf, true, timestamp.clock == .real };
18123 },
18124 .duration => |duration| timeout: {
18125 ts_buf = timestampToPosix(duration.raw.nanoseconds);
18126 break :timeout .{ &ts_buf, false, duration.clock == .real };
18127 },
18128 };
18129 // It's okay to pass the same timeout in a loop. If it's a duration, the OS actually
18130 // writes the remaining time into the buffer when the syscall returns.
18131 while (!unpark_flag.swap(false, .acquire)) {
18132 switch (posix.errno(std.c._lwp_park(
18133 if (clock_real) .REALTIME else .MONOTONIC,
18134 .{ .ABSTIME = abstime },
18135 ts,
18136 0,
18137 addr_hint,
18138 null,
18139 ))) {
18140 .SUCCESS, .ALREADY, .INTR => {},
18141 .TIMEDOUT => return error.Timeout,
18142 .INVAL => unreachable,
18143 .SRCH => unreachable,
18144 else => unreachable,
18145 }
18146 }
18147 },
18148 .illumos => @panic("TODO: illumos lwp_park"),
18149 .haiku => {
18150 const timeout_flags: u32, const timeout_us = switch (timeout) {
18151 .none => .{ 0, 0 },
18152 .deadline => |deadline| .{
18153 if (deadline.clock == .real) std.c.B_ABSOLUTE_TIMEOUT | std.c.B_TIMEOUT_REAL_TIME_BASE else std.c.B_ABSOLUTE_TIMEOUT,
18154 deadline.raw.toMicroseconds(),
18155 },
18156 .duration => |duration| .{
18157 if (duration.clock == .real) std.c.B_ABSOLUTE_TIMEOUT | std.c.B_TIMEOUT_REAL_TIME_BASE else std.c.B_ABSOLUTE_TIMEOUT,
18158 nowPosix(duration.clock).addDuration(duration.raw).toMicroseconds(),
18159 },
18160 };
18161 while (true) {
18162 switch (std.c._kern_acquire_sem_etc(park_sem, 1, timeout_flags, timeout_us)) {
18163 0 => return,
18164 std.c.E.B_TIMED_OUT => return error.Timeout,
18165 std.c.E.B_INTERRUPTED => {},
18166 else => unreachable,
18167 }
18168 }
18169 },
18170 else => comptime unreachable,
18171 }
18172}
18173/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
18174fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void {
18175 comptime assert(use_parking_futex or use_parking_sleep);
18176 switch (native_os) {
18177 .windows => {
18178 // TODO: this condition is currently disabled because mingw-w64 does not contain this
18179 // symbol. Once it's added, enable this check to use the new bulk API where possible.
18180 if (false and (builtin.os.version_range.windows.isAtLeast(.win11_dt) orelse false)) {
18181 _ = windows.ntdll.NtAlertMultipleThreadByThreadId(tids.ptr, @intCast(tids.len), null, null);
18182 } else {
18183 for (tids) |tid| {
18184 _ = windows.ntdll.NtAlertThreadByThreadId(@intCast(tid));
18185 }
18186 }
18187 },
18188 .netbsd => {
18189 switch (posix.errno(std.c._lwp_unpark_all(@ptrCast(tids.ptr), tids.len, addr_hint))) {
18190 .SUCCESS => return,
18191 // For errors, fall through to a loop over `tids`, though this is only expected to
18192 // be possible for ENOMEM (even that is questionable) and ESRCH (see comment below).
18193 .SRCH => {},
18194 .FAULT => recoverableOsBugDetected(),
18195 .INVAL => recoverableOsBugDetected(),
18196 .NOMEM => {},
18197 else => recoverableOsBugDetected(),
18198 }
18199 for (tids) |tid| {
18200 switch (posix.errno(std.c._lwp_unpark(@bitCast(tid), addr_hint))) {
18201 .SUCCESS => {},
18202 .SRCH => {
18203 // This can happen in a rare race: the thread might have been spuriously
18204 // unparked, so already observed the changing status, and from there have
18205 // exited. That's okay, because the thread has woken up like we wanted.
18206 },
18207 else => recoverableOsBugDetected(),
18208 }
18209 }
18210 },
18211 .illumos => @panic("TODO: illumos lwp_unpark"),
18212 .haiku => {
18213 for (tids) |tid| {
18214 switch (std.c._kern_release_sem_etc(tid, 1, 0)) {
18215 0 => {},
18216 else => recoverableOsBugDetected(),
18217 }
18218 }
18219 },
18220 else => comptime unreachable,
18221 }
18222}
18223
18224pub const PipeError = error{
18225 SystemFdQuotaExceeded,
18226 ProcessFdQuotaExceeded,
18227} || Io.UnexpectedError;
18228
18229pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t {
18230 var fds: [2]posix.fd_t = undefined;
18231
18232 if (@TypeOf(posix.system.pipe2) != void) {
18233 switch (posix.errno(posix.system.pipe2(&fds, flags))) {
18234 .SUCCESS => return fds,
18235 .INVAL => |err| return errnoBug(err), // Invalid flags
18236 .NFILE => return error.SystemFdQuotaExceeded,
18237 .MFILE => return error.ProcessFdQuotaExceeded,
18238 else => |err| return posix.unexpectedErrno(err),
18239 }
18240 }
18241
18242 switch (posix.errno(posix.system.pipe(&fds))) {
18243 .SUCCESS => {},
18244 .NFILE => return error.SystemFdQuotaExceeded,
18245 .MFILE => return error.ProcessFdQuotaExceeded,
18246 else => |err| return posix.unexpectedErrno(err),
18247 }
18248 errdefer {
18249 closeFd(fds[0]);
18250 closeFd(fds[1]);
18251 }
18252
18253 // https://github.com/ziglang/zig/issues/18882
18254 if (@as(u32, @bitCast(flags)) == 0) return fds;
18255
18256 // CLOEXEC is special, it's a file descriptor flag and must be set using
18257 // F.SETFD.
18258 if (flags.CLOEXEC) for (fds) |fd| {
18259 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(u32, posix.FD_CLOEXEC)))) {
18260 .SUCCESS => {},
18261 else => |err| return posix.unexpectedErrno(err),
18262 }
18263 };
18264
18265 const new_flags: u32 = f: {
18266 var new_flags = flags;
18267 new_flags.CLOEXEC = false;
18268 break :f @bitCast(new_flags);
18269 };
18270
18271 // Set every other flag affecting the file status using F.SETFL.
18272 if (new_flags != 0) for (fds) |fd| {
18273 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, new_flags))) {
18274 .SUCCESS => {},
18275 .INVAL => |err| return errnoBug(err),
18276 else => |err| return posix.unexpectedErrno(err),
18277 }
18278 };
18279
18280 return fds;
18281}
18282
18283pub const DupError = error{
18284 ProcessFdQuotaExceeded,
18285 SystemResources,
18286} || Io.UnexpectedError || Io.Cancelable;
18287
18288pub fn dup2(old_fd: posix.fd_t, new_fd: posix.fd_t) DupError!void {
18289 const syscall: Syscall = try .start();
18290 while (true) switch (posix.errno(posix.system.dup2(old_fd, new_fd))) {
18291 .SUCCESS => return syscall.finish(),
18292 .BUSY, .INTR => {
18293 try syscall.checkCancel();
18294 continue;
18295 },
18296 .INVAL => |err| return syscall.errnoBug(err), // invalid parameters
18297 .BADF => |err| return syscall.errnoBug(err), // use after free
18298 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
18299 .NOMEM => return syscall.fail(error.SystemResources),
18300 else => |err| return syscall.unexpectedErrno(err),
18301 };
18302}
18303
18304pub const FchdirError = error{
18305 AccessDenied,
18306 NotDir,
18307 FileSystem,
18308} || Io.Cancelable || Io.UnexpectedError;
18309
18310pub fn fchdir(fd: posix.fd_t) FchdirError!void {
18311 if (fd == posix.AT.FDCWD) return;
18312 const syscall: Syscall = try .start();
18313 while (true) switch (posix.errno(posix.system.fchdir(fd))) {
18314 .SUCCESS => return syscall.finish(),
18315 .INTR => {
18316 try syscall.checkCancel();
18317 continue;
18318 },
18319 .ACCES => return syscall.fail(error.AccessDenied),
18320 .NOTDIR => return syscall.fail(error.NotDir),
18321 .IO => return syscall.fail(error.FileSystem),
18322 .BADF => |err| return syscall.errnoBug(err),
18323 else => |err| return syscall.unexpectedErrno(err),
18324 };
18325}
18326
18327pub const ChdirError = error{
18328 AccessDenied,
18329 FileSystem,
18330 SymLinkLoop,
18331 NameTooLong,
18332 FileNotFound,
18333 SystemResources,
18334 NotDir,
18335 BadPathName,
18336} || Io.Cancelable || Io.UnexpectedError;
18337
18338pub fn chdir(dir_path: []const u8) ChdirError!void {
18339 var path_buffer: [posix.PATH_MAX]u8 = undefined;
18340 const dir_path_posix = try pathToPosix(dir_path, &path_buffer);
18341 const syscall: Syscall = try .start();
18342 while (true) switch (posix.errno(posix.system.chdir(dir_path_posix))) {
18343 .SUCCESS => return syscall.finish(),
18344 .INTR => {
18345 try syscall.checkCancel();
18346 continue;
18347 },
18348 .ACCES => return syscall.fail(error.AccessDenied),
18349 .IO => return syscall.fail(error.FileSystem),
18350 .LOOP => return syscall.fail(error.SymLinkLoop),
18351 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
18352 .NOENT => return syscall.fail(error.FileNotFound),
18353 .NOMEM => return syscall.fail(error.SystemResources),
18354 .NOTDIR => return syscall.fail(error.NotDir),
18355 .ILSEQ => return syscall.fail(error.BadPathName),
18356 .FAULT => |err| return syscall.errnoBug(err),
18357 else => |err| return syscall.unexpectedErrno(err),
18358 };
18359}
18360
18361fn fileMemoryMapCreate(
18362 userdata: ?*anyopaque,
18363 file: File,
18364 options: File.MemoryMap.CreateOptions,
18365) File.MemoryMap.CreateError!File.MemoryMap {
18366 const t: *Threaded = @ptrCast(@alignCast(userdata));
18367 const offset = options.offset;
18368 const len = options.len;
18369
18370 if (!t.disable_memory_mapping) {
18371 if (createFileMap(file, options.protection, offset, options.populate, len)) |result| {
18372 return result;
18373 } else |err| switch (err) {
18374 error.Unseekable, error.Canceled, error.AccessDenied => |e| return e,
18375 error.OperationUnsupported => {},
18376 else => {
18377 if (builtin.mode == .debug)
18378 std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err});
18379 },
18380 }
18381 }
18382
18383 const gpa = t.allocator;
18384 const page_size = std.heap.pageSize();
18385 const alignment: Alignment = .fromByteUnits(page_size);
18386 const memory = m: {
18387 const ptr = gpa.rawAlloc(len, alignment, @returnAddress()) orelse return error.OutOfMemory;
18388 break :m ptr[0..len];
18389 };
18390 errdefer gpa.rawFree(memory, alignment, @returnAddress());
18391
18392 if (!options.undefined_contents) try mmSyncRead(file, memory, offset);
18393
18394 return .{
18395 .file = file,
18396 .offset = offset,
18397 .memory = @alignCast(memory),
18398 .section = null,
18399 };
18400}
18401
18402const CreateFileMapError = error{
18403 /// MaximumSize is greater than the system-defined maximum for sections, or
18404 /// greater than the specified file and the section is not writable.
18405 SectionOversize,
18406 /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
18407 /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
18408 /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
18409 /// Or `PROT_WRITE` is set, but the file is append-only.
18410 AccessDenied,
18411 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
18412 /// a filesystem that was mounted no-exec.
18413 PermissionDenied,
18414 FileBusy,
18415 LockedMemoryLimitExceeded,
18416 OperationUnsupported,
18417 ProcessFdQuotaExceeded,
18418 SystemFdQuotaExceeded,
18419 OutOfMemory,
18420 MappingAlreadyExists,
18421 Unseekable,
18422 LockViolation,
18423} || Io.Cancelable || Io.UnexpectedError;
18424
18425fn createFileMap(
18426 file: File,
18427 protection: std.process.MemoryProtection,
18428 offset: u64,
18429 populate: bool,
18430 len: usize,
18431) CreateFileMapError!File.MemoryMap {
18432 if (is_windows) {
18433 try Thread.checkCancel();
18434
18435 var section = windows.INVALID_HANDLE_VALUE;
18436 const section_size: windows.LARGE_INTEGER = @intCast(len);
18437 const page = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied;
18438 switch (windows.ntdll.NtCreateSection(
18439 &section,
18440 .{
18441 .SPECIFIC = .{ .SECTION = .{
18442 .QUERY = true,
18443 .MAP_WRITE = protection.write,
18444 .MAP_READ = protection.read,
18445 .MAP_EXECUTE = protection.execute,
18446 .EXTEND_SIZE = true,
18447 } },
18448 .STANDARD = .{ .RIGHTS = .REQUIRED },
18449 },
18450 null,
18451 &section_size,
18452 page,
18453 .{ .COMMIT = populate },
18454 file.handle,
18455 )) {
18456 .SUCCESS => {},
18457 .FILE_LOCK_CONFLICT => return error.LockViolation,
18458 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,
18459 .ACCESS_DENIED => return error.AccessDenied,
18460 .SECTION_TOO_BIG => return error.SectionOversize,
18461 else => |status| return windows.unexpectedStatus(status),
18462 }
18463 var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null;
18464 var contents_len = len;
18465 switch (windows.ntdll.NtMapViewOfSection(
18466 section,
18467 windows.current_process,
18468 @ptrCast(&contents_ptr),
18469 null,
18470 0,
18471 null,
18472 &contents_len,
18473 .Unmap,
18474 .{},
18475 page,
18476 )) {
18477 .SUCCESS => {},
18478 .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists,
18479 .SECTION_PROTECTION => return error.PermissionDenied,
18480 .ACCESS_DENIED => return error.AccessDenied,
18481 .INVALID_VIEW_SIZE => |status| return windows.statusBug(status),
18482 else => |status| return windows.unexpectedStatus(status),
18483 }
18484 if (builtin.mode == .debug) {
18485 const page_size = std.heap.pageSize();
18486 const alignment: Alignment = .fromByteUnits(page_size);
18487 assert(contents_len == alignment.forward(len));
18488 }
18489 return .{
18490 .file = file,
18491 .offset = offset,
18492 .memory = contents_ptr.?[0..len],
18493 .section = section,
18494 };
18495 } else if (have_mmap) {
18496 const prot: posix.PROT = .{
18497 .READ = protection.read,
18498 .WRITE = protection.write,
18499 .EXEC = protection.execute,
18500 };
18501 const flags: posix.MAP = switch (native_os) {
18502 .linux => .{
18503 .TYPE = .SHARED_VALIDATE,
18504 .POPULATE = populate,
18505 },
18506 else => .{
18507 .TYPE = .SHARED,
18508 },
18509 };
18510
18511 const page_align = std.heap.page_size_min;
18512
18513 const contents = while (true) {
18514 const syscall: Syscall = try .start();
18515 const casted_offset = std.math.cast(i64, offset) orelse return error.Unseekable;
18516 const rc = mmap_sym(null, len, prot, flags, file.handle, casted_offset);
18517 syscall.finish();
18518 const err: posix.E = if (builtin.link_libc) e: {
18519 if (rc != std.c.MAP_FAILED) {
18520 break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..len];
18521 }
18522 break :e @fromBackingInt(@intCast(posix.system._errno().*));
18523 } else e: {
18524 const err = posix.errno(rc);
18525 if (err == .SUCCESS) {
18526 break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..len];
18527 }
18528 break :e err;
18529 };
18530 switch (err) {
18531 .SUCCESS => unreachable,
18532 .INTR => continue,
18533 .ACCES => return error.AccessDenied,
18534 .AGAIN => return error.LockedMemoryLimitExceeded,
18535 .EXIST => return error.MappingAlreadyExists,
18536 .MFILE => return error.ProcessFdQuotaExceeded,
18537 .NFILE => return error.SystemFdQuotaExceeded,
18538 .NODEV => return error.OperationUnsupported,
18539 .NOMEM => return error.OutOfMemory,
18540 .PERM => return error.PermissionDenied,
18541 .TXTBSY => return error.FileBusy,
18542 .OVERFLOW => return error.Unseekable,
18543 .BADF => return errnoBug(err), // Always a race condition.
18544 .INVAL => return errnoBug(err), // Invalid parameters to mmap()
18545 .OPNOTSUPP => return errnoBug(err), // Bad flags with MAP.SHARED_VALIDATE on Linux.
18546 else => return posix.unexpectedErrno(err),
18547 }
18548 };
18549 return .{
18550 .file = file,
18551 .offset = offset,
18552 .memory = contents,
18553 .section = {},
18554 };
18555 }
18556
18557 return error.OperationUnsupported;
18558}
18559
18560fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
18561 const t: *Threaded = @ptrCast(@alignCast(userdata));
18562 const memory = mm.memory;
18563 if (mm.section) |section| switch (native_os) {
18564 .windows => {
18565 if (section == windows.INVALID_HANDLE_VALUE) return;
18566 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr);
18567 windows.CloseHandle(section);
18568 },
18569 .wasi => unreachable,
18570 else => {
18571 if (memory.len == 0) return;
18572 switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) {
18573 .SUCCESS => {},
18574 else => |e| {
18575 if (builtin.mode == .debug)
18576 std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e });
18577 },
18578 }
18579 },
18580 } else {
18581 const gpa = t.allocator;
18582 gpa.rawFree(memory, .fromByteUnits(std.heap.pageSize()), @returnAddress());
18583 }
18584 mm.* = undefined;
18585}
18586
18587fn fileMemoryMapSetLength(
18588 userdata: ?*anyopaque,
18589 mm: *File.MemoryMap,
18590 new_len: usize,
18591) File.MemoryMap.SetLengthError!void {
18592 const t: *Threaded = @ptrCast(@alignCast(userdata));
18593 const page_size = std.heap.pageSize();
18594 const alignment: Alignment = .fromByteUnits(page_size);
18595 const page_align = std.heap.page_size_min;
18596 const old_memory = mm.memory;
18597
18598 if (mm.section) |section| {
18599 _ = section;
18600 if (alignment.forward(new_len) == alignment.forward(old_memory.len)) {
18601 mm.memory.len = new_len;
18602 return;
18603 }
18604 switch (native_os) {
18605 .wasi => unreachable,
18606 .linux => {
18607 const flags: posix.MREMAP = .{ .MAYMOVE = true };
18608 const addr_hint: ?[*]const u8 = null;
18609 const new_memory = while (true) {
18610 const syscall: Syscall = try .start();
18611 const rc = posix.system.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);
18612 syscall.finish();
18613 const err: posix.E = if (builtin.link_libc) e: {
18614 if (rc != std.c.MAP_FAILED) break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..new_len];
18615 break :e @fromBackingInt(@intCast(posix.system._errno().*));
18616 } else e: {
18617 const err = posix.errno(rc);
18618 if (err == .SUCCESS) break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len];
18619 break :e err;
18620 };
18621 switch (err) {
18622 .SUCCESS => unreachable,
18623 .INTR => continue,
18624 .AGAIN => return error.LockedMemoryLimitExceeded,
18625 .NOMEM => return error.OutOfMemory,
18626 .INVAL => return errnoBug(err),
18627 .FAULT => return errnoBug(err),
18628 else => return posix.unexpectedErrno(err),
18629 }
18630 };
18631 mm.memory = new_memory;
18632 return;
18633 },
18634 else => return error.OperationUnsupported,
18635 }
18636 } else {
18637 const gpa = t.allocator;
18638 if (gpa.rawRemap(old_memory, alignment, new_len, @returnAddress())) |new_ptr| {
18639 mm.memory = @alignCast(new_ptr[0..new_len]);
18640 } else {
18641 const new_ptr: [*]align(page_align) u8 = @alignCast(
18642 gpa.rawAlloc(new_len, alignment, @returnAddress()) orelse return error.OutOfMemory,
18643 );
18644 const copy_len = @min(new_len, old_memory.len);
18645 @memcpy(new_ptr[0..copy_len], old_memory[0..copy_len]);
18646 mm.memory = new_ptr[0..new_len];
18647 gpa.rawFree(old_memory, alignment, @returnAddress());
18648 }
18649 }
18650}
18651
18652fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositionalError!void {
18653 const t: *Threaded = @ptrCast(@alignCast(userdata));
18654 _ = t;
18655 const section = mm.section orelse return mmSyncRead(mm.file, mm.memory, mm.offset);
18656 _ = section;
18657}
18658
18659fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void {
18660 const t: *Threaded = @ptrCast(@alignCast(userdata));
18661 _ = t;
18662 const section = mm.section orelse return mmSyncWrite(mm.file, mm.memory, mm.offset);
18663 _ = section;
18664}
18665
18666fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void {
18667 if (is_windows) {
18668 var i: usize = 0;
18669 while (true) {
18670 const buf = memory[i..];
18671 if (buf.len == 0) break;
18672 const n = try readFilePositionalWindows(file, buf, offset + i);
18673 if (n == 0) {
18674 @memset(memory[i..], 0);
18675 break;
18676 }
18677 i += n;
18678 }
18679 } else if (native_os == .wasi and !builtin.link_libc) {
18680 var i: usize = 0;
18681 const syscall: Syscall = try .start();
18682 while (true) {
18683 const buf = memory[i..];
18684 if (buf.len == 0) {
18685 syscall.finish();
18686 break;
18687 }
18688 var n: usize = undefined;
18689 const vec: std.os.wasi.iovec_t = .{ .base = buf.ptr, .len = buf.len };
18690 switch (std.os.wasi.fd_pread(file.handle, (&vec)[0..1], 1, offset + i, &n)) {
18691 .SUCCESS => {
18692 if (n == 0) {
18693 syscall.finish();
18694 @memset(memory[i..], 0);
18695 break;
18696 }
18697 i += n;
18698 try syscall.checkCancel();
18699 continue;
18700 },
18701 .INTR, .TIMEDOUT => {
18702 try syscall.checkCancel();
18703 continue;
18704 },
18705 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
18706 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
18707 .BADF => |err| return syscall.errnoBug(err), // use after free
18708 .INVAL => |err| return syscall.errnoBug(err),
18709 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault
18710 .AGAIN => |err| return syscall.errnoBug(err),
18711 .IO => return syscall.fail(error.InputOutput),
18712 .ISDIR => return syscall.fail(error.IsDir),
18713 .NOBUFS => return syscall.fail(error.SystemResources),
18714 .NOMEM => return syscall.fail(error.SystemResources),
18715 .NXIO => return syscall.fail(error.Unseekable),
18716 .SPIPE => return syscall.fail(error.Unseekable),
18717 .OVERFLOW => return syscall.fail(error.Unseekable),
18718 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
18719 else => |err| return syscall.unexpectedErrno(err),
18720 }
18721 }
18722 } else {
18723 var i: usize = 0;
18724 const syscall: Syscall = try .start();
18725 while (true) {
18726 const buf = memory[i..];
18727 if (buf.len == 0) {
18728 syscall.finish();
18729 break;
18730 }
18731 const rc = pread_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i));
18732 switch (posix.errno(rc)) {
18733 .SUCCESS => {
18734 const n: usize = @intCast(rc);
18735 if (n == 0) {
18736 syscall.finish();
18737 @memset(memory[i..], 0);
18738 break;
18739 }
18740 i += n;
18741 try syscall.checkCancel();
18742 continue;
18743 },
18744 .INTR, .TIMEDOUT => {
18745 try syscall.checkCancel();
18746 continue;
18747 },
18748 .NXIO => return syscall.fail(error.Unseekable),
18749 .SPIPE => return syscall.fail(error.Unseekable),
18750 .OVERFLOW => return syscall.fail(error.Unseekable),
18751 .NOBUFS => return syscall.fail(error.SystemResources),
18752 .NOMEM => return syscall.fail(error.SystemResources),
18753 .AGAIN => return syscall.fail(error.WouldBlock),
18754 .IO => return syscall.fail(error.InputOutput),
18755 .ISDIR => return syscall.fail(error.IsDir),
18756 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
18757 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
18758 .INVAL => |err| return syscall.errnoBug(err),
18759 .FAULT => |err| return syscall.errnoBug(err),
18760 .BADF => |err| return syscall.errnoBug(err), // use after free
18761 else => |err| return syscall.unexpectedErrno(err),
18762 }
18763 }
18764 }
18765}
18766
18767fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError!void {
18768 if (is_windows) {
18769 var i: usize = 0;
18770 while (true) {
18771 const buf = memory[i..];
18772 if (buf.len == 0) break;
18773 i += try writeFilePositionalWindows(file, memory[i..], offset + i);
18774 }
18775 } else if (native_os == .wasi and !builtin.link_libc) {
18776 var i: usize = 0;
18777 var n: usize = undefined;
18778 const syscall: Syscall = try .start();
18779 while (true) {
18780 const buf = memory[i..];
18781 if (buf.len == 0) {
18782 syscall.finish();
18783 break;
18784 }
18785 const iovec: std.os.wasi.ciovec_t = .{ .base = buf.ptr, .len = buf.len };
18786 switch (std.os.wasi.fd_pwrite(file.handle, (&iovec)[0..1], 1, offset + i, &n)) {
18787 .SUCCESS => {
18788 i += n;
18789 try syscall.checkCancel();
18790 continue;
18791 },
18792 .INTR => {
18793 try syscall.checkCancel();
18794 continue;
18795 },
18796 .DQUOT => return syscall.fail(error.DiskQuota),
18797 .FBIG => return syscall.fail(error.FileTooBig),
18798 .IO => return syscall.fail(error.InputOutput),
18799 .NOSPC => return syscall.fail(error.NoSpaceLeft),
18800 .PERM => return syscall.fail(error.PermissionDenied),
18801 .PIPE => return syscall.fail(error.BrokenPipe),
18802 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
18803 .NXIO => return syscall.fail(error.Unseekable),
18804 .SPIPE => return syscall.fail(error.Unseekable),
18805 .OVERFLOW => return syscall.fail(error.Unseekable),
18806 .INVAL => |err| return syscall.errnoBug(err),
18807 .FAULT => |err| return syscall.errnoBug(err),
18808 .AGAIN => |err| return syscall.errnoBug(err),
18809 .BADF => |err| return syscall.errnoBug(err), // use after free
18810 .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket
18811 else => |err| return syscall.unexpectedErrno(err),
18812 }
18813 }
18814 } else {
18815 var i: usize = 0;
18816 const syscall: Syscall = try .start();
18817 while (true) {
18818 const buf = memory[i..];
18819 if (buf.len == 0) {
18820 syscall.finish();
18821 break;
18822 }
18823 const rc = pwrite_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i));
18824 switch (posix.errno(rc)) {
18825 .SUCCESS => {
18826 const n: usize = @bitCast(rc);
18827 i += n;
18828 try syscall.checkCancel();
18829 continue;
18830 },
18831 .INTR => {
18832 try syscall.checkCancel();
18833 continue;
18834 },
18835 .INVAL => |err| return syscall.errnoBug(err),
18836 .FAULT => |err| return syscall.errnoBug(err),
18837 .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket
18838 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
18839 .BADF => return syscall.fail(error.NotOpenForWriting),
18840 .AGAIN => return syscall.fail(error.WouldBlock),
18841 .DQUOT => return syscall.fail(error.DiskQuota),
18842 .FBIG => return syscall.fail(error.FileTooBig),
18843 .IO => return syscall.fail(error.InputOutput),
18844 .NOSPC => return syscall.fail(error.NoSpaceLeft),
18845 .PERM => return syscall.fail(error.PermissionDenied),
18846 .PIPE => return syscall.fail(error.BrokenPipe),
18847 .BUSY => return syscall.fail(error.DeviceBusy),
18848 .TXTBSY => return syscall.fail(error.FileBusy),
18849 .NXIO => return syscall.fail(error.Unseekable),
18850 .SPIPE => return syscall.fail(error.Unseekable),
18851 .OVERFLOW => return syscall.fail(error.Unseekable),
18852 else => |err| return syscall.unexpectedErrno(err),
18853 }
18854 }
18855 }
18856}
18857
18858fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result {
18859 if (is_windows) {
18860 const NtControlFile = switch (o.code.DeviceType) {
18861 .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile,
18862 else => &windows.ntdll.NtDeviceIoControlFile,
18863 };
18864 var iosb: windows.IO_STATUS_BLOCK = undefined;
18865 if (o.file.flags.nonblocking) {
18866 var done: bool = false;
18867 switch (NtControlFile(
18868 o.file.handle,
18869 null, // event
18870 flagApc,
18871 &done, // APC context
18872 &iosb,
18873 o.code,
18874 if (o.in.len > 0) o.in.ptr else null,
18875 @intCast(o.in.len),
18876 if (o.out.len > 0) o.out.ptr else null,
18877 @intCast(o.out.len),
18878 )) {
18879 // We must wait for the APC routine.
18880 .PENDING, .SUCCESS => while (!done) {
18881 // Once we get here we must not return from the function until the
18882 // operation completes, thereby releasing reference to io_status_block.
18883 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
18884 error.Canceled => |e| {
18885 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
18886 _ = windows.ntdll.NtCancelIoFileEx(o.file.handle, &iosb, &cancel_iosb);
18887 while (!done) waitForApcOrAlert();
18888 return e;
18889 },
18890 };
18891 waitForApcOrAlert();
18892 alertable_syscall.finish();
18893 },
18894 else => |status| iosb.u.Status = status,
18895 }
18896 } else {
18897 const syscall: Syscall = try .start();
18898 while (true) switch (NtControlFile(
18899 o.file.handle,
18900 null, // event
18901 null, // APC routine
18902 null, // APC context
18903 &iosb,
18904 o.code,
18905 if (o.in.len > 0) o.in.ptr else null,
18906 @intCast(o.in.len),
18907 if (o.out.len > 0) o.out.ptr else null,
18908 @intCast(o.out.len),
18909 )) {
18910 .PENDING => unreachable, // unrecoverable: wrong asynchronous flag
18911 .CANCELLED => {
18912 try syscall.checkCancel();
18913 continue;
18914 },
18915 else => |status| {
18916 syscall.finish();
18917 iosb.u.Status = status;
18918 break;
18919 },
18920 };
18921 }
18922 return iosb;
18923 } else {
18924 const syscall: Syscall = try .start();
18925 while (true) {
18926 const rc = posix.system.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));
18927 switch (posix.errno(rc)) {
18928 .SUCCESS => {
18929 syscall.finish();
18930 if (@TypeOf(rc) == usize) return @bitCast(@as(u32, @truncate(rc)));
18931 return rc;
18932 },
18933 .INTR => {
18934 try syscall.checkCancel();
18935 continue;
18936 },
18937 else => |err| {
18938 syscall.finish();
18939 return -@as(i32, @backingInt(err));
18940 },
18941 }
18942 }
18943 }
18944}
18945
18946const WaitGroup = struct {
18947 state: std.atomic.Value(usize),
18948 event: Io.Event,
18949
18950 const init: WaitGroup = .{ .state = .{ .raw = 0 }, .event = .unset };
18951
18952 const is_waiting: usize = 1 << 0;
18953 const one_pending: usize = 1 << 1;
18954
18955 fn start(wg: *WaitGroup) void {
18956 const prev_state = wg.state.fetchAdd(one_pending, .monotonic);
18957 assert((prev_state / one_pending) < (std.math.maxInt(usize) / one_pending));
18958 }
18959
18960 fn value(wg: *WaitGroup) usize {
18961 return wg.state.load(.monotonic) / one_pending;
18962 }
18963
18964 fn wait(wg: *WaitGroup) void {
18965 const prev_state = wg.state.fetchAdd(is_waiting, .acquire);
18966 assert(prev_state & is_waiting == 0);
18967 if ((prev_state / one_pending) > 0) eventWait(&wg.event);
18968 }
18969
18970 fn finish(wg: *WaitGroup) void {
18971 const state = wg.state.fetchSub(one_pending, .acq_rel);
18972 assert((state / one_pending) > 0);
18973
18974 if (state == (one_pending | is_waiting)) {
18975 eventSet(&wg.event);
18976 }
18977 }
18978};
18979
18980/// Same as `Io.Event.wait` but avoids the VTable.
18981fn eventWait(event: *Io.Event) void {
18982 if (@cmpxchgStrong(Io.Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) {
18983 .unset => unreachable,
18984 .waiting => {},
18985 .is_set => return,
18986 };
18987 while (true) {
18988 Thread.futexWaitUncancelable(@ptrCast(event), @backingInt(Io.Event.waiting), null);
18989 switch (@atomicLoad(Io.Event, event, .acquire)) {
18990 .unset => unreachable, // `reset` called before pending `wait` returned
18991 .waiting => continue,
18992 .is_set => return,
18993 }
18994 }
18995}
18996
18997/// Same as `Io.Event.set` but avoids the VTable.
18998fn eventSet(event: *Io.Event) void {
18999 switch (@atomicRmw(Io.Event, event, .Xchg, .is_set, .release)) {
19000 .unset, .is_set => {},
19001 .waiting => Thread.futexWake(@ptrCast(event), std.math.maxInt(u32)),
19002 }
19003}
19004
19005/// Same as `Io.Condition.broadcast` but avoids the VTable.
19006fn condBroadcast(cond: *Io.Condition) void {
19007 var prev_state = cond.state.load(.monotonic);
19008 while (prev_state.waiters > prev_state.signals) {
19009 @branchHint(.unlikely);
19010 prev_state = cond.state.cmpxchgWeak(prev_state, .{
19011 .waiters = prev_state.waiters,
19012 .signals = prev_state.waiters,
19013 }, .release, .monotonic) orelse {
19014 // Update the epoch to tell the waiting threads that there are new signals for them.
19015 // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen
19016 // between it observing the epoch and sleeping on it, but this is extraordinarily
19017 // unlikely due to the precise number of calls required.
19018 _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update
19019 Thread.futexWake(&cond.epoch.raw, prev_state.waiters - prev_state.signals);
19020 return;
19021 };
19022 }
19023}
19024
19025/// Same as `Io.Condition.signal` but avoids the VTable.
19026fn condSignal(cond: *Io.Condition) void {
19027 var prev_state = cond.state.load(.monotonic);
19028 while (prev_state.waiters > prev_state.signals) {
19029 @branchHint(.unlikely);
19030 prev_state = cond.state.cmpxchgWeak(prev_state, .{
19031 .waiters = prev_state.waiters,
19032 .signals = prev_state.signals + 1,
19033 }, .release, .monotonic) orelse {
19034 // Update the epoch to tell the waiting threads that there are new signals for them.
19035 // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen
19036 // between it observing the epoch and sleeping on it, but this is extraordinarily
19037 // unlikely due to the precise number of calls required.
19038 _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update
19039 Thread.futexWake(&cond.epoch.raw, 1);
19040 return;
19041 };
19042 }
19043}
19044
19045/// Same as `Io.Condition.waitUncancelable` but avoids the VTable.
19046fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void {
19047 var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load
19048
19049 {
19050 const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic);
19051 assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters
19052 }
19053
19054 mutexUnlock(mutex);
19055 defer mutexLock(mutex);
19056
19057 while (true) {
19058 Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null);
19059
19060 epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod
19061
19062 var prev_state = cond.state.load(.monotonic);
19063 while (prev_state.signals > 0) {
19064 prev_state = cond.state.cmpxchgWeak(prev_state, .{
19065 .waiters = prev_state.waiters - 1,
19066 .signals = prev_state.signals - 1,
19067 }, .acquire, .monotonic) orelse {
19068 // We successfully consumed a signal.
19069 return;
19070 };
19071 }
19072 }
19073}
19074
19075/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable.
19076pub fn mutexLock(m: *Io.Mutex) void {
19077 const initial_state = m.state.cmpxchgStrong(
19078 .unlocked,
19079 .locked_once,
19080 .acquire,
19081 .monotonic,
19082 ) orelse {
19083 @branchHint(.likely);
19084 return;
19085 };
19086 if (initial_state == .contended) {
19087 Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null);
19088 }
19089 while (m.state.swap(.contended, .acquire) != .unlocked) {
19090 Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null);
19091 }
19092}
19093
19094/// Same as `Io.Mutex.unlock` but avoids the VTable.
19095pub fn mutexUnlock(m: *Io.Mutex) void {
19096 switch (m.state.swap(.unlocked, .release)) {
19097 .unlocked => unreachable,
19098 .locked_once => {},
19099 .contended => {
19100 @branchHint(.unlikely);
19101 Thread.futexWake(@ptrCast(&m.state.raw), 1);
19102 },
19103 }
19104}
19105
19106const OpenError = error{
19107 IsDir,
19108 NotDir,
19109 FileNotFound,
19110 NoDevice,
19111 AccessDenied,
19112 PipeBusy,
19113 PathAlreadyExists,
19114 WouldBlock,
19115 NetworkNotFound,
19116 AntivirusInterference,
19117 FileBusy,
19118} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
19119
19120const OpenFileOptions = struct {
19121 access_mask: windows.ACCESS_MASK,
19122 dir: ?windows.HANDLE = null,
19123 sa: ?*const windows.SECURITY_ATTRIBUTES = null,
19124 share_access: windows.FILE.SHARE = .VALID_FLAGS,
19125 creation: windows.FILE.CREATE_DISPOSITION,
19126 filter: Filter = .non_directory_only,
19127 /// If false, tries to open path as a reparse point without dereferencing it.
19128 /// Defaults to true.
19129 follow_symlinks: bool = true,
19130
19131 pub const Filter = enum {
19132 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
19133 non_directory_only,
19134 /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory.
19135 dir_only,
19136 /// `OpenFile` does not discriminate between opening files and directories.
19137 any,
19138 };
19139};
19140
19141/// TODO: inline this logic everywhere and delete this function
19142fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows.HANDLE {
19143 if (std.mem.eql(u16, sub_path_w, &.{'.'}) and options.filter == .non_directory_only) {
19144 return error.IsDir;
19145 }
19146 if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' }) and options.filter == .non_directory_only) {
19147 return error.IsDir;
19148 }
19149
19150 var result: windows.HANDLE = undefined;
19151
19152 const attr: windows.OBJECT.ATTRIBUTES = .{
19153 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
19154 .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle.toBool() else false },
19155 .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w)),
19156 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
19157 };
19158
19159 var iosb: windows.IO_STATUS_BLOCK = undefined;
19160 var attempt: u5 = 0;
19161 var syscall: Syscall = try .start();
19162 while (true) {
19163 switch (windows.ntdll.NtCreateFile(
19164 &result,
19165 options.access_mask,
19166 &attr,
19167 &iosb,
19168 null,
19169 .{ .NORMAL = true },
19170 options.share_access,
19171 options.creation,
19172 .{
19173 .DIRECTORY_FILE = options.filter == .dir_only,
19174 .NON_DIRECTORY_FILE = options.filter == .non_directory_only,
19175 .IO = .SYNCHRONOUS_NONALERT,
19176 .OPEN_REPARSE_POINT = !options.follow_symlinks,
19177 },
19178 null,
19179 0,
19180 )) {
19181 .SUCCESS => {
19182 syscall.finish();
19183 return result;
19184 },
19185 .CANCELLED => {
19186 try syscall.checkCancel();
19187 continue;
19188 },
19189 .SHARING_VIOLATION => {
19190 // This occurs if the file attempting to be opened is a running
19191 // executable. However, there's a kernel bug: the error may be
19192 // incorrectly returned for an indeterminate amount of time
19193 // after an executable file is closed. Here we work around the
19194 // kernel bug with retry attempts.
19195 syscall.finish();
19196 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
19197 try parking_sleep.sleep(.{ .duration = .{
19198 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
19199 .clock = .awake,
19200 } });
19201 attempt += 1;
19202 syscall = try .start();
19203 continue;
19204 },
19205 .DELETE_PENDING => {
19206 // This error means that there *was* a file in this location on
19207 // the file system, but it was deleted. However, the OS is not
19208 // finished with the deletion operation, and so this CreateFile
19209 // call has failed. There is not really a sane way to handle
19210 // this other than retrying the creation after the OS finishes
19211 // the deletion.
19212 syscall.finish();
19213 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy;
19214 try parking_sleep.sleep(.{ .duration = .{
19215 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
19216 .clock = .awake,
19217 } });
19218 attempt += 1;
19219 syscall = try .start();
19220 continue;
19221 },
19222 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
19223 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
19224 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
19225 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
19226 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
19227 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
19228 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
19229 .PIPE_BUSY => return syscall.fail(error.PipeBusy),
19230 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
19231 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
19232 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
19233 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
19234 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
19235 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
19236 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
19237 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
19238 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
19239 else => |status| return syscall.unexpectedNtstatus(status),
19240 }
19241 }
19242}
19243
19244pub fn closeFd(fd: posix.fd_t) void {
19245 if (native_os == .wasi and !builtin.link_libc) {
19246 switch (std.os.wasi.fd_close(fd)) {
19247 .SUCCESS, .INTR => {},
19248 .BADF => recoverableOsBugDetected(), // use after free
19249 else => recoverableOsBugDetected(), // unexpected failure
19250 }
19251 } else switch (posix.errno(posix.system.close(fd))) {
19252 .SUCCESS, .INTR => {}, // INTR still a success, see https://github.com/ziglang/zig/issues/2425
19253 .BADF => recoverableOsBugDetected(), // use after free
19254 else => recoverableOsBugDetected(), // unexpected failure
19255 }
19256}