lib/std/Io/Threaded.zig (433260B) - Raw
1 const Threaded = @This(); 2 3 const builtin = @import("builtin"); 4 const native_os = builtin.os.tag; 5 const is_windows = native_os == .windows; 6 const is_darwin = native_os.isDarwin(); 7 const is_debug = builtin.mode == .Debug; 8 9 const std = @import("../std.zig"); 10 const Io = std.Io; 11 const net = std.Io.net; 12 const File = std.Io.File; 13 const Dir = std.Io.Dir; 14 const HostName = std.Io.net.HostName; 15 const IpAddress = std.Io.net.IpAddress; 16 const Allocator = std.mem.Allocator; 17 const Alignment = std.mem.Alignment; 18 const assert = std.debug.assert; 19 const posix = std.posix; 20 const windows = std.os.windows; 21 const ws2_32 = std.os.windows.ws2_32; 22 23 /// Thread-safe. 24 allocator: Allocator, 25 mutex: std.Thread.Mutex = .{}, 26 cond: std.Thread.Condition = .{}, 27 run_queue: std.SinglyLinkedList = .{}, 28 join_requested: bool = false, 29 stack_size: usize, 30 /// All threads are spawned detached; this is how we wait until they all exit. 31 wait_group: std.Thread.WaitGroup = .{}, 32 /// Maximum thread pool size (excluding main thread) when dispatching async 33 /// tasks. Until this limit, calls to `Io.async` when all threads are busy will 34 /// cause a new thread to be spawned and permanently added to the pool. After 35 /// this limit, calls to `Io.async` when all threads are busy run the task 36 /// immediately. 37 /// 38 /// Defaults to a number equal to logical CPU cores. 39 /// 40 /// Protected by `mutex` once the I/O instance is already in use. See 41 /// `setAsyncLimit`. 42 async_limit: Io.Limit, 43 /// Maximum thread pool size (excluding main thread) for dispatching concurrent 44 /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread 45 /// pool size. 46 /// 47 /// concurrent tasks. After this number, calls to `Io.concurrent` return 48 /// `error.ConcurrencyUnavailable`. 49 concurrent_limit: Io.Limit = .unlimited, 50 /// Error from calling `std.Thread.getCpuCount` in `init`. 51 cpu_count_error: ?std.Thread.CpuCountError, 52 /// Number of threads that are unavailable to take tasks. To calculate 53 /// available count, subtract this from either `async_limit` or 54 /// `concurrent_limit`. 55 busy_count: usize = 0, 56 main_thread: Thread, 57 pid: Pid = .unknown, 58 /// When a cancel request is made, blocking syscalls can be unblocked by 59 /// issuing a signal. However, if the signal arrives after the check and before 60 /// the syscall instruction, it is missed. 61 /// 62 /// This option solves the race condition by retrying the signal delivery 63 /// until it is acknowledged, with an exponential backoff. 64 /// 65 /// Unfortunately, trying again until the cancellation request is acknowledged 66 /// has been observed to be relatively slow, and usually strong cancellation 67 /// guarantees are not needed, so this defaults to off. 68 robust_cancel: RobustCancel = .disabled, 69 70 wsa: if (is_windows) Wsa else struct {} = .{}, 71 72 have_signal_handler: bool, 73 old_sig_io: if (have_sig_io) posix.Sigaction else void, 74 old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, 75 76 use_sendfile: UseSendfile = .default, 77 use_copy_file_range: UseCopyFileRange = .default, 78 use_fcopyfile: UseFcopyfile = .default, 79 use_fchmodat2: UseFchmodat2 = .default, 80 81 stderr_writer: File.Writer = .{ 82 .io = undefined, 83 .interface = Io.File.Writer.initInterface(&.{}), 84 .file = if (is_windows) undefined else .stderr(), 85 .mode = undefined, 86 }, 87 stderr_writer_initialized: bool = false, 88 89 pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { 90 enabled, 91 disabled, 92 } else enum { 93 disabled, 94 }; 95 96 pub const Pid = if (native_os == .linux) enum(posix.pid_t) { 97 unknown = 0, 98 _, 99 } else enum(u0) { unknown = 0 }; 100 101 pub const UseSendfile = if (have_sendfile) enum { 102 enabled, 103 disabled, 104 pub const default: UseSendfile = .enabled; 105 } else enum { 106 disabled, 107 pub const default: UseSendfile = .disabled; 108 }; 109 110 pub const UseCopyFileRange = if (have_copy_file_range) enum { 111 enabled, 112 disabled, 113 pub const default: UseCopyFileRange = .enabled; 114 } else enum { 115 disabled, 116 pub const default: UseCopyFileRange = .disabled; 117 }; 118 119 pub const UseFcopyfile = if (have_fcopyfile) enum { 120 enabled, 121 disabled, 122 pub const default: UseFcopyfile = .enabled; 123 } else enum { 124 disabled, 125 pub const default: UseFcopyfile = .disabled; 126 }; 127 128 pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum { 129 enabled, 130 disabled, 131 pub const default: UseFchmodat2 = .enabled; 132 } else enum { 133 disabled, 134 pub const default: UseFchmodat2 = .disabled; 135 }; 136 137 const Thread = struct { 138 /// The value that needs to be passed to pthread_kill or tgkill in order to 139 /// send a signal. 140 signal_id: SignaleeId, 141 current_closure: ?*Closure, 142 /// Only populated if `current_closure != null`. Indicates the current cancel protection mode. 143 cancel_protection: Io.CancelProtection, 144 145 const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; 146 147 threadlocal var current: ?*Thread = null; 148 149 fn getCurrent(t: *Threaded) *Thread { 150 return current orelse return &t.main_thread; 151 } 152 153 fn checkCancel(thread: *Thread) error{Canceled}!void { 154 const closure = thread.current_closure orelse return; 155 156 switch (thread.cancel_protection) { 157 .unblocked => {}, 158 .blocked => return, 159 } 160 161 switch (@cmpxchgStrong( 162 CancelStatus, 163 &closure.cancel_status, 164 .requested, 165 .acknowledged, 166 .acq_rel, 167 .acquire, 168 ) orelse return error.Canceled) { 169 .requested => unreachable, 170 .acknowledged => unreachable, 171 .none, _ => {}, 172 } 173 } 174 175 fn beginSyscall(thread: *Thread) error{Canceled}!void { 176 const closure = thread.current_closure orelse return; 177 178 switch (thread.cancel_protection) { 179 .unblocked => {}, 180 .blocked => return, 181 } 182 183 switch (@cmpxchgStrong( 184 CancelStatus, 185 &closure.cancel_status, 186 .none, 187 .fromSignaleeId(thread.signal_id), 188 .acq_rel, 189 .acquire, 190 ) orelse return) { 191 .none => unreachable, 192 .requested => { 193 @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); 194 return error.Canceled; 195 }, 196 .acknowledged => return, 197 _ => unreachable, 198 } 199 } 200 201 fn endSyscall(thread: *Thread) void { 202 const closure = thread.current_closure orelse return; 203 204 switch (thread.cancel_protection) { 205 .unblocked => {}, 206 .blocked => return, 207 } 208 209 _ = @cmpxchgStrong( 210 CancelStatus, 211 &closure.cancel_status, 212 .fromSignaleeId(thread.signal_id), 213 .none, 214 .acq_rel, 215 .acquire, 216 ) orelse return; 217 } 218 219 fn endSyscallCanceled(thread: *Thread) Io.Cancelable { 220 if (thread.current_closure) |closure| { 221 @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); 222 } 223 return error.Canceled; 224 } 225 226 fn currentSignalId() SignaleeId { 227 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); 228 } 229 230 fn futexWaitUncancelable(ptr: *const u32, expect: u32) void { 231 return Thread.futexWaitTimed(null, ptr, expect, null) catch unreachable; 232 } 233 234 fn futexWait(thread: *Thread, ptr: *const u32, expect: u32) Io.Cancelable!void { 235 return Thread.futexWaitTimed(thread, ptr, expect, null) catch |err| switch (err) { 236 error.Canceled => return error.Canceled, 237 error.Timeout => unreachable, 238 }; 239 } 240 241 fn futexWaitTimed(thread: ?*Thread, ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void { 242 @branchHint(.cold); 243 244 if (builtin.single_threaded) unreachable; // nobody would ever wake us 245 246 if (builtin.cpu.arch.isWasm()) { 247 comptime assert(builtin.cpu.has(.wasm, .atomics)); 248 if (thread) |t| try t.checkCancel(); 249 const to: i64 = if (timeout_ns) |ns| ns else -1; 250 const signed_expect: i32 = @bitCast(expect); 251 const result = asm volatile ( 252 \\local.get %[ptr] 253 \\local.get %[expected] 254 \\local.get %[timeout] 255 \\memory.atomic.wait32 0 256 \\local.set %[ret] 257 : [ret] "=r" (-> u32), 258 : [ptr] "r" (ptr), 259 [expected] "r" (signed_expect), 260 [timeout] "r" (to), 261 ); 262 switch (result) { 263 0 => {}, // ok 264 1 => {}, // expected != loaded 265 2 => {}, // timeout 266 else => assert(!is_debug), 267 } 268 } else switch (native_os) { 269 .linux => { 270 const linux = std.os.linux; 271 var ts_buffer: linux.timespec = undefined; 272 const ts: ?*linux.timespec = if (timeout_ns) |ns| ts: { 273 ts_buffer = timestampToPosix(ns); 274 break :ts &ts_buffer; 275 } else null; 276 if (thread) |t| try t.beginSyscall(); 277 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts); 278 if (thread) |t| t.endSyscall(); 279 switch (linux.errno(rc)) { 280 .SUCCESS => {}, // notified by `wake()` 281 .INTR => {}, // caller's responsibility to retry 282 .AGAIN => {}, // ptr.* != expect 283 .INVAL => {}, // possibly timeout overflow 284 .TIMEDOUT => {}, // timeout 285 .FAULT => recoverableOsBugDetected(), // ptr was invalid 286 else => recoverableOsBugDetected(), 287 } 288 }, 289 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 290 const c = std.c; 291 const flags: c.UL = .{ 292 .op = .COMPARE_AND_WAIT, 293 .NO_ERRNO = true, 294 }; 295 if (thread) |t| try t.beginSyscall(); 296 const status = switch (darwin_supports_ulock_wait2) { 297 true => c.__ulock_wait2(flags, ptr, expect, ns: { 298 const ns = timeout_ns orelse break :ns 0; 299 if (ns == 0) break :ns 1; 300 break :ns ns; 301 }, 0), 302 false => c.__ulock_wait(flags, ptr, expect, us: { 303 const ns = timeout_ns orelse break :us 0; 304 const us = std.math.lossyCast(u32, ns / std.time.ns_per_us); 305 if (us == 0) break :us 1; 306 break :us us; 307 }), 308 }; 309 if (thread) |t| t.endSyscall(); 310 if (status >= 0) return; 311 switch (@as(c.E, @enumFromInt(-status))) { 312 .INTR => {}, // spurious wake 313 // Address of the futex was paged out. This is unlikely, but possible in theory, and 314 // pthread/libdispatch on darwin bother to handle it. In this case we'll return 315 // without waiting, but the caller should retry anyway. 316 .FAULT => {}, 317 .TIMEDOUT => {}, // timeout 318 else => recoverableOsBugDetected(), 319 } 320 }, 321 .windows => { 322 var timeout_value: windows.LARGE_INTEGER = undefined; 323 var timeout_ptr: ?*const windows.LARGE_INTEGER = null; 324 // NTDLL functions work with time in units of 100 nanoseconds. 325 // Positive values are absolute deadlines while negative values are relative durations. 326 if (timeout_ns) |delay| { 327 timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100)); 328 timeout_value = -timeout_value; 329 timeout_ptr = &timeout_value; 330 } 331 if (thread) |t| try t.checkCancel(); 332 switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), timeout_ptr)) { 333 .SUCCESS => {}, 334 .CANCELLED => {}, 335 .TIMEOUT => {}, // timeout 336 else => recoverableOsBugDetected(), 337 } 338 }, 339 .freebsd => { 340 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); 341 var tm_size: usize = 0; 342 var tm: std.c._umtx_time = undefined; 343 var tm_ptr: ?*const std.c._umtx_time = null; 344 if (timeout_ns) |ns| { 345 tm_ptr = &tm; 346 tm_size = @sizeOf(@TypeOf(tm)); 347 tm.flags = 0; // use relative time not UMTX_ABSTIME 348 tm.clockid = .MONOTONIC; 349 tm.timeout = timestampToPosix(ns); 350 } 351 if (thread) |t| try t.beginSyscall(); 352 const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr)); 353 if (thread) |t| t.endSyscall(); 354 if (is_debug) switch (posix.errno(rc)) { 355 .SUCCESS => {}, 356 .FAULT => unreachable, // one of the args points to invalid memory 357 .INVAL => unreachable, // arguments should be correct 358 .TIMEDOUT => {}, // timeout 359 .INTR => {}, // spurious wake 360 else => unreachable, 361 }; 362 }, 363 else => @compileError("unimplemented: futexWait"), 364 } 365 } 366 367 fn futexWake(ptr: *const u32, max_waiters: u32) void { 368 @branchHint(.cold); 369 370 if (builtin.single_threaded) return; // nothing to wake up 371 372 if (builtin.cpu.arch.isWasm()) { 373 comptime assert(builtin.cpu.has(.wasm, .atomics)); 374 assert(max_waiters != 0); 375 const woken_count = asm volatile ( 376 \\local.get %[ptr] 377 \\local.get %[waiters] 378 \\memory.atomic.notify 0 379 \\local.set %[ret] 380 : [ret] "=r" (-> u32), 381 : [ptr] "r" (ptr), 382 [waiters] "r" (max_waiters), 383 ); 384 _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled 385 } else switch (native_os) { 386 .linux => { 387 const linux = std.os.linux; 388 switch (linux.errno(linux.futex_3arg( 389 ptr, 390 .{ .cmd = .WAKE, .private = true }, 391 @min(max_waiters, std.math.maxInt(i32)), 392 ))) { 393 .SUCCESS => return, // successful wake up 394 .INVAL => return, // invalid futex_wait() on ptr done elsewhere 395 .FAULT => return, // pointer became invalid while doing the wake 396 else => return recoverableOsBugDetected(), // deadlock due to operating system bug 397 } 398 }, 399 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 400 const c = std.c; 401 const flags: c.UL = .{ 402 .op = .COMPARE_AND_WAIT, 403 .NO_ERRNO = true, 404 .WAKE_ALL = max_waiters > 1, 405 }; 406 while (true) { 407 const status = c.__ulock_wake(flags, ptr, 0); 408 if (status >= 0) return; 409 switch (@as(c.E, @enumFromInt(-status))) { 410 .INTR, .CANCELED => continue, // spurious wake() 411 .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t 412 .NOENT => return, // nothing was woken up 413 .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD 414 else => unreachable, // deadlock due to operating system bug 415 } 416 } 417 }, 418 .windows => { 419 assert(max_waiters != 0); 420 switch (max_waiters) { 421 1 => windows.ntdll.RtlWakeAddressSingle(ptr), 422 else => windows.ntdll.RtlWakeAddressAll(ptr), 423 } 424 }, 425 .freebsd => { 426 const rc = std.c._umtx_op( 427 @intFromPtr(ptr), 428 @intFromEnum(std.c.UMTX_OP.WAKE_PRIVATE), 429 @as(c_ulong, max_waiters), 430 0, // there is no timeout struct 431 0, // there is no timeout struct pointer 432 ); 433 switch (posix.errno(rc)) { 434 .SUCCESS => {}, 435 .FAULT => {}, // it's ok if the ptr doesn't point to valid memory 436 .INVAL => unreachable, // arguments should be correct 437 else => unreachable, // deadlock due to operating system bug 438 } 439 }, 440 else => @compileError("unimplemented: futexWake"), 441 } 442 } 443 }; 444 445 const max_iovecs_len = 8; 446 const splat_buffer_size = 64; 447 448 comptime { 449 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX); 450 } 451 452 const CancelStatus = enum(usize) { 453 /// Cancellation has neither been requested, nor checked. The async 454 /// operation will check status before entering a blocking syscall. 455 /// This is also the status used for uninteruptible tasks. 456 none = 0, 457 /// Cancellation has been requested and the status will be checked before 458 /// entering a blocking syscall. 459 requested = std.math.maxInt(usize) - 1, 460 /// Cancellation has been acknowledged and is in progress. Signals should 461 /// not be sent. 462 acknowledged = std.math.maxInt(usize), 463 /// Stores a `Thread.SignaleeId` and indicates that sending a signal to this thread 464 /// is needed in order to cancel. This state is set before going into 465 /// a blocking operation that needs to get unblocked via signal. 466 _, 467 468 const Unpacked = union(enum) { 469 none, 470 requested, 471 acknowledged, 472 signal_id: Thread.SignaleeId, 473 }; 474 475 fn unpack(cs: CancelStatus) Unpacked { 476 return switch (cs) { 477 .none => .none, 478 .requested => .requested, 479 .acknowledged => .acknowledged, 480 _ => |signal_id| .{ 481 .signal_id = if (std.Thread.use_pthreads) 482 @ptrFromInt(@intFromEnum(signal_id)) 483 else 484 @truncate(@intFromEnum(signal_id)), 485 }, 486 }; 487 } 488 489 fn fromSignaleeId(signal_id: Thread.SignaleeId) CancelStatus { 490 return if (std.Thread.use_pthreads) 491 @enumFromInt(@intFromPtr(signal_id)) 492 else 493 @enumFromInt(signal_id); 494 } 495 }; 496 497 const Closure = struct { 498 start: Start, 499 node: std.SinglyLinkedList.Node = .{}, 500 cancel_status: CancelStatus, 501 502 const Start = *const fn (*Closure, *Threaded) void; 503 504 fn requestCancel(closure: *Closure, t: *Threaded) void { 505 var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { 506 .none, .acknowledged, .requested => return, 507 .signal_id => |signal_id| signal_id, 508 }; 509 // The task will enter a blocking syscall before checking for cancellation again. 510 // We can send a signal to interrupt the syscall, but if it arrives before 511 // the syscall instruction, it will be missed. Therefore, this code tries 512 // again until the cancellation request is acknowledged. 513 514 // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. 515 // 1 << 20 ns is about 1 millisecond. 516 // 1 << 30 ns is about 1 second. 517 // 518 // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 519 // attempts not acknowledged before the timeout (including exponential 520 // backoff) was sufficient, despite the heavy load. 521 const max_attempts = 22; 522 523 for (0..max_attempts) |attempt_index| { 524 if (std.Thread.use_pthreads) { 525 if (std.c.pthread_kill(signal_id, .IO) != 0) return; 526 } else if (native_os == .linux) { 527 const pid: posix.pid_t = p: { 528 const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); 529 if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); 530 const pid = std.os.linux.getpid(); 531 @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); 532 break :p pid; 533 }; 534 if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; 535 } else { 536 return; 537 } 538 539 if (t.robust_cancel != .enabled) return; 540 541 var timespec: posix.timespec = .{ 542 .sec = 0, 543 .nsec = @as(isize, 1) << @intCast(attempt_index), 544 }; 545 if (native_os == .linux) { 546 _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec); 547 } else { 548 _ = posix.system.nanosleep(×pec, ×pec); 549 } 550 551 switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { 552 .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. 553 .none, .acknowledged => return, 554 .signal_id => |new_signal_id| signal_id = new_signal_id, 555 } 556 } 557 } 558 }; 559 560 /// Related: 561 /// * `init_single_threaded` 562 pub fn init( 563 /// Must be threadsafe. Only used for the following functions: 564 /// * `Io.VTable.async` 565 /// * `Io.VTable.concurrent` 566 /// * `Io.VTable.groupAsync` 567 /// * `Io.VTable.groupConcurrent` 568 /// If these functions are avoided, then `Allocator.failing` may be passed 569 /// here. 570 gpa: Allocator, 571 ) Threaded { 572 if (builtin.single_threaded) return .init_single_threaded; 573 574 const cpu_count = std.Thread.getCpuCount(); 575 576 var t: Threaded = .{ 577 .allocator = gpa, 578 .stack_size = std.Thread.SpawnConfig.default_stack_size, 579 .async_limit = if (cpu_count) |n| .limited(n - 1) else |_| .nothing, 580 .cpu_count_error = if (cpu_count) |_| null else |e| e, 581 .old_sig_io = undefined, 582 .old_sig_pipe = undefined, 583 .have_signal_handler = false, 584 .main_thread = .{ 585 .signal_id = Thread.currentSignalId(), 586 .current_closure = null, 587 .cancel_protection = undefined, 588 }, 589 }; 590 591 if (posix.Sigaction != void) { 592 // This causes sending `posix.SIG.IO` to thread to interrupt blocking 593 // syscalls, returning `posix.E.INTR`. 594 const act: posix.Sigaction = .{ 595 .handler = .{ .handler = doNothingSignalHandler }, 596 .mask = posix.sigemptyset(), 597 .flags = 0, 598 }; 599 if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); 600 if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe); 601 t.have_signal_handler = true; 602 } 603 604 return t; 605 } 606 607 /// Statically initialize such that calls to `Io.VTable.concurrent` will fail 608 /// with `error.ConcurrencyUnavailable`. 609 /// 610 /// When initialized this way: 611 /// * cancel requests have no effect. 612 /// * `deinit` is safe, but unnecessary to call. 613 pub const init_single_threaded: Threaded = .{ 614 .allocator = .failing, 615 .stack_size = std.Thread.SpawnConfig.default_stack_size, 616 .async_limit = .nothing, 617 .cpu_count_error = null, 618 .concurrent_limit = .nothing, 619 .old_sig_io = undefined, 620 .old_sig_pipe = undefined, 621 .have_signal_handler = false, 622 .main_thread = .{ 623 .signal_id = undefined, 624 .current_closure = null, 625 .cancel_protection = undefined, 626 }, 627 }; 628 629 pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { 630 t.mutex.lock(); 631 defer t.mutex.unlock(); 632 t.async_limit = new_limit; 633 } 634 635 pub fn deinit(t: *Threaded) void { 636 t.join(); 637 if (is_windows and t.wsa.status == .initialized) { 638 if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); 639 } 640 if (posix.Sigaction != void and t.have_signal_handler) { 641 if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); 642 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); 643 } 644 t.* = undefined; 645 } 646 647 fn join(t: *Threaded) void { 648 if (builtin.single_threaded) return; 649 { 650 t.mutex.lock(); 651 defer t.mutex.unlock(); 652 t.join_requested = true; 653 } 654 t.cond.broadcast(); 655 t.wait_group.wait(); 656 } 657 658 fn worker(t: *Threaded) void { 659 var thread: Thread = .{ 660 .signal_id = Thread.currentSignalId(), 661 .current_closure = null, 662 .cancel_protection = undefined, 663 }; 664 Thread.current = &thread; 665 666 defer t.wait_group.finish(); 667 668 t.mutex.lock(); 669 defer t.mutex.unlock(); 670 671 while (true) { 672 while (t.run_queue.popFirst()) |closure_node| { 673 t.mutex.unlock(); 674 const closure: *Closure = @fieldParentPtr("node", closure_node); 675 closure.start(closure, t); 676 t.mutex.lock(); 677 t.busy_count -= 1; 678 } 679 if (t.join_requested) break; 680 t.cond.wait(&t.mutex); 681 } 682 } 683 684 pub fn io(t: *Threaded) Io { 685 return .{ 686 .userdata = t, 687 .vtable = &.{ 688 .async = async, 689 .concurrent = concurrent, 690 .await = await, 691 .cancel = cancel, 692 .select = select, 693 694 .groupAsync = groupAsync, 695 .groupConcurrent = groupConcurrent, 696 .groupWait = groupWait, 697 .groupCancel = groupCancel, 698 699 .recancel = recancel, 700 .swapCancelProtection = swapCancelProtection, 701 .checkCancel = checkCancel, 702 703 .futexWait = futexWait, 704 .futexWaitUncancelable = futexWaitUncancelable, 705 .futexWake = futexWake, 706 707 .dirMake = dirMake, 708 .dirMakePath = dirMakePath, 709 .dirMakeOpenPath = dirMakeOpenPath, 710 .dirStat = dirStat, 711 .dirStatFile = dirStatFile, 712 .dirAccess = dirAccess, 713 .dirCreateFile = dirCreateFile, 714 .dirOpenFile = dirOpenFile, 715 .dirOpenDir = dirOpenDir, 716 .dirClose = dirClose, 717 .dirRead = dirRead, 718 .dirRealPath = dirRealPath, 719 .dirDeleteFile = dirDeleteFile, 720 .dirDeleteDir = dirDeleteDir, 721 .dirRename = dirRename, 722 .dirSymLink = dirSymLink, 723 .dirReadLink = dirReadLink, 724 .dirSetOwner = dirSetOwner, 725 .dirSetFileOwner = dirSetFileOwner, 726 .dirSetPermissions = dirSetPermissions, 727 .dirSetFilePermissions = dirSetFilePermissions, 728 .dirSetTimestamps = dirSetTimestamps, 729 .dirSetTimestampsNow = dirSetTimestampsNow, 730 731 .fileStat = fileStat, 732 .fileLength = fileLength, 733 .fileClose = fileClose, 734 .fileWriteStreaming = fileWriteStreaming, 735 .fileWritePositional = fileWritePositional, 736 .fileWriteFileStreaming = fileWriteFileStreaming, 737 .fileWriteFilePositional = fileWriteFilePositional, 738 .fileReadStreaming = fileReadStreaming, 739 .fileReadPositional = fileReadPositional, 740 .fileSeekBy = fileSeekBy, 741 .fileSeekTo = fileSeekTo, 742 .fileSync = fileSync, 743 .fileIsTty = fileIsTty, 744 .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, 745 .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, 746 .fileSetLength = fileSetLength, 747 .fileSetOwner = fileSetOwner, 748 .fileSetPermissions = fileSetPermissions, 749 .fileSetTimestamps = fileSetTimestamps, 750 .fileSetTimestampsNow = fileSetTimestampsNow, 751 .fileLock = fileLock, 752 .fileTryLock = fileTryLock, 753 .fileUnlock = fileUnlock, 754 .fileDowngradeLock = fileDowngradeLock, 755 756 .processExecutableOpen = processExecutableOpen, 757 .processExecutablePath = processExecutablePath, 758 .lockStderrWriter = lockStderrWriter, 759 .tryLockStderrWriter = tryLockStderrWriter, 760 .unlockStderrWriter = unlockStderrWriter, 761 762 .now = now, 763 .sleep = sleep, 764 765 .netListenIp = switch (native_os) { 766 .windows => netListenIpWindows, 767 else => netListenIpPosix, 768 }, 769 .netListenUnix = switch (native_os) { 770 .windows => netListenUnixWindows, 771 else => netListenUnixPosix, 772 }, 773 .netAccept = switch (native_os) { 774 .windows => netAcceptWindows, 775 else => netAcceptPosix, 776 }, 777 .netBindIp = switch (native_os) { 778 .windows => netBindIpWindows, 779 else => netBindIpPosix, 780 }, 781 .netConnectIp = switch (native_os) { 782 .windows => netConnectIpWindows, 783 else => netConnectIpPosix, 784 }, 785 .netConnectUnix = switch (native_os) { 786 .windows => netConnectUnixWindows, 787 else => netConnectUnixPosix, 788 }, 789 .netClose = netClose, 790 .netRead = switch (native_os) { 791 .windows => netReadWindows, 792 else => netReadPosix, 793 }, 794 .netWrite = switch (native_os) { 795 .windows => netWriteWindows, 796 else => netWritePosix, 797 }, 798 .netWriteFile = netWriteFile, 799 .netSend = switch (native_os) { 800 .windows => netSendWindows, 801 else => netSendPosix, 802 }, 803 .netReceive = switch (native_os) { 804 .windows => netReceiveWindows, 805 else => netReceivePosix, 806 }, 807 .netInterfaceNameResolve = netInterfaceNameResolve, 808 .netInterfaceName = netInterfaceName, 809 .netLookup = netLookup, 810 }, 811 }; 812 } 813 814 /// Same as `io` but disables all networking functionality, which has 815 /// an additional dependency on Windows (ws2_32). 816 pub fn ioBasic(t: *Threaded) Io { 817 return .{ 818 .userdata = t, 819 .vtable = &.{ 820 .async = async, 821 .concurrent = concurrent, 822 .await = await, 823 .cancel = cancel, 824 .select = select, 825 826 .groupAsync = groupAsync, 827 .groupConcurrent = groupConcurrent, 828 .groupWait = groupWait, 829 .groupCancel = groupCancel, 830 831 .recancel = recancel, 832 .swapCancelProtection = swapCancelProtection, 833 .checkCancel = checkCancel, 834 835 .futexWait = futexWait, 836 .futexWaitUncancelable = futexWaitUncancelable, 837 .futexWake = futexWake, 838 839 .dirMake = dirMake, 840 .dirMakePath = dirMakePath, 841 .dirMakeOpenPath = dirMakeOpenPath, 842 .dirStat = dirStat, 843 .dirStatFile = dirStatFile, 844 .dirAccess = dirAccess, 845 .dirCreateFile = dirCreateFile, 846 .dirOpenFile = dirOpenFile, 847 .dirOpenDir = dirOpenDir, 848 .dirClose = dirClose, 849 .dirRead = dirRead, 850 .dirRealPath = dirRealPath, 851 .dirDeleteFile = dirDeleteFile, 852 .dirDeleteDir = dirDeleteDir, 853 .dirRename = dirRename, 854 .dirSymLink = dirSymLink, 855 .dirReadLink = dirReadLink, 856 .dirSetOwner = dirSetOwner, 857 .dirSetFileOwner = dirSetFileOwner, 858 .dirSetPermissions = dirSetPermissions, 859 .dirSetFilePermissions = dirSetFilePermissions, 860 .dirSetTimestamps = dirSetTimestamps, 861 .dirSetTimestampsNow = dirSetTimestampsNow, 862 863 .fileStat = fileStat, 864 .fileLength = fileLength, 865 .fileClose = fileClose, 866 .fileWriteStreaming = fileWriteStreaming, 867 .fileWritePositional = fileWritePositional, 868 .fileWriteFileStreaming = fileWriteFileStreaming, 869 .fileWriteFilePositional = fileWriteFilePositional, 870 .fileReadStreaming = fileReadStreaming, 871 .fileReadPositional = fileReadPositional, 872 .fileSeekBy = fileSeekBy, 873 .fileSeekTo = fileSeekTo, 874 .fileSync = fileSync, 875 .fileIsTty = fileIsTty, 876 .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, 877 .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, 878 .fileSetLength = fileSetLength, 879 .fileSetOwner = fileSetOwner, 880 .fileSetPermissions = fileSetPermissions, 881 .fileSetTimestamps = fileSetTimestamps, 882 .fileSetTimestampsNow = fileSetTimestampsNow, 883 .fileLock = fileLock, 884 .fileTryLock = fileTryLock, 885 .fileUnlock = fileUnlock, 886 .fileDowngradeLock = fileDowngradeLock, 887 888 .processExecutableOpen = processExecutableOpen, 889 .processExecutablePath = processExecutablePath, 890 .lockStderrWriter = lockStderrWriter, 891 .tryLockStderrWriter = tryLockStderrWriter, 892 .unlockStderrWriter = unlockStderrWriter, 893 894 .now = now, 895 .sleep = sleep, 896 897 .netListenIp = netListenIpUnavailable, 898 .netListenUnix = netListenUnixUnavailable, 899 .netAccept = netAcceptUnavailable, 900 .netBindIp = netBindIpUnavailable, 901 .netConnectIp = netConnectIpUnavailable, 902 .netConnectUnix = netConnectUnixUnavailable, 903 .netClose = netCloseUnavailable, 904 .netRead = netReadUnavailable, 905 .netWrite = netWriteUnavailable, 906 .netWriteFile = netWriteFileUnavailable, 907 .netSend = netSendUnavailable, 908 .netReceive = netReceiveUnavailable, 909 .netInterfaceNameResolve = netInterfaceNameResolveUnavailable, 910 .netInterfaceName = netInterfaceNameUnavailable, 911 .netLookup = netLookupUnavailable, 912 }, 913 }; 914 } 915 916 pub const socket_flags_unsupported = is_darwin or native_os == .haiku; 917 const have_accept4 = !socket_flags_unsupported; 918 const have_flock_open_flags = @hasField(posix.O, "EXLOCK"); 919 const have_networking = native_os != .wasi; 920 const have_flock = @TypeOf(posix.system.flock) != void; 921 const have_sendmmsg = native_os == .linux; 922 const have_futex = switch (builtin.cpu.arch) { 923 .wasm32, .wasm64 => builtin.cpu.has(.wasm, .atomics), 924 else => true, 925 }; 926 const have_preadv = switch (native_os) { 927 .windows, .haiku => false, 928 else => true, 929 }; 930 const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO"); 931 const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE"); 932 const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux; 933 const have_copy_file_range = switch (native_os) { 934 .linux, .freebsd => true, 935 else => false, 936 }; 937 const have_fcopyfile = is_darwin; 938 const have_fchmodat2 = native_os == .linux and 939 (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and 940 (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); 941 const have_fchmodat_flags = native_os != .linux or 942 (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); 943 944 const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; 945 const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; 946 const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; 947 const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; 948 const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; 949 const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; 950 const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev; 951 const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile; 952 const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ 953 .major = 34, 954 .minor = 0, 955 .patch = 0, 956 } else .{ 957 .major = 2, 958 .minor = 27, 959 .patch = 0, 960 }); 961 const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; 962 963 /// Trailing data: 964 /// 1. context 965 /// 2. result 966 const AsyncClosure = struct { 967 closure: Closure, 968 func: *const fn (context: *anyopaque, result: *anyopaque) void, 969 event: Io.Event, 970 select_condition: ?*Io.Event, 971 context_alignment: Alignment, 972 result_offset: usize, 973 alloc_len: usize, 974 975 const done_event: *Io.Event = @ptrFromInt(@alignOf(Io.Event)); 976 977 fn start(closure: *Closure, t: *Threaded) void { 978 const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure)); 979 const current_thread = Thread.getCurrent(t); 980 981 current_thread.current_closure = closure; 982 current_thread.cancel_protection = .unblocked; 983 984 ac.func(ac.contextPointer(), ac.resultPointer()); 985 986 current_thread.current_closure = null; 987 current_thread.cancel_protection = undefined; 988 989 if (@atomicRmw(?*Io.Event, &ac.select_condition, .Xchg, done_event, .release)) |select_event| { 990 assert(select_event != done_event); 991 select_event.set(ioBasic(t)); 992 } 993 ac.event.set(ioBasic(t)); 994 } 995 996 fn resultPointer(ac: *AsyncClosure) [*]u8 { 997 const base: [*]u8 = @ptrCast(ac); 998 return base + ac.result_offset; 999 } 1000 1001 fn contextPointer(ac: *AsyncClosure) [*]u8 { 1002 const base: [*]u8 = @ptrCast(ac); 1003 const context_offset = ac.context_alignment.forward(@intFromPtr(ac) + @sizeOf(AsyncClosure)) - @intFromPtr(ac); 1004 return base + context_offset; 1005 } 1006 1007 fn init( 1008 gpa: Allocator, 1009 result_len: usize, 1010 result_alignment: Alignment, 1011 context: []const u8, 1012 context_alignment: Alignment, 1013 func: *const fn (context: *const anyopaque, result: *anyopaque) void, 1014 ) Allocator.Error!*AsyncClosure { 1015 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(AsyncClosure); 1016 const worst_case_context_offset = context_alignment.forward(@sizeOf(AsyncClosure) + max_context_misalignment); 1017 const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len); 1018 const alloc_len = worst_case_result_offset + result_len; 1019 1020 const ac: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), alloc_len))); 1021 errdefer comptime unreachable; 1022 1023 const actual_context_addr = context_alignment.forward(@intFromPtr(ac) + @sizeOf(AsyncClosure)); 1024 const actual_result_addr = result_alignment.forward(actual_context_addr + context.len); 1025 const actual_result_offset = actual_result_addr - @intFromPtr(ac); 1026 ac.* = .{ 1027 .closure = .{ 1028 .cancel_status = .none, 1029 .start = start, 1030 }, 1031 .func = func, 1032 .context_alignment = context_alignment, 1033 .result_offset = actual_result_offset, 1034 .alloc_len = alloc_len, 1035 .event = .unset, 1036 .select_condition = null, 1037 }; 1038 @memcpy(ac.contextPointer()[0..context.len], context); 1039 return ac; 1040 } 1041 1042 fn waitAndDeinit(ac: *AsyncClosure, t: *Threaded, result: []u8) void { 1043 ac.event.wait(ioBasic(t)) catch |err| switch (err) { 1044 error.Canceled => { 1045 ac.closure.requestCancel(t); 1046 ac.event.waitUncancelable(ioBasic(t)); 1047 }, 1048 }; 1049 @memcpy(result, ac.resultPointer()[0..result.len]); 1050 ac.deinit(t.allocator); 1051 } 1052 1053 fn deinit(ac: *AsyncClosure, gpa: Allocator) void { 1054 const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(ac); 1055 gpa.free(base[0..ac.alloc_len]); 1056 } 1057 }; 1058 1059 fn async( 1060 userdata: ?*anyopaque, 1061 result: []u8, 1062 result_alignment: Alignment, 1063 context: []const u8, 1064 context_alignment: Alignment, 1065 start: *const fn (context: *const anyopaque, result: *anyopaque) void, 1066 ) ?*Io.AnyFuture { 1067 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1068 if (builtin.single_threaded) { 1069 start(context.ptr, result.ptr); 1070 return null; 1071 } 1072 const gpa = t.allocator; 1073 const ac = AsyncClosure.init(gpa, result.len, result_alignment, context, context_alignment, start) catch { 1074 start(context.ptr, result.ptr); 1075 return null; 1076 }; 1077 1078 t.mutex.lock(); 1079 1080 const busy_count = t.busy_count; 1081 1082 if (busy_count >= @intFromEnum(t.async_limit)) { 1083 t.mutex.unlock(); 1084 ac.deinit(gpa); 1085 start(context.ptr, result.ptr); 1086 return null; 1087 } 1088 1089 t.busy_count = busy_count + 1; 1090 1091 const pool_size = t.wait_group.value(); 1092 if (pool_size - busy_count == 0) { 1093 t.wait_group.start(); 1094 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { 1095 t.wait_group.finish(); 1096 t.busy_count = busy_count; 1097 t.mutex.unlock(); 1098 ac.deinit(gpa); 1099 start(context.ptr, result.ptr); 1100 return null; 1101 }; 1102 thread.detach(); 1103 } 1104 1105 t.run_queue.prepend(&ac.closure.node); 1106 t.mutex.unlock(); 1107 t.cond.signal(); 1108 return @ptrCast(ac); 1109 } 1110 1111 fn concurrent( 1112 userdata: ?*anyopaque, 1113 result_len: usize, 1114 result_alignment: Alignment, 1115 context: []const u8, 1116 context_alignment: Alignment, 1117 start: *const fn (context: *const anyopaque, result: *anyopaque) void, 1118 ) Io.ConcurrentError!*Io.AnyFuture { 1119 if (builtin.single_threaded) return error.ConcurrencyUnavailable; 1120 1121 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1122 1123 const gpa = t.allocator; 1124 const ac = AsyncClosure.init(gpa, result_len, result_alignment, context, context_alignment, start) catch 1125 return error.ConcurrencyUnavailable; 1126 errdefer ac.deinit(gpa); 1127 1128 t.mutex.lock(); 1129 defer t.mutex.unlock(); 1130 1131 const busy_count = t.busy_count; 1132 1133 if (busy_count >= @intFromEnum(t.concurrent_limit)) 1134 return error.ConcurrencyUnavailable; 1135 1136 t.busy_count = busy_count + 1; 1137 errdefer t.busy_count = busy_count; 1138 1139 const pool_size = t.wait_group.value(); 1140 if (pool_size - busy_count == 0) { 1141 t.wait_group.start(); 1142 errdefer t.wait_group.finish(); 1143 1144 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch 1145 return error.ConcurrencyUnavailable; 1146 thread.detach(); 1147 } 1148 1149 t.run_queue.prepend(&ac.closure.node); 1150 t.cond.signal(); 1151 return @ptrCast(ac); 1152 } 1153 1154 const GroupClosure = struct { 1155 closure: Closure, 1156 group: *Io.Group, 1157 /// Points to sibling `GroupClosure`. Used for walking the group to cancel all. 1158 node: std.SinglyLinkedList.Node, 1159 func: *const fn (*Io.Group, context: *anyopaque) void, 1160 context_alignment: Alignment, 1161 alloc_len: usize, 1162 1163 fn start(closure: *Closure, t: *Threaded) void { 1164 const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure)); 1165 const current_thread = Thread.getCurrent(t); 1166 const group = gc.group; 1167 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); 1168 const event: *Io.Event = @ptrCast(&group.context); 1169 current_thread.current_closure = closure; 1170 current_thread.cancel_protection = .unblocked; 1171 1172 gc.func(group, gc.contextPointer()); 1173 1174 current_thread.current_closure = null; 1175 current_thread.cancel_protection = undefined; 1176 1177 const prev_state = group_state.fetchSub(sync_one_pending, .acq_rel); 1178 assert((prev_state / sync_one_pending) > 0); 1179 if (prev_state == (sync_one_pending | sync_is_waiting)) event.set(ioBasic(t)); 1180 } 1181 1182 fn contextPointer(gc: *GroupClosure) [*]u8 { 1183 const base: [*]u8 = @ptrCast(gc); 1184 const context_offset = gc.context_alignment.forward(@intFromPtr(gc) + @sizeOf(GroupClosure)) - @intFromPtr(gc); 1185 return base + context_offset; 1186 } 1187 1188 /// Does not initialize the `node` field. 1189 fn init( 1190 gpa: Allocator, 1191 group: *Io.Group, 1192 context: []const u8, 1193 context_alignment: Alignment, 1194 func: *const fn (*Io.Group, context: *const anyopaque) void, 1195 ) Allocator.Error!*GroupClosure { 1196 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(GroupClosure); 1197 const worst_case_context_offset = context_alignment.forward(@sizeOf(GroupClosure) + max_context_misalignment); 1198 const alloc_len = worst_case_context_offset + context.len; 1199 1200 const gc: *GroupClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(GroupClosure), alloc_len))); 1201 errdefer comptime unreachable; 1202 1203 gc.* = .{ 1204 .closure = .{ 1205 .cancel_status = .none, 1206 .start = start, 1207 }, 1208 .group = group, 1209 .node = undefined, 1210 .func = func, 1211 .context_alignment = context_alignment, 1212 .alloc_len = alloc_len, 1213 }; 1214 @memcpy(gc.contextPointer()[0..context.len], context); 1215 return gc; 1216 } 1217 1218 fn deinit(gc: *GroupClosure, gpa: Allocator) void { 1219 const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(gc); 1220 gpa.free(base[0..gc.alloc_len]); 1221 } 1222 1223 const sync_is_waiting: usize = 1 << 0; 1224 const sync_one_pending: usize = 1 << 1; 1225 }; 1226 1227 fn groupAsync( 1228 userdata: ?*anyopaque, 1229 group: *Io.Group, 1230 context: []const u8, 1231 context_alignment: Alignment, 1232 start: *const fn (*Io.Group, context: *const anyopaque) void, 1233 ) void { 1234 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1235 if (builtin.single_threaded) return start(group, context.ptr); 1236 1237 const gpa = t.allocator; 1238 const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch 1239 return start(group, context.ptr); 1240 1241 t.mutex.lock(); 1242 1243 const busy_count = t.busy_count; 1244 1245 if (busy_count >= @intFromEnum(t.async_limit)) { 1246 t.mutex.unlock(); 1247 gc.deinit(gpa); 1248 return start(group, context.ptr); 1249 } 1250 1251 t.busy_count = busy_count + 1; 1252 1253 const pool_size = t.wait_group.value(); 1254 if (pool_size - busy_count == 0) { 1255 t.wait_group.start(); 1256 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { 1257 t.wait_group.finish(); 1258 t.busy_count = busy_count; 1259 t.mutex.unlock(); 1260 gc.deinit(gpa); 1261 return start(group, context.ptr); 1262 }; 1263 thread.detach(); 1264 } 1265 1266 // Append to the group linked list inside the mutex to make `Io.Group.async` thread-safe. 1267 gc.node = .{ .next = @ptrCast(@alignCast(group.token.load(.monotonic))) }; 1268 group.token.store(&gc.node, .monotonic); 1269 1270 t.run_queue.prepend(&gc.closure.node); 1271 1272 // This needs to be done before unlocking the mutex to avoid a race with 1273 // the associated task finishing. 1274 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); 1275 const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic); 1276 assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending)); 1277 1278 t.mutex.unlock(); 1279 t.cond.signal(); 1280 } 1281 1282 fn groupConcurrent( 1283 userdata: ?*anyopaque, 1284 group: *Io.Group, 1285 context: []const u8, 1286 context_alignment: Alignment, 1287 start: *const fn (*Io.Group, context: *const anyopaque) void, 1288 ) Io.ConcurrentError!void { 1289 if (builtin.single_threaded) return error.ConcurrencyUnavailable; 1290 1291 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1292 1293 const gpa = t.allocator; 1294 const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch 1295 return error.ConcurrencyUnavailable; 1296 1297 t.mutex.lock(); 1298 defer t.mutex.unlock(); 1299 1300 const busy_count = t.busy_count; 1301 1302 if (busy_count >= @intFromEnum(t.concurrent_limit)) 1303 return error.ConcurrencyUnavailable; 1304 1305 t.busy_count = busy_count + 1; 1306 errdefer t.busy_count = busy_count; 1307 1308 const pool_size = t.wait_group.value(); 1309 if (pool_size - busy_count == 0) { 1310 t.wait_group.start(); 1311 errdefer t.wait_group.finish(); 1312 1313 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch 1314 return error.ConcurrencyUnavailable; 1315 thread.detach(); 1316 } 1317 1318 // Append to the group linked list inside the mutex to make `Io.Group.concurrent` thread-safe. 1319 gc.node = .{ .next = @ptrCast(@alignCast(group.token.load(.monotonic))) }; 1320 group.token.store(&gc.node, .monotonic); 1321 1322 t.run_queue.prepend(&gc.closure.node); 1323 1324 // This needs to be done before unlocking the mutex to avoid a race with 1325 // the associated task finishing. 1326 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); 1327 const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic); 1328 assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending)); 1329 1330 t.cond.signal(); 1331 } 1332 1333 fn groupWait(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) void { 1334 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1335 const gpa = t.allocator; 1336 1337 _ = initial_token; // we need to load `token` *after* the group finishes 1338 1339 if (builtin.single_threaded) unreachable; // we never set `group.token` to non-`null` 1340 1341 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); 1342 const event: *Io.Event = @ptrCast(&group.context); 1343 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire); 1344 assert(prev_state & GroupClosure.sync_is_waiting == 0); 1345 if ((prev_state / GroupClosure.sync_one_pending) > 0) event.wait(ioBasic(t)) catch |err| switch (err) { 1346 error.Canceled => { 1347 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.load(.monotonic))); 1348 while (it) |node| : (it = node.next) { 1349 const gc: *GroupClosure = @fieldParentPtr("node", node); 1350 gc.closure.requestCancel(t); 1351 } 1352 event.waitUncancelable(ioBasic(t)); 1353 }, 1354 }; 1355 1356 // Since the group has now finished, it's illegal to add more tasks to it until we return. It's 1357 // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only 1358 // thread who can access `group` right now. 1359 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.raw)); 1360 group.token.raw = null; 1361 while (it) |node| { 1362 it = node.next; // update `it` now, because `deinit` will invalidate `node` 1363 const gc: *GroupClosure = @fieldParentPtr("node", node); 1364 gc.deinit(gpa); 1365 } 1366 } 1367 1368 fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, initial_token: *anyopaque) void { 1369 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1370 const gpa = t.allocator; 1371 1372 _ = initial_token; // we need to load `token` *after* the group finishes 1373 1374 if (builtin.single_threaded) unreachable; // we never set `group.token` to non-`null` 1375 1376 { 1377 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.load(.monotonic))); 1378 while (it) |node| : (it = node.next) { 1379 const gc: *GroupClosure = @fieldParentPtr("node", node); 1380 gc.closure.requestCancel(t); 1381 } 1382 } 1383 1384 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); 1385 const event: *Io.Event = @ptrCast(&group.context); 1386 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire); 1387 assert(prev_state & GroupClosure.sync_is_waiting == 0); 1388 if ((prev_state / GroupClosure.sync_one_pending) > 0) event.waitUncancelable(ioBasic(t)); 1389 1390 // Since the group has now finished, it's illegal to add more tasks to it until we return. It's 1391 // also illegal for us to race with another `await` or `cancel`. Therefore, we must be the only 1392 // thread who can access `group` right now. 1393 var it: ?*std.SinglyLinkedList.Node = @ptrCast(@alignCast(group.token.raw)); 1394 group.token.raw = null; 1395 while (it) |node| { 1396 it = node.next; // update `it` now, because `deinit` will invalidate `node` 1397 const gc: *GroupClosure = @fieldParentPtr("node", node); 1398 gc.deinit(gpa); 1399 } 1400 } 1401 1402 fn recancel(userdata: ?*anyopaque) void { 1403 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1404 const current_thread: *Thread = .getCurrent(t); 1405 const cancel_status = ¤t_thread.current_closure.?.cancel_status; 1406 switch (@atomicLoad(CancelStatus, cancel_status, .monotonic)) { 1407 .none => unreachable, // called `recancel` when not canceled 1408 .requested => unreachable, // called `recancel` when cancelation was already outstanding 1409 .acknowledged => {}, 1410 _ => unreachable, // invalid state: not in a syscall 1411 } 1412 @atomicStore(CancelStatus, cancel_status, .requested, .monotonic); 1413 } 1414 1415 fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection { 1416 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1417 const current_thread: *Thread = .getCurrent(t); 1418 const old = current_thread.cancel_protection; 1419 current_thread.cancel_protection = new; 1420 return old; 1421 } 1422 1423 fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void { 1424 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1425 return Thread.getCurrent(t).checkCancel(); 1426 } 1427 1428 fn await( 1429 userdata: ?*anyopaque, 1430 any_future: *Io.AnyFuture, 1431 result: []u8, 1432 result_alignment: Alignment, 1433 ) void { 1434 _ = result_alignment; 1435 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1436 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future)); 1437 closure.waitAndDeinit(t, result); 1438 } 1439 1440 fn cancel( 1441 userdata: ?*anyopaque, 1442 any_future: *Io.AnyFuture, 1443 result: []u8, 1444 result_alignment: Alignment, 1445 ) void { 1446 _ = result_alignment; 1447 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1448 const ac: *AsyncClosure = @ptrCast(@alignCast(any_future)); 1449 ac.closure.requestCancel(t); 1450 ac.waitAndDeinit(t, result); 1451 } 1452 1453 fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void { 1454 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1455 const current_thread = Thread.getCurrent(t); 1456 const t_io = ioBasic(t); 1457 const timeout_ns: ?u64 = ns: { 1458 const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null; 1459 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); 1460 }; 1461 switch (native_os) { 1462 .illumos, .netbsd, .openbsd => @panic("TODO"), 1463 else => try current_thread.futexWaitTimed(ptr, expected, timeout_ns), 1464 } 1465 } 1466 1467 fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { 1468 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1469 _ = t; 1470 switch (native_os) { 1471 .illumos, .netbsd, .openbsd => @panic("TODO"), 1472 else => Thread.futexWaitUncancelable(ptr, expected), 1473 } 1474 } 1475 1476 fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { 1477 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1478 _ = t; 1479 switch (native_os) { 1480 .illumos, .netbsd, .openbsd => @panic("TODO"), 1481 else => Thread.futexWake(ptr, max_waiters), 1482 } 1483 } 1484 1485 const dirMake = switch (native_os) { 1486 .windows => dirMakeWindows, 1487 .wasi => dirMakeWasi, 1488 else => dirMakePosix, 1489 }; 1490 1491 fn dirMakePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { 1492 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1493 const current_thread = Thread.getCurrent(t); 1494 1495 var path_buffer: [posix.PATH_MAX]u8 = undefined; 1496 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 1497 1498 try current_thread.beginSyscall(); 1499 while (true) { 1500 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) { 1501 .SUCCESS => { 1502 current_thread.endSyscall(); 1503 return; 1504 }, 1505 .INTR => { 1506 try current_thread.checkCancel(); 1507 continue; 1508 }, 1509 .CANCELED => return current_thread.endSyscallCanceled(), 1510 else => |e| { 1511 current_thread.endSyscall(); 1512 switch (e) { 1513 .ACCES => return error.AccessDenied, 1514 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 1515 .PERM => return error.PermissionDenied, 1516 .DQUOT => return error.DiskQuota, 1517 .EXIST => return error.PathAlreadyExists, 1518 .FAULT => |err| return errnoBug(err), 1519 .LOOP => return error.SymLinkLoop, 1520 .MLINK => return error.LinkQuotaExceeded, 1521 .NAMETOOLONG => return error.NameTooLong, 1522 .NOENT => return error.FileNotFound, 1523 .NOMEM => return error.SystemResources, 1524 .NOSPC => return error.NoSpaceLeft, 1525 .NOTDIR => return error.NotDir, 1526 .ROFS => return error.ReadOnlyFileSystem, 1527 // dragonfly: when dir_fd is unlinked from filesystem 1528 .NOTCONN => return error.FileNotFound, 1529 .ILSEQ => return error.BadPathName, 1530 else => |err| return posix.unexpectedErrno(err), 1531 } 1532 }, 1533 } 1534 } 1535 } 1536 1537 fn dirMakeWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { 1538 if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, permissions); 1539 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1540 const current_thread = Thread.getCurrent(t); 1541 try current_thread.beginSyscall(); 1542 while (true) { 1543 switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) { 1544 .SUCCESS => { 1545 current_thread.endSyscall(); 1546 return; 1547 }, 1548 .INTR => { 1549 try current_thread.checkCancel(); 1550 continue; 1551 }, 1552 .CANCELED => return current_thread.endSyscallCanceled(), 1553 else => |e| { 1554 current_thread.endSyscall(); 1555 switch (e) { 1556 .ACCES => return error.AccessDenied, 1557 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 1558 .PERM => return error.PermissionDenied, 1559 .DQUOT => return error.DiskQuota, 1560 .EXIST => return error.PathAlreadyExists, 1561 .FAULT => |err| return errnoBug(err), 1562 .LOOP => return error.SymLinkLoop, 1563 .MLINK => return error.LinkQuotaExceeded, 1564 .NAMETOOLONG => return error.NameTooLong, 1565 .NOENT => return error.FileNotFound, 1566 .NOMEM => return error.SystemResources, 1567 .NOSPC => return error.NoSpaceLeft, 1568 .NOTDIR => return error.NotDir, 1569 .ROFS => return error.ReadOnlyFileSystem, 1570 .NOTCAPABLE => return error.AccessDenied, 1571 .ILSEQ => return error.BadPathName, 1572 else => |err| return posix.unexpectedErrno(err), 1573 } 1574 }, 1575 } 1576 } 1577 } 1578 1579 fn dirMakeWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.MakeError!void { 1580 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1581 const current_thread = Thread.getCurrent(t); 1582 try current_thread.checkCancel(); 1583 1584 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); 1585 _ = permissions; // TODO use this value 1586 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{ 1587 .dir = dir.handle, 1588 .access_mask = .{ 1589 .GENERIC = .{ .READ = true }, 1590 .STANDARD = .{ .SYNCHRONIZE = true }, 1591 }, 1592 .creation = .CREATE, 1593 .filter = .dir_only, 1594 }) catch |err| switch (err) { 1595 error.IsDir => return error.Unexpected, 1596 error.PipeBusy => return error.Unexpected, 1597 error.NoDevice => return error.Unexpected, 1598 error.WouldBlock => return error.Unexpected, 1599 error.AntivirusInterference => return error.Unexpected, 1600 else => |e| return e, 1601 }; 1602 windows.CloseHandle(sub_dir_handle); 1603 } 1604 1605 fn dirMakePath( 1606 userdata: ?*anyopaque, 1607 dir: Dir, 1608 sub_path: []const u8, 1609 permissions: Dir.Permissions, 1610 ) Dir.MakePathError!Dir.MakePathStatus { 1611 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1612 1613 var it = std.fs.path.componentIterator(sub_path); 1614 var status: Dir.MakePathStatus = .existed; 1615 var component = it.last() orelse return error.BadPathName; 1616 while (true) { 1617 if (dirMake(t, dir, component.path, permissions)) |_| { 1618 status = .created; 1619 } else |err| switch (err) { 1620 error.PathAlreadyExists => { 1621 // stat the file and return an error if it's not a directory 1622 // this is important because otherwise a dangling symlink 1623 // could cause an infinite loop 1624 check_dir: { 1625 // workaround for windows, see https://github.com/ziglang/zig/issues/16738 1626 const fstat = dirStatFile(t, dir, component.path, .{}) catch |stat_err| switch (stat_err) { 1627 error.IsDir => break :check_dir, 1628 else => |e| return e, 1629 }; 1630 if (fstat.kind != .directory) return error.NotDir; 1631 } 1632 }, 1633 error.FileNotFound => |e| { 1634 component = it.previous() orelse return e; 1635 continue; 1636 }, 1637 else => |e| return e, 1638 } 1639 component = it.next() orelse return status; 1640 } 1641 } 1642 1643 const dirMakeOpenPath = switch (native_os) { 1644 .windows => dirMakeOpenPathWindows, 1645 .wasi => dirMakeOpenPathWasi, 1646 else => dirMakeOpenPathPosix, 1647 }; 1648 1649 fn dirMakeOpenPathPosix( 1650 userdata: ?*anyopaque, 1651 dir: Dir, 1652 sub_path: []const u8, 1653 permissions: Dir.Permissions, 1654 options: Dir.OpenOptions, 1655 ) Dir.MakeOpenPathError!Dir { 1656 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1657 const t_io = ioBasic(t); 1658 return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { 1659 error.FileNotFound => { 1660 _ = try dir.makePathStatus(t_io, sub_path, permissions); 1661 return dirOpenDirPosix(t, dir, sub_path, options); 1662 }, 1663 else => |e| return e, 1664 }; 1665 } 1666 1667 fn dirMakeOpenPathWindows( 1668 userdata: ?*anyopaque, 1669 dir: Dir, 1670 sub_path: []const u8, 1671 permissions: Dir.Permissions, 1672 options: Dir.OpenOptions, 1673 ) Dir.MakeOpenPathError!Dir { 1674 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1675 const current_thread = Thread.getCurrent(t); 1676 const w = windows; 1677 1678 _ = permissions; // TODO apply these permissions 1679 1680 var it = std.fs.path.componentIterator(sub_path); 1681 // If there are no components in the path, then create a dummy component with the full path. 1682 var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{ 1683 .name = "", 1684 .path = sub_path, 1685 }; 1686 1687 while (true) { 1688 try current_thread.checkCancel(); 1689 1690 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path); 1691 const sub_path_w = sub_path_w_array.span(); 1692 const is_last = it.peekNext() == null; 1693 const create_disposition: w.FILE.CREATE_DISPOSITION = if (is_last) .OPEN_IF else .CREATE; 1694 1695 var result: Dir = .{ .handle = undefined }; 1696 1697 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2); 1698 var nt_name: w.UNICODE_STRING = .{ 1699 .Length = path_len_bytes, 1700 .MaximumLength = path_len_bytes, 1701 .Buffer = @constCast(sub_path_w.ptr), 1702 }; 1703 var io_status_block: w.IO_STATUS_BLOCK = undefined; 1704 const rc = w.ntdll.NtCreateFile( 1705 &result.handle, 1706 .{ 1707 .SPECIFIC = .{ .FILE_DIRECTORY = .{ 1708 .LIST = options.iterate, 1709 .READ_EA = true, 1710 .READ_ATTRIBUTES = true, 1711 .TRAVERSE = true, 1712 } }, 1713 .STANDARD = .{ 1714 .RIGHTS = .READ, 1715 .SYNCHRONIZE = true, 1716 }, 1717 }, 1718 &.{ 1719 .Length = @sizeOf(w.OBJECT_ATTRIBUTES), 1720 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, 1721 .Attributes = .{}, 1722 .ObjectName = &nt_name, 1723 .SecurityDescriptor = null, 1724 .SecurityQualityOfService = null, 1725 }, 1726 &io_status_block, 1727 null, 1728 .{ .NORMAL = true }, 1729 .VALID_FLAGS, 1730 create_disposition, 1731 .{ 1732 .DIRECTORY_FILE = true, 1733 .IO = .SYNCHRONOUS_NONALERT, 1734 .OPEN_FOR_BACKUP_INTENT = true, 1735 .OPEN_REPARSE_POINT = !options.follow_symlinks, 1736 }, 1737 null, 1738 0, 1739 ); 1740 1741 switch (rc) { 1742 .SUCCESS => { 1743 component = it.next() orelse return result; 1744 w.CloseHandle(result.handle); 1745 continue; 1746 }, 1747 .OBJECT_NAME_INVALID => return error.BadPathName, 1748 .OBJECT_NAME_COLLISION => { 1749 assert(!is_last); 1750 // stat the file and return an error if it's not a directory 1751 // this is important because otherwise a dangling symlink 1752 // could cause an infinite loop 1753 check_dir: { 1754 // workaround for windows, see https://github.com/ziglang/zig/issues/16738 1755 const fstat = dirStatFileWindows(t, dir, component.path, .{ 1756 .follow_symlinks = options.follow_symlinks, 1757 }) catch |stat_err| switch (stat_err) { 1758 error.IsDir => break :check_dir, 1759 else => |e| return e, 1760 }; 1761 if (fstat.kind != .directory) return error.NotDir; 1762 } 1763 1764 component = it.next().?; 1765 continue; 1766 }, 1767 1768 .OBJECT_NAME_NOT_FOUND, 1769 .OBJECT_PATH_NOT_FOUND, 1770 => { 1771 component = it.previous() orelse return error.FileNotFound; 1772 continue; 1773 }, 1774 1775 .NOT_A_DIRECTORY => return error.NotDir, 1776 // This can happen if the directory has 'List folder contents' permission set to 'Deny' 1777 // and the directory is trying to be opened for iteration. 1778 .ACCESS_DENIED => return error.AccessDenied, 1779 .INVALID_PARAMETER => |err| return w.statusBug(err), 1780 else => return w.unexpectedStatus(rc), 1781 } 1782 } 1783 } 1784 1785 fn dirMakeOpenPathWasi( 1786 userdata: ?*anyopaque, 1787 dir: Dir, 1788 sub_path: []const u8, 1789 permissions: Dir.Permissions, 1790 options: Dir.OpenOptions, 1791 ) Dir.MakeOpenPathError!Dir { 1792 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1793 const t_io = ioBasic(t); 1794 return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) { 1795 error.FileNotFound => { 1796 _ = try dir.makePathStatus(t_io, sub_path, permissions); 1797 return dirOpenDirWasi(t, dir, sub_path, options); 1798 }, 1799 else => |e| return e, 1800 }; 1801 } 1802 1803 fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { 1804 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1805 const file: File = .{ .handle = dir.handle }; 1806 return fileStat(t, file); 1807 } 1808 1809 const dirStatFile = switch (native_os) { 1810 .linux => dirStatFileLinux, 1811 .windows => dirStatFileWindows, 1812 .wasi => dirStatFileWasi, 1813 else => dirStatFilePosix, 1814 }; 1815 1816 fn dirStatFileLinux( 1817 userdata: ?*anyopaque, 1818 dir: Dir, 1819 sub_path: []const u8, 1820 options: Dir.StatFileOptions, 1821 ) Dir.StatFileError!File.Stat { 1822 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1823 const current_thread = Thread.getCurrent(t); 1824 const linux = std.os.linux; 1825 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) 1826 .{ .major = 30, .minor = 0, .patch = 0 } 1827 else 1828 .{ .major = 2, .minor = 28, .patch = 0 }); 1829 const sys = if (use_c) std.c else std.os.linux; 1830 1831 var path_buffer: [posix.PATH_MAX]u8 = undefined; 1832 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 1833 1834 const flags: u32 = linux.AT.NO_AUTOMOUNT | 1835 @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0); 1836 1837 try current_thread.beginSyscall(); 1838 while (true) { 1839 var statx = std.mem.zeroes(linux.Statx); 1840 const rc = sys.statx( 1841 dir.handle, 1842 sub_path_posix, 1843 flags, 1844 .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, 1845 &statx, 1846 ); 1847 switch (sys.errno(rc)) { 1848 .SUCCESS => { 1849 current_thread.endSyscall(); 1850 assert(statx.mask.TYPE); 1851 assert(statx.mask.MODE); 1852 assert(statx.mask.ATIME); 1853 assert(statx.mask.MTIME); 1854 assert(statx.mask.CTIME); 1855 assert(statx.mask.INO); 1856 assert(statx.mask.SIZE); 1857 return statFromLinux(&statx); 1858 }, 1859 .INTR => { 1860 try current_thread.checkCancel(); 1861 continue; 1862 }, 1863 .CANCELED => return current_thread.endSyscallCanceled(), 1864 else => |e| { 1865 current_thread.endSyscall(); 1866 switch (e) { 1867 .ACCES => return error.AccessDenied, 1868 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 1869 .FAULT => |err| return errnoBug(err), 1870 .INVAL => |err| return errnoBug(err), 1871 .LOOP => return error.SymLinkLoop, 1872 .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above. 1873 .NOENT => return error.FileNotFound, 1874 .NOTDIR => return error.NotDir, 1875 .NOMEM => return error.SystemResources, 1876 else => |err| return posix.unexpectedErrno(err), 1877 } 1878 }, 1879 } 1880 } 1881 } 1882 1883 fn dirStatFilePosix( 1884 userdata: ?*anyopaque, 1885 dir: Dir, 1886 sub_path: []const u8, 1887 options: Dir.StatFileOptions, 1888 ) Dir.StatFileError!File.Stat { 1889 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1890 const current_thread = Thread.getCurrent(t); 1891 1892 var path_buffer: [posix.PATH_MAX]u8 = undefined; 1893 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 1894 1895 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; 1896 1897 return posixStatFile(current_thread, dir.handle, sub_path_posix, flags); 1898 } 1899 1900 fn posixStatFile(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat { 1901 try current_thread.beginSyscall(); 1902 while (true) { 1903 var stat = std.mem.zeroes(posix.Stat); 1904 switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) { 1905 .SUCCESS => { 1906 current_thread.endSyscall(); 1907 return statFromPosix(&stat); 1908 }, 1909 .INTR => { 1910 try current_thread.checkCancel(); 1911 continue; 1912 }, 1913 .CANCELED => return current_thread.endSyscallCanceled(), 1914 else => |e| { 1915 current_thread.endSyscall(); 1916 switch (e) { 1917 .INVAL => |err| return errnoBug(err), 1918 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 1919 .NOMEM => return error.SystemResources, 1920 .ACCES => return error.AccessDenied, 1921 .PERM => return error.PermissionDenied, 1922 .FAULT => |err| return errnoBug(err), 1923 .NAMETOOLONG => return error.NameTooLong, 1924 .LOOP => return error.SymLinkLoop, 1925 .NOENT => return error.FileNotFound, 1926 .NOTDIR => return error.FileNotFound, 1927 .ILSEQ => return error.BadPathName, 1928 else => |err| return posix.unexpectedErrno(err), 1929 } 1930 }, 1931 } 1932 } 1933 } 1934 1935 fn dirStatFileWindows( 1936 userdata: ?*anyopaque, 1937 dir: Dir, 1938 sub_path: []const u8, 1939 options: Dir.StatFileOptions, 1940 ) Dir.StatFileError!File.Stat { 1941 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1942 const file = try dirOpenFileWindows(t, dir, sub_path, .{ 1943 .follow_symlinks = options.follow_symlinks, 1944 }); 1945 defer windows.CloseHandle(file.handle); 1946 return fileStatWindows(t, file); 1947 } 1948 1949 fn dirStatFileWasi( 1950 userdata: ?*anyopaque, 1951 dir: Dir, 1952 sub_path: []const u8, 1953 options: Dir.StatFileOptions, 1954 ) Dir.StatFileError!File.Stat { 1955 if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options); 1956 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1957 const current_thread = Thread.getCurrent(t); 1958 const wasi = std.os.wasi; 1959 const flags: wasi.lookupflags_t = .{ 1960 .SYMLINK_FOLLOW = options.follow_symlinks, 1961 }; 1962 var stat: wasi.filestat_t = undefined; 1963 try current_thread.beginSyscall(); 1964 while (true) { 1965 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { 1966 .SUCCESS => { 1967 current_thread.endSyscall(); 1968 return statFromWasi(&stat); 1969 }, 1970 .INTR => { 1971 try current_thread.checkCancel(); 1972 continue; 1973 }, 1974 .CANCELED => return current_thread.endSyscallCanceled(), 1975 else => |e| { 1976 current_thread.endSyscall(); 1977 switch (e) { 1978 .INVAL => |err| return errnoBug(err), 1979 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 1980 .NOMEM => return error.SystemResources, 1981 .ACCES => return error.AccessDenied, 1982 .FAULT => |err| return errnoBug(err), 1983 .NAMETOOLONG => return error.NameTooLong, 1984 .NOENT => return error.FileNotFound, 1985 .NOTDIR => return error.FileNotFound, 1986 .NOTCAPABLE => return error.AccessDenied, 1987 .ILSEQ => return error.BadPathName, 1988 else => |err| return posix.unexpectedErrno(err), 1989 } 1990 }, 1991 } 1992 } 1993 } 1994 1995 fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { 1996 const t: *Threaded = @ptrCast(@alignCast(userdata)); 1997 1998 if (native_os == .linux) { 1999 const current_thread = Thread.getCurrent(t); 2000 const linux = std.os.linux; 2001 2002 try current_thread.beginSyscall(); 2003 while (true) { 2004 var statx = std.mem.zeroes(linux.Statx); 2005 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) { 2006 .SUCCESS => { 2007 current_thread.endSyscall(); 2008 return statx.size; 2009 }, 2010 .INTR => { 2011 try current_thread.checkCancel(); 2012 continue; 2013 }, 2014 .CANCELED => return current_thread.endSyscallCanceled(), 2015 else => |e| { 2016 current_thread.endSyscall(); 2017 switch (e) { 2018 .ACCES => |err| return errnoBug(err), 2019 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2020 .FAULT => |err| return errnoBug(err), 2021 .INVAL => |err| return errnoBug(err), 2022 .LOOP => |err| return errnoBug(err), 2023 .NAMETOOLONG => |err| return errnoBug(err), 2024 .NOENT => |err| return errnoBug(err), 2025 .NOMEM => return error.SystemResources, 2026 .NOTDIR => |err| return errnoBug(err), 2027 else => |err| return posix.unexpectedErrno(err), 2028 } 2029 }, 2030 } 2031 } 2032 } else if (is_windows) { 2033 // TODO call NtQueryInformationFile and ask for only the size instead of "all" 2034 } 2035 2036 const stat = try fileStat(t, file); 2037 return stat.size; 2038 } 2039 2040 const fileStat = switch (native_os) { 2041 .linux => fileStatLinux, 2042 .windows => fileStatWindows, 2043 .wasi => fileStatWasi, 2044 else => fileStatPosix, 2045 }; 2046 2047 fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 2048 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2049 const current_thread = Thread.getCurrent(t); 2050 2051 if (posix.Stat == void) return error.Streaming; 2052 2053 try current_thread.beginSyscall(); 2054 while (true) { 2055 var stat = std.mem.zeroes(posix.Stat); 2056 switch (posix.errno(fstat_sym(file.handle, &stat))) { 2057 .SUCCESS => { 2058 current_thread.endSyscall(); 2059 return statFromPosix(&stat); 2060 }, 2061 .INTR => { 2062 try current_thread.checkCancel(); 2063 continue; 2064 }, 2065 .CANCELED => return current_thread.endSyscallCanceled(), 2066 else => |e| { 2067 current_thread.endSyscall(); 2068 switch (e) { 2069 .INVAL => |err| return errnoBug(err), 2070 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2071 .NOMEM => return error.SystemResources, 2072 .ACCES => return error.AccessDenied, 2073 else => |err| return posix.unexpectedErrno(err), 2074 } 2075 }, 2076 } 2077 } 2078 } 2079 2080 fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 2081 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2082 const current_thread = Thread.getCurrent(t); 2083 const linux = std.os.linux; 2084 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) 2085 .{ .major = 30, .minor = 0, .patch = 0 } 2086 else 2087 .{ .major = 2, .minor = 28, .patch = 0 }); 2088 const sys = if (use_c) std.c else std.os.linux; 2089 2090 try current_thread.beginSyscall(); 2091 while (true) { 2092 var statx = std.mem.zeroes(linux.Statx); 2093 const rc = sys.statx( 2094 file.handle, 2095 "", 2096 linux.AT.EMPTY_PATH, 2097 .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true }, 2098 &statx, 2099 ); 2100 switch (sys.errno(rc)) { 2101 .SUCCESS => { 2102 current_thread.endSyscall(); 2103 assert(statx.mask.TYPE); 2104 assert(statx.mask.MODE); 2105 assert(statx.mask.ATIME); 2106 assert(statx.mask.MTIME); 2107 assert(statx.mask.CTIME); 2108 assert(statx.mask.INO); 2109 assert(statx.mask.SIZE); 2110 return statFromLinux(&statx); 2111 }, 2112 .INTR => { 2113 try current_thread.checkCancel(); 2114 continue; 2115 }, 2116 .CANCELED => return current_thread.endSyscallCanceled(), 2117 else => |e| { 2118 current_thread.endSyscall(); 2119 switch (e) { 2120 .ACCES => |err| return errnoBug(err), 2121 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2122 .FAULT => |err| return errnoBug(err), 2123 .INVAL => |err| return errnoBug(err), 2124 .LOOP => |err| return errnoBug(err), 2125 .NAMETOOLONG => |err| return errnoBug(err), 2126 .NOENT => |err| return errnoBug(err), 2127 .NOMEM => return error.SystemResources, 2128 .NOTDIR => |err| return errnoBug(err), 2129 else => |err| return posix.unexpectedErrno(err), 2130 } 2131 }, 2132 } 2133 } 2134 } 2135 2136 fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 2137 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2138 const current_thread = Thread.getCurrent(t); 2139 try current_thread.checkCancel(); 2140 2141 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 2142 var info: windows.FILE.ALL_INFORMATION = undefined; 2143 const rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &info, @sizeOf(windows.FILE.ALL_INFORMATION), .All); 2144 switch (rc) { 2145 .SUCCESS => {}, 2146 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer 2147 // size provided. This is treated as success because the type of variable-length information that this would be relevant for 2148 // (name, volume name, etc) we don't care about. 2149 .BUFFER_OVERFLOW => {}, 2150 .INVALID_PARAMETER => |err| return windows.statusBug(err), 2151 .ACCESS_DENIED => return error.AccessDenied, 2152 else => return windows.unexpectedStatus(rc), 2153 } 2154 return .{ 2155 .inode = info.InternalInformation.IndexNumber, 2156 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)), 2157 .mode = 0, 2158 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { 2159 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; 2160 const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag); 2161 switch (tag_rc) { 2162 .SUCCESS => {}, 2163 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors 2164 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e 2165 .INFO_LENGTH_MISMATCH => |err| return windows.statusBug(err), 2166 .ACCESS_DENIED => return error.AccessDenied, 2167 else => return windows.unexpectedStatus(rc), 2168 } 2169 if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link; 2170 // Unknown reparse point 2171 break :reparse_point .unknown; 2172 } else if (info.BasicInformation.FileAttributes.DIRECTORY) 2173 .directory 2174 else 2175 .file, 2176 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), 2177 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), 2178 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), 2179 }; 2180 } 2181 2182 fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 2183 if (builtin.link_libc) return fileStatPosix(userdata, file); 2184 2185 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2186 const current_thread = Thread.getCurrent(t); 2187 2188 try current_thread.beginSyscall(); 2189 while (true) { 2190 var stat: std.os.wasi.filestat_t = undefined; 2191 switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) { 2192 .SUCCESS => { 2193 current_thread.endSyscall(); 2194 return statFromWasi(&stat); 2195 }, 2196 .INTR => { 2197 try current_thread.checkCancel(); 2198 continue; 2199 }, 2200 .CANCELED => return current_thread.endSyscallCanceled(), 2201 else => |e| { 2202 current_thread.endSyscall(); 2203 switch (e) { 2204 .INVAL => |err| return errnoBug(err), 2205 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2206 .NOMEM => return error.SystemResources, 2207 .ACCES => return error.AccessDenied, 2208 .NOTCAPABLE => return error.AccessDenied, 2209 else => |err| return posix.unexpectedErrno(err), 2210 } 2211 }, 2212 } 2213 } 2214 } 2215 2216 const dirAccess = switch (native_os) { 2217 .windows => dirAccessWindows, 2218 .wasi => dirAccessWasi, 2219 else => dirAccessPosix, 2220 }; 2221 2222 fn dirAccessPosix( 2223 userdata: ?*anyopaque, 2224 dir: Dir, 2225 sub_path: []const u8, 2226 options: Dir.AccessOptions, 2227 ) Dir.AccessError!void { 2228 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2229 const current_thread = Thread.getCurrent(t); 2230 2231 var path_buffer: [posix.PATH_MAX]u8 = undefined; 2232 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 2233 2234 const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0); 2235 2236 const mode: u32 = 2237 @as(u32, if (options.read) posix.R_OK else 0) | 2238 @as(u32, if (options.write) posix.W_OK else 0) | 2239 @as(u32, if (options.execute) posix.X_OK else 0); 2240 2241 try current_thread.beginSyscall(); 2242 while (true) { 2243 switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) { 2244 .SUCCESS => { 2245 current_thread.endSyscall(); 2246 return; 2247 }, 2248 .INTR => { 2249 try current_thread.checkCancel(); 2250 continue; 2251 }, 2252 .CANCELED => return current_thread.endSyscallCanceled(), 2253 else => |e| { 2254 current_thread.endSyscall(); 2255 switch (e) { 2256 .ACCES => return error.AccessDenied, 2257 .PERM => return error.PermissionDenied, 2258 .ROFS => return error.ReadOnlyFileSystem, 2259 .LOOP => return error.SymLinkLoop, 2260 .TXTBSY => return error.FileBusy, 2261 .NOTDIR => return error.FileNotFound, 2262 .NOENT => return error.FileNotFound, 2263 .NAMETOOLONG => return error.NameTooLong, 2264 .INVAL => |err| return errnoBug(err), 2265 .FAULT => |err| return errnoBug(err), 2266 .IO => return error.InputOutput, 2267 .NOMEM => return error.SystemResources, 2268 .ILSEQ => return error.BadPathName, 2269 else => |err| return posix.unexpectedErrno(err), 2270 } 2271 }, 2272 } 2273 } 2274 } 2275 2276 fn dirAccessWasi( 2277 userdata: ?*anyopaque, 2278 dir: Dir, 2279 sub_path: []const u8, 2280 options: Dir.AccessOptions, 2281 ) Dir.AccessError!void { 2282 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options); 2283 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2284 const current_thread = Thread.getCurrent(t); 2285 const wasi = std.os.wasi; 2286 const flags: wasi.lookupflags_t = .{ 2287 .SYMLINK_FOLLOW = options.follow_symlinks, 2288 }; 2289 var stat: wasi.filestat_t = undefined; 2290 2291 try current_thread.beginSyscall(); 2292 while (true) { 2293 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { 2294 .SUCCESS => { 2295 current_thread.endSyscall(); 2296 break; 2297 }, 2298 .INTR => { 2299 try current_thread.checkCancel(); 2300 continue; 2301 }, 2302 .CANCELED => return current_thread.endSyscallCanceled(), 2303 else => |e| { 2304 current_thread.endSyscall(); 2305 switch (e) { 2306 .INVAL => |err| return errnoBug(err), 2307 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2308 .NOMEM => return error.SystemResources, 2309 .ACCES => return error.AccessDenied, 2310 .FAULT => |err| return errnoBug(err), 2311 .NAMETOOLONG => return error.NameTooLong, 2312 .NOENT => return error.FileNotFound, 2313 .NOTDIR => return error.FileNotFound, 2314 .NOTCAPABLE => return error.AccessDenied, 2315 .ILSEQ => return error.BadPathName, 2316 else => |err| return posix.unexpectedErrno(err), 2317 } 2318 }, 2319 } 2320 } 2321 2322 if (!options.read and !options.write and !options.execute) 2323 return; 2324 2325 var directory: wasi.fdstat_t = undefined; 2326 if (wasi.fd_fdstat_get(dir.handle, &directory) != .SUCCESS) 2327 return error.AccessDenied; 2328 2329 var rights: wasi.rights_t = .{}; 2330 if (options.read) { 2331 if (stat.filetype == .DIRECTORY) { 2332 rights.FD_READDIR = true; 2333 } else { 2334 rights.FD_READ = true; 2335 } 2336 } 2337 if (options.write) 2338 rights.FD_WRITE = true; 2339 2340 // No validation for execution. 2341 2342 // https://github.com/ziglang/zig/issues/18882 2343 const rights_int: u64 = @bitCast(rights); 2344 const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting); 2345 if ((rights_int & inheriting_int) != rights_int) 2346 return error.AccessDenied; 2347 } 2348 2349 fn dirAccessWindows( 2350 userdata: ?*anyopaque, 2351 dir: Dir, 2352 sub_path: []const u8, 2353 options: Dir.AccessOptions, 2354 ) Dir.AccessError!void { 2355 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2356 const current_thread = Thread.getCurrent(t); 2357 try current_thread.checkCancel(); 2358 2359 _ = options; // TODO 2360 2361 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); 2362 const sub_path_w = sub_path_w_array.span(); 2363 2364 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) return; 2365 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) return; 2366 2367 const path_len_bytes = std.math.cast(u16, std.mem.sliceTo(sub_path_w, 0).len * 2) orelse 2368 return error.NameTooLong; 2369 var nt_name: windows.UNICODE_STRING = .{ 2370 .Length = path_len_bytes, 2371 .MaximumLength = path_len_bytes, 2372 .Buffer = @constCast(sub_path_w.ptr), 2373 }; 2374 var attr: windows.OBJECT_ATTRIBUTES = .{ 2375 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), 2376 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, 2377 .Attributes = .{}, 2378 .ObjectName = &nt_name, 2379 .SecurityDescriptor = null, 2380 .SecurityQualityOfService = null, 2381 }; 2382 var basic_info: windows.FILE.BASIC_INFORMATION = undefined; 2383 switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { 2384 .SUCCESS => return, 2385 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, 2386 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, 2387 .OBJECT_NAME_INVALID => |err| return windows.statusBug(err), 2388 .INVALID_PARAMETER => |err| return windows.statusBug(err), 2389 .ACCESS_DENIED => return error.AccessDenied, 2390 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err), 2391 else => |rc| return windows.unexpectedStatus(rc), 2392 } 2393 } 2394 2395 const dirCreateFile = switch (native_os) { 2396 .windows => dirCreateFileWindows, 2397 .wasi => dirCreateFileWasi, 2398 else => dirCreateFilePosix, 2399 }; 2400 2401 fn dirCreateFilePosix( 2402 userdata: ?*anyopaque, 2403 dir: Dir, 2404 sub_path: []const u8, 2405 flags: File.CreateFlags, 2406 ) File.OpenError!File { 2407 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2408 const current_thread = Thread.getCurrent(t); 2409 2410 var path_buffer: [posix.PATH_MAX]u8 = undefined; 2411 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 2412 2413 var os_flags: posix.O = .{ 2414 .ACCMODE = if (flags.read) .RDWR else .WRONLY, 2415 .CREAT = true, 2416 .TRUNC = flags.truncate, 2417 .EXCL = flags.exclusive, 2418 }; 2419 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; 2420 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; 2421 2422 // Use the O locking flags if the os supports them to acquire the lock 2423 // atomically. Note that the NONBLOCK flag is removed after the openat() 2424 // call is successful. 2425 if (have_flock_open_flags) switch (flags.lock) { 2426 .none => {}, 2427 .shared => { 2428 os_flags.SHLOCK = true; 2429 os_flags.NONBLOCK = flags.lock_nonblocking; 2430 }, 2431 .exclusive => { 2432 os_flags.EXLOCK = true; 2433 os_flags.NONBLOCK = flags.lock_nonblocking; 2434 }, 2435 }; 2436 2437 try current_thread.beginSyscall(); 2438 const fd: posix.fd_t = while (true) { 2439 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode()); 2440 switch (posix.errno(rc)) { 2441 .SUCCESS => { 2442 current_thread.endSyscall(); 2443 break @intCast(rc); 2444 }, 2445 .INTR => { 2446 try current_thread.checkCancel(); 2447 continue; 2448 }, 2449 .CANCELED => return current_thread.endSyscallCanceled(), 2450 else => |e| { 2451 current_thread.endSyscall(); 2452 switch (e) { 2453 .FAULT => |err| return errnoBug(err), 2454 .INVAL => return error.BadPathName, 2455 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2456 .ACCES => return error.AccessDenied, 2457 .FBIG => return error.FileTooBig, 2458 .OVERFLOW => return error.FileTooBig, 2459 .ISDIR => return error.IsDir, 2460 .LOOP => return error.SymLinkLoop, 2461 .MFILE => return error.ProcessFdQuotaExceeded, 2462 .NAMETOOLONG => return error.NameTooLong, 2463 .NFILE => return error.SystemFdQuotaExceeded, 2464 .NODEV => return error.NoDevice, 2465 .NOENT => return error.FileNotFound, 2466 .SRCH => return error.FileNotFound, // Linux when accessing procfs. 2467 .NOMEM => return error.SystemResources, 2468 .NOSPC => return error.NoSpaceLeft, 2469 .NOTDIR => return error.NotDir, 2470 .PERM => return error.PermissionDenied, 2471 .EXIST => return error.PathAlreadyExists, 2472 .BUSY => return error.DeviceBusy, 2473 .OPNOTSUPP => return error.FileLocksUnsupported, 2474 .AGAIN => return error.WouldBlock, 2475 .TXTBSY => return error.FileBusy, 2476 .NXIO => return error.NoDevice, 2477 .ILSEQ => return error.BadPathName, 2478 else => |err| return posix.unexpectedErrno(err), 2479 } 2480 }, 2481 } 2482 }; 2483 errdefer posix.close(fd); 2484 2485 if (have_flock and !have_flock_open_flags and flags.lock != .none) { 2486 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0; 2487 const lock_flags = switch (flags.lock) { 2488 .none => unreachable, 2489 .shared => posix.LOCK.SH | lock_nonblocking, 2490 .exclusive => posix.LOCK.EX | lock_nonblocking, 2491 }; 2492 2493 try current_thread.beginSyscall(); 2494 while (true) { 2495 switch (posix.errno(posix.system.flock(fd, lock_flags))) { 2496 .SUCCESS => { 2497 current_thread.endSyscall(); 2498 break; 2499 }, 2500 .INTR => { 2501 try current_thread.checkCancel(); 2502 continue; 2503 }, 2504 .CANCELED => return current_thread.endSyscallCanceled(), 2505 else => |e| { 2506 current_thread.endSyscall(); 2507 switch (e) { 2508 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2509 .INVAL => |err| return errnoBug(err), // invalid parameters 2510 .NOLCK => return error.SystemResources, 2511 .AGAIN => return error.WouldBlock, 2512 .OPNOTSUPP => return error.FileLocksUnsupported, 2513 else => |err| return posix.unexpectedErrno(err), 2514 } 2515 }, 2516 } 2517 } 2518 } 2519 2520 if (have_flock_open_flags and flags.lock_nonblocking) { 2521 try current_thread.beginSyscall(); 2522 var fl_flags: usize = while (true) { 2523 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); 2524 switch (posix.errno(rc)) { 2525 .SUCCESS => { 2526 current_thread.endSyscall(); 2527 break @intCast(rc); 2528 }, 2529 .INTR => { 2530 try current_thread.checkCancel(); 2531 continue; 2532 }, 2533 else => |err| { 2534 current_thread.endSyscall(); 2535 return posix.unexpectedErrno(err); 2536 }, 2537 } 2538 }; 2539 2540 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); 2541 2542 try current_thread.beginSyscall(); 2543 while (true) { 2544 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { 2545 .SUCCESS => { 2546 current_thread.endSyscall(); 2547 break; 2548 }, 2549 .INTR => { 2550 try current_thread.checkCancel(); 2551 continue; 2552 }, 2553 else => |err| { 2554 current_thread.endSyscall(); 2555 return posix.unexpectedErrno(err); 2556 }, 2557 } 2558 } 2559 } 2560 2561 return .{ .handle = fd }; 2562 } 2563 2564 fn dirCreateFileWindows( 2565 userdata: ?*anyopaque, 2566 dir: Dir, 2567 sub_path: []const u8, 2568 flags: File.CreateFlags, 2569 ) File.OpenError!File { 2570 const w = windows; 2571 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2572 const current_thread = Thread.getCurrent(t); 2573 try current_thread.checkCancel(); 2574 2575 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); 2576 const sub_path_w = sub_path_w_array.span(); 2577 2578 const handle = try w.OpenFile(sub_path_w, .{ 2579 .dir = dir.handle, 2580 .access_mask = .{ 2581 .STANDARD = .{ .SYNCHRONIZE = true }, 2582 .GENERIC = .{ 2583 .WRITE = true, 2584 .READ = flags.read, 2585 }, 2586 }, 2587 .creation = if (flags.exclusive) 2588 .CREATE 2589 else if (flags.truncate) 2590 .OVERWRITE_IF 2591 else 2592 .OPEN_IF, 2593 }); 2594 errdefer w.CloseHandle(handle); 2595 var io_status_block: w.IO_STATUS_BLOCK = undefined; 2596 const range_off: w.LARGE_INTEGER = 0; 2597 const range_len: w.LARGE_INTEGER = 1; 2598 const exclusive = switch (flags.lock) { 2599 .none => return .{ .handle = handle }, 2600 .shared => false, 2601 .exclusive => true, 2602 }; 2603 try w.LockFile( 2604 handle, 2605 null, 2606 null, 2607 null, 2608 &io_status_block, 2609 &range_off, 2610 &range_len, 2611 null, 2612 @intFromBool(flags.lock_nonblocking), 2613 @intFromBool(exclusive), 2614 ); 2615 return .{ .handle = handle }; 2616 } 2617 2618 fn dirCreateFileWasi( 2619 userdata: ?*anyopaque, 2620 dir: Dir, 2621 sub_path: []const u8, 2622 flags: File.CreateFlags, 2623 ) File.OpenError!File { 2624 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2625 const current_thread = Thread.getCurrent(t); 2626 const wasi = std.os.wasi; 2627 const lookup_flags: wasi.lookupflags_t = .{}; 2628 const oflags: wasi.oflags_t = .{ 2629 .CREAT = true, 2630 .TRUNC = flags.truncate, 2631 .EXCL = flags.exclusive, 2632 }; 2633 const fdflags: wasi.fdflags_t = .{}; 2634 const base: wasi.rights_t = .{ 2635 .FD_READ = flags.read, 2636 .FD_WRITE = true, 2637 .FD_DATASYNC = true, 2638 .FD_SEEK = true, 2639 .FD_TELL = true, 2640 .FD_FDSTAT_SET_FLAGS = true, 2641 .FD_SYNC = true, 2642 .FD_ALLOCATE = true, 2643 .FD_ADVISE = true, 2644 .FD_FILESTAT_SET_TIMES = true, 2645 .FD_FILESTAT_SET_SIZE = true, 2646 .FD_FILESTAT_GET = true, 2647 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or 2648 // FD_WRITE is also set. 2649 .POLL_FD_READWRITE = true, 2650 }; 2651 const inheriting: wasi.rights_t = .{}; 2652 var fd: posix.fd_t = undefined; 2653 try current_thread.beginSyscall(); 2654 while (true) { 2655 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { 2656 .SUCCESS => { 2657 current_thread.endSyscall(); 2658 return .{ .handle = fd }; 2659 }, 2660 .INTR => { 2661 try current_thread.checkCancel(); 2662 continue; 2663 }, 2664 .CANCELED => return current_thread.endSyscallCanceled(), 2665 else => |e| { 2666 current_thread.endSyscall(); 2667 switch (e) { 2668 .FAULT => |err| return errnoBug(err), 2669 .INVAL => return error.BadPathName, 2670 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2671 .ACCES => return error.AccessDenied, 2672 .FBIG => return error.FileTooBig, 2673 .OVERFLOW => return error.FileTooBig, 2674 .ISDIR => return error.IsDir, 2675 .LOOP => return error.SymLinkLoop, 2676 .MFILE => return error.ProcessFdQuotaExceeded, 2677 .NAMETOOLONG => return error.NameTooLong, 2678 .NFILE => return error.SystemFdQuotaExceeded, 2679 .NODEV => return error.NoDevice, 2680 .NOENT => return error.FileNotFound, 2681 .NOMEM => return error.SystemResources, 2682 .NOSPC => return error.NoSpaceLeft, 2683 .NOTDIR => return error.NotDir, 2684 .PERM => return error.PermissionDenied, 2685 .EXIST => return error.PathAlreadyExists, 2686 .BUSY => return error.DeviceBusy, 2687 .NOTCAPABLE => return error.AccessDenied, 2688 .ILSEQ => return error.BadPathName, 2689 else => |err| return posix.unexpectedErrno(err), 2690 } 2691 }, 2692 } 2693 } 2694 } 2695 2696 const dirOpenFile = switch (native_os) { 2697 .windows => dirOpenFileWindows, 2698 .wasi => dirOpenFileWasi, 2699 else => dirOpenFilePosix, 2700 }; 2701 2702 fn dirOpenFilePosix( 2703 userdata: ?*anyopaque, 2704 dir: Dir, 2705 sub_path: []const u8, 2706 flags: File.OpenFlags, 2707 ) File.OpenError!File { 2708 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2709 const current_thread = Thread.getCurrent(t); 2710 2711 var path_buffer: [posix.PATH_MAX]u8 = undefined; 2712 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 2713 2714 var os_flags: posix.O = switch (native_os) { 2715 .wasi => .{ 2716 .read = flags.mode != .write_only, 2717 .write = flags.mode != .read_only, 2718 }, 2719 else => .{ 2720 .ACCMODE = switch (flags.mode) { 2721 .read_only => .RDONLY, 2722 .write_only => .WRONLY, 2723 .read_write => .RDWR, 2724 }, 2725 }, 2726 }; 2727 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; 2728 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; 2729 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty; 2730 2731 // Use the O locking flags if the os supports them to acquire the lock 2732 // atomically. Note that the NONBLOCK flag is removed after the openat() 2733 // call is successful. 2734 if (have_flock_open_flags) switch (flags.lock) { 2735 .none => {}, 2736 .shared => { 2737 os_flags.SHLOCK = true; 2738 os_flags.NONBLOCK = flags.lock_nonblocking; 2739 }, 2740 .exclusive => { 2741 os_flags.EXLOCK = true; 2742 os_flags.NONBLOCK = flags.lock_nonblocking; 2743 }, 2744 }; 2745 2746 try current_thread.beginSyscall(); 2747 const fd: posix.fd_t = while (true) { 2748 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0)); 2749 switch (posix.errno(rc)) { 2750 .SUCCESS => { 2751 current_thread.endSyscall(); 2752 break @intCast(rc); 2753 }, 2754 .INTR => { 2755 try current_thread.checkCancel(); 2756 continue; 2757 }, 2758 .CANCELED => return current_thread.endSyscallCanceled(), 2759 else => |e| { 2760 current_thread.endSyscall(); 2761 switch (e) { 2762 .FAULT => |err| return errnoBug(err), 2763 .INVAL => return error.BadPathName, 2764 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2765 .ACCES => return error.AccessDenied, 2766 .FBIG => return error.FileTooBig, 2767 .OVERFLOW => return error.FileTooBig, 2768 .ISDIR => return error.IsDir, 2769 .LOOP => return error.SymLinkLoop, 2770 .MFILE => return error.ProcessFdQuotaExceeded, 2771 .NAMETOOLONG => return error.NameTooLong, 2772 .NFILE => return error.SystemFdQuotaExceeded, 2773 .NODEV => return error.NoDevice, 2774 .NOENT => return error.FileNotFound, 2775 .SRCH => return error.FileNotFound, // Linux when opening procfs files. 2776 .NOMEM => return error.SystemResources, 2777 .NOSPC => return error.NoSpaceLeft, 2778 .NOTDIR => return error.NotDir, 2779 .PERM => return error.PermissionDenied, 2780 .EXIST => return error.PathAlreadyExists, 2781 .BUSY => return error.DeviceBusy, 2782 .OPNOTSUPP => return error.FileLocksUnsupported, 2783 .AGAIN => return error.WouldBlock, 2784 .TXTBSY => return error.FileBusy, 2785 .NXIO => return error.NoDevice, 2786 .ILSEQ => return error.BadPathName, 2787 else => |err| return posix.unexpectedErrno(err), 2788 } 2789 }, 2790 } 2791 }; 2792 errdefer posix.close(fd); 2793 2794 if (have_flock and !have_flock_open_flags and flags.lock != .none) { 2795 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0; 2796 const lock_flags = switch (flags.lock) { 2797 .none => unreachable, 2798 .shared => posix.LOCK.SH | lock_nonblocking, 2799 .exclusive => posix.LOCK.EX | lock_nonblocking, 2800 }; 2801 try current_thread.beginSyscall(); 2802 while (true) { 2803 switch (posix.errno(posix.system.flock(fd, lock_flags))) { 2804 .SUCCESS => { 2805 current_thread.endSyscall(); 2806 break; 2807 }, 2808 .INTR => { 2809 try current_thread.checkCancel(); 2810 continue; 2811 }, 2812 .CANCELED => return current_thread.endSyscallCanceled(), 2813 else => |e| { 2814 current_thread.endSyscall(); 2815 switch (e) { 2816 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 2817 .INVAL => |err| return errnoBug(err), // invalid parameters 2818 .NOLCK => return error.SystemResources, 2819 .AGAIN => return error.WouldBlock, 2820 .OPNOTSUPP => return error.FileLocksUnsupported, 2821 else => |err| return posix.unexpectedErrno(err), 2822 } 2823 }, 2824 } 2825 } 2826 } 2827 2828 if (have_flock_open_flags and flags.lock_nonblocking) { 2829 try current_thread.beginSyscall(); 2830 var fl_flags: usize = while (true) { 2831 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); 2832 switch (posix.errno(rc)) { 2833 .SUCCESS => { 2834 current_thread.endSyscall(); 2835 break @intCast(rc); 2836 }, 2837 .INTR => { 2838 try current_thread.checkCancel(); 2839 continue; 2840 }, 2841 .CANCELED => return current_thread.endSyscallCanceled(), 2842 else => |err| { 2843 current_thread.endSyscall(); 2844 return posix.unexpectedErrno(err); 2845 }, 2846 } 2847 }; 2848 2849 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); 2850 2851 try current_thread.beginSyscall(); 2852 while (true) { 2853 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { 2854 .SUCCESS => { 2855 current_thread.endSyscall(); 2856 break; 2857 }, 2858 .INTR => { 2859 try current_thread.checkCancel(); 2860 continue; 2861 }, 2862 .CANCELED => return current_thread.endSyscallCanceled(), 2863 else => |err| { 2864 current_thread.endSyscall(); 2865 return posix.unexpectedErrno(err); 2866 }, 2867 } 2868 } 2869 } 2870 2871 return .{ .handle = fd }; 2872 } 2873 2874 fn dirOpenFileWindows( 2875 userdata: ?*anyopaque, 2876 dir: Dir, 2877 sub_path: []const u8, 2878 flags: File.OpenFlags, 2879 ) File.OpenError!File { 2880 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2881 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); 2882 const sub_path_w = sub_path_w_array.span(); 2883 const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; 2884 return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags); 2885 } 2886 2887 pub fn dirOpenFileWtf16( 2888 t: *Threaded, 2889 dir_handle: ?windows.HANDLE, 2890 sub_path_w: [:0]const u16, 2891 flags: File.OpenFlags, 2892 ) File.OpenError!File { 2893 if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; 2894 if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; 2895 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; 2896 const current_thread = Thread.getCurrent(t); 2897 const w = windows; 2898 2899 var nt_name: w.UNICODE_STRING = .{ 2900 .Length = path_len_bytes, 2901 .MaximumLength = path_len_bytes, 2902 .Buffer = @constCast(sub_path_w.ptr), 2903 }; 2904 var attr: w.OBJECT_ATTRIBUTES = .{ 2905 .Length = @sizeOf(w.OBJECT_ATTRIBUTES), 2906 .RootDirectory = dir_handle, 2907 .Attributes = .{}, 2908 .ObjectName = &nt_name, 2909 .SecurityDescriptor = null, 2910 .SecurityQualityOfService = null, 2911 }; 2912 var io_status_block: w.IO_STATUS_BLOCK = undefined; 2913 2914 // There are multiple kernel bugs being worked around with retries. 2915 const max_attempts = 13; 2916 var attempt: u5 = 0; 2917 2918 const handle = while (true) { 2919 try current_thread.checkCancel(); 2920 2921 var result: w.HANDLE = undefined; 2922 const rc = w.ntdll.NtCreateFile( 2923 &result, 2924 .{ 2925 .STANDARD = .{ .SYNCHRONIZE = true }, 2926 .GENERIC = .{ 2927 .READ = flags.isRead(), 2928 .WRITE = flags.isWrite(), 2929 }, 2930 }, 2931 &attr, 2932 &io_status_block, 2933 null, 2934 .{ .NORMAL = true }, 2935 .VALID_FLAGS, 2936 .OPEN, 2937 .{ 2938 .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, 2939 .NON_DIRECTORY_FILE = true, 2940 .OPEN_REPARSE_POINT = !flags.follow_symlinks, 2941 }, 2942 null, 2943 0, 2944 ); 2945 switch (rc) { 2946 .SUCCESS => break result, 2947 .OBJECT_NAME_INVALID => return error.BadPathName, 2948 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, 2949 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, 2950 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found 2951 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't 2952 .NO_MEDIA_IN_DEVICE => return error.NoDevice, 2953 .INVALID_PARAMETER => |err| return w.statusBug(err), 2954 .SHARING_VIOLATION => { 2955 // This occurs if the file attempting to be opened is a running 2956 // executable. However, there's a kernel bug: the error may be 2957 // incorrectly returned for an indeterminate amount of time 2958 // after an executable file is closed. Here we work around the 2959 // kernel bug with retry attempts. 2960 if (max_attempts - attempt == 0) return error.SharingViolation; 2961 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); 2962 attempt += 1; 2963 continue; 2964 }, 2965 .ACCESS_DENIED => return error.AccessDenied, 2966 .PIPE_BUSY => return error.PipeBusy, 2967 .PIPE_NOT_AVAILABLE => return error.NoDevice, 2968 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err), 2969 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, 2970 .FILE_IS_A_DIRECTORY => return error.IsDir, 2971 .NOT_A_DIRECTORY => return error.NotDir, 2972 .USER_MAPPED_FILE => return error.AccessDenied, 2973 .INVALID_HANDLE => |err| return w.statusBug(err), 2974 .DELETE_PENDING => { 2975 // This error means that there *was* a file in this location on 2976 // the file system, but it was deleted. However, the OS is not 2977 // finished with the deletion operation, and so this CreateFile 2978 // call has failed. Here, we simulate the kernel bug being 2979 // fixed by sleeping and retrying until the error goes away. 2980 if (max_attempts - attempt == 0) return error.SharingViolation; 2981 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); 2982 attempt += 1; 2983 continue; 2984 }, 2985 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference, 2986 else => return w.unexpectedStatus(rc), 2987 } 2988 }; 2989 errdefer w.CloseHandle(handle); 2990 2991 const range_off: w.LARGE_INTEGER = 0; 2992 const range_len: w.LARGE_INTEGER = 1; 2993 const exclusive = switch (flags.lock) { 2994 .none => return .{ .handle = handle }, 2995 .shared => false, 2996 .exclusive => true, 2997 }; 2998 try w.LockFile( 2999 handle, 3000 null, 3001 null, 3002 null, 3003 &io_status_block, 3004 &range_off, 3005 &range_len, 3006 null, 3007 @intFromBool(flags.lock_nonblocking), 3008 @intFromBool(exclusive), 3009 ); 3010 return .{ .handle = handle }; 3011 } 3012 3013 fn dirOpenFileWasi( 3014 userdata: ?*anyopaque, 3015 dir: Dir, 3016 sub_path: []const u8, 3017 flags: File.OpenFlags, 3018 ) File.OpenError!File { 3019 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags); 3020 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3021 const current_thread = Thread.getCurrent(t); 3022 const wasi = std.os.wasi; 3023 var base: std.os.wasi.rights_t = .{}; 3024 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE 3025 // is also set. 3026 if (flags.isRead()) { 3027 base.FD_READ = true; 3028 base.FD_TELL = true; 3029 base.FD_SEEK = true; 3030 base.FD_FILESTAT_GET = true; 3031 base.POLL_FD_READWRITE = true; 3032 } 3033 if (flags.isWrite()) { 3034 base.FD_WRITE = true; 3035 base.FD_TELL = true; 3036 base.FD_SEEK = true; 3037 base.FD_DATASYNC = true; 3038 base.FD_FDSTAT_SET_FLAGS = true; 3039 base.FD_SYNC = true; 3040 base.FD_ALLOCATE = true; 3041 base.FD_ADVISE = true; 3042 base.FD_FILESTAT_SET_TIMES = true; 3043 base.FD_FILESTAT_SET_SIZE = true; 3044 base.POLL_FD_READWRITE = true; 3045 } 3046 const lookup_flags: wasi.lookupflags_t = .{}; 3047 const oflags: wasi.oflags_t = .{}; 3048 const inheriting: wasi.rights_t = .{}; 3049 const fdflags: wasi.fdflags_t = .{}; 3050 var fd: posix.fd_t = undefined; 3051 try current_thread.beginSyscall(); 3052 while (true) { 3053 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { 3054 .SUCCESS => { 3055 errdefer posix.close(fd); 3056 current_thread.endSyscall(); 3057 return .{ .handle = fd }; 3058 }, 3059 .INTR => { 3060 try current_thread.checkCancel(); 3061 continue; 3062 }, 3063 .CANCELED => return current_thread.endSyscallCanceled(), 3064 else => |e| { 3065 current_thread.endSyscall(); 3066 switch (e) { 3067 .FAULT => |err| return errnoBug(err), 3068 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3069 .ACCES => return error.AccessDenied, 3070 .FBIG => return error.FileTooBig, 3071 .OVERFLOW => return error.FileTooBig, 3072 .ISDIR => return error.IsDir, 3073 .LOOP => return error.SymLinkLoop, 3074 .MFILE => return error.ProcessFdQuotaExceeded, 3075 .NFILE => return error.SystemFdQuotaExceeded, 3076 .NODEV => return error.NoDevice, 3077 .NOENT => return error.FileNotFound, 3078 .NOMEM => return error.SystemResources, 3079 .NOTDIR => return error.NotDir, 3080 .PERM => return error.PermissionDenied, 3081 .BUSY => return error.DeviceBusy, 3082 .NOTCAPABLE => return error.AccessDenied, 3083 .NAMETOOLONG => return error.NameTooLong, 3084 .INVAL => return error.BadPathName, 3085 .ILSEQ => return error.BadPathName, 3086 else => |err| return posix.unexpectedErrno(err), 3087 } 3088 }, 3089 } 3090 } 3091 } 3092 3093 const dirOpenDir = switch (native_os) { 3094 .wasi => dirOpenDirWasi, 3095 .haiku => dirOpenDirHaiku, 3096 else => dirOpenDirPosix, 3097 }; 3098 3099 /// This function is also used for WASI when libc is linked. 3100 fn dirOpenDirPosix( 3101 userdata: ?*anyopaque, 3102 dir: Dir, 3103 sub_path: []const u8, 3104 options: Dir.OpenOptions, 3105 ) Dir.OpenError!Dir { 3106 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3107 3108 if (is_windows) { 3109 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); 3110 return dirOpenDirWindows(t, dir, sub_path_w.span(), options); 3111 } 3112 3113 const current_thread = Thread.getCurrent(t); 3114 3115 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3116 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3117 3118 var flags: posix.O = switch (native_os) { 3119 .wasi => .{ 3120 .read = true, 3121 .NOFOLLOW = !options.follow_symlinks, 3122 .DIRECTORY = true, 3123 }, 3124 else => .{ 3125 .ACCMODE = .RDONLY, 3126 .NOFOLLOW = !options.follow_symlinks, 3127 .DIRECTORY = true, 3128 .CLOEXEC = true, 3129 }, 3130 }; 3131 3132 if (@hasField(posix.O, "PATH") and !options.iterate) 3133 flags.PATH = true; 3134 3135 try current_thread.beginSyscall(); 3136 while (true) { 3137 const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0)); 3138 switch (posix.errno(rc)) { 3139 .SUCCESS => { 3140 current_thread.endSyscall(); 3141 return .{ .handle = @intCast(rc) }; 3142 }, 3143 .INTR => { 3144 try current_thread.checkCancel(); 3145 continue; 3146 }, 3147 .CANCELED => return current_thread.endSyscallCanceled(), 3148 else => |e| { 3149 current_thread.endSyscall(); 3150 switch (e) { 3151 .FAULT => |err| return errnoBug(err), 3152 .INVAL => return error.BadPathName, 3153 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3154 .ACCES => return error.AccessDenied, 3155 .LOOP => return error.SymLinkLoop, 3156 .MFILE => return error.ProcessFdQuotaExceeded, 3157 .NAMETOOLONG => return error.NameTooLong, 3158 .NFILE => return error.SystemFdQuotaExceeded, 3159 .NODEV => return error.NoDevice, 3160 .NOENT => return error.FileNotFound, 3161 .NOMEM => return error.SystemResources, 3162 .NOTDIR => return error.NotDir, 3163 .PERM => return error.PermissionDenied, 3164 .BUSY => return error.DeviceBusy, 3165 .NXIO => return error.NoDevice, 3166 .ILSEQ => return error.BadPathName, 3167 else => |err| return posix.unexpectedErrno(err), 3168 } 3169 }, 3170 } 3171 } 3172 } 3173 3174 fn dirOpenDirHaiku( 3175 userdata: ?*anyopaque, 3176 dir: Dir, 3177 sub_path: []const u8, 3178 options: Dir.OpenOptions, 3179 ) Dir.OpenError!Dir { 3180 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3181 const current_thread = Thread.getCurrent(t); 3182 3183 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3184 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3185 3186 _ = options; 3187 3188 try current_thread.beginSyscall(); 3189 while (true) { 3190 const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix); 3191 if (rc >= 0) { 3192 current_thread.endSyscall(); 3193 return .{ .handle = rc }; 3194 } 3195 switch (@as(posix.E, @enumFromInt(rc))) { 3196 .INTR => { 3197 try current_thread.checkCancel(); 3198 continue; 3199 }, 3200 .CANCELED => return current_thread.endSyscallCanceled(), 3201 else => |e| { 3202 current_thread.endSyscall(); 3203 switch (e) { 3204 .FAULT => |err| return errnoBug(err), 3205 .INVAL => |err| return errnoBug(err), 3206 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3207 .ACCES => return error.AccessDenied, 3208 .LOOP => return error.SymLinkLoop, 3209 .MFILE => return error.ProcessFdQuotaExceeded, 3210 .NAMETOOLONG => return error.NameTooLong, 3211 .NFILE => return error.SystemFdQuotaExceeded, 3212 .NODEV => return error.NoDevice, 3213 .NOENT => return error.FileNotFound, 3214 .NOMEM => return error.SystemResources, 3215 .NOTDIR => return error.NotDir, 3216 .PERM => return error.PermissionDenied, 3217 .BUSY => return error.DeviceBusy, 3218 else => |err| return posix.unexpectedErrno(err), 3219 } 3220 }, 3221 } 3222 } 3223 } 3224 3225 pub fn dirOpenDirWindows( 3226 t: *Io.Threaded, 3227 dir: Dir, 3228 sub_path_w: [:0]const u16, 3229 options: Dir.OpenOptions, 3230 ) Dir.OpenError!Dir { 3231 const current_thread = Thread.getCurrent(t); 3232 const w = windows; 3233 3234 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2); 3235 var nt_name: w.UNICODE_STRING = .{ 3236 .Length = path_len_bytes, 3237 .MaximumLength = path_len_bytes, 3238 .Buffer = @constCast(sub_path_w.ptr), 3239 }; 3240 var io_status_block: w.IO_STATUS_BLOCK = undefined; 3241 var result: Dir = .{ .handle = undefined }; 3242 try current_thread.checkCancel(); 3243 const rc = w.ntdll.NtCreateFile( 3244 &result.handle, 3245 // TODO remove some of these flags if options.access_sub_paths is false 3246 .{ 3247 .SPECIFIC = .{ .FILE_DIRECTORY = .{ 3248 .LIST = options.iterate, 3249 .READ_EA = true, 3250 .TRAVERSE = true, 3251 .READ_ATTRIBUTES = true, 3252 } }, 3253 .STANDARD = .{ 3254 .RIGHTS = .READ, 3255 .SYNCHRONIZE = true, 3256 }, 3257 }, 3258 &.{ 3259 .Length = @sizeOf(w.OBJECT_ATTRIBUTES), 3260 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, 3261 .Attributes = .{}, 3262 .ObjectName = &nt_name, 3263 .SecurityDescriptor = null, 3264 .SecurityQualityOfService = null, 3265 }, 3266 &io_status_block, 3267 null, 3268 .{ .NORMAL = true }, 3269 .VALID_FLAGS, 3270 .OPEN, 3271 .{ 3272 .DIRECTORY_FILE = true, 3273 .IO = .SYNCHRONOUS_NONALERT, 3274 .OPEN_FOR_BACKUP_INTENT = true, 3275 .OPEN_REPARSE_POINT = !options.follow_symlinks, 3276 }, 3277 null, 3278 0, 3279 ); 3280 3281 switch (rc) { 3282 .SUCCESS => return result, 3283 .OBJECT_NAME_INVALID => return error.BadPathName, 3284 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, 3285 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err), 3286 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, 3287 .NOT_A_DIRECTORY => return error.NotDir, 3288 // This can happen if the directory has 'List folder contents' permission set to 'Deny' 3289 // and the directory is trying to be opened for iteration. 3290 .ACCESS_DENIED => return error.AccessDenied, 3291 .INVALID_PARAMETER => |err| return w.statusBug(err), 3292 else => return w.unexpectedStatus(rc), 3293 } 3294 } 3295 3296 const MakeOpenDirAccessMaskWOptions = struct { 3297 no_follow: bool, 3298 create_disposition: u32, 3299 }; 3300 3301 fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { 3302 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3303 _ = t; 3304 for (dirs) |dir| posix.close(dir.handle); 3305 } 3306 3307 const dirRead = switch (native_os) { 3308 .linux => dirReadLinux, 3309 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin, 3310 .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd, 3311 .illumos => dirReadIllumos, 3312 .haiku => dirReadHaiku, 3313 .windows => dirReadWindows, 3314 .wasi => dirReadWasi, 3315 else => dirReadUnimplemented, 3316 }; 3317 3318 fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3319 const linux = std.os.linux; 3320 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3321 const current_thread = Thread.getCurrent(t); 3322 var buffer_index: usize = 0; 3323 while (buffer.len - buffer_index != 0) { 3324 if (dr.end - dr.index == 0) { 3325 // Refill the buffer, unless we've already created references to 3326 // buffered data. 3327 if (buffer_index != 0) break; 3328 if (dr.state == .reset) { 3329 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { 3330 error.Unseekable => return error.Unexpected, 3331 else => |e| return e, 3332 }; 3333 dr.state = .reading; 3334 } 3335 try current_thread.beginSyscall(); 3336 const n = while (true) { 3337 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); 3338 switch (linux.errno(rc)) { 3339 .SUCCESS => { 3340 current_thread.endSyscall(); 3341 break rc; 3342 }, 3343 .INTR => { 3344 try current_thread.checkCancel(); 3345 continue; 3346 }, 3347 .CANCELED => return current_thread.endSyscallCanceled(), 3348 else => |e| { 3349 current_thread.endSyscall(); 3350 switch (e) { 3351 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. 3352 .FAULT => |err| return errnoBug(err), 3353 .NOTDIR => |err| return errnoBug(err), 3354 // To be consistent across platforms, iteration 3355 // ends if the directory being iterated is deleted 3356 // during iteration. This matches the behavior of 3357 // non-Linux UNIX platforms. 3358 .NOENT => { 3359 dr.state = .finished; 3360 return 0; 3361 }, 3362 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. 3363 .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory. 3364 else => |err| return posix.unexpectedErrno(err), 3365 } 3366 }, 3367 } 3368 }; 3369 if (n == 0) { 3370 dr.state = .finished; 3371 return 0; 3372 } 3373 dr.index = 0; 3374 dr.end = n; 3375 } 3376 const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); 3377 const next_index = dr.index + linux_entry.reclen; 3378 dr.index = next_index; 3379 3380 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0); 3381 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; 3382 3383 const entry_kind: File.Kind = switch (linux_entry.type) { 3384 linux.DT.BLK => .block_device, 3385 linux.DT.CHR => .character_device, 3386 linux.DT.DIR => .directory, 3387 linux.DT.FIFO => .named_pipe, 3388 linux.DT.LNK => .sym_link, 3389 linux.DT.REG => .file, 3390 linux.DT.SOCK => .unix_domain_socket, 3391 else => .unknown, 3392 }; 3393 buffer[buffer_index] = .{ 3394 .name = name, 3395 .kind = entry_kind, 3396 .inode = linux_entry.ino, 3397 }; 3398 buffer_index += 1; 3399 } 3400 return buffer_index; 3401 } 3402 3403 fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3404 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3405 const current_thread = Thread.getCurrent(t); 3406 const Header = extern struct { 3407 seek: i64, 3408 }; 3409 const header: *Header = @ptrCast(dr.buffer.ptr); 3410 const header_end: usize = @sizeOf(Header); 3411 if (dr.index < header_end) { 3412 // Initialize header. 3413 dr.index = header_end; 3414 dr.end = header_end; 3415 header.* = .{ .seek = 0 }; 3416 } 3417 var buffer_index: usize = 0; 3418 while (buffer.len - buffer_index != 0) { 3419 if (dr.end - dr.index == 0) { 3420 // Refill the buffer, unless we've already created references to 3421 // buffered data. 3422 if (buffer_index != 0) break; 3423 if (dr.state == .reset) { 3424 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { 3425 error.Unseekable => return error.Unexpected, 3426 else => |e| return e, 3427 }; 3428 dr.state = .reading; 3429 } 3430 const dents_buffer = dr.buffer[header_end..]; 3431 try current_thread.beginSyscall(); 3432 const n: usize = while (true) { 3433 const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek); 3434 switch (posix.errno(rc)) { 3435 .SUCCESS => { 3436 current_thread.endSyscall(); 3437 break @intCast(rc); 3438 }, 3439 .INTR => { 3440 try current_thread.checkCancel(); 3441 continue; 3442 }, 3443 .CANCELED => return current_thread.endSyscallCanceled(), 3444 else => |e| { 3445 current_thread.endSyscall(); 3446 switch (e) { 3447 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability 3448 .FAULT => |err| return errnoBug(err), 3449 .NOTDIR => |err| return errnoBug(err), 3450 .INVAL => |err| return errnoBug(err), 3451 else => |err| return posix.unexpectedErrno(err), 3452 } 3453 }, 3454 } 3455 }; 3456 if (n == 0) { 3457 dr.state = .finished; 3458 return 0; 3459 } 3460 dr.index = header_end; 3461 dr.end = header_end + n; 3462 } 3463 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); 3464 const next_index = dr.index + darwin_entry.reclen; 3465 dr.index = next_index; 3466 3467 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen]; 3468 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) 3469 continue; 3470 3471 const entry_kind: File.Kind = switch (darwin_entry.type) { 3472 posix.DT.BLK => .block_device, 3473 posix.DT.CHR => .character_device, 3474 posix.DT.DIR => .directory, 3475 posix.DT.FIFO => .named_pipe, 3476 posix.DT.LNK => .sym_link, 3477 posix.DT.REG => .file, 3478 posix.DT.SOCK => .unix_domain_socket, 3479 posix.DT.WHT => .whiteout, 3480 else => .unknown, 3481 }; 3482 buffer[buffer_index] = .{ 3483 .name = name, 3484 .kind = entry_kind, 3485 .inode = darwin_entry.ino, 3486 }; 3487 buffer_index += 1; 3488 } 3489 return buffer_index; 3490 } 3491 3492 fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3493 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3494 const current_thread = Thread.getCurrent(t); 3495 var buffer_index: usize = 0; 3496 while (buffer.len - buffer_index != 0) { 3497 if (dr.end - dr.index == 0) { 3498 // Refill the buffer, unless we've already created references to 3499 // buffered data. 3500 if (buffer_index != 0) break; 3501 if (dr.state == .reset) { 3502 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { 3503 error.Unseekable => return error.Unexpected, 3504 else => |e| return e, 3505 }; 3506 dr.state = .reading; 3507 } 3508 try current_thread.beginSyscall(); 3509 const n: usize = while (true) { 3510 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); 3511 switch (posix.errno(rc)) { 3512 .SUCCESS => { 3513 current_thread.endSyscall(); 3514 break @intCast(rc); 3515 }, 3516 .INTR => { 3517 try current_thread.checkCancel(); 3518 continue; 3519 }, 3520 .CANCELED => return current_thread.endSyscallCanceled(), 3521 else => |e| { 3522 current_thread.endSyscall(); 3523 switch (e) { 3524 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability 3525 .FAULT => |err| return errnoBug(err), 3526 .NOTDIR => |err| return errnoBug(err), 3527 .INVAL => |err| return errnoBug(err), 3528 // Introduced in freebsd 13.2: directory unlinked 3529 // but still open. To be consistent, iteration ends 3530 // if the directory being iterated is deleted 3531 // during iteration. 3532 .NOENT => { 3533 dr.state = .finished; 3534 return 0; 3535 }, 3536 else => |err| return posix.unexpectedErrno(err), 3537 } 3538 }, 3539 } 3540 }; 3541 if (n == 0) { 3542 dr.state = .finished; 3543 return 0; 3544 } 3545 dr.index = 0; 3546 dr.end = n; 3547 } 3548 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); 3549 const next_index = dr.index + 3550 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen(); 3551 dr.index = next_index; 3552 3553 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen]; 3554 3555 const skip_zero_fileno = switch (native_os) { 3556 // fileno=0 is used to mark invalid entries or deleted files. 3557 .openbsd, .netbsd => true, 3558 else => false, 3559 }; 3560 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or 3561 (skip_zero_fileno and bsd_entry.fileno == 0)) 3562 { 3563 continue; 3564 } 3565 3566 const entry_kind: File.Kind = switch (bsd_entry.type) { 3567 posix.DT.BLK => .block_device, 3568 posix.DT.CHR => .character_device, 3569 posix.DT.DIR => .directory, 3570 posix.DT.FIFO => .named_pipe, 3571 posix.DT.LNK => .sym_link, 3572 posix.DT.REG => .file, 3573 posix.DT.SOCK => .unix_domain_socket, 3574 posix.DT.WHT => .whiteout, 3575 else => .unknown, 3576 }; 3577 buffer[buffer_index] = .{ 3578 .name = name, 3579 .kind = entry_kind, 3580 .inode = bsd_entry.fileno, 3581 }; 3582 buffer_index += 1; 3583 } 3584 return buffer_index; 3585 } 3586 3587 fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3588 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3589 const current_thread = Thread.getCurrent(t); 3590 var buffer_index: usize = 0; 3591 while (buffer.len - buffer_index != 0) { 3592 if (dr.end - dr.index == 0) { 3593 // Refill the buffer, unless we've already created references to 3594 // buffered data. 3595 if (buffer_index != 0) break; 3596 if (dr.state == .reset) { 3597 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) { 3598 error.Unseekable => return error.Unexpected, 3599 else => |e| return e, 3600 }; 3601 dr.state = .reading; 3602 } 3603 try current_thread.beginSyscall(); 3604 const n: usize = while (true) { 3605 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); 3606 switch (posix.errno(rc)) { 3607 .SUCCESS => { 3608 current_thread.endSyscall(); 3609 break rc; 3610 }, 3611 .INTR => { 3612 try current_thread.checkCancel(); 3613 continue; 3614 }, 3615 .CANCELED => return current_thread.endSyscallCanceled(), 3616 else => |e| { 3617 current_thread.endSyscall(); 3618 switch (e) { 3619 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability 3620 .FAULT => |err| return errnoBug(err), 3621 .NOTDIR => |err| return errnoBug(err), 3622 .INVAL => |err| return errnoBug(err), 3623 else => |err| return posix.unexpectedErrno(err), 3624 } 3625 }, 3626 } 3627 }; 3628 if (n == 0) { 3629 dr.state = .finished; 3630 return 0; 3631 } 3632 dr.index = 0; 3633 dr.end = n; 3634 } 3635 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); 3636 const next_index = dr.index + entry.reclen; 3637 dr.index = next_index; 3638 3639 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0); 3640 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; 3641 3642 // illumos dirent doesn't expose type, so we have to call stat to get it. 3643 const stat = try posixStatFile(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW); 3644 3645 buffer[buffer_index] = .{ 3646 .name = name, 3647 .kind = stat.kind, 3648 .inode = entry.ino, 3649 }; 3650 buffer_index += 1; 3651 } 3652 return buffer_index; 3653 } 3654 3655 fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3656 _ = userdata; 3657 _ = dr; 3658 _ = buffer; 3659 @panic("TODO"); 3660 } 3661 3662 fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3663 _ = userdata; 3664 _ = dr; 3665 _ = buffer; 3666 @panic("TODO"); 3667 } 3668 3669 fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3670 _ = userdata; 3671 _ = dr; 3672 _ = buffer; 3673 @panic("TODO"); 3674 } 3675 3676 fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 3677 _ = userdata; 3678 _ = dir_reader; 3679 _ = buffer; 3680 return error.Unimplemented; 3681 } 3682 3683 const dirRealPath = switch (native_os) { 3684 .windows => dirRealPathWindows, 3685 else => dirRealPathPosix, 3686 }; 3687 3688 fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize { 3689 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3690 const w = windows; 3691 const current_thread = Thread.getCurrent(t); 3692 3693 try current_thread.checkCancel(); 3694 3695 var path_name_w = try w.sliceToPrefixedFileW(dir.handle, sub_path); 3696 3697 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; 3698 const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; 3699 const creation = w.FILE_OPEN; 3700 const h_file = blk: { 3701 const res = w.OpenFile(path_name_w.span(), .{ 3702 .dir = dir.handle, 3703 .access_mask = access_mask, 3704 .share_access = share_access, 3705 .creation = creation, 3706 .filter = .any, 3707 }) catch |err| switch (err) { 3708 error.WouldBlock => unreachable, 3709 else => |e| return e, 3710 }; 3711 break :blk res; 3712 }; 3713 defer w.CloseHandle(h_file); 3714 3715 const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); 3716 3717 const len = std.unicode.calcWtf8Len(wide_slice); 3718 if (len > out_buffer.len) 3719 return error.NameTooLong; 3720 3721 return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); 3722 } 3723 3724 fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize { 3725 if (native_os == .wasi) @compileError("unsupported operating system"); 3726 const max_path_bytes = std.fs.max_path_bytes; 3727 3728 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3729 const current_thread = Thread.getCurrent(t); 3730 3731 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3732 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3733 3734 var flags: posix.O = .{}; 3735 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true; 3736 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; 3737 if (@hasField(posix.O, "PATH")) flags.PATH = true; 3738 3739 const mode: posix.mode_t = 0; 3740 3741 try current_thread.beginSyscall(); 3742 const fd: posix.fd_t = while (true) { 3743 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); 3744 switch (posix.errno(rc)) { 3745 .SUCCESS => { 3746 current_thread.endSyscall(); 3747 break @intCast(rc); 3748 }, 3749 .INTR => { 3750 try current_thread.checkCancel(); 3751 continue; 3752 }, 3753 .CANCELED => return current_thread.endSyscallCanceled(), 3754 else => |e| { 3755 current_thread.endSyscall(); 3756 switch (e) { 3757 .FAULT => |err| return errnoBug(err), 3758 .INVAL => return error.BadPathName, 3759 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3760 .ACCES => return error.AccessDenied, 3761 .FBIG => return error.FileTooBig, 3762 .OVERFLOW => return error.FileTooBig, 3763 .ISDIR => return error.IsDir, 3764 .LOOP => return error.SymLinkLoop, 3765 .MFILE => return error.ProcessFdQuotaExceeded, 3766 .NAMETOOLONG => return error.NameTooLong, 3767 .NFILE => return error.SystemFdQuotaExceeded, 3768 .NODEV => return error.NoDevice, 3769 .NOENT => return error.FileNotFound, 3770 .SRCH => return error.FileNotFound, // Linux when accessing procfs. 3771 .NOMEM => return error.SystemResources, 3772 .NOSPC => return error.NoSpaceLeft, 3773 .NOTDIR => return error.NotDir, 3774 .PERM => return error.PermissionDenied, 3775 .EXIST => return error.PathAlreadyExists, 3776 .BUSY => return error.DeviceBusy, 3777 .NXIO => return error.NoDevice, 3778 .ILSEQ => return error.BadPathName, 3779 else => |err| return posix.unexpectedErrno(err), 3780 } 3781 }, 3782 } 3783 }; 3784 errdefer posix.close(fd); 3785 3786 switch (native_os) { 3787 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 3788 // On macOS, we can use F.GETPATH fcntl command to query the OS for 3789 // the path to the file descriptor. 3790 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; 3791 @memset(&sufficient_buffer, 0); 3792 try current_thread.beginSyscall(); 3793 while (true) { 3794 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) { 3795 .SUCCESS => { 3796 current_thread.endSyscall(); 3797 break; 3798 }, 3799 .INTR => { 3800 try current_thread.checkCancel(); 3801 continue; 3802 }, 3803 .CANCELED => return current_thread.endSyscallCanceled(), 3804 else => |e| { 3805 current_thread.endSyscall(); 3806 switch (e) { 3807 .BADF => return error.FileNotFound, 3808 .NOSPC => return error.NameTooLong, 3809 .NOENT => return error.FileNotFound, 3810 else => |err| return posix.unexpectedErrno(err), 3811 } 3812 }, 3813 } 3814 } 3815 const n = std.mem.indexOfScalar(u8, &sufficient_buffer, 0) orelse sufficient_buffer.len; 3816 if (n > out_buffer.len) return error.NameTooLong; 3817 @memcpy(out_buffer[0..n], sufficient_buffer[0..n]); 3818 return n; 3819 }, 3820 .linux, .serenity, .illumos => { 3821 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; 3822 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}"; 3823 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable; 3824 try current_thread.beginSyscall(); 3825 while (true) { 3826 const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len); 3827 switch (posix.errno(rc)) { 3828 .SUCCESS => { 3829 current_thread.endSyscall(); 3830 const len: usize = @bitCast(rc); 3831 return len; 3832 }, 3833 .INTR => { 3834 try current_thread.checkCancel(); 3835 continue; 3836 }, 3837 .CANCELED => return current_thread.endSyscallCanceled(), 3838 else => |e| { 3839 current_thread.endSyscall(); 3840 switch (e) { 3841 .ACCES => return error.AccessDenied, 3842 .FAULT => |err| return errnoBug(err), 3843 .IO => return error.FileSystem, 3844 .LOOP => return error.SymLinkLoop, 3845 .NAMETOOLONG => return error.NameTooLong, 3846 .NOENT => return error.FileNotFound, 3847 .NOMEM => return error.SystemResources, 3848 .NOTDIR => return error.NotDir, 3849 .ILSEQ => |err| return errnoBug(err), 3850 else => |err| return posix.unexpectedErrno(err), 3851 } 3852 }, 3853 } 3854 } 3855 }, 3856 .freebsd => { 3857 var kfile: std.c.kinfo_file = undefined; 3858 kfile.structsize = std.c.KINFO_FILE_SIZE; 3859 try current_thread.beginSyscall(); 3860 while (true) { 3861 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) { 3862 .SUCCESS => { 3863 current_thread.endSyscall(); 3864 break; 3865 }, 3866 .INTR => { 3867 try current_thread.checkCancel(); 3868 continue; 3869 }, 3870 .CANCELED => return current_thread.endSyscallCanceled(), 3871 else => |e| { 3872 current_thread.endSyscall(); 3873 switch (e) { 3874 .BADF => return error.FileNotFound, 3875 else => |err| return posix.unexpectedErrno(err), 3876 } 3877 }, 3878 } 3879 } 3880 const len = std.mem.indexOfScalar(u8, &kfile.path, 0) orelse kfile.path.len; 3881 if (len == 0) return error.NameTooLong; 3882 return len; 3883 }, 3884 .netbsd, .dragonfly => { 3885 @memset(out_buffer[0..max_path_bytes], 0); 3886 try current_thread.beginSyscall(); 3887 while (true) { 3888 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) { 3889 .SUCCESS => { 3890 current_thread.endSyscall(); 3891 break; 3892 }, 3893 .INTR => { 3894 try current_thread.checkCancel(); 3895 continue; 3896 }, 3897 .CANCELED => return current_thread.endSyscallCanceled(), 3898 else => |e| { 3899 current_thread.endSyscall(); 3900 switch (e) { 3901 .ACCES => return error.AccessDenied, 3902 .BADF => return error.FileNotFound, 3903 .NOENT => return error.FileNotFound, 3904 .NOMEM => return error.SystemResources, 3905 .RANGE => return error.NameTooLong, 3906 else => |err| return posix.unexpectedErrno(err), 3907 } 3908 }, 3909 } 3910 } 3911 return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len; 3912 }, 3913 else => @compileError("unsupported OS"), 3914 } 3915 comptime unreachable; 3916 } 3917 3918 const dirDeleteFile = switch (native_os) { 3919 .windows => dirDeleteFileWindows, 3920 .wasi => dirDeleteFileWasi, 3921 else => dirDeleteFilePosix, 3922 }; 3923 3924 fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { 3925 return dirDeleteWindows(userdata, dir, sub_path, false); 3926 } 3927 3928 fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { 3929 if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path); 3930 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3931 const current_thread = Thread.getCurrent(t); 3932 try current_thread.beginSyscall(); 3933 while (true) { 3934 const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len); 3935 switch (res) { 3936 .SUCCESS => { 3937 current_thread.endSyscall(); 3938 return; 3939 }, 3940 .INTR => { 3941 try current_thread.checkCancel(); 3942 continue; 3943 }, 3944 .CANCELED => return current_thread.endSyscallCanceled(), 3945 else => |e| { 3946 current_thread.endSyscall(); 3947 switch (e) { 3948 .ACCES => return error.AccessDenied, 3949 .PERM => return error.PermissionDenied, 3950 .BUSY => return error.FileBusy, 3951 .FAULT => |err| return errnoBug(err), 3952 .IO => return error.FileSystem, 3953 .ISDIR => return error.IsDir, 3954 .LOOP => return error.SymLinkLoop, 3955 .NAMETOOLONG => return error.NameTooLong, 3956 .NOENT => return error.FileNotFound, 3957 .NOTDIR => return error.NotDir, 3958 .NOMEM => return error.SystemResources, 3959 .ROFS => return error.ReadOnlyFileSystem, 3960 .NOTEMPTY => return error.DirNotEmpty, 3961 .NOTCAPABLE => return error.AccessDenied, 3962 .ILSEQ => return error.BadPathName, 3963 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 3964 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3965 else => |err| return posix.unexpectedErrno(err), 3966 } 3967 }, 3968 } 3969 } 3970 } 3971 3972 fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { 3973 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3974 const current_thread = Thread.getCurrent(t); 3975 3976 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3977 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3978 3979 try current_thread.beginSyscall(); 3980 while (true) { 3981 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) { 3982 .SUCCESS => { 3983 current_thread.endSyscall(); 3984 return; 3985 }, 3986 .INTR => { 3987 try current_thread.checkCancel(); 3988 continue; 3989 }, 3990 .CANCELED => return current_thread.endSyscallCanceled(), 3991 // Some systems return permission errors when trying to delete a 3992 // directory, so we need to handle that case specifically and 3993 // translate the error. 3994 .PERM => switch (native_os) { 3995 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => { 3996 3997 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them). 3998 var st = std.mem.zeroes(posix.Stat); 3999 while (true) { 4000 try current_thread.checkCancel(); 4001 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) { 4002 .SUCCESS => { 4003 current_thread.endSyscall(); 4004 break; 4005 }, 4006 .INTR => continue, 4007 .CANCELED => return current_thread.endSyscallCanceled(), 4008 else => { 4009 current_thread.endSyscall(); 4010 return error.PermissionDenied; 4011 }, 4012 } 4013 } 4014 const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR; 4015 if (is_dir) 4016 return error.IsDir 4017 else 4018 return error.PermissionDenied; 4019 }, 4020 else => { 4021 current_thread.endSyscall(); 4022 return error.PermissionDenied; 4023 }, 4024 }, 4025 else => |e| { 4026 current_thread.endSyscall(); 4027 switch (e) { 4028 .ACCES => return error.AccessDenied, 4029 .BUSY => return error.FileBusy, 4030 .FAULT => |err| return errnoBug(err), 4031 .IO => return error.FileSystem, 4032 .ISDIR => return error.IsDir, 4033 .LOOP => return error.SymLinkLoop, 4034 .NAMETOOLONG => return error.NameTooLong, 4035 .NOENT => return error.FileNotFound, 4036 .NOTDIR => return error.NotDir, 4037 .NOMEM => return error.SystemResources, 4038 .ROFS => return error.ReadOnlyFileSystem, 4039 .EXIST => |err| return errnoBug(err), 4040 .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR 4041 .ILSEQ => return error.BadPathName, 4042 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 4043 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4044 else => |err| return posix.unexpectedErrno(err), 4045 } 4046 }, 4047 } 4048 } 4049 } 4050 4051 const dirDeleteDir = switch (native_os) { 4052 .windows => dirDeleteDirWindows, 4053 .wasi => dirDeleteDirWasi, 4054 else => dirDeleteDirPosix, 4055 }; 4056 4057 fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { 4058 return dirDeleteWindows(userdata, dir, sub_path, true); 4059 } 4060 4061 fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) Dir.DeleteFileError!void { 4062 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4063 const current_thread = Thread.getCurrent(t); 4064 const w = windows; 4065 4066 try current_thread.checkCancel(); 4067 4068 const sub_path_w = try w.sliceToPrefixedFileW(dir.handle, sub_path); 4069 4070 const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2)); 4071 var nt_name: w.UNICODE_STRING = .{ 4072 .Length = path_len_bytes, 4073 .MaximumLength = path_len_bytes, 4074 // The Windows API makes this mutable, but it will not mutate here. 4075 .Buffer = @constCast(sub_path_w.ptr), 4076 }; 4077 4078 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { 4079 // Windows does not recognize this, but it does work with empty string. 4080 nt_name.Length = 0; 4081 } 4082 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { 4083 // Can't remove the parent directory with an open handle. 4084 return error.FileBusy; 4085 } 4086 4087 const create_options_flags: w.ULONG = if (remove_dir) 4088 w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT 4089 else 4090 w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT; 4091 4092 var attr: w.OBJECT_ATTRIBUTES = .{ 4093 .Length = @sizeOf(w.OBJECT_ATTRIBUTES), 4094 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, 4095 .Attributes = w.OBJ_CASE_INSENSITIVE, 4096 .ObjectName = &nt_name, 4097 .SecurityDescriptor = null, 4098 .SecurityQualityOfService = null, 4099 }; 4100 var io_status_block: w.IO_STATUS_BLOCK = undefined; 4101 var tmp_handle: w.HANDLE = undefined; 4102 var rc = w.ntdll.NtCreateFile( 4103 &tmp_handle, 4104 w.SYNCHRONIZE | w.DELETE, 4105 &attr, 4106 &io_status_block, 4107 null, 4108 0, 4109 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE, 4110 w.FILE_OPEN, 4111 create_options_flags, 4112 null, 4113 0, 4114 ); 4115 switch (rc) { 4116 .SUCCESS => {}, 4117 .OBJECT_NAME_INVALID => |err| return w.statusBug(err), 4118 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, 4119 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, 4120 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found 4121 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't 4122 .INVALID_PARAMETER => |err| return w.statusBug(err), 4123 .FILE_IS_A_DIRECTORY => return error.IsDir, 4124 .NOT_A_DIRECTORY => return error.NotDir, 4125 .SHARING_VIOLATION => return error.FileBusy, 4126 .ACCESS_DENIED => return error.AccessDenied, 4127 .DELETE_PENDING => return, 4128 else => return w.unexpectedStatus(rc), 4129 } 4130 defer w.CloseHandle(tmp_handle); 4131 4132 // FileDispositionInformationEx has varying levels of support: 4133 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1 4134 // (INVALID_INFO_CLASS is returned if not supported) 4135 // - Requires the NTFS filesystem 4136 // (on filesystems like FAT32, INVALID_PARAMETER is returned) 4137 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1 4138 // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 4139 // (NOT_SUPPORTED is returned if a flag is unsupported) 4140 // 4141 // The strategy here is just to try using FileDispositionInformationEx and fall back to 4142 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. 4143 const need_fallback = need_fallback: { 4144 try current_thread.checkCancel(); 4145 4146 // Deletion with posix semantics if the filesystem supports it. 4147 var info: w.FILE_DISPOSITION_INFORMATION_EX = .{ 4148 .Flags = w.FILE_DISPOSITION_DELETE | 4149 w.FILE_DISPOSITION_POSIX_SEMANTICS | 4150 w.FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE, 4151 }; 4152 4153 rc = w.ntdll.NtSetInformationFile( 4154 tmp_handle, 4155 &io_status_block, 4156 &info, 4157 @sizeOf(w.FILE_DISPOSITION_INFORMATION_EX), 4158 .FileDispositionInformationEx, 4159 ); 4160 switch (rc) { 4161 .SUCCESS => return, 4162 // The filesystem does not support FileDispositionInformationEx 4163 .INVALID_PARAMETER, 4164 // The operating system does not support FileDispositionInformationEx 4165 .INVALID_INFO_CLASS, 4166 // The operating system does not support one of the flags 4167 .NOT_SUPPORTED, 4168 => break :need_fallback true, 4169 // For all other statuses, fall down to the switch below to handle them. 4170 else => break :need_fallback false, 4171 } 4172 }; 4173 4174 if (need_fallback) { 4175 try current_thread.checkCancel(); 4176 4177 // Deletion with file pending semantics, which requires waiting or moving 4178 // files to get them removed (from here). 4179 var file_dispo: w.FILE_DISPOSITION_INFORMATION = .{ 4180 .DeleteFile = w.TRUE, 4181 }; 4182 4183 rc = w.ntdll.NtSetInformationFile( 4184 tmp_handle, 4185 &io_status_block, 4186 &file_dispo, 4187 @sizeOf(w.FILE_DISPOSITION_INFORMATION), 4188 .FileDispositionInformation, 4189 ); 4190 } 4191 switch (rc) { 4192 .SUCCESS => {}, 4193 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, 4194 .INVALID_PARAMETER => |err| return w.statusBug(err), 4195 .CANNOT_DELETE => return error.AccessDenied, 4196 .MEDIA_WRITE_PROTECTED => return error.AccessDenied, 4197 .ACCESS_DENIED => return error.AccessDenied, 4198 else => return w.unexpectedStatus(rc), 4199 } 4200 } 4201 4202 fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { 4203 if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path); 4204 4205 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4206 const current_thread = Thread.getCurrent(t); 4207 4208 try current_thread.beginSyscall(); 4209 while (true) { 4210 const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len); 4211 switch (res) { 4212 .SUCCESS => { 4213 current_thread.endSyscall(); 4214 return; 4215 }, 4216 .INTR => { 4217 try current_thread.checkCancel(); 4218 continue; 4219 }, 4220 .CANCELED => return current_thread.endSyscallCanceled(), 4221 else => |e| { 4222 current_thread.endSyscall(); 4223 switch (e) { 4224 .ACCES => return error.AccessDenied, 4225 .PERM => return error.PermissionDenied, 4226 .BUSY => return error.FileBusy, 4227 .FAULT => |err| return errnoBug(err), 4228 .IO => return error.FileSystem, 4229 .ISDIR => return error.IsDir, 4230 .LOOP => return error.SymLinkLoop, 4231 .NAMETOOLONG => return error.NameTooLong, 4232 .NOENT => return error.FileNotFound, 4233 .NOTDIR => return error.NotDir, 4234 .NOMEM => return error.SystemResources, 4235 .ROFS => return error.ReadOnlyFileSystem, 4236 .NOTEMPTY => return error.DirNotEmpty, 4237 .NOTCAPABLE => return error.AccessDenied, 4238 .ILSEQ => return error.BadPathName, 4239 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 4240 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4241 else => |err| return posix.unexpectedErrno(err), 4242 } 4243 }, 4244 } 4245 } 4246 } 4247 4248 fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { 4249 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4250 const current_thread = Thread.getCurrent(t); 4251 4252 var path_buffer: [posix.PATH_MAX]u8 = undefined; 4253 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 4254 4255 try current_thread.beginSyscall(); 4256 while (true) { 4257 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) { 4258 .SUCCESS => { 4259 current_thread.endSyscall(); 4260 return; 4261 }, 4262 .INTR => { 4263 try current_thread.checkCancel(); 4264 continue; 4265 }, 4266 .CANCELED => return current_thread.endSyscallCanceled(), 4267 else => |e| { 4268 current_thread.endSyscall(); 4269 switch (e) { 4270 .ACCES => return error.AccessDenied, 4271 .PERM => return error.PermissionDenied, 4272 .BUSY => return error.FileBusy, 4273 .FAULT => |err| return errnoBug(err), 4274 .IO => return error.FileSystem, 4275 .ISDIR => |err| return errnoBug(err), 4276 .LOOP => return error.SymLinkLoop, 4277 .NAMETOOLONG => return error.NameTooLong, 4278 .NOENT => return error.FileNotFound, 4279 .NOTDIR => return error.NotDir, 4280 .NOMEM => return error.SystemResources, 4281 .ROFS => return error.ReadOnlyFileSystem, 4282 .EXIST => |err| return errnoBug(err), 4283 .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR 4284 .ILSEQ => return error.BadPathName, 4285 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 4286 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4287 else => |err| return posix.unexpectedErrno(err), 4288 } 4289 }, 4290 } 4291 } 4292 } 4293 4294 const dirRename = switch (native_os) { 4295 .windows => dirRenameWindows, 4296 .wasi => dirRenameWasi, 4297 else => dirRenamePosix, 4298 }; 4299 4300 fn dirRenameWindows( 4301 userdata: ?*anyopaque, 4302 old_dir: Dir, 4303 old_sub_path: []const u8, 4304 new_dir: Dir, 4305 new_sub_path: []const u8, 4306 ) Dir.RenameError!void { 4307 const w = windows; 4308 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4309 const current_thread = Thread.getCurrent(t); 4310 4311 const old_path_w = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path); 4312 const new_path_w = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path); 4313 const replace_if_exists = true; 4314 4315 try current_thread.checkCancel(); 4316 4317 const src_fd = w.OpenFile(old_path_w, .{ 4318 .dir = old_dir.handle, 4319 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | w.DELETE, 4320 .creation = w.FILE_OPEN, 4321 .filter = .any, // This function is supposed to rename both files and directories. 4322 .follow_symlinks = false, 4323 }) catch |err| switch (err) { 4324 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. 4325 else => |e| return e, 4326 }; 4327 defer w.CloseHandle(src_fd); 4328 4329 var rc: w.NTSTATUS = undefined; 4330 // FileRenameInformationEx has varying levels of support: 4331 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 4332 // (INVALID_INFO_CLASS is returned if not supported) 4333 // - Requires the NTFS filesystem 4334 // (on filesystems like FAT32, INVALID_PARAMETER is returned) 4335 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 4336 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 4337 // (NOT_SUPPORTED is returned if a flag is unsupported) 4338 // 4339 // The strategy here is just to try using FileRenameInformationEx and fall back to 4340 // FileRenameInformation if the return value lets us know that some aspect of it is not supported. 4341 const need_fallback = need_fallback: { 4342 const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + (w.PATH_MAX_WIDE * 2); 4343 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION_EX)) = undefined; 4344 const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2; 4345 if (struct_len > struct_buf_len) return error.NameTooLong; 4346 4347 const rename_info: *w.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); 4348 var io_status_block: w.IO_STATUS_BLOCK = undefined; 4349 4350 var flags: w.ULONG = w.FILE_RENAME_POSIX_SEMANTICS | w.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; 4351 if (replace_if_exists) flags |= w.FILE_RENAME_REPLACE_IF_EXISTS; 4352 rename_info.* = .{ 4353 .Flags = flags, 4354 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, 4355 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong 4356 .FileName = undefined, 4357 }; 4358 @memcpy((&rename_info.FileName).ptr, new_path_w); 4359 rc = w.ntdll.NtSetInformationFile( 4360 src_fd, 4361 &io_status_block, 4362 rename_info, 4363 @intCast(struct_len), // already checked for error.NameTooLong 4364 .FileRenameInformationEx, 4365 ); 4366 switch (rc) { 4367 .SUCCESS => return, 4368 // The filesystem does not support FileDispositionInformationEx 4369 .INVALID_PARAMETER, 4370 // The operating system does not support FileDispositionInformationEx 4371 .INVALID_INFO_CLASS, 4372 // The operating system does not support one of the flags 4373 .NOT_SUPPORTED, 4374 => break :need_fallback true, 4375 // For all other statuses, fall down to the switch below to handle them. 4376 else => break :need_fallback false, 4377 } 4378 }; 4379 4380 if (need_fallback) { 4381 const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION) + (w.PATH_MAX_WIDE * 2); 4382 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION)) = undefined; 4383 const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION) + new_path_w.len * 2; 4384 if (struct_len > struct_buf_len) return error.NameTooLong; 4385 4386 const rename_info: *w.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); 4387 var io_status_block: w.IO_STATUS_BLOCK = undefined; 4388 4389 rename_info.* = .{ 4390 .Flags = @intFromBool(replace_if_exists), 4391 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, 4392 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong 4393 .FileName = undefined, 4394 }; 4395 @memcpy((&rename_info.FileName).ptr, new_path_w); 4396 4397 rc = w.ntdll.NtSetInformationFile( 4398 src_fd, 4399 &io_status_block, 4400 rename_info, 4401 @intCast(struct_len), // already checked for error.NameTooLong 4402 .FileRenameInformation, 4403 ); 4404 } 4405 4406 switch (rc) { 4407 .SUCCESS => {}, 4408 .INVALID_HANDLE => |err| return w.statusBug(err), 4409 .INVALID_PARAMETER => |err| return w.statusBug(err), 4410 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err), 4411 .ACCESS_DENIED => return error.AccessDenied, 4412 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, 4413 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, 4414 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, 4415 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, 4416 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, 4417 .FILE_IS_A_DIRECTORY => return error.IsDir, 4418 .NOT_A_DIRECTORY => return error.NotDir, 4419 else => return w.unexpectedStatus(rc), 4420 } 4421 } 4422 4423 fn dirRenameWasi( 4424 userdata: ?*anyopaque, 4425 old_dir: Dir, 4426 old_sub_path: []const u8, 4427 new_dir: Dir, 4428 new_sub_path: []const u8, 4429 ) Dir.RenameError!void { 4430 if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path); 4431 4432 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4433 const current_thread = Thread.getCurrent(t); 4434 4435 try current_thread.beginSyscall(); 4436 while (true) { 4437 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)) { 4438 .SUCCESS => return current_thread.endSyscall(), 4439 .CANCELED => return current_thread.endSyscallCanceled(), 4440 .INTR => { 4441 try current_thread.checkCancel(); 4442 continue; 4443 }, 4444 else => |e| { 4445 current_thread.endSyscall(); 4446 switch (e) { 4447 .ACCES => return error.AccessDenied, 4448 .PERM => return error.PermissionDenied, 4449 .BUSY => return error.FileBusy, 4450 .DQUOT => return error.DiskQuota, 4451 .FAULT => |err| return errnoBug(err), 4452 .INVAL => |err| return errnoBug(err), 4453 .ISDIR => return error.IsDir, 4454 .LOOP => return error.SymLinkLoop, 4455 .MLINK => return error.LinkQuotaExceeded, 4456 .NAMETOOLONG => return error.NameTooLong, 4457 .NOENT => return error.FileNotFound, 4458 .NOTDIR => return error.NotDir, 4459 .NOMEM => return error.SystemResources, 4460 .NOSPC => return error.NoSpaceLeft, 4461 .EXIST => return error.PathAlreadyExists, 4462 .NOTEMPTY => return error.PathAlreadyExists, 4463 .ROFS => return error.ReadOnlyFileSystem, 4464 .XDEV => return error.RenameAcrossMountPoints, 4465 .NOTCAPABLE => return error.AccessDenied, 4466 .ILSEQ => return error.BadPathName, 4467 else => |err| return posix.unexpectedErrno(err), 4468 } 4469 }, 4470 } 4471 } 4472 } 4473 4474 fn dirRenamePosix( 4475 userdata: ?*anyopaque, 4476 old_dir: Dir, 4477 old_sub_path: []const u8, 4478 new_dir: Dir, 4479 new_sub_path: []const u8, 4480 ) Dir.RenameError!void { 4481 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4482 const current_thread = Thread.getCurrent(t); 4483 4484 var old_path_buffer: [posix.PATH_MAX]u8 = undefined; 4485 var new_path_buffer: [posix.PATH_MAX]u8 = undefined; 4486 4487 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); 4488 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); 4489 4490 try current_thread.beginSyscall(); 4491 while (true) { 4492 switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) { 4493 .SUCCESS => return current_thread.endSyscall(), 4494 .CANCELED => return current_thread.endSyscallCanceled(), 4495 .INTR => { 4496 try current_thread.checkCancel(); 4497 continue; 4498 }, 4499 else => |e| { 4500 current_thread.endSyscall(); 4501 switch (e) { 4502 .ACCES => return error.AccessDenied, 4503 .PERM => return error.PermissionDenied, 4504 .BUSY => return error.FileBusy, 4505 .DQUOT => return error.DiskQuota, 4506 .FAULT => |err| return errnoBug(err), 4507 .INVAL => |err| return errnoBug(err), 4508 .ISDIR => return error.IsDir, 4509 .LOOP => return error.SymLinkLoop, 4510 .MLINK => return error.LinkQuotaExceeded, 4511 .NAMETOOLONG => return error.NameTooLong, 4512 .NOENT => return error.FileNotFound, 4513 .NOTDIR => return error.NotDir, 4514 .NOMEM => return error.SystemResources, 4515 .NOSPC => return error.NoSpaceLeft, 4516 .EXIST => return error.PathAlreadyExists, 4517 .NOTEMPTY => return error.PathAlreadyExists, 4518 .ROFS => return error.ReadOnlyFileSystem, 4519 .XDEV => return error.RenameAcrossMountPoints, 4520 .ILSEQ => return error.BadPathName, 4521 else => |err| return posix.unexpectedErrno(err), 4522 } 4523 }, 4524 } 4525 } 4526 } 4527 4528 const dirSymLink = switch (native_os) { 4529 .windows => dirSymLinkWindows, 4530 .wasi => dirSymLinkWasi, 4531 else => dirSymLinkPosix, 4532 }; 4533 4534 fn dirSymLinkWindows( 4535 userdata: ?*anyopaque, 4536 dir: Dir, 4537 target_path: []const u8, 4538 sym_link_path: []const u8, 4539 flags: Dir.SymLinkFlags, 4540 ) Dir.SymLinkError!void { 4541 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4542 const current_thread = Thread.getCurrent(t); 4543 const w = windows; 4544 4545 try current_thread.checkCancel(); 4546 4547 // Target path does not use sliceToPrefixedFileW because certain paths 4548 // are handled differently when creating a symlink than they would be 4549 // when converting to an NT namespaced path. CreateSymbolicLink in 4550 // symLinkW will handle the necessary conversion. 4551 var target_path_w: w.PathSpace = undefined; 4552 target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path); 4553 target_path_w.data[target_path_w.len] = 0; 4554 // However, we need to canonicalize any path separators to `\`, since if 4555 // the target path is relative, then it must use `\` as the path separator. 4556 std.mem.replaceScalar( 4557 u16, 4558 target_path_w.data[0..target_path_w.len], 4559 std.mem.nativeToLittle(u16, '/'), 4560 std.mem.nativeToLittle(u16, '\\'), 4561 ); 4562 4563 const sym_link_path_w = try w.sliceToPrefixedFileW(dir.handle, sym_link_path); 4564 4565 const SYMLINK_DATA = extern struct { 4566 ReparseTag: w.ULONG, 4567 ReparseDataLength: w.USHORT, 4568 Reserved: w.USHORT, 4569 SubstituteNameOffset: w.USHORT, 4570 SubstituteNameLength: w.USHORT, 4571 PrintNameOffset: w.USHORT, 4572 PrintNameLength: w.USHORT, 4573 Flags: w.ULONG, 4574 }; 4575 4576 const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{ 4577 .access_mask = w.SYNCHRONIZE | w.GENERIC_READ | w.GENERIC_WRITE, 4578 .dir = dir, 4579 .creation = w.FILE_CREATE, 4580 .filter = if (flags.is_directory) .dir_only else .file_only, 4581 }) catch |err| switch (err) { 4582 error.IsDir => return error.PathAlreadyExists, 4583 error.NotDir => return error.Unexpected, 4584 error.WouldBlock => return error.Unexpected, 4585 error.PipeBusy => return error.Unexpected, 4586 error.NoDevice => return error.Unexpected, 4587 error.AntivirusInterference => return error.Unexpected, 4588 else => |e| return e, 4589 }; 4590 defer w.CloseHandle(symlink_handle); 4591 4592 // Relevant portions of the documentation: 4593 // > Relative links are specified using the following conventions: 4594 // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32". 4595 // > - Current working directory–relative—for example, if the current working directory is 4596 // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt". 4597 // > Note: If you specify a current working directory–relative link, it is created as an absolute 4598 // > link, due to the way the current working directory is processed based on the user and the thread. 4599 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw 4600 var is_target_absolute = false; 4601 const final_target_path = target_path: { 4602 if (w.hasCommonNtPrefix(u16, target_path)) { 4603 // Already an NT path, no need to do anything to it 4604 break :target_path target_path; 4605 } else { 4606 switch (w.getWin32PathType(u16, target_path)) { 4607 // Rooted paths need to avoid getting put through wToPrefixedFileW 4608 // (and they are treated as relative in this context) 4609 // Note: It seems that rooted paths in symbolic links are relative to 4610 // the drive that the symbolic exists on, not to the CWD's drive. 4611 // So, if the symlink is on C:\ and the CWD is on D:\, 4612 // it will still resolve the path relative to the root of 4613 // the C:\ drive. 4614 .rooted => break :target_path target_path, 4615 // Keep relative paths relative, but anything else needs to get NT-prefixed. 4616 else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path)) 4617 break :target_path target_path, 4618 } 4619 } 4620 var prefixed_target_path = try w.wToPrefixedFileW(dir, target_path); 4621 // We do this after prefixing to ensure that drive-relative paths are treated as absolute 4622 is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); 4623 break :target_path prefixed_target_path.span(); 4624 }; 4625 4626 // prepare reparse data buffer 4627 var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; 4628 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; 4629 const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2; 4630 const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path); 4631 const symlink_data = SYMLINK_DATA{ 4632 .ReparseTag = w.IO_REPARSE_TAG_SYMLINK, 4633 .ReparseDataLength = @intCast(buf_len - header_len), 4634 .Reserved = 0, 4635 .SubstituteNameOffset = @intCast(final_target_path.len * 2), 4636 .SubstituteNameLength = @intCast(final_target_path.len * 2), 4637 .PrintNameOffset = 0, 4638 .PrintNameLength = @intCast(final_target_path.len * 2), 4639 .Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0, 4640 }; 4641 4642 @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data)); 4643 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); 4644 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; 4645 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); 4646 _ = try w.DeviceIoControl(symlink_handle, w.FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null); 4647 } 4648 4649 fn dirSymLinkWasi( 4650 userdata: ?*anyopaque, 4651 dir: Dir, 4652 target_path: []const u8, 4653 sym_link_path: []const u8, 4654 flags: Dir.SymLinkFlags, 4655 ) Dir.SymLinkError!void { 4656 if (builtin.link_libc) return dirSymLinkPosix(dir, target_path, sym_link_path, flags); 4657 4658 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4659 const current_thread = Thread.getCurrent(t); 4660 4661 try current_thread.beginSyscall(); 4662 while (true) { 4663 switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) { 4664 .SUCCESS => return current_thread.endSyscall(), 4665 .CANCELED => return current_thread.endSyscallCanceled(), 4666 .INTR => { 4667 try current_thread.checkCancel(); 4668 continue; 4669 }, 4670 else => |e| { 4671 current_thread.endSyscall(); 4672 switch (e) { 4673 .FAULT => |err| return errnoBug(err), 4674 .INVAL => |err| return errnoBug(err), 4675 .BADF => |err| return errnoBug(err), 4676 .ACCES => return error.AccessDenied, 4677 .PERM => return error.PermissionDenied, 4678 .DQUOT => return error.DiskQuota, 4679 .EXIST => return error.PathAlreadyExists, 4680 .IO => return error.FileSystem, 4681 .LOOP => return error.SymLinkLoop, 4682 .NAMETOOLONG => return error.NameTooLong, 4683 .NOENT => return error.FileNotFound, 4684 .NOTDIR => return error.NotDir, 4685 .NOMEM => return error.SystemResources, 4686 .NOSPC => return error.NoSpaceLeft, 4687 .ROFS => return error.ReadOnlyFileSystem, 4688 .NOTCAPABLE => return error.AccessDenied, 4689 .ILSEQ => return error.BadPathName, 4690 else => |err| return posix.unexpectedErrno(err), 4691 } 4692 }, 4693 } 4694 } 4695 } 4696 4697 fn dirSymLinkPosix( 4698 userdata: ?*anyopaque, 4699 dir: Dir, 4700 target_path: []const u8, 4701 sym_link_path: []const u8, 4702 flags: Dir.SymLinkFlags, 4703 ) Dir.SymLinkError!void { 4704 _ = flags; 4705 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4706 const current_thread = Thread.getCurrent(t); 4707 4708 var target_path_buffer: [posix.PATH_MAX]u8 = undefined; 4709 var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined; 4710 4711 const target_path_posix = try pathToPosix(target_path, &target_path_buffer); 4712 const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer); 4713 4714 try current_thread.beginSyscall(); 4715 while (true) { 4716 switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) { 4717 .SUCCESS => return current_thread.endSyscall(), 4718 .CANCELED => return current_thread.endSyscallCanceled(), 4719 .INTR => { 4720 try current_thread.checkCancel(); 4721 continue; 4722 }, 4723 else => |e| { 4724 current_thread.endSyscall(); 4725 switch (e) { 4726 .FAULT => |err| return errnoBug(err), 4727 .INVAL => |err| return errnoBug(err), 4728 .ACCES => return error.AccessDenied, 4729 .PERM => return error.PermissionDenied, 4730 .DQUOT => return error.DiskQuota, 4731 .EXIST => return error.PathAlreadyExists, 4732 .IO => return error.FileSystem, 4733 .LOOP => return error.SymLinkLoop, 4734 .NAMETOOLONG => return error.NameTooLong, 4735 .NOENT => return error.FileNotFound, 4736 .NOTDIR => return error.NotDir, 4737 .NOMEM => return error.SystemResources, 4738 .NOSPC => return error.NoSpaceLeft, 4739 .ROFS => return error.ReadOnlyFileSystem, 4740 .ILSEQ => return error.BadPathName, 4741 else => |err| return posix.unexpectedErrno(err), 4742 } 4743 }, 4744 } 4745 } 4746 } 4747 4748 const dirReadLink = switch (native_os) { 4749 .windows => dirReadLinkWindows, 4750 .wasi => dirReadLinkWasi, 4751 else => dirReadLinkPosix, 4752 }; 4753 4754 fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { 4755 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4756 const current_thread = Thread.getCurrent(t); 4757 const w = windows; 4758 4759 try current_thread.checkCancel(); 4760 4761 var sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); 4762 4763 const result_handle = w.OpenFile(sub_path_w.span(), .{ 4764 .access_mask = w.FILE_READ_ATTRIBUTES | w.SYNCHRONIZE, 4765 .dir = dir, 4766 .creation = w.FILE_OPEN, 4767 .follow_symlinks = false, 4768 .filter = .any, 4769 }) catch |err| switch (err) { 4770 error.IsDir, error.NotDir => return error.Unexpected, // filter = .any 4771 error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN 4772 error.WouldBlock => return error.Unexpected, 4773 error.NoDevice => return error.FileNotFound, 4774 error.PipeBusy => return error.AccessDenied, 4775 else => |e| return e, 4776 }; 4777 defer w.CloseHandle(result_handle); 4778 4779 var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(w.REPARSE_DATA_BUFFER)) = undefined; 4780 _ = w.DeviceIoControl(result_handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) { 4781 error.AccessDenied => return error.Unexpected, 4782 error.UnrecognizedVolume => return error.Unexpected, 4783 else => |e| return e, 4784 }; 4785 4786 const reparse_struct: *const w.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); 4787 const wide_result = switch (reparse_struct.ReparseTag) { 4788 w.IO_REPARSE_TAG_SYMLINK => r: { 4789 const buf: *const w.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); 4790 const offset = buf.SubstituteNameOffset >> 1; 4791 const len = buf.SubstituteNameLength >> 1; 4792 const path_buf: [*]const u16 = &buf.PathBuffer; 4793 const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0; 4794 break :r try w.parseReadLinkPath(path_buf[offset..][0..len], is_relative, buffer); 4795 }, 4796 w.IO_REPARSE_TAG_MOUNT_POINT => r: { 4797 const buf: *const w.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); 4798 const offset = buf.SubstituteNameOffset >> 1; 4799 const len = buf.SubstituteNameLength >> 1; 4800 const path_buf: [*]const u16 = &buf.PathBuffer; 4801 break :r try w.parseReadLinkPath(path_buf[offset..][0..len], false, buffer); 4802 }, 4803 else => return error.UnsupportedReparsePointType, 4804 }; 4805 4806 const len = std.unicode.calcWtf8Len(wide_result); 4807 if (len > buffer.len) return error.NameTooLong; 4808 4809 return std.unicode.wtf16LeToWtf8(buffer, wide_result); 4810 } 4811 4812 fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { 4813 if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer); 4814 4815 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4816 const current_thread = Thread.getCurrent(t); 4817 4818 var n: usize = undefined; 4819 try current_thread.beginSyscall(); 4820 while (true) { 4821 switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) { 4822 .SUCCESS => { 4823 current_thread.endSyscall(); 4824 return buffer[0..n]; 4825 }, 4826 .CANCELED => return current_thread.endSyscallCanceled(), 4827 .INTR => { 4828 try current_thread.checkCancel(); 4829 continue; 4830 }, 4831 else => |e| { 4832 current_thread.endSyscall(); 4833 switch (e) { 4834 .ACCES => return error.AccessDenied, 4835 .FAULT => |err| return errnoBug(err), 4836 .INVAL => return error.NotLink, 4837 .IO => return error.FileSystem, 4838 .LOOP => return error.SymLinkLoop, 4839 .NAMETOOLONG => return error.NameTooLong, 4840 .NOENT => return error.FileNotFound, 4841 .NOMEM => return error.SystemResources, 4842 .NOTDIR => return error.NotDir, 4843 .NOTCAPABLE => return error.AccessDenied, 4844 .ILSEQ => return error.BadPathName, 4845 else => |err| return posix.unexpectedErrno(err), 4846 } 4847 }, 4848 } 4849 } 4850 } 4851 4852 fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { 4853 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4854 const current_thread = Thread.getCurrent(t); 4855 4856 var sub_path_buffer: [posix.PATH_MAX]u8 = undefined; 4857 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer); 4858 4859 try current_thread.beginSyscall(); 4860 while (true) { 4861 const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len); 4862 switch (posix.errno(rc)) { 4863 .SUCCESS => { 4864 current_thread.endSyscall(); 4865 const len: usize = @bitCast(rc); 4866 return len; 4867 }, 4868 .CANCELED => return current_thread.endSyscallCanceled(), 4869 .INTR => { 4870 try current_thread.checkCancel(); 4871 continue; 4872 }, 4873 else => |e| { 4874 current_thread.endSyscall(); 4875 switch (e) { 4876 .ACCES => return error.AccessDenied, 4877 .FAULT => |err| return errnoBug(err), 4878 .INVAL => return error.NotLink, 4879 .IO => return error.FileSystem, 4880 .LOOP => return error.SymLinkLoop, 4881 .NAMETOOLONG => return error.NameTooLong, 4882 .NOENT => return error.FileNotFound, 4883 .NOMEM => return error.SystemResources, 4884 .NOTDIR => return error.NotDir, 4885 .ILSEQ => return error.BadPathName, 4886 else => |err| return posix.unexpectedErrno(err), 4887 } 4888 }, 4889 } 4890 } 4891 } 4892 4893 const dirSetPermissions = switch (native_os) { 4894 .windows => dirSetPermissionsWindows, 4895 else => dirSetPermissionsPosix, 4896 }; 4897 4898 fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { 4899 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4900 _ = t; 4901 _ = dir; 4902 _ = permissions; 4903 @panic("TODO"); 4904 } 4905 4906 fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { 4907 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4908 const current_thread = Thread.getCurrent(t); 4909 return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); 4910 } 4911 4912 fn dirSetFilePermissions( 4913 userdata: ?*anyopaque, 4914 dir: Dir, 4915 sub_path: []const u8, 4916 permissions: Dir.Permissions, 4917 options: Dir.SetFilePermissionsOptions, 4918 ) Dir.SetFilePermissionsError!void { 4919 if (!Dir.Permissions.has_executable_bit) return error.Unexpected; 4920 if (is_windows) @panic("TODO"); 4921 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4922 const current_thread = Thread.getCurrent(t); 4923 4924 var path_buffer: [posix.PATH_MAX]u8 = undefined; 4925 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 4926 4927 const mode = permissions.toMode(); 4928 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; 4929 4930 return posixFchmodat(t, current_thread, dir.handle, sub_path_posix, mode, flags); 4931 } 4932 4933 fn posixFchmodat( 4934 t: *Threaded, 4935 current_thread: *Thread, 4936 dir_fd: posix.fd_t, 4937 path: [*:0]const u8, 4938 mode: posix.mode_t, 4939 flags: u32, 4940 ) Dir.SetFilePermissionsError!void { 4941 // No special handling for linux is needed if we can use the libc fallback 4942 // or `flags` is empty. Glibc only added the fallback in 2.32. 4943 if (have_fchmodat_flags or flags == 0) { 4944 try current_thread.beginSyscall(); 4945 while (true) { 4946 const rc = if (have_fchmodat_flags) 4947 posix.system.fchmodat(dir_fd, path, mode, flags) 4948 else 4949 posix.system.fchmodat(dir_fd, path, mode); 4950 switch (posix.errno(rc)) { 4951 .SUCCESS => return current_thread.endSyscall(), 4952 .CANCELED => return current_thread.endSyscallCanceled(), 4953 .INTR => { 4954 try current_thread.checkCancel(); 4955 continue; 4956 }, 4957 else => |e| { 4958 current_thread.endSyscall(); 4959 switch (e) { 4960 .BADF => |err| return errnoBug(err), 4961 .FAULT => |err| return errnoBug(err), 4962 .INVAL => |err| return errnoBug(err), 4963 .ACCES => return error.AccessDenied, 4964 .IO => return error.InputOutput, 4965 .LOOP => return error.SymLinkLoop, 4966 .MFILE => return error.ProcessFdQuotaExceeded, 4967 .NAMETOOLONG => return error.NameTooLong, 4968 .NFILE => return error.SystemFdQuotaExceeded, 4969 .NOENT => return error.FileNotFound, 4970 .NOTDIR => return error.FileNotFound, 4971 .NOMEM => return error.SystemResources, 4972 .OPNOTSUPP => return error.OperationNotSupported, 4973 .PERM => return error.PermissionDenied, 4974 .ROFS => return error.ReadOnlyFileSystem, 4975 else => |err| return posix.unexpectedErrno(err), 4976 } 4977 }, 4978 } 4979 } 4980 } 4981 4982 if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled) 4983 return fchmodatFallback(current_thread, dir_fd, path, mode, flags); 4984 4985 comptime assert(native_os == .linux); 4986 4987 try current_thread.beginSyscall(); 4988 while (true) { 4989 switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) { 4990 .SUCCESS => return current_thread.endSyscall(), 4991 .CANCELED => return current_thread.endSyscallCanceled(), 4992 .INTR => { 4993 try current_thread.checkCancel(); 4994 continue; 4995 }, 4996 else => |e| { 4997 current_thread.endSyscall(); 4998 switch (e) { 4999 .BADF => |err| return errnoBug(err), 5000 .FAULT => |err| return errnoBug(err), 5001 .INVAL => |err| return errnoBug(err), 5002 .ACCES => return error.AccessDenied, 5003 .IO => return error.InputOutput, 5004 .LOOP => return error.SymLinkLoop, 5005 .NOENT => return error.FileNotFound, 5006 .NOMEM => return error.SystemResources, 5007 .NOTDIR => return error.FileNotFound, 5008 .OPNOTSUPP => return error.OperationNotSupported, 5009 .PERM => return error.PermissionDenied, 5010 .ROFS => return error.ReadOnlyFileSystem, 5011 .NOSYS => { 5012 @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic); 5013 return fchmodatFallback(current_thread, dir_fd, path, mode, flags); 5014 }, 5015 else => |err| return posix.unexpectedErrno(err), 5016 } 5017 }, 5018 } 5019 } 5020 } 5021 5022 fn fchmodatFallback( 5023 current_thread: *Thread, 5024 dir_fd: posix.fd_t, 5025 path: [*:0]const u8, 5026 mode: posix.mode_t, 5027 flags: u32, 5028 ) Dir.SetFilePermissionsError!void { 5029 _ = current_thread; 5030 _ = dir_fd; 5031 _ = path; 5032 _ = mode; 5033 _ = flags; 5034 // I deleted the previous fallback implementation because it looked wrong to me. Please cross-reference 5035 // fhmodat.c in musl libc before blindly restoring the implementation. 5036 @panic("TODO"); 5037 } 5038 5039 const dirSetOwner = switch (native_os) { 5040 .windows => dirSetOwnerUnsupported, 5041 else => dirSetOwnerPosix, 5042 }; 5043 5044 fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { 5045 _ = userdata; 5046 _ = dir; 5047 _ = owner; 5048 _ = group; 5049 return error.Unexpected; 5050 } 5051 5052 fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { 5053 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5054 const current_thread = Thread.getCurrent(t); 5055 const uid = owner orelse ~@as(posix.uid_t, 0); 5056 const gid = group orelse ~@as(posix.gid_t, 0); 5057 return setOwnerPosix(current_thread, dir.handle, uid, gid); 5058 } 5059 5060 fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void { 5061 try current_thread.beginSyscall(); 5062 while (true) { 5063 switch (posix.errno(posix.system.fchown(fd, uid, gid))) { 5064 .SUCCESS => return current_thread.endSyscall(), 5065 .CANCELED => return current_thread.endSyscallCanceled(), 5066 .INTR => { 5067 try current_thread.checkCancel(); 5068 continue; 5069 }, 5070 else => |e| { 5071 current_thread.endSyscall(); 5072 switch (e) { 5073 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate` 5074 .FAULT => |err| return errnoBug(err), 5075 .INVAL => |err| return errnoBug(err), 5076 .ACCES => return error.AccessDenied, 5077 .IO => return error.InputOutput, 5078 .LOOP => return error.SymLinkLoop, 5079 .NOENT => return error.FileNotFound, 5080 .NOMEM => return error.SystemResources, 5081 .NOTDIR => return error.FileNotFound, 5082 .PERM => return error.PermissionDenied, 5083 .ROFS => return error.ReadOnlyFileSystem, 5084 else => |err| return posix.unexpectedErrno(err), 5085 } 5086 }, 5087 } 5088 } 5089 } 5090 5091 fn dirSetFileOwner( 5092 userdata: ?*anyopaque, 5093 dir: Dir, 5094 sub_path: []const u8, 5095 owner: ?File.Uid, 5096 group: ?File.Gid, 5097 options: Dir.SetFileOwnerOptions, 5098 ) Dir.SetFileOwnerError!void { 5099 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5100 const current_thread = Thread.getCurrent(t); 5101 5102 if (is_windows) { 5103 @panic("TODO"); 5104 } 5105 5106 var path_buffer: [posix.PATH_MAX]u8 = undefined; 5107 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 5108 5109 _ = current_thread; 5110 _ = dir; 5111 _ = sub_path_posix; 5112 _ = owner; 5113 _ = group; 5114 _ = options; 5115 @panic("TODO"); 5116 } 5117 5118 const fileSync = switch (native_os) { 5119 .windows => fileSyncWindows, 5120 else => fileSyncPosix, 5121 }; 5122 5123 fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { 5124 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5125 const current_thread = Thread.getCurrent(t); 5126 5127 try current_thread.checkCancel(); 5128 5129 if (windows.kernel32.FlushFileBuffers(file.handle) != 0) 5130 return; 5131 5132 switch (windows.GetLastError()) { 5133 .SUCCESS => return, 5134 .INVALID_HANDLE => unreachable, 5135 .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time 5136 .UNEXP_NET_ERR => return error.InputOutput, 5137 else => |err| return windows.unexpectedError(err), 5138 } 5139 } 5140 5141 fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void { 5142 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5143 const current_thread = Thread.getCurrent(t); 5144 try current_thread.beginSyscall(); 5145 while (true) { 5146 switch (posix.errno(posix.system.fsync(file.handle))) { 5147 .SUCCESS => return current_thread.endSyscall(), 5148 .CANCELED => return current_thread.endSyscallCanceled(), 5149 .INTR => { 5150 try current_thread.checkCancel(); 5151 continue; 5152 }, 5153 else => |e| { 5154 current_thread.endSyscall(); 5155 switch (e) { 5156 .BADF => |err| return errnoBug(err), 5157 .INVAL => |err| return errnoBug(err), 5158 .ROFS => |err| return errnoBug(err), 5159 .IO => return error.InputOutput, 5160 .NOSPC => return error.NoSpaceLeft, 5161 .DQUOT => return error.DiskQuota, 5162 else => |err| return posix.unexpectedErrno(err), 5163 } 5164 }, 5165 } 5166 } 5167 } 5168 5169 fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { 5170 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5171 const current_thread = Thread.getCurrent(t); 5172 return isTty(current_thread, file); 5173 } 5174 5175 fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool { 5176 if (is_windows) { 5177 if (try isCygwinPty(current_thread, file)) return true; 5178 try current_thread.checkCancel(); 5179 var out: windows.DWORD = undefined; 5180 return windows.kernel32.GetConsoleMode(file.handle, &out) != 0; 5181 } 5182 5183 if (builtin.link_libc) { 5184 try current_thread.beginSyscall(); 5185 while (true) { 5186 const rc = posix.system.isatty(file.handle); 5187 switch (posix.errno(rc - 1)) { 5188 .SUCCESS => { 5189 current_thread.endSyscall(); 5190 return true; 5191 }, 5192 .CANCELED => return current_thread.endSyscallCanceled(), 5193 .INTR => { 5194 try current_thread.checkCancel(); 5195 continue; 5196 }, 5197 else => { 5198 current_thread.endSyscall(); 5199 return false; 5200 }, 5201 } 5202 } 5203 } 5204 5205 if (native_os == .wasi) { 5206 var statbuf: std.os.wasi.fdstat_t = undefined; 5207 const err = std.os.wasi.fd_fdstat_get(file.handle, &statbuf); 5208 if (err != .SUCCESS) 5209 return false; 5210 5211 // A tty is a character device that we can't seek or tell on. 5212 if (statbuf.fs_filetype != .CHARACTER_DEVICE) 5213 return false; 5214 if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) 5215 return false; 5216 5217 return true; 5218 } 5219 5220 if (native_os == .linux) { 5221 const linux = std.os.linux; 5222 try current_thread.beginSyscall(); 5223 while (true) { 5224 var wsz: posix.winsize = undefined; 5225 const fd: usize = @bitCast(@as(isize, file.handle)); 5226 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); 5227 switch (linux.errno(rc)) { 5228 .SUCCESS => { 5229 current_thread.endSyscall(); 5230 return true; 5231 }, 5232 .CANCELED => return current_thread.endSyscallCanceled(), 5233 .INTR => { 5234 try current_thread.checkCancel(); 5235 continue; 5236 }, 5237 else => { 5238 current_thread.endSyscall(); 5239 return false; 5240 }, 5241 } 5242 } 5243 } 5244 5245 @compileError("unimplemented"); 5246 } 5247 5248 fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { 5249 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5250 const current_thread = Thread.getCurrent(t); 5251 5252 if (is_windows) { 5253 try current_thread.checkCancel(); 5254 5255 // For Windows Terminal, VT Sequences processing is enabled by default. 5256 var original_console_mode: windows.DWORD = 0; 5257 if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) { 5258 if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; 5259 5260 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. 5261 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ 5262 // 5263 // Note: In Microsoft's example for enabling virtual terminal processing, it 5264 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: 5265 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing 5266 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) 5267 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). 5268 // Additionally, the default console mode in Windows Terminal does not have 5269 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` 5270 // we end up matching the mode of Windows Terminal. 5271 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; 5272 const console_mode = original_console_mode | requested_console_modes; 5273 try current_thread.checkCancel(); 5274 if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return; 5275 } 5276 if (try isCygwinPty(current_thread, file)) return; 5277 } else { 5278 if (try supportsAnsiEscapeCodes(current_thread, file)) return; 5279 } 5280 return error.NotTerminalDevice; 5281 } 5282 5283 fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { 5284 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5285 const current_thread = Thread.getCurrent(t); 5286 return supportsAnsiEscapeCodes(current_thread, file); 5287 } 5288 5289 fn supportsAnsiEscapeCodes(current_thread: *Thread, file: File) Io.Cancelable!bool { 5290 if (is_windows) { 5291 try current_thread.checkCancel(); 5292 var console_mode: windows.DWORD = 0; 5293 if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) { 5294 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; 5295 } 5296 return isCygwinPty(current_thread, file); 5297 } 5298 5299 if (native_os == .wasi) { 5300 // WASI sanitizes stdout when fd is a tty so ANSI escape codes will not 5301 // be interpreted as actual cursor commands, and stderr is always 5302 // sanitized. 5303 return false; 5304 } 5305 5306 if (try isTty(current_thread, file)) return true; 5307 5308 return false; 5309 } 5310 5311 fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool { 5312 if (!is_windows) return false; 5313 5314 const handle = file.handle; 5315 5316 // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: 5317 // msys-[...]-ptyN-[...] 5318 // cygwin-[...]-ptyN-[...] 5319 // 5320 // Example: msys-1888ae32e00d56aa-pty0-to-master 5321 5322 // First, just check that the handle is a named pipe. 5323 // This allows us to avoid the more costly NtQueryInformationFile call 5324 // for handles that aren't named pipes. 5325 { 5326 try current_thread.checkCancel(); 5327 var io_status: windows.IO_STATUS_BLOCK = undefined; 5328 var device_info: windows.FILE_FS_DEVICE_INFORMATION = undefined; 5329 const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE_FS_DEVICE_INFORMATION), .FileFsDeviceInformation); 5330 switch (rc) { 5331 .SUCCESS => {}, 5332 else => return false, 5333 } 5334 if (device_info.DeviceType != windows.FILE_DEVICE_NAMED_PIPE) return false; 5335 } 5336 5337 const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName"); 5338 // `NAME_MAX` UTF-16 code units (2 bytes each) 5339 // This buffer may not be long enough to handle *all* possible paths 5340 // (PATH_MAX_WIDE would be necessary for that), but because we only care 5341 // about certain paths and we know they must be within a reasonable length, 5342 // we can use this smaller buffer and just return false on any error from 5343 // NtQueryInformationFile. 5344 const num_name_bytes = windows.MAX_PATH * 2; 5345 var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); 5346 5347 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 5348 try current_thread.checkCancel(); 5349 const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .FileNameInformation); 5350 switch (rc) { 5351 .SUCCESS => {}, 5352 .INVALID_PARAMETER => unreachable, 5353 else => return false, 5354 } 5355 5356 const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); 5357 const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; 5358 const name_wide = std.mem.bytesAsSlice(u16, name_bytes); 5359 // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master 5360 return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or 5361 std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and 5362 std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; 5363 } 5364 5365 fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void { 5366 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5367 const current_thread = Thread.getCurrent(t); 5368 5369 const signed_len: i64 = @bitCast(length); 5370 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. 5371 5372 if (is_windows) { 5373 try current_thread.checkCancel(); 5374 5375 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 5376 var eof_info: windows.FILE_END_OF_FILE_INFORMATION = .{ 5377 .EndOfFile = signed_len, 5378 }; 5379 5380 const status = windows.ntdll.NtSetInformationFile( 5381 file.handle, 5382 &io_status_block, 5383 &eof_info, 5384 @sizeOf(windows.FILE_END_OF_FILE_INFORMATION), 5385 .FileEndOfFileInformation, 5386 ); 5387 switch (status) { 5388 .SUCCESS => return, 5389 .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing. 5390 .ACCESS_DENIED => return error.AccessDenied, 5391 .USER_MAPPED_FILE => return error.AccessDenied, 5392 .INVALID_PARAMETER => return error.FileTooBig, 5393 else => return windows.unexpectedStatus(status), 5394 } 5395 } 5396 5397 if (native_os == .wasi and !builtin.link_libc) { 5398 try current_thread.beginSyscall(); 5399 while (true) { 5400 switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) { 5401 .SUCCESS => return current_thread.endSyscall(), 5402 .CANCELED => return current_thread.endSyscallCanceled(), 5403 .INTR => { 5404 try current_thread.checkCancel(); 5405 continue; 5406 }, 5407 else => |e| { 5408 current_thread.endSyscall(); 5409 switch (e) { 5410 .FBIG => return error.FileTooBig, 5411 .IO => return error.InputOutput, 5412 .PERM => return error.PermissionDenied, 5413 .TXTBSY => return error.FileBusy, 5414 .BADF => |err| return errnoBug(err), // Handle not open for writing 5415 .INVAL => return error.NonResizable, 5416 .NOTCAPABLE => return error.AccessDenied, 5417 else => |err| return posix.unexpectedErrno(err), 5418 } 5419 }, 5420 } 5421 } 5422 } 5423 5424 try current_thread.beginSyscall(); 5425 while (true) { 5426 switch (posix.errno(ftruncate_sym(file.handle, signed_len))) { 5427 .SUCCESS => return current_thread.endSyscall(), 5428 .CANCELED => return current_thread.endSyscallCanceled(), 5429 .INTR => { 5430 try current_thread.checkCancel(); 5431 continue; 5432 }, 5433 else => |e| { 5434 current_thread.endSyscall(); 5435 switch (e) { 5436 .FBIG => return error.FileTooBig, 5437 .IO => return error.InputOutput, 5438 .PERM => return error.PermissionDenied, 5439 .TXTBSY => return error.FileBusy, 5440 .BADF => |err| return errnoBug(err), // Handle not open for writing. 5441 .INVAL => return error.NonResizable, // This is returned for /dev/null for example. 5442 else => |err| return posix.unexpectedErrno(err), 5443 } 5444 }, 5445 } 5446 } 5447 } 5448 5449 fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void { 5450 switch (native_os) { 5451 .windows, .wasi => return error.Unexpected, 5452 else => {}, 5453 } 5454 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5455 const current_thread = Thread.getCurrent(t); 5456 const uid = owner orelse ~@as(posix.uid_t, 0); 5457 const gid = group orelse ~@as(posix.gid_t, 0); 5458 return setOwnerPosix(current_thread, file.handle, uid, gid); 5459 } 5460 5461 fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void { 5462 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5463 const current_thread = Thread.getCurrent(t); 5464 switch (native_os) { 5465 .windows => { 5466 try current_thread.checkCancel(); 5467 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 5468 var info: windows.FILE_BASIC_INFORMATION = .{ 5469 .CreationTime = 0, 5470 .LastAccessTime = 0, 5471 .LastWriteTime = 0, 5472 .ChangeTime = 0, 5473 .FileAttributes = permissions.inner.attributes, 5474 }; 5475 const status = windows.ntdll.NtSetInformationFile( 5476 file.handle, 5477 &io_status_block, 5478 &info, 5479 @sizeOf(windows.FILE_BASIC_INFORMATION), 5480 .FileBasicInformation, 5481 ); 5482 switch (status) { 5483 .SUCCESS => return, 5484 .INVALID_HANDLE => |err| return windows.statusBug(err), 5485 .ACCESS_DENIED => return error.AccessDenied, 5486 else => return windows.unexpectedStatus(status), 5487 } 5488 }, 5489 .wasi => return error.Unexpected, // Unsupported OS. 5490 else => return setPermissionsPosix(current_thread, file.handle, permissions.toMode()), 5491 } 5492 } 5493 5494 fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void { 5495 try current_thread.beginSyscall(); 5496 while (true) { 5497 switch (posix.errno(posix.system.fchmod(fd, mode))) { 5498 .SUCCESS => return current_thread.endSyscall(), 5499 .CANCELED => return current_thread.endSyscallCanceled(), 5500 .INTR => { 5501 try current_thread.checkCancel(); 5502 continue; 5503 }, 5504 else => |e| { 5505 current_thread.endSyscall(); 5506 switch (e) { 5507 .BADF => |err| return errnoBug(err), 5508 .FAULT => |err| return errnoBug(err), 5509 .INVAL => |err| return errnoBug(err), 5510 .ACCES => return error.AccessDenied, 5511 .IO => return error.InputOutput, 5512 .LOOP => return error.SymLinkLoop, 5513 .NOENT => return error.FileNotFound, 5514 .NOMEM => return error.SystemResources, 5515 .NOTDIR => return error.FileNotFound, 5516 .PERM => return error.PermissionDenied, 5517 .ROFS => return error.ReadOnlyFileSystem, 5518 else => |err| return posix.unexpectedErrno(err), 5519 } 5520 }, 5521 } 5522 } 5523 } 5524 5525 fn dirSetTimestamps( 5526 userdata: ?*anyopaque, 5527 dir: Dir, 5528 sub_path: []const u8, 5529 last_accessed: Io.Timestamp, 5530 last_modified: Io.Timestamp, 5531 options: Dir.SetTimestampsOptions, 5532 ) Dir.SetTimestampsError!void { 5533 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5534 const current_thread = Thread.getCurrent(t); 5535 5536 if (is_windows) { 5537 @panic("TODO"); 5538 } 5539 5540 if (native_os == .wasi and !builtin.link_libc) { 5541 @panic("TODO"); 5542 } 5543 5544 const times: [2]posix.timespec = .{ 5545 timestampToPosix(last_accessed.nanoseconds), 5546 timestampToPosix(last_modified.nanoseconds), 5547 }; 5548 5549 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; 5550 5551 var path_buffer: [posix.PATH_MAX]u8 = undefined; 5552 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 5553 5554 try current_thread.beginSyscall(); 5555 while (true) { 5556 switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, ×, flags))) { 5557 .SUCCESS => return current_thread.endSyscall(), 5558 .CANCELED => return current_thread.endSyscallCanceled(), 5559 .INTR => { 5560 try current_thread.checkCancel(); 5561 continue; 5562 }, 5563 else => |e| { 5564 current_thread.endSyscall(); 5565 switch (e) { 5566 .ACCES => return error.AccessDenied, 5567 .PERM => return error.PermissionDenied, 5568 .BADF => |err| return errnoBug(err), // always a race condition 5569 .FAULT => |err| return errnoBug(err), 5570 .INVAL => |err| return errnoBug(err), 5571 .ROFS => return error.ReadOnlyFileSystem, 5572 else => |err| return posix.unexpectedErrno(err), 5573 } 5574 }, 5575 } 5576 } 5577 } 5578 5579 fn dirSetTimestampsNow( 5580 userdata: ?*anyopaque, 5581 dir: Dir, 5582 sub_path: []const u8, 5583 options: Dir.SetTimestampsOptions, 5584 ) Dir.SetTimestampsError!void { 5585 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5586 const current_thread = Thread.getCurrent(t); 5587 5588 if (is_windows) { 5589 @panic("TODO"); 5590 } 5591 5592 if (native_os == .wasi and !builtin.link_libc) { 5593 @panic("TODO"); 5594 } 5595 5596 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; 5597 5598 var path_buffer: [posix.PATH_MAX]u8 = undefined; 5599 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 5600 5601 try current_thread.beginSyscall(); 5602 while (true) { 5603 switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) { 5604 .SUCCESS => return current_thread.endSyscall(), 5605 .CANCELED => return current_thread.endSyscallCanceled(), 5606 .INTR => { 5607 try current_thread.checkCancel(); 5608 continue; 5609 }, 5610 else => |e| { 5611 current_thread.endSyscall(); 5612 switch (e) { 5613 .ACCES => return error.AccessDenied, 5614 .PERM => return error.PermissionDenied, 5615 .BADF => |err| return errnoBug(err), // always a race condition 5616 .FAULT => |err| return errnoBug(err), 5617 .INVAL => |err| return errnoBug(err), 5618 .ROFS => return error.ReadOnlyFileSystem, 5619 else => |err| return posix.unexpectedErrno(err), 5620 } 5621 }, 5622 } 5623 } 5624 } 5625 5626 fn fileSetTimestamps( 5627 userdata: ?*anyopaque, 5628 file: File, 5629 last_accessed: Io.Timestamp, 5630 last_modified: Io.Timestamp, 5631 ) File.SetTimestampsError!void { 5632 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5633 const current_thread = Thread.getCurrent(t); 5634 5635 if (is_windows) { 5636 try current_thread.checkCancel(); 5637 5638 const atime_ft = windows.nanoSecondsToFileTime(last_accessed.toNanoseconds()); 5639 const mtime_ft = windows.nanoSecondsToFileTime(last_modified.toNanoseconds()); 5640 5641 // https://github.com/ziglang/zig/issues/1840 5642 const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft); 5643 if (rc == 0) { 5644 switch (windows.GetLastError()) { 5645 else => |err| return windows.unexpectedError(err), 5646 } 5647 } 5648 return; 5649 } 5650 5651 const times: [2]posix.timespec = .{ 5652 timestampToPosix(last_accessed.nanoseconds), 5653 timestampToPosix(last_modified.nanoseconds), 5654 }; 5655 5656 if (native_os == .wasi and !builtin.link_libc) { 5657 const atim = times[0].toTimestamp(); 5658 const mtim = times[1].toTimestamp(); 5659 try current_thread.beginSyscall(); 5660 while (true) { 5661 switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{ 5662 .ATIM = true, 5663 .MTIM = true, 5664 })) { 5665 .SUCCESS => return current_thread.endSyscall(), 5666 .CANCELED => return current_thread.endSyscallCanceled(), 5667 .INTR => { 5668 try current_thread.checkCancel(); 5669 continue; 5670 }, 5671 else => |e| { 5672 current_thread.endSyscall(); 5673 switch (e) { 5674 .ACCES => return error.AccessDenied, 5675 .PERM => return error.PermissionDenied, 5676 .BADF => |err| return errnoBug(err), // File descriptor use-after-free. 5677 .FAULT => |err| return errnoBug(err), 5678 .INVAL => |err| return errnoBug(err), 5679 .ROFS => return error.ReadOnlyFileSystem, 5680 else => |err| return posix.unexpectedErrno(err), 5681 } 5682 }, 5683 } 5684 } 5685 } 5686 5687 try current_thread.beginSyscall(); 5688 while (true) { 5689 switch (posix.errno(posix.system.futimens(file.handle, ×))) { 5690 .SUCCESS => return current_thread.endSyscall(), 5691 .CANCELED => return current_thread.endSyscallCanceled(), 5692 .INTR => { 5693 try current_thread.checkCancel(); 5694 continue; 5695 }, 5696 else => |e| { 5697 current_thread.endSyscall(); 5698 switch (e) { 5699 .ACCES => return error.AccessDenied, 5700 .PERM => return error.PermissionDenied, 5701 .BADF => |err| return errnoBug(err), // always a race condition 5702 .FAULT => |err| return errnoBug(err), 5703 .INVAL => |err| return errnoBug(err), 5704 .ROFS => return error.ReadOnlyFileSystem, 5705 else => |err| return posix.unexpectedErrno(err), 5706 } 5707 }, 5708 } 5709 } 5710 } 5711 5712 fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsError!void { 5713 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5714 const current_thread = Thread.getCurrent(t); 5715 5716 if (is_windows) { 5717 @panic("TODO"); 5718 } 5719 5720 if (native_os == .wasi and !builtin.link_libc) { 5721 try current_thread.beginSyscall(); 5722 while (true) { 5723 switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{ 5724 .ATIM_NOW = true, 5725 .MTIM_NOW = true, 5726 })) { 5727 .SUCCESS => return current_thread.endSyscall(), 5728 .CANCELED => return current_thread.endSyscallCanceled(), 5729 .INTR => { 5730 try current_thread.checkCancel(); 5731 continue; 5732 }, 5733 else => |e| { 5734 current_thread.endSyscall(); 5735 switch (e) { 5736 .ACCES => return error.AccessDenied, 5737 .PERM => return error.PermissionDenied, 5738 .BADF => |err| return errnoBug(err), // always a race condition 5739 .FAULT => |err| return errnoBug(err), 5740 .INVAL => |err| return errnoBug(err), 5741 .ROFS => return error.ReadOnlyFileSystem, 5742 else => |err| return posix.unexpectedErrno(err), 5743 } 5744 }, 5745 } 5746 } 5747 } 5748 5749 try current_thread.beginSyscall(); 5750 while (true) { 5751 switch (posix.errno(posix.system.futimens(file.handle, null))) { 5752 .SUCCESS => return current_thread.endSyscall(), 5753 .CANCELED => return current_thread.endSyscallCanceled(), 5754 .INTR => { 5755 try current_thread.checkCancel(); 5756 continue; 5757 }, 5758 else => |e| { 5759 current_thread.endSyscall(); 5760 switch (e) { 5761 .ACCES => return error.AccessDenied, 5762 .PERM => return error.PermissionDenied, 5763 .BADF => |err| return errnoBug(err), // always a race condition 5764 .FAULT => |err| return errnoBug(err), 5765 .INVAL => |err| return errnoBug(err), 5766 .ROFS => return error.ReadOnlyFileSystem, 5767 else => |err| return posix.unexpectedErrno(err), 5768 } 5769 }, 5770 } 5771 } 5772 } 5773 5774 const windows_lock_range_off: windows.LARGE_INTEGER = 0; 5775 const windows_lock_range_len: windows.LARGE_INTEGER = 1; 5776 5777 fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void { 5778 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5779 const current_thread = Thread.getCurrent(t); 5780 5781 if (is_windows) { 5782 const exclusive = switch (lock) { 5783 .none => return, 5784 .shared => false, 5785 .exclusive => true, 5786 }; 5787 try current_thread.checkCancel(); 5788 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 5789 const status = windows.ntdll.NtLockFile( 5790 file.handle, 5791 null, 5792 null, 5793 null, 5794 &io_status_block, 5795 &windows_lock_range_off, 5796 &windows_lock_range_len, 5797 null, 5798 windows.FALSE, 5799 @intFromBool(exclusive), 5800 ); 5801 switch (status) { 5802 .SUCCESS => return, 5803 .INSUFFICIENT_RESOURCES => return error.SystemResources, 5804 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false 5805 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer 5806 else => return windows.unexpectedStatus(status), 5807 } 5808 } 5809 5810 const operation: i32 = switch (lock) { 5811 .none => posix.LOCK.UN, 5812 .shared => posix.LOCK.SH, 5813 .exclusive => posix.LOCK.EX, 5814 }; 5815 try current_thread.beginSyscall(); 5816 while (true) { 5817 switch (posix.errno(posix.system.flock(file.handle, operation))) { 5818 .SUCCESS => return current_thread.endSyscall(), 5819 .CANCELED => return current_thread.endSyscallCanceled(), 5820 .INTR => { 5821 try current_thread.checkCancel(); 5822 continue; 5823 }, 5824 else => |e| { 5825 current_thread.endSyscall(); 5826 switch (e) { 5827 .BADF => |err| return errnoBug(err), 5828 .INVAL => |err| return errnoBug(err), // invalid parameters 5829 .NOLCK => return error.SystemResources, 5830 .AGAIN => |err| return errnoBug(err), 5831 .OPNOTSUPP => return error.FileLocksUnsupported, 5832 else => |err| return posix.unexpectedErrno(err), 5833 } 5834 }, 5835 } 5836 } 5837 } 5838 5839 fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool { 5840 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5841 const current_thread = Thread.getCurrent(t); 5842 5843 if (is_windows) { 5844 const exclusive = switch (lock) { 5845 .none => return, 5846 .shared => false, 5847 .exclusive => true, 5848 }; 5849 try current_thread.checkCancel(); 5850 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 5851 const status = windows.ntdll.NtLockFile( 5852 file.handle, 5853 null, 5854 null, 5855 null, 5856 &io_status_block, 5857 &windows_lock_range_off, 5858 &windows_lock_range_len, 5859 null, 5860 windows.TRUE, 5861 @intFromBool(exclusive), 5862 ); 5863 switch (status) { 5864 .SUCCESS => return true, 5865 .INSUFFICIENT_RESOURCES => return error.SystemResources, 5866 .LOCK_NOT_GRANTED => return false, 5867 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer 5868 else => return windows.unexpectedStatus(status), 5869 } 5870 } 5871 5872 const operation: i32 = switch (lock) { 5873 .none => posix.LOCK.UN, 5874 .shared => posix.LOCK.SH | posix.LOCK.NB, 5875 .exclusive => posix.LOCK.EX | posix.LOCK.NB, 5876 }; 5877 try current_thread.beginSyscall(); 5878 while (true) { 5879 switch (posix.errno(posix.system.flock(file.handle, operation))) { 5880 .SUCCESS => { 5881 current_thread.endSyscall(); 5882 return true; 5883 }, 5884 .CANCELED => return current_thread.endSyscallCanceled(), 5885 .INTR => { 5886 try current_thread.checkCancel(); 5887 continue; 5888 }, 5889 .AGAIN => { 5890 current_thread.endSyscall(); 5891 return false; 5892 }, 5893 else => |e| { 5894 current_thread.endSyscall(); 5895 switch (e) { 5896 .BADF => |err| return errnoBug(err), 5897 .INVAL => |err| return errnoBug(err), // invalid parameters 5898 .NOLCK => return error.SystemResources, 5899 .OPNOTSUPP => return error.FileLocksUnsupported, 5900 else => |err| return posix.unexpectedErrno(err), 5901 } 5902 }, 5903 } 5904 } 5905 } 5906 5907 fn fileUnlock(userdata: ?*anyopaque, file: File) void { 5908 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5909 const current_thread = Thread.getCurrent(t); 5910 5911 if (is_windows) { 5912 try current_thread.checkCancel(); 5913 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 5914 const status = windows.ntdll.NtUnlockFile( 5915 file.handle, 5916 &io_status_block, 5917 &windows_lock_range_off, 5918 &windows_lock_range_len, 5919 null, 5920 ); 5921 if (is_debug) switch (status) { 5922 .SUCCESS => {}, 5923 .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked. 5924 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer 5925 else => unreachable, // Resource deallocation must succeed. 5926 }; 5927 return; 5928 } 5929 5930 while (true) { 5931 switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) { 5932 .SUCCESS => return, 5933 .CANCELED, .INTR => continue, 5934 .AGAIN => return assert(!is_debug), // unlocking can't block 5935 .BADF => return assert(!is_debug), // File descriptor used after closed. 5936 .INVAL => return assert(!is_debug), // invalid parameters 5937 .NOLCK => return assert(!is_debug), // Resource deallocation. 5938 .OPNOTSUPP => return assert(!is_debug), // We already got the lock. 5939 else => return assert(!is_debug), // Resource deallocation must succeed. 5940 } 5941 } 5942 } 5943 5944 fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void { 5945 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5946 const current_thread = Thread.getCurrent(t); 5947 5948 if (is_windows) { 5949 try current_thread.checkCancel(); 5950 // On Windows it works like a semaphore + exclusivity flag. To 5951 // implement this function, we first obtain another lock in shared 5952 // mode. This changes the exclusivity flag, but increments the 5953 // semaphore to 2. So we follow up with an NtUnlockFile which 5954 // decrements the semaphore but does not modify the exclusivity flag. 5955 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 5956 switch (windows.ntdll.NtLockFile( 5957 file.handle, 5958 null, 5959 null, 5960 null, 5961 &io_status_block, 5962 &windows_lock_range_off, 5963 &windows_lock_range_len, 5964 null, 5965 windows.TRUE, 5966 windows.FALSE, 5967 )) { 5968 .SUCCESS => {}, 5969 .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err), 5970 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode. 5971 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer 5972 else => |status| return windows.unexpectedStatus(status), 5973 } 5974 const status = windows.ntdll.NtUnlockFile( 5975 file.handle, 5976 &io_status_block, 5977 &windows_lock_range_off, 5978 &windows_lock_range_len, 5979 null, 5980 ); 5981 if (is_debug) switch (status) { 5982 .SUCCESS => {}, 5983 .RANGE_NOT_LOCKED => unreachable, // File was not locked. 5984 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer 5985 else => unreachable, // Resource deallocation must succeed. 5986 }; 5987 return; 5988 } 5989 5990 const operation = posix.LOCK.SH | posix.LOCK.NB; 5991 5992 try current_thread.beginSyscall(); 5993 while (true) { 5994 switch (posix.errno(posix.system.flock(file.handle, operation))) { 5995 .SUCCESS => { 5996 current_thread.endSyscall(); 5997 return; 5998 }, 5999 .CANCELED => return current_thread.endSyscallCanceled(), 6000 .INTR => { 6001 try current_thread.checkCancel(); 6002 continue; 6003 }, 6004 else => |e| { 6005 current_thread.endSyscall(); 6006 switch (e) { 6007 .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode. 6008 .BADF => |err| return errnoBug(err), 6009 .INVAL => |err| return errnoBug(err), // invalid parameters 6010 .NOLCK => |err| return errnoBug(err), // Lock already obtained. 6011 .OPNOTSUPP => |err| return errnoBug(err), // Lock already obtained. 6012 else => |err| return posix.unexpectedErrno(err), 6013 } 6014 }, 6015 } 6016 } 6017 } 6018 6019 fn dirOpenDirWasi( 6020 userdata: ?*anyopaque, 6021 dir: Dir, 6022 sub_path: []const u8, 6023 options: Dir.OpenOptions, 6024 ) Dir.OpenError!Dir { 6025 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); 6026 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6027 const current_thread = Thread.getCurrent(t); 6028 const wasi = std.os.wasi; 6029 6030 var base: std.os.wasi.rights_t = .{ 6031 .FD_FILESTAT_GET = true, 6032 .FD_FDSTAT_SET_FLAGS = true, 6033 .FD_FILESTAT_SET_TIMES = true, 6034 }; 6035 if (options.access_sub_paths) { 6036 base.FD_READDIR = true; 6037 base.PATH_CREATE_DIRECTORY = true; 6038 base.PATH_CREATE_FILE = true; 6039 base.PATH_LINK_SOURCE = true; 6040 base.PATH_LINK_TARGET = true; 6041 base.PATH_OPEN = true; 6042 base.PATH_READLINK = true; 6043 base.PATH_RENAME_SOURCE = true; 6044 base.PATH_RENAME_TARGET = true; 6045 base.PATH_FILESTAT_GET = true; 6046 base.PATH_FILESTAT_SET_SIZE = true; 6047 base.PATH_FILESTAT_SET_TIMES = true; 6048 base.PATH_SYMLINK = true; 6049 base.PATH_REMOVE_DIRECTORY = true; 6050 base.PATH_UNLINK_FILE = true; 6051 } 6052 6053 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks }; 6054 const oflags: wasi.oflags_t = .{ .DIRECTORY = true }; 6055 const fdflags: wasi.fdflags_t = .{}; 6056 var fd: posix.fd_t = undefined; 6057 try current_thread.beginSyscall(); 6058 while (true) { 6059 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) { 6060 .SUCCESS => { 6061 current_thread.endSyscall(); 6062 return .{ .handle = fd }; 6063 }, 6064 .INTR => { 6065 try current_thread.checkCancel(); 6066 continue; 6067 }, 6068 .CANCELED => return current_thread.endSyscallCanceled(), 6069 else => |e| { 6070 current_thread.endSyscall(); 6071 switch (e) { 6072 .FAULT => |err| return errnoBug(err), 6073 .INVAL => return error.BadPathName, 6074 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6075 .ACCES => return error.AccessDenied, 6076 .LOOP => return error.SymLinkLoop, 6077 .MFILE => return error.ProcessFdQuotaExceeded, 6078 .NAMETOOLONG => return error.NameTooLong, 6079 .NFILE => return error.SystemFdQuotaExceeded, 6080 .NODEV => return error.NoDevice, 6081 .NOENT => return error.FileNotFound, 6082 .NOMEM => return error.SystemResources, 6083 .NOTDIR => return error.NotDir, 6084 .PERM => return error.PermissionDenied, 6085 .BUSY => return error.DeviceBusy, 6086 .NOTCAPABLE => return error.AccessDenied, 6087 .ILSEQ => return error.BadPathName, 6088 else => |err| return posix.unexpectedErrno(err), 6089 } 6090 }, 6091 } 6092 } 6093 } 6094 6095 fn fileClose(userdata: ?*anyopaque, files: []const File) void { 6096 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6097 _ = t; 6098 for (files) |file| posix.close(file.handle); 6099 } 6100 6101 const fileReadStreaming = switch (native_os) { 6102 .windows => fileReadStreamingWindows, 6103 else => fileReadStreamingPosix, 6104 }; 6105 6106 fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { 6107 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6108 const current_thread = Thread.getCurrent(t); 6109 6110 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; 6111 var i: usize = 0; 6112 for (data) |buf| { 6113 if (iovecs_buffer.len - i == 0) break; 6114 if (buf.len != 0) { 6115 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; 6116 i += 1; 6117 } 6118 } 6119 const dest = iovecs_buffer[0..i]; 6120 assert(dest[0].len > 0); 6121 6122 if (native_os == .wasi and !builtin.link_libc) { 6123 try current_thread.beginSyscall(); 6124 while (true) { 6125 var nread: usize = undefined; 6126 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { 6127 .SUCCESS => { 6128 current_thread.endSyscall(); 6129 return nread; 6130 }, 6131 .INTR => { 6132 try current_thread.checkCancel(); 6133 continue; 6134 }, 6135 .CANCELED => return current_thread.endSyscallCanceled(), 6136 else => |e| { 6137 current_thread.endSyscall(); 6138 switch (e) { 6139 .INVAL => |err| return errnoBug(err), 6140 .FAULT => |err| return errnoBug(err), 6141 .BADF => return error.NotOpenForReading, // File operation on directory. 6142 .IO => return error.InputOutput, 6143 .ISDIR => return error.IsDir, 6144 .NOBUFS => return error.SystemResources, 6145 .NOMEM => return error.SystemResources, 6146 .NOTCONN => return error.SocketUnconnected, 6147 .CONNRESET => return error.ConnectionResetByPeer, 6148 .TIMEDOUT => return error.Timeout, 6149 .NOTCAPABLE => return error.AccessDenied, 6150 else => |err| return posix.unexpectedErrno(err), 6151 } 6152 }, 6153 } 6154 } 6155 } 6156 6157 try current_thread.beginSyscall(); 6158 while (true) { 6159 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len)); 6160 switch (posix.errno(rc)) { 6161 .SUCCESS => { 6162 current_thread.endSyscall(); 6163 return @intCast(rc); 6164 }, 6165 .INTR => { 6166 try current_thread.checkCancel(); 6167 continue; 6168 }, 6169 .CANCELED => return current_thread.endSyscallCanceled(), 6170 else => |e| { 6171 current_thread.endSyscall(); 6172 switch (e) { 6173 .INVAL => |err| return errnoBug(err), 6174 .FAULT => |err| return errnoBug(err), 6175 .AGAIN => return error.WouldBlock, 6176 .BADF => |err| { 6177 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. 6178 return errnoBug(err); // File descriptor used after closed. 6179 }, 6180 .IO => return error.InputOutput, 6181 .ISDIR => return error.IsDir, 6182 .NOBUFS => return error.SystemResources, 6183 .NOMEM => return error.SystemResources, 6184 .NOTCONN => return error.SocketUnconnected, 6185 .CONNRESET => return error.ConnectionResetByPeer, 6186 .TIMEDOUT => return error.Timeout, 6187 else => |err| return posix.unexpectedErrno(err), 6188 } 6189 }, 6190 } 6191 } 6192 } 6193 6194 fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { 6195 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6196 const current_thread = Thread.getCurrent(t); 6197 6198 const DWORD = windows.DWORD; 6199 var index: usize = 0; 6200 while (data[index].len == 0) index += 1; 6201 const buffer = data[index]; 6202 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); 6203 6204 while (true) { 6205 try current_thread.checkCancel(); 6206 var n: DWORD = undefined; 6207 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) 6208 return n; 6209 switch (windows.GetLastError()) { 6210 .IO_PENDING => |err| return windows.errorBug(err), 6211 .OPERATION_ABORTED => continue, 6212 .BROKEN_PIPE => return 0, 6213 .HANDLE_EOF => return 0, 6214 .NETNAME_DELETED => return error.ConnectionResetByPeer, 6215 .LOCK_VIOLATION => return error.LockViolation, 6216 .ACCESS_DENIED => return error.AccessDenied, 6217 .INVALID_HANDLE => return error.NotOpenForReading, 6218 else => |err| return windows.unexpectedError(err), 6219 } 6220 } 6221 } 6222 6223 fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { 6224 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6225 const current_thread = Thread.getCurrent(t); 6226 6227 if (!have_preadv) @compileError("TODO"); 6228 6229 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; 6230 var i: usize = 0; 6231 for (data) |buf| { 6232 if (iovecs_buffer.len - i == 0) break; 6233 if (buf.len != 0) { 6234 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; 6235 i += 1; 6236 } 6237 } 6238 const dest = iovecs_buffer[0..i]; 6239 assert(dest[0].len > 0); 6240 6241 if (native_os == .wasi and !builtin.link_libc) { 6242 try current_thread.beginSyscall(); 6243 while (true) { 6244 var nread: usize = undefined; 6245 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { 6246 .SUCCESS => { 6247 current_thread.endSyscall(); 6248 return nread; 6249 }, 6250 .INTR => { 6251 try current_thread.checkCancel(); 6252 continue; 6253 }, 6254 .CANCELED => return current_thread.endSyscallCanceled(), 6255 else => |e| { 6256 current_thread.endSyscall(); 6257 switch (e) { 6258 .INVAL => |err| return errnoBug(err), 6259 .FAULT => |err| return errnoBug(err), 6260 .AGAIN => |err| return errnoBug(err), 6261 .BADF => return error.NotOpenForReading, // File operation on directory. 6262 .IO => return error.InputOutput, 6263 .ISDIR => return error.IsDir, 6264 .NOBUFS => return error.SystemResources, 6265 .NOMEM => return error.SystemResources, 6266 .NOTCONN => return error.SocketUnconnected, 6267 .CONNRESET => return error.ConnectionResetByPeer, 6268 .TIMEDOUT => return error.Timeout, 6269 .NXIO => return error.Unseekable, 6270 .SPIPE => return error.Unseekable, 6271 .OVERFLOW => return error.Unseekable, 6272 .NOTCAPABLE => return error.AccessDenied, 6273 else => |err| return posix.unexpectedErrno(err), 6274 } 6275 }, 6276 } 6277 } 6278 } 6279 6280 try current_thread.beginSyscall(); 6281 while (true) { 6282 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); 6283 switch (posix.errno(rc)) { 6284 .SUCCESS => { 6285 current_thread.endSyscall(); 6286 return @bitCast(rc); 6287 }, 6288 .INTR => { 6289 try current_thread.checkCancel(); 6290 continue; 6291 }, 6292 .CANCELED => return current_thread.endSyscallCanceled(), 6293 else => |e| { 6294 current_thread.endSyscall(); 6295 switch (e) { 6296 .INVAL => |err| return errnoBug(err), 6297 .FAULT => |err| return errnoBug(err), 6298 .AGAIN => return error.WouldBlock, 6299 .BADF => |err| { 6300 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. 6301 return errnoBug(err); // File descriptor used after closed. 6302 }, 6303 .IO => return error.InputOutput, 6304 .ISDIR => return error.IsDir, 6305 .NOBUFS => return error.SystemResources, 6306 .NOMEM => return error.SystemResources, 6307 .NOTCONN => return error.SocketUnconnected, 6308 .CONNRESET => return error.ConnectionResetByPeer, 6309 .TIMEDOUT => return error.Timeout, 6310 .NXIO => return error.Unseekable, 6311 .SPIPE => return error.Unseekable, 6312 .OVERFLOW => return error.Unseekable, 6313 else => |err| return posix.unexpectedErrno(err), 6314 } 6315 }, 6316 } 6317 } 6318 } 6319 6320 const fileReadPositional = switch (native_os) { 6321 .windows => fileReadPositionalWindows, 6322 else => fileReadPositionalPosix, 6323 }; 6324 6325 fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { 6326 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6327 const current_thread = Thread.getCurrent(t); 6328 6329 const DWORD = windows.DWORD; 6330 6331 var index: usize = 0; 6332 while (data[index].len == 0) index += 1; 6333 const buffer = data[index]; 6334 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); 6335 6336 var overlapped: windows.OVERLAPPED = .{ 6337 .Internal = 0, 6338 .InternalHigh = 0, 6339 .DUMMYUNIONNAME = .{ 6340 .DUMMYSTRUCTNAME = .{ 6341 .Offset = @truncate(offset), 6342 .OffsetHigh = @truncate(offset >> 32), 6343 }, 6344 }, 6345 .hEvent = null, 6346 }; 6347 6348 while (true) { 6349 try current_thread.checkCancel(); 6350 var n: DWORD = undefined; 6351 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) 6352 return n; 6353 switch (windows.GetLastError()) { 6354 .IO_PENDING => |err| return windows.errorBug(err), 6355 .OPERATION_ABORTED => continue, 6356 .BROKEN_PIPE => return 0, 6357 .HANDLE_EOF => return 0, 6358 .NETNAME_DELETED => return error.ConnectionResetByPeer, 6359 .LOCK_VIOLATION => return error.LockViolation, 6360 .ACCESS_DENIED => return error.AccessDenied, 6361 .INVALID_HANDLE => return error.NotOpenForReading, 6362 else => |err| return windows.unexpectedError(err), 6363 } 6364 } 6365 } 6366 6367 fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void { 6368 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6369 const current_thread = Thread.getCurrent(t); 6370 const fd = file.handle; 6371 6372 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { 6373 var result: u64 = undefined; 6374 try current_thread.beginSyscall(); 6375 while (true) { 6376 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.CUR))) { 6377 .SUCCESS => { 6378 current_thread.endSyscall(); 6379 return; 6380 }, 6381 .INTR => { 6382 try current_thread.checkCancel(); 6383 continue; 6384 }, 6385 .CANCELED => return current_thread.endSyscallCanceled(), 6386 else => |e| { 6387 current_thread.endSyscall(); 6388 switch (e) { 6389 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6390 .INVAL => return error.Unseekable, 6391 .OVERFLOW => return error.Unseekable, 6392 .SPIPE => return error.Unseekable, 6393 .NXIO => return error.Unseekable, 6394 else => |err| return posix.unexpectedErrno(err), 6395 } 6396 }, 6397 } 6398 } 6399 } 6400 6401 if (native_os == .windows) { 6402 try current_thread.checkCancel(); 6403 return windows.SetFilePointerEx_CURRENT(fd, offset); 6404 } 6405 6406 if (native_os == .wasi and !builtin.link_libc) { 6407 var new_offset: std.os.wasi.filesize_t = undefined; 6408 try current_thread.beginSyscall(); 6409 while (true) { 6410 switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) { 6411 .SUCCESS => { 6412 current_thread.endSyscall(); 6413 return; 6414 }, 6415 .INTR => { 6416 try current_thread.checkCancel(); 6417 continue; 6418 }, 6419 .CANCELED => return current_thread.endSyscallCanceled(), 6420 else => |e| { 6421 current_thread.endSyscall(); 6422 switch (e) { 6423 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6424 .INVAL => return error.Unseekable, 6425 .OVERFLOW => return error.Unseekable, 6426 .SPIPE => return error.Unseekable, 6427 .NXIO => return error.Unseekable, 6428 .NOTCAPABLE => return error.AccessDenied, 6429 else => |err| return posix.unexpectedErrno(err), 6430 } 6431 }, 6432 } 6433 } 6434 } 6435 6436 if (posix.SEEK == void) return error.Unseekable; 6437 6438 try current_thread.beginSyscall(); 6439 while (true) { 6440 switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) { 6441 .SUCCESS => { 6442 current_thread.endSyscall(); 6443 return; 6444 }, 6445 .INTR => { 6446 try current_thread.checkCancel(); 6447 continue; 6448 }, 6449 .CANCELED => return current_thread.endSyscallCanceled(), 6450 else => |e| { 6451 current_thread.endSyscall(); 6452 switch (e) { 6453 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6454 .INVAL => return error.Unseekable, 6455 .OVERFLOW => return error.Unseekable, 6456 .SPIPE => return error.Unseekable, 6457 .NXIO => return error.Unseekable, 6458 else => |err| return posix.unexpectedErrno(err), 6459 } 6460 }, 6461 } 6462 } 6463 } 6464 6465 fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void { 6466 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6467 const current_thread = Thread.getCurrent(t); 6468 const fd = file.handle; 6469 6470 if (native_os == .windows) { 6471 try current_thread.checkCancel(); 6472 return windows.SetFilePointerEx_BEGIN(fd, offset); 6473 } 6474 6475 if (native_os == .wasi and !builtin.link_libc) { 6476 try current_thread.beginSyscall(); 6477 while (true) { 6478 var new_offset: std.os.wasi.filesize_t = undefined; 6479 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { 6480 .SUCCESS => { 6481 current_thread.endSyscall(); 6482 return; 6483 }, 6484 .INTR => { 6485 try current_thread.checkCancel(); 6486 continue; 6487 }, 6488 .CANCELED => return current_thread.endSyscallCanceled(), 6489 else => |e| { 6490 current_thread.endSyscall(); 6491 switch (e) { 6492 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6493 .INVAL => return error.Unseekable, 6494 .OVERFLOW => return error.Unseekable, 6495 .SPIPE => return error.Unseekable, 6496 .NXIO => return error.Unseekable, 6497 .NOTCAPABLE => return error.AccessDenied, 6498 else => |err| return posix.unexpectedErrno(err), 6499 } 6500 }, 6501 } 6502 } 6503 } 6504 6505 if (posix.SEEK == void) return error.Unseekable; 6506 6507 return posixSeekTo(current_thread, fd, offset); 6508 } 6509 6510 fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekError!void { 6511 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { 6512 try current_thread.beginSyscall(); 6513 while (true) { 6514 var result: u64 = undefined; 6515 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { 6516 .SUCCESS => { 6517 current_thread.endSyscall(); 6518 return; 6519 }, 6520 .INTR => { 6521 try current_thread.checkCancel(); 6522 continue; 6523 }, 6524 .CANCELED => return current_thread.endSyscallCanceled(), 6525 else => |e| { 6526 current_thread.endSyscall(); 6527 switch (e) { 6528 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6529 .INVAL => return error.Unseekable, 6530 .OVERFLOW => return error.Unseekable, 6531 .SPIPE => return error.Unseekable, 6532 .NXIO => return error.Unseekable, 6533 else => |err| return posix.unexpectedErrno(err), 6534 } 6535 }, 6536 } 6537 } 6538 } 6539 6540 try current_thread.beginSyscall(); 6541 while (true) { 6542 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) { 6543 .SUCCESS => { 6544 current_thread.endSyscall(); 6545 return; 6546 }, 6547 .INTR => { 6548 try current_thread.checkCancel(); 6549 continue; 6550 }, 6551 .CANCELED => return current_thread.endSyscallCanceled(), 6552 else => |e| { 6553 current_thread.endSyscall(); 6554 switch (e) { 6555 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6556 .INVAL => return error.Unseekable, 6557 .OVERFLOW => return error.Unseekable, 6558 .SPIPE => return error.Unseekable, 6559 .NXIO => return error.Unseekable, 6560 else => |err| return posix.unexpectedErrno(err), 6561 } 6562 }, 6563 } 6564 } 6565 } 6566 6567 fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.process.OpenExecutableError!File { 6568 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6569 switch (native_os) { 6570 .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), 6571 .windows => { 6572 // If ImagePathName is a symlink, then it will contain the path of the symlink, 6573 // not the path that the symlink points to. However, because we are opening 6574 // the file, we can let the openFileW call follow the symlink for us. 6575 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; 6576 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; 6577 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); 6578 return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags); 6579 }, 6580 else => @panic("TODO implement processExecutableOpen"), 6581 } 6582 } 6583 6584 fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize { 6585 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6586 6587 switch (native_os) { 6588 .driverkit, 6589 .ios, 6590 .maccatalyst, 6591 .macos, 6592 .tvos, 6593 .visionos, 6594 .watchos, 6595 => { 6596 // Note that _NSGetExecutablePath() will return "a path" to 6597 // the executable not a "real path" to the executable. 6598 var symlink_path_buf: [posix.PATH_MAX:0]u8 = undefined; 6599 var u32_len: u32 = posix.PATH_MAX + 1; // include the sentinel 6600 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); 6601 if (rc != 0) return error.NameTooLong; 6602 6603 var real_path_buf: [posix.PATH_MAX]u8 = undefined; 6604 const n = Io.Dir.realPathAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { 6605 error.NetworkNotFound => unreachable, // Windows-only 6606 else => |e| return e, 6607 }; 6608 if (n > out_buffer.len) return error.NameTooLong; 6609 @memcpy(out_buffer[0..n], real_path_buf[0..n]); 6610 return n; 6611 }, 6612 .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { 6613 error.UnsupportedReparsePointType => unreachable, // Windows-only 6614 error.NetworkNotFound => unreachable, // Windows-only 6615 else => |e| return e, 6616 }, 6617 .illumos => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) { 6618 error.UnsupportedReparsePointType => unreachable, // Windows-only 6619 error.NetworkNotFound => unreachable, // Windows-only 6620 else => |e| return e, 6621 }, 6622 .freebsd, .dragonfly => { 6623 const current_thread = Thread.getCurrent(t); 6624 try current_thread.checkCancel(); 6625 var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; 6626 var out_len: usize = out_buffer.len; 6627 try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); 6628 return out_len; 6629 }, 6630 .netbsd => { 6631 const current_thread = Thread.getCurrent(t); 6632 try current_thread.checkCancel(); 6633 var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; 6634 var out_len: usize = out_buffer.len; 6635 try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); 6636 return out_len; 6637 }, 6638 .openbsd, .haiku => { 6639 // OpenBSD doesn't support getting the path of a running process, so try to guess it 6640 if (std.os.argv.len == 0) 6641 return error.FileNotFound; 6642 6643 const argv0 = std.mem.span(std.os.argv[0]); 6644 if (std.mem.indexOf(u8, argv0, "/") != null) { 6645 // argv[0] is a path (relative or absolute): use realpath(3) directly 6646 var real_path_buf: [posix.PATH_MAX]u8 = undefined; 6647 const real_path = Io.Dir.realPathAbsolute(ioBasic(t), std.os.argv[0], &real_path_buf) catch |err| switch (err) { 6648 error.NetworkNotFound => unreachable, // Windows-only 6649 else => |e| return e, 6650 }; 6651 if (real_path.len > out_buffer.len) 6652 return error.NameTooLong; 6653 const result = out_buffer[0..real_path.len]; 6654 @memcpy(result, real_path); 6655 return result.len; 6656 } else if (argv0.len != 0) { 6657 // argv[0] is not empty (and not a path): search it inside PATH 6658 const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; 6659 var path_it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); 6660 while (path_it.next()) |a_path| { 6661 var resolved_path_buf: [posix.PATH_MAX - 1:0]u8 = undefined; 6662 const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ 6663 a_path, std.os.argv[0], 6664 }, 0) catch continue; 6665 6666 var real_path_buf: [posix.PATH_MAX]u8 = undefined; 6667 if (Io.Dir.realPathAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path| { 6668 // found a file, and hope it is the right file 6669 if (real_path.len > out_buffer.len) 6670 return error.NameTooLong; 6671 const result = out_buffer[0..real_path.len]; 6672 @memcpy(result, real_path); 6673 return result.len; 6674 } else |_| continue; 6675 } 6676 } 6677 return error.FileNotFound; 6678 }, 6679 .windows => { 6680 const current_thread = Thread.getCurrent(t); 6681 try current_thread.checkCancel(); 6682 const w = windows; 6683 const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName; 6684 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; 6685 6686 // If ImagePathName is a symlink, then it will contain the path of the 6687 // symlink, not the path that the symlink points to. We want the path 6688 // that the symlink points to, though, so we need to get the realpath. 6689 var path_name_w = try w.wToPrefixedFileW(null, image_path_name); 6690 6691 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; 6692 const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; 6693 const creation = w.FILE_OPEN; 6694 const h_file = blk: { 6695 const res = w.OpenFile(path_name_w.span(), .{ 6696 .dir = null, 6697 .access_mask = access_mask, 6698 .share_access = share_access, 6699 .creation = creation, 6700 .filter = .any, 6701 }) catch |err| switch (err) { 6702 error.WouldBlock => unreachable, 6703 else => |e| return e, 6704 }; 6705 break :blk res; 6706 }; 6707 defer w.CloseHandle(h_file); 6708 6709 const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); 6710 6711 const len = std.unicode.calcWtf8Len(wide_slice); 6712 if (len > out_buffer.len) 6713 return error.NameTooLong; 6714 6715 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); 6716 return end_index; 6717 }, 6718 else => @compileError("unsupported OS"), 6719 } 6720 } 6721 6722 fn fileWritePositional( 6723 userdata: ?*anyopaque, 6724 file: File, 6725 header: []const u8, 6726 data: []const []const u8, 6727 splat: usize, 6728 offset: u64, 6729 ) File.WritePositionalError!usize { 6730 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6731 const current_thread = Thread.getCurrent(t); 6732 6733 if (is_windows) @panic("TODO"); 6734 6735 var iovecs: [max_iovecs_len]posix.iovec_const = undefined; 6736 var iovlen: iovlen_t = 0; 6737 addBuf(&iovecs, &iovlen, header); 6738 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); 6739 const pattern = data[data.len - 1]; 6740 if (iovecs.len - iovlen != 0) switch (splat) { 6741 0 => {}, 6742 1 => addBuf(&iovecs, &iovlen, pattern), 6743 else => switch (pattern.len) { 6744 0 => {}, 6745 1 => { 6746 var backup_buffer: [splat_buffer_size]u8 = undefined; 6747 const splat_buffer = &backup_buffer; 6748 const memset_len = @min(splat_buffer.len, splat); 6749 const buf = splat_buffer[0..memset_len]; 6750 @memset(buf, pattern[0]); 6751 addBuf(&iovecs, &iovlen, buf); 6752 var remaining_splat = splat - buf.len; 6753 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { 6754 assert(buf.len == splat_buffer.len); 6755 addBuf(&iovecs, &iovlen, splat_buffer); 6756 remaining_splat -= splat_buffer.len; 6757 } 6758 addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); 6759 }, 6760 else => for (0..@min(splat, iovecs.len - iovlen)) |_| { 6761 addBuf(&iovecs, &iovlen, pattern); 6762 }, 6763 }, 6764 }; 6765 6766 if (native_os == .wasi and !builtin.link_libc) { 6767 var n_written: usize = undefined; 6768 try current_thread.beginSyscall(); 6769 while (true) { 6770 switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) { 6771 .SUCCESS => { 6772 current_thread.endSyscall(); 6773 return n_written; 6774 }, 6775 .INTR => { 6776 try current_thread.checkCancel(); 6777 continue; 6778 }, 6779 .CANCELED => return current_thread.endSyscallCanceled(), 6780 else => |e| { 6781 current_thread.endSyscall(); 6782 switch (e) { 6783 .INVAL => |err| return errnoBug(err), 6784 .FAULT => |err| return errnoBug(err), 6785 .AGAIN => |err| return errnoBug(err), 6786 .BADF => return error.NotOpenForWriting, // can be a race condition. 6787 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. 6788 .DQUOT => return error.DiskQuota, 6789 .FBIG => return error.FileTooBig, 6790 .IO => return error.InputOutput, 6791 .NOSPC => return error.NoSpaceLeft, 6792 .PERM => return error.PermissionDenied, 6793 .PIPE => return error.BrokenPipe, 6794 .NOTCAPABLE => return error.AccessDenied, 6795 .NXIO => return error.Unseekable, 6796 .SPIPE => return error.Unseekable, 6797 .OVERFLOW => return error.Unseekable, 6798 else => |err| return posix.unexpectedErrno(err), 6799 } 6800 }, 6801 } 6802 } 6803 } 6804 6805 try current_thread.beginSyscall(); 6806 while (true) { 6807 const rc = pwritev_sym(file.handle, &iovecs, iovlen, @bitCast(offset)); 6808 switch (posix.errno(rc)) { 6809 .SUCCESS => { 6810 current_thread.endSyscall(); 6811 return @intCast(rc); 6812 }, 6813 .INTR => { 6814 try current_thread.checkCancel(); 6815 continue; 6816 }, 6817 .CANCELED => return current_thread.endSyscallCanceled(), 6818 else => |e| { 6819 current_thread.endSyscall(); 6820 switch (e) { 6821 .INVAL => return error.InvalidArgument, 6822 .FAULT => |err| return errnoBug(err), 6823 .AGAIN => return error.WouldBlock, 6824 .BADF => return error.NotOpenForWriting, // Usually a race condition. 6825 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. 6826 .DQUOT => return error.DiskQuota, 6827 .FBIG => return error.FileTooBig, 6828 .IO => return error.InputOutput, 6829 .NOSPC => return error.NoSpaceLeft, 6830 .PERM => return error.PermissionDenied, 6831 .PIPE => return error.BrokenPipe, 6832 .CONNRESET => |err| return errnoBug(err), // Not a socket handle. 6833 .BUSY => return error.DeviceBusy, 6834 .TXTBSY => return error.FileBusy, 6835 .NXIO => return error.Unseekable, 6836 .SPIPE => return error.Unseekable, 6837 .OVERFLOW => return error.Unseekable, 6838 else => |err| return posix.unexpectedErrno(err), 6839 } 6840 }, 6841 } 6842 } 6843 } 6844 6845 fn fileWriteStreaming( 6846 userdata: ?*anyopaque, 6847 file: File, 6848 header: []const u8, 6849 data: []const []const u8, 6850 splat: usize, 6851 ) File.Writer.Error!usize { 6852 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6853 const current_thread = Thread.getCurrent(t); 6854 6855 if (is_windows) @panic("TODO"); 6856 6857 var iovecs: [max_iovecs_len]posix.iovec_const = undefined; 6858 var iovlen: iovlen_t = 0; 6859 addBuf(&iovecs, &iovlen, header); 6860 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); 6861 const pattern = data[data.len - 1]; 6862 if (iovecs.len - iovlen != 0) switch (splat) { 6863 0 => {}, 6864 1 => addBuf(&iovecs, &iovlen, pattern), 6865 else => switch (pattern.len) { 6866 0 => {}, 6867 1 => { 6868 var backup_buffer: [splat_buffer_size]u8 = undefined; 6869 const splat_buffer = &backup_buffer; 6870 const memset_len = @min(splat_buffer.len, splat); 6871 const buf = splat_buffer[0..memset_len]; 6872 @memset(buf, pattern[0]); 6873 addBuf(&iovecs, &iovlen, buf); 6874 var remaining_splat = splat - buf.len; 6875 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { 6876 assert(buf.len == splat_buffer.len); 6877 addBuf(&iovecs, &iovlen, splat_buffer); 6878 remaining_splat -= splat_buffer.len; 6879 } 6880 addBuf(&iovecs, &iovlen, splat_buffer[0..remaining_splat]); 6881 }, 6882 else => for (0..@min(splat, iovecs.len - iovlen)) |_| { 6883 addBuf(&iovecs, &iovlen, pattern); 6884 }, 6885 }, 6886 }; 6887 6888 if (native_os == .wasi and !builtin.link_libc) { 6889 var n_written: usize = undefined; 6890 try current_thread.beginSyscall(); 6891 while (true) { 6892 switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) { 6893 .SUCCESS => { 6894 current_thread.endSyscall(); 6895 return n_written; 6896 }, 6897 .INTR => { 6898 try current_thread.checkCancel(); 6899 continue; 6900 }, 6901 .CANCELED => return current_thread.endSyscallCanceled(), 6902 else => |e| { 6903 current_thread.endSyscall(); 6904 switch (e) { 6905 .INVAL => |err| return errnoBug(err), 6906 .FAULT => |err| return errnoBug(err), 6907 .AGAIN => |err| return errnoBug(err), 6908 .BADF => return error.NotOpenForWriting, // can be a race condition. 6909 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. 6910 .DQUOT => return error.DiskQuota, 6911 .FBIG => return error.FileTooBig, 6912 .IO => return error.InputOutput, 6913 .NOSPC => return error.NoSpaceLeft, 6914 .PERM => return error.PermissionDenied, 6915 .PIPE => return error.BrokenPipe, 6916 .NOTCAPABLE => return error.AccessDenied, 6917 else => |err| return posix.unexpectedErrno(err), 6918 } 6919 }, 6920 } 6921 } 6922 } 6923 6924 try current_thread.beginSyscall(); 6925 while (true) { 6926 const rc = posix.system.writev(file.handle, &iovecs, iovlen); 6927 switch (posix.errno(rc)) { 6928 .SUCCESS => { 6929 current_thread.endSyscall(); 6930 return @intCast(rc); 6931 }, 6932 .INTR => { 6933 try current_thread.checkCancel(); 6934 continue; 6935 }, 6936 .CANCELED => return current_thread.endSyscallCanceled(), 6937 else => |e| { 6938 current_thread.endSyscall(); 6939 switch (e) { 6940 .INVAL => return error.InvalidArgument, 6941 .FAULT => |err| return errnoBug(err), 6942 .AGAIN => return error.WouldBlock, 6943 .BADF => return error.NotOpenForWriting, // Can be a race condition. 6944 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. 6945 .DQUOT => return error.DiskQuota, 6946 .FBIG => return error.FileTooBig, 6947 .IO => return error.InputOutput, 6948 .NOSPC => return error.NoSpaceLeft, 6949 .PERM => return error.PermissionDenied, 6950 .PIPE => return error.BrokenPipe, 6951 .CONNRESET => |err| return errnoBug(err), // Not a socket handle. 6952 .BUSY => return error.DeviceBusy, 6953 else => |err| return posix.unexpectedErrno(err), 6954 } 6955 }, 6956 } 6957 } 6958 } 6959 6960 fn fileWriteFileStreaming( 6961 userdata: ?*anyopaque, 6962 file: File, 6963 header: []const u8, 6964 file_reader: *File.Reader, 6965 limit: Io.Limit, 6966 ) File.Writer.WriteFileError!usize { 6967 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6968 const reader_buffered = file_reader.interface.buffered(); 6969 if (reader_buffered.len >= @intFromEnum(limit)) { 6970 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 6971 file_reader.interface.toss(n -| header.len); 6972 return n; 6973 } 6974 const file_limit = @intFromEnum(limit) - reader_buffered.len; 6975 const out_fd = file.handle; 6976 const in_fd = file_reader.file.handle; 6977 6978 if (file_reader.size) |size| { 6979 if (size - file_reader.pos == 0) { 6980 if (reader_buffered.len != 0) { 6981 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 6982 file_reader.interface.toss(n -| header.len); 6983 return n; 6984 } else { 6985 return error.EndOfStream; 6986 } 6987 } 6988 } 6989 6990 if (native_os == .freebsd) sf: { 6991 // Try using sendfile on FreeBSD. 6992 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; 6993 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; 6994 var hdtr_data: std.c.sf_hdtr = undefined; 6995 var headers: [2]posix.iovec_const = undefined; 6996 var headers_i: u8 = 0; 6997 if (header.len != 0) { 6998 headers[headers_i] = .{ .base = header.ptr, .len = header.len }; 6999 headers_i += 1; 7000 } 7001 if (reader_buffered.len != 0) { 7002 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; 7003 headers_i += 1; 7004 } 7005 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { 7006 hdtr_data = .{ 7007 .headers = &headers, 7008 .hdr_cnt = headers_i, 7009 .trailers = null, 7010 .trl_cnt = 0, 7011 }; 7012 break :b &hdtr_data; 7013 }; 7014 var sbytes: std.c.off_t = 0; 7015 const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); 7016 const flags = 0; 7017 7018 const current_thread = Thread.getCurrent(t); 7019 try current_thread.beginSyscall(); 7020 while (true) { 7021 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { 7022 .SUCCESS => { 7023 current_thread.endSyscall(); 7024 break; 7025 }, 7026 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { 7027 // Give calling code chance to observe before trying 7028 // something else. 7029 current_thread.endSyscall(); 7030 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 7031 return 0; 7032 }, 7033 .INTR, .BUSY => { 7034 if (sbytes == 0) { 7035 try current_thread.checkCancel(); 7036 continue; 7037 } else { 7038 // Even if we are being canceled, there have been side 7039 // effects, so it is better to report those side 7040 // effects to the caller. 7041 current_thread.endSyscall(); 7042 break; 7043 } 7044 }, 7045 .AGAIN => { 7046 current_thread.endSyscall(); 7047 if (sbytes == 0) return error.WouldBlock; 7048 break; 7049 }, 7050 else => |e| { 7051 current_thread.endSyscall(); 7052 assert(error.Unexpected == switch (e) { 7053 .NOTCONN => return error.BrokenPipe, 7054 .IO => return error.InputOutput, 7055 .PIPE => return error.BrokenPipe, 7056 .NOBUFS => return error.SystemResources, 7057 .BADF => |err| errnoBug(err), 7058 .FAULT => |err| errnoBug(err), 7059 else => |err| posix.unexpectedErrno(err), 7060 }); 7061 // Give calling code chance to observe the error before trying 7062 // something else. 7063 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 7064 return 0; 7065 }, 7066 } 7067 } 7068 if (sbytes == 0) { 7069 file_reader.size = file_reader.pos; 7070 return error.EndOfStream; 7071 } 7072 const ubytes: usize = @intCast(sbytes); 7073 file_reader.interface.toss(ubytes -| header.len); 7074 return ubytes; 7075 } 7076 7077 if (is_darwin) sf: { 7078 // Try using sendfile on macOS. 7079 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; 7080 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; 7081 var hdtr_data: std.c.sf_hdtr = undefined; 7082 var headers: [2]posix.iovec_const = undefined; 7083 var headers_i: u8 = 0; 7084 if (header.len != 0) { 7085 headers[headers_i] = .{ .base = header.ptr, .len = header.len }; 7086 headers_i += 1; 7087 } 7088 if (reader_buffered.len != 0) { 7089 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; 7090 headers_i += 1; 7091 } 7092 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { 7093 hdtr_data = .{ 7094 .headers = &headers, 7095 .hdr_cnt = headers_i, 7096 .trailers = null, 7097 .trl_cnt = 0, 7098 }; 7099 break :b &hdtr_data; 7100 }; 7101 const max_count = std.math.maxInt(i32); // Avoid EINVAL. 7102 var len: std.c.off_t = @min(file_limit, max_count); 7103 const flags = 0; 7104 const current_thread = Thread.getCurrent(t); 7105 try current_thread.beginSyscall(); 7106 while (true) { 7107 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { 7108 .SUCCESS => { 7109 current_thread.endSyscall(); 7110 break; 7111 }, 7112 .OPNOTSUPP, .NOTSOCK, .NOSYS => { 7113 // Give calling code chance to observe before trying 7114 // something else. 7115 current_thread.endSyscall(); 7116 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 7117 return 0; 7118 }, 7119 .INTR => { 7120 if (len == 0) { 7121 try current_thread.checkCancel(); 7122 continue; 7123 } else { 7124 // Even if we are being canceled, there have been side 7125 // effects, so it is better to report those side 7126 // effects to the caller. 7127 current_thread.endSyscall(); 7128 break; 7129 } 7130 }, 7131 .AGAIN => { 7132 current_thread.endSyscall(); 7133 if (len == 0) return error.WouldBlock; 7134 break; 7135 }, 7136 else => |e| { 7137 current_thread.endSyscall(); 7138 assert(error.Unexpected == switch (e) { 7139 .NOTCONN => return error.BrokenPipe, 7140 .IO => return error.InputOutput, 7141 .PIPE => return error.BrokenPipe, 7142 .BADF => |err| errnoBug(err), 7143 .FAULT => |err| errnoBug(err), 7144 .INVAL => |err| errnoBug(err), 7145 else => |err| posix.unexpectedErrno(err), 7146 }); 7147 // Give calling code chance to observe the error before trying 7148 // something else. 7149 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 7150 return 0; 7151 }, 7152 } 7153 } 7154 if (len == 0) { 7155 file_reader.size = file_reader.pos; 7156 return error.EndOfStream; 7157 } 7158 const u_len: usize = @bitCast(len); 7159 file_reader.interface.toss(u_len -| header.len); 7160 return u_len; 7161 } 7162 7163 if (native_os == .linux) sf: { 7164 // Try using sendfile on Linux. 7165 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; 7166 // Linux sendfile does not support headers. 7167 if (header.len != 0 or reader_buffered.len != 0) { 7168 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 7169 file_reader.interface.toss(n -| header.len); 7170 return n; 7171 } 7172 const max_count = 0x7ffff000; // Avoid EINVAL. 7173 var off: std.os.linux.off_t = undefined; 7174 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { 7175 .positional => o: { 7176 const size = file_reader.getSize() catch return 0; 7177 off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; 7178 break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; 7179 }, 7180 .streaming => .{ null, limit.minInt(max_count) }, 7181 .streaming_reading, .positional_reading => break :sf, 7182 .failure => return error.ReadFailed, 7183 }; 7184 const current_thread = Thread.getCurrent(t); 7185 try current_thread.beginSyscall(); 7186 const n: usize = while (true) { 7187 const rc = sendfile_sym(out_fd, in_fd, off_ptr, count); 7188 switch (posix.errno(rc)) { 7189 .SUCCESS => { 7190 current_thread.endSyscall(); 7191 break @intCast(rc); 7192 }, 7193 .NOSYS, .INVAL => { 7194 // Give calling code chance to observe before trying 7195 // something else. 7196 current_thread.endSyscall(); 7197 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 7198 return 0; 7199 }, 7200 .INTR => { 7201 try current_thread.checkCancel(); 7202 continue; 7203 }, 7204 .CANCELED => return current_thread.endSyscallCanceled(), 7205 else => |e| { 7206 current_thread.endSyscall(); 7207 assert(error.Unexpected == switch (e) { 7208 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket 7209 .AGAIN => return error.WouldBlock, 7210 .IO => return error.InputOutput, 7211 .PIPE => return error.BrokenPipe, 7212 .NOMEM => return error.SystemResources, 7213 .NXIO, .SPIPE => { 7214 file_reader.mode = file_reader.mode.toStreaming(); 7215 const pos = file_reader.pos; 7216 if (pos != 0) { 7217 file_reader.pos = 0; 7218 file_reader.seekBy(@intCast(pos)) catch { 7219 file_reader.mode = .failure; 7220 return error.ReadFailed; 7221 }; 7222 } 7223 return 0; 7224 }, 7225 .BADF => |err| errnoBug(err), // Always a race condition. 7226 .FAULT => |err| errnoBug(err), // Segmentation fault. 7227 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`. 7228 else => |err| posix.unexpectedErrno(err), 7229 }); 7230 // Give calling code chance to observe the error before trying 7231 // something else. 7232 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 7233 return 0; 7234 }, 7235 } 7236 }; 7237 if (n == 0) { 7238 file_reader.size = file_reader.pos; 7239 return error.EndOfStream; 7240 } 7241 file_reader.pos += n; 7242 return n; 7243 } 7244 7245 if (have_copy_file_range) cfr: { 7246 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; 7247 if (header.len != 0 or reader_buffered.len != 0) { 7248 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 7249 file_reader.interface.toss(n -| header.len); 7250 return n; 7251 } 7252 var off_in: i64 = undefined; 7253 const off_in_ptr: ?*i64 = switch (file_reader.mode) { 7254 .positional_reading, .streaming_reading => return error.Unimplemented, 7255 .positional => p: { 7256 off_in = @intCast(file_reader.pos); 7257 break :p &off_in; 7258 }, 7259 .streaming => null, 7260 .failure => return error.ReadFailed, 7261 }; 7262 const current_thread = Thread.getCurrent(t); 7263 const n: usize = switch (native_os) { 7264 .linux => n: { 7265 try current_thread.beginSyscall(); 7266 while (true) { 7267 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); 7268 switch (linux_copy_file_range_sys.errno(rc)) { 7269 .SUCCESS => { 7270 current_thread.endSyscall(); 7271 break :n @intCast(rc); 7272 }, 7273 .INTR => { 7274 try current_thread.checkCancel(); 7275 continue; 7276 }, 7277 .CANCELED => return current_thread.endSyscallCanceled(), 7278 .OPNOTSUPP, .INVAL, .NOSYS => { 7279 // Give calling code chance to observe before trying 7280 // something else. 7281 current_thread.endSyscall(); 7282 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7283 return 0; 7284 }, 7285 else => |e| { 7286 current_thread.endSyscall(); 7287 assert(error.Unexpected == switch (e) { 7288 .FBIG => return error.FileTooBig, 7289 .IO => return error.InputOutput, 7290 .NOMEM => return error.SystemResources, 7291 .NOSPC => return error.NoSpaceLeft, 7292 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. 7293 .PERM => return error.PermissionDenied, 7294 .BUSY => return error.DeviceBusy, 7295 .TXTBSY => return error.FileBusy, 7296 // copy_file_range can still work but not on 7297 // this pair of file descriptors. 7298 .XDEV => return error.Unimplemented, 7299 .ISDIR => |err| errnoBug(err), 7300 .BADF => |err| errnoBug(err), 7301 else => |err| posix.unexpectedErrno(err), 7302 }); 7303 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7304 return 0; 7305 }, 7306 } 7307 } 7308 }, 7309 .freebsd => n: { 7310 try current_thread.beginSyscall(); 7311 while (true) { 7312 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); 7313 switch (std.c.errno(rc)) { 7314 .SUCCESS => { 7315 current_thread.endSyscall(); 7316 break :n @intCast(rc); 7317 }, 7318 .INTR => { 7319 try current_thread.checkCancel(); 7320 continue; 7321 }, 7322 .OPNOTSUPP, .INVAL, .NOSYS => { 7323 // Give calling code chance to observe before trying 7324 // something else. 7325 current_thread.endSyscall(); 7326 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7327 return 0; 7328 }, 7329 else => |e| { 7330 current_thread.endSyscall(); 7331 assert(error.Unexpected == switch (e) { 7332 .FBIG => return error.FileTooBig, 7333 .IO => return error.InputOutput, 7334 .INTEGRITY => return error.CorruptedData, 7335 .ISDIR => return error.IsDir, 7336 .NOSPC => return error.NoSpaceLeft, 7337 .BADF => |err| errnoBug(err), 7338 else => |err| posix.unexpectedErrno(err), 7339 }); 7340 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7341 return 0; 7342 }, 7343 } 7344 } 7345 }, 7346 else => comptime unreachable, 7347 }; 7348 if (n == 0) { 7349 file_reader.size = file_reader.pos; 7350 return error.EndOfStream; 7351 } 7352 file_reader.pos += n; 7353 return n; 7354 } 7355 7356 return error.Unimplemented; 7357 } 7358 7359 fn netWriteFile( 7360 userdata: ?*anyopaque, 7361 socket_handle: net.Socket.Handle, 7362 header: []const u8, 7363 file_reader: *File.Reader, 7364 limit: Io.Limit, 7365 ) net.Stream.Writer.WriteFileError!usize { 7366 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7367 _ = t; 7368 _ = socket_handle; 7369 _ = header; 7370 _ = file_reader; 7371 _ = limit; 7372 @panic("TODO"); 7373 } 7374 7375 fn netWriteFileUnavailable( 7376 userdata: ?*anyopaque, 7377 socket_handle: net.Socket.Handle, 7378 header: []const u8, 7379 file_reader: *File.Reader, 7380 limit: Io.Limit, 7381 ) net.Stream.Writer.WriteFileError!usize { 7382 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7383 _ = t; 7384 _ = socket_handle; 7385 _ = header; 7386 _ = file_reader; 7387 _ = limit; 7388 return error.NetworkDown; 7389 } 7390 7391 fn fileWriteFilePositional( 7392 userdata: ?*anyopaque, 7393 file: File, 7394 header: []const u8, 7395 file_reader: *File.Reader, 7396 limit: Io.Limit, 7397 offset: u64, 7398 ) File.WriteFilePositionalError!usize { 7399 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7400 const reader_buffered = file_reader.interface.buffered(); 7401 if (reader_buffered.len >= @intFromEnum(limit)) { 7402 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 7403 file_reader.interface.toss(n -| header.len); 7404 return n; 7405 } 7406 const out_fd = file.handle; 7407 const in_fd = file_reader.file.handle; 7408 7409 if (file_reader.size) |size| { 7410 if (size - file_reader.pos == 0) { 7411 if (reader_buffered.len != 0) { 7412 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 7413 file_reader.interface.toss(n -| header.len); 7414 return n; 7415 } else { 7416 return error.EndOfStream; 7417 } 7418 } 7419 } 7420 7421 if (have_copy_file_range) cfr: { 7422 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; 7423 if (header.len != 0 or reader_buffered.len != 0) { 7424 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 7425 file_reader.interface.toss(n -| header.len); 7426 return n; 7427 } 7428 var off_in: i64 = undefined; 7429 const off_in_ptr: ?*i64 = switch (file_reader.mode) { 7430 .positional_reading, .streaming_reading => return error.Unimplemented, 7431 .positional => p: { 7432 off_in = @intCast(file_reader.pos); 7433 break :p &off_in; 7434 }, 7435 .streaming => null, 7436 .failure => return error.ReadFailed, 7437 }; 7438 var off_out: i64 = @intCast(offset); 7439 const current_thread = Thread.getCurrent(t); 7440 const n: usize = switch (native_os) { 7441 .linux => n: { 7442 try current_thread.beginSyscall(); 7443 while (true) { 7444 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); 7445 switch (linux_copy_file_range_sys.errno(rc)) { 7446 .SUCCESS => { 7447 current_thread.endSyscall(); 7448 break :n @intCast(rc); 7449 }, 7450 .INTR => { 7451 try current_thread.checkCancel(); 7452 continue; 7453 }, 7454 .CANCELED => return current_thread.endSyscallCanceled(), 7455 .OPNOTSUPP, .INVAL, .NOSYS => { 7456 // Give calling code chance to observe before trying 7457 // something else. 7458 current_thread.endSyscall(); 7459 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7460 return 0; 7461 }, 7462 else => |e| { 7463 current_thread.endSyscall(); 7464 assert(error.Unexpected == switch (e) { 7465 .FBIG => return error.FileTooBig, 7466 .IO => return error.InputOutput, 7467 .NOMEM => return error.SystemResources, 7468 .NOSPC => return error.NoSpaceLeft, 7469 .OVERFLOW => return error.Unseekable, 7470 .NXIO => return error.Unseekable, 7471 .SPIPE => return error.Unseekable, 7472 .PERM => return error.PermissionDenied, 7473 .TXTBSY => return error.FileBusy, 7474 // copy_file_range can still work but not on 7475 // this pair of file descriptors. 7476 .XDEV => return error.Unimplemented, 7477 .ISDIR => |err| errnoBug(err), 7478 .BADF => |err| errnoBug(err), 7479 else => |err| posix.unexpectedErrno(err), 7480 }); 7481 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7482 return 0; 7483 }, 7484 } 7485 } 7486 }, 7487 .freebsd => n: { 7488 try current_thread.beginSyscall(); 7489 while (true) { 7490 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); 7491 switch (std.c.errno(rc)) { 7492 .SUCCESS => { 7493 current_thread.endSyscall(); 7494 break :n @intCast(rc); 7495 }, 7496 .INTR => { 7497 try current_thread.checkCancel(); 7498 continue; 7499 }, 7500 .OPNOTSUPP, .INVAL, .NOSYS => { 7501 // Give calling code chance to observe before trying 7502 // something else. 7503 current_thread.endSyscall(); 7504 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7505 return 0; 7506 }, 7507 else => |e| { 7508 current_thread.endSyscall(); 7509 assert(error.Unexpected == switch (e) { 7510 .FBIG => return error.FileTooBig, 7511 .IO => return error.InputOutput, 7512 .INTEGRITY => return error.CorruptedData, 7513 .ISDIR => return error.IsDir, 7514 .NOSPC => return error.NoSpaceLeft, 7515 .OVERFLOW => return error.Unseekable, 7516 .NXIO => return error.Unseekable, 7517 .SPIPE => return error.Unseekable, 7518 .BADF => |err| errnoBug(err), 7519 else => |err| posix.unexpectedErrno(err), 7520 }); 7521 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 7522 return 0; 7523 }, 7524 } 7525 } 7526 }, 7527 else => comptime unreachable, 7528 }; 7529 if (n == 0) { 7530 file_reader.size = file_reader.pos; 7531 return error.EndOfStream; 7532 } 7533 file_reader.pos += n; 7534 return n; 7535 } 7536 7537 if (is_darwin) fcf: { 7538 if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf; 7539 if (file_reader.pos != 0) break :fcf; 7540 if (offset != 0) break :fcf; 7541 if (limit != .unlimited) break :fcf; 7542 const size = file_reader.getSize() catch break :fcf; 7543 if (header.len != 0 or reader_buffered.len != 0) { 7544 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 7545 file_reader.interface.toss(n -| header.len); 7546 return n; 7547 } 7548 const current_thread = Thread.getCurrent(t); 7549 try current_thread.beginSyscall(); 7550 while (true) { 7551 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); 7552 switch (posix.errno(rc)) { 7553 .SUCCESS => { 7554 current_thread.endSyscall(); 7555 break; 7556 }, 7557 .INTR => { 7558 try current_thread.checkCancel(); 7559 continue; 7560 }, 7561 .OPNOTSUPP => { 7562 // Give calling code chance to observe before trying 7563 // something else. 7564 current_thread.endSyscall(); 7565 @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic); 7566 return 0; 7567 }, 7568 else => |e| { 7569 current_thread.endSyscall(); 7570 assert(error.Unexpected == switch (e) { 7571 .NOMEM => return error.SystemResources, 7572 .INVAL => |err| errnoBug(err), 7573 else => |err| posix.unexpectedErrno(err), 7574 }); 7575 return 0; 7576 }, 7577 } 7578 } 7579 file_reader.pos = size; 7580 return size; 7581 } 7582 7583 return error.Unimplemented; 7584 } 7585 7586 fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { 7587 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7588 _ = t; 7589 const clock_id: posix.clockid_t = clockToPosix(clock); 7590 var tp: posix.timespec = undefined; 7591 switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) { 7592 .SUCCESS => return timestampFromPosix(&tp), 7593 .INVAL => return error.UnsupportedClock, 7594 else => |err| return posix.unexpectedErrno(err), 7595 } 7596 } 7597 7598 const now = switch (native_os) { 7599 .windows => nowWindows, 7600 .wasi => nowWasi, 7601 else => nowPosix, 7602 }; 7603 7604 fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { 7605 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7606 _ = t; 7607 switch (clock) { 7608 .real => { 7609 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds 7610 // and uses the NTFS/Windows epoch, which is 1601-01-01. 7611 const epoch_ns = std.time.epoch.windows * std.time.ns_per_s; 7612 return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 + epoch_ns }; 7613 }, 7614 .awake, .boot => { 7615 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. 7616 const qpc = windows.QueryPerformanceCounter(); 7617 // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA 7618 // (a read-only page of info updated and mapped by the kernel to all processes): 7619 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data 7620 // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm 7621 const qpf = windows.QueryPerformanceFrequency(); 7622 7623 // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. 7624 // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 7625 const common_qpf = 10_000_000; 7626 if (qpf == common_qpf) return .{ .nanoseconds = qpc * (std.time.ns_per_s / common_qpf) }; 7627 7628 // Convert to ns using fixed point. 7629 const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); 7630 const result = (@as(u96, qpc) * scale) >> 32; 7631 return .{ .nanoseconds = @intCast(result) }; 7632 }, 7633 .cpu_process, 7634 .cpu_thread, 7635 => return error.UnsupportedClock, 7636 } 7637 } 7638 7639 fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { 7640 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7641 _ = t; 7642 var ns: std.os.wasi.timestamp_t = undefined; 7643 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); 7644 if (err != .SUCCESS) return error.Unexpected; 7645 return .fromNanoseconds(ns); 7646 } 7647 7648 const sleep = switch (native_os) { 7649 .windows => sleepWindows, 7650 .wasi => sleepWasi, 7651 .linux => sleepLinux, 7652 else => sleepPosix, 7653 }; 7654 7655 fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { 7656 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7657 const current_thread = Thread.getCurrent(t); 7658 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { 7659 .none => .awake, 7660 .duration => |d| d.clock, 7661 .deadline => |d| d.clock, 7662 }); 7663 const deadline_nanoseconds: i96 = switch (timeout) { 7664 .none => std.math.maxInt(i96), 7665 .duration => |duration| duration.raw.nanoseconds, 7666 .deadline => |deadline| deadline.raw.nanoseconds, 7667 }; 7668 var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds); 7669 try current_thread.beginSyscall(); 7670 while (true) { 7671 switch (std.os.linux.errno(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { 7672 .none, .duration => false, 7673 .deadline => true, 7674 } }, ×pec, ×pec))) { 7675 .SUCCESS => { 7676 current_thread.endSyscall(); 7677 return; 7678 }, 7679 .INTR => { 7680 try current_thread.checkCancel(); 7681 continue; 7682 }, 7683 .CANCELED => return current_thread.endSyscallCanceled(), 7684 else => |e| { 7685 current_thread.endSyscall(); 7686 switch (e) { 7687 .INVAL => return error.UnsupportedClock, 7688 else => |err| return posix.unexpectedErrno(err), 7689 } 7690 }, 7691 } 7692 } 7693 } 7694 7695 fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { 7696 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7697 const current_thread = Thread.getCurrent(t); 7698 const t_io = ioBasic(t); 7699 try current_thread.checkCancel(); 7700 const ms = ms: { 7701 const d = (try timeout.toDurationFromNow(t_io)) orelse 7702 break :ms std.math.maxInt(windows.DWORD); 7703 break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds()); 7704 }; 7705 // TODO: alertable true with checkCancel in a loop plus deadline 7706 _ = windows.kernel32.SleepEx(ms, windows.FALSE); 7707 } 7708 7709 fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { 7710 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7711 const current_thread = Thread.getCurrent(t); 7712 const t_io = ioBasic(t); 7713 const w = std.os.wasi; 7714 7715 const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t_io)) |d| .{ 7716 .id = clockToWasi(d.clock), 7717 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds), 7718 .precision = 0, 7719 .flags = 0, 7720 } else .{ 7721 .id = .MONOTONIC, 7722 .timeout = std.math.maxInt(u64), 7723 .precision = 0, 7724 .flags = 0, 7725 }; 7726 const in: w.subscription_t = .{ 7727 .userdata = 0, 7728 .u = .{ 7729 .tag = .CLOCK, 7730 .u = .{ .clock = clock }, 7731 }, 7732 }; 7733 var event: w.event_t = undefined; 7734 var nevents: usize = undefined; 7735 try current_thread.beginSyscall(); 7736 _ = w.poll_oneoff(&in, &event, 1, &nevents); 7737 current_thread.endSyscall(); 7738 } 7739 7740 fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { 7741 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7742 const current_thread = Thread.getCurrent(t); 7743 const t_io = ioBasic(t); 7744 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; 7745 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; 7746 7747 var timespec: posix.timespec = t: { 7748 const d = (try timeout.toDurationFromNow(t_io)) orelse break :t .{ 7749 .sec = std.math.maxInt(sec_type), 7750 .nsec = std.math.maxInt(nsec_type), 7751 }; 7752 break :t timestampToPosix(d.raw.toNanoseconds()); 7753 }; 7754 try current_thread.beginSyscall(); 7755 while (true) { 7756 switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { 7757 .INTR => { 7758 try current_thread.checkCancel(); 7759 continue; 7760 }, 7761 .CANCELED => return current_thread.endSyscallCanceled(), 7762 // This prong handles success as well as unexpected errors. 7763 else => return current_thread.endSyscall(), 7764 } 7765 } 7766 } 7767 7768 fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize { 7769 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7770 7771 var event: Io.Event = .unset; 7772 7773 for (futures, 0..) |future, i| { 7774 const closure: *AsyncClosure = @ptrCast(@alignCast(future)); 7775 if (@atomicRmw(?*Io.Event, &closure.select_condition, .Xchg, &event, .seq_cst) == AsyncClosure.done_event) { 7776 for (futures[0..i]) |cleanup_future| { 7777 const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future)); 7778 if (@atomicRmw(?*Io.Event, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_event) { 7779 cleanup_closure.event.waitUncancelable(ioBasic(t)); // Ensure no reference to our stack-allocated event. 7780 } 7781 } 7782 return i; 7783 } 7784 } 7785 7786 try event.wait(ioBasic(t)); 7787 7788 var result: ?usize = null; 7789 for (futures, 0..) |future, i| { 7790 const closure: *AsyncClosure = @ptrCast(@alignCast(future)); 7791 if (@atomicRmw(?*Io.Event, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_event) { 7792 closure.event.waitUncancelable(ioBasic(t)); // Ensure no reference to our stack-allocated event. 7793 if (result == null) result = i; // In case multiple are ready, return first. 7794 } 7795 } 7796 return result.?; 7797 } 7798 7799 fn netListenIpPosix( 7800 userdata: ?*anyopaque, 7801 address: IpAddress, 7802 options: IpAddress.ListenOptions, 7803 ) IpAddress.ListenError!net.Server { 7804 if (!have_networking) return error.NetworkDown; 7805 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7806 const current_thread = Thread.getCurrent(t); 7807 const family = posixAddressFamily(&address); 7808 const socket_fd = try openSocketPosix(current_thread, family, .{ 7809 .mode = options.mode, 7810 .protocol = options.protocol, 7811 }); 7812 errdefer posix.close(socket_fd); 7813 7814 if (options.reuse_address) { 7815 try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); 7816 if (@hasDecl(posix.SO, "REUSEPORT")) 7817 try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); 7818 } 7819 7820 var storage: PosixAddress = undefined; 7821 var addr_len = addressToPosix(&address, &storage); 7822 try posixBind(current_thread, socket_fd, &storage.any, addr_len); 7823 7824 try current_thread.beginSyscall(); 7825 while (true) { 7826 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { 7827 .SUCCESS => { 7828 current_thread.endSyscall(); 7829 break; 7830 }, 7831 .INTR => { 7832 try current_thread.checkCancel(); 7833 continue; 7834 }, 7835 .CANCELED => return current_thread.endSyscallCanceled(), 7836 else => |e| { 7837 current_thread.endSyscall(); 7838 switch (e) { 7839 .ADDRINUSE => return error.AddressInUse, 7840 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 7841 else => |err| return posix.unexpectedErrno(err), 7842 } 7843 }, 7844 } 7845 } 7846 7847 try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); 7848 return .{ 7849 .socket = .{ 7850 .handle = socket_fd, 7851 .address = addressFromPosix(&storage), 7852 }, 7853 }; 7854 } 7855 7856 fn netListenIpWindows( 7857 userdata: ?*anyopaque, 7858 address: IpAddress, 7859 options: IpAddress.ListenOptions, 7860 ) IpAddress.ListenError!net.Server { 7861 if (!have_networking) return error.NetworkDown; 7862 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7863 const current_thread = Thread.getCurrent(t); 7864 const family = posixAddressFamily(&address); 7865 const socket_handle = try openSocketWsa(t, current_thread, family, .{ 7866 .mode = options.mode, 7867 .protocol = options.protocol, 7868 }); 7869 errdefer closeSocketWindows(socket_handle); 7870 7871 if (options.reuse_address) 7872 try setSocketOptionWsa(t, socket_handle, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); 7873 7874 var storage: WsaAddress = undefined; 7875 var addr_len = addressToWsa(&address, &storage); 7876 7877 try current_thread.beginSyscall(); 7878 while (true) { 7879 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); 7880 if (rc != ws2_32.SOCKET_ERROR) { 7881 current_thread.endSyscall(); 7882 break; 7883 } 7884 switch (ws2_32.WSAGetLastError()) { 7885 .EINTR => { 7886 try current_thread.checkCancel(); 7887 continue; 7888 }, 7889 .NOTINITIALISED => { 7890 try initializeWsa(t); 7891 try current_thread.checkCancel(); 7892 continue; 7893 }, 7894 else => |e| { 7895 current_thread.endSyscall(); 7896 switch (e) { 7897 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 7898 .EADDRINUSE => return error.AddressInUse, 7899 .EADDRNOTAVAIL => return error.AddressUnavailable, 7900 .ENOTSOCK => |err| return wsaErrorBug(err), 7901 .EFAULT => |err| return wsaErrorBug(err), 7902 .EINVAL => |err| return wsaErrorBug(err), 7903 .ENOBUFS => return error.SystemResources, 7904 .ENETDOWN => return error.NetworkDown, 7905 else => |err| return windows.unexpectedWSAError(err), 7906 } 7907 }, 7908 } 7909 } 7910 7911 try current_thread.beginSyscall(); 7912 while (true) { 7913 const rc = ws2_32.listen(socket_handle, options.kernel_backlog); 7914 if (rc != ws2_32.SOCKET_ERROR) { 7915 current_thread.endSyscall(); 7916 break; 7917 } 7918 switch (ws2_32.WSAGetLastError()) { 7919 .EINTR => { 7920 try current_thread.checkCancel(); 7921 continue; 7922 }, 7923 .NOTINITIALISED => { 7924 try initializeWsa(t); 7925 try current_thread.checkCancel(); 7926 continue; 7927 }, 7928 else => |e| { 7929 current_thread.endSyscall(); 7930 switch (e) { 7931 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 7932 .ENETDOWN => return error.NetworkDown, 7933 .EADDRINUSE => return error.AddressInUse, 7934 .EISCONN => |err| return wsaErrorBug(err), 7935 .EINVAL => |err| return wsaErrorBug(err), 7936 .EMFILE, .ENOBUFS => return error.SystemResources, 7937 .ENOTSOCK => |err| return wsaErrorBug(err), 7938 .EOPNOTSUPP => |err| return wsaErrorBug(err), 7939 .EINPROGRESS => |err| return wsaErrorBug(err), 7940 else => |err| return windows.unexpectedWSAError(err), 7941 } 7942 }, 7943 } 7944 } 7945 7946 try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); 7947 7948 return .{ 7949 .socket = .{ 7950 .handle = socket_handle, 7951 .address = addressFromWsa(&storage), 7952 }, 7953 }; 7954 } 7955 7956 fn netListenIpUnavailable( 7957 userdata: ?*anyopaque, 7958 address: IpAddress, 7959 options: IpAddress.ListenOptions, 7960 ) IpAddress.ListenError!net.Server { 7961 _ = userdata; 7962 _ = address; 7963 _ = options; 7964 return error.NetworkDown; 7965 } 7966 7967 fn netListenUnixPosix( 7968 userdata: ?*anyopaque, 7969 address: *const net.UnixAddress, 7970 options: net.UnixAddress.ListenOptions, 7971 ) net.UnixAddress.ListenError!net.Socket.Handle { 7972 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 7973 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7974 const current_thread = Thread.getCurrent(t); 7975 const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { 7976 error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported, 7977 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, 7978 error.SocketModeUnsupported => return error.AddressFamilyUnsupported, 7979 error.OptionUnsupported => return error.Unexpected, 7980 else => |e| return e, 7981 }; 7982 errdefer posix.close(socket_fd); 7983 7984 var storage: UnixAddress = undefined; 7985 const addr_len = addressUnixToPosix(address, &storage); 7986 try posixBindUnix(current_thread, socket_fd, &storage.any, addr_len); 7987 7988 try current_thread.beginSyscall(); 7989 while (true) { 7990 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { 7991 .SUCCESS => { 7992 current_thread.endSyscall(); 7993 break; 7994 }, 7995 .INTR => { 7996 try current_thread.checkCancel(); 7997 continue; 7998 }, 7999 .CANCELED => return current_thread.endSyscallCanceled(), 8000 else => |e| { 8001 current_thread.endSyscall(); 8002 switch (e) { 8003 .ADDRINUSE => return error.AddressInUse, 8004 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8005 else => |err| return posix.unexpectedErrno(err), 8006 } 8007 }, 8008 } 8009 } 8010 8011 return socket_fd; 8012 } 8013 8014 fn netListenUnixWindows( 8015 userdata: ?*anyopaque, 8016 address: *const net.UnixAddress, 8017 options: net.UnixAddress.ListenOptions, 8018 ) net.UnixAddress.ListenError!net.Socket.Handle { 8019 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 8020 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8021 const current_thread = Thread.getCurrent(t); 8022 8023 const socket_handle = openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { 8024 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, 8025 else => |e| return e, 8026 }; 8027 errdefer closeSocketWindows(socket_handle); 8028 8029 var storage: WsaAddress = undefined; 8030 const addr_len = addressUnixToWsa(address, &storage); 8031 8032 try current_thread.beginSyscall(); 8033 while (true) { 8034 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); 8035 if (rc != ws2_32.SOCKET_ERROR) break; 8036 switch (ws2_32.WSAGetLastError()) { 8037 .EINTR => { 8038 try current_thread.checkCancel(); 8039 continue; 8040 }, 8041 .NOTINITIALISED => { 8042 try initializeWsa(t); 8043 try current_thread.checkCancel(); 8044 continue; 8045 }, 8046 else => |e| { 8047 current_thread.endSyscall(); 8048 switch (e) { 8049 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8050 .EADDRINUSE => return error.AddressInUse, 8051 .EADDRNOTAVAIL => return error.AddressUnavailable, 8052 .ENOTSOCK => |err| return wsaErrorBug(err), 8053 .EFAULT => |err| return wsaErrorBug(err), 8054 .EINVAL => |err| return wsaErrorBug(err), 8055 .ENOBUFS => return error.SystemResources, 8056 .ENETDOWN => return error.NetworkDown, 8057 else => |err| return windows.unexpectedWSAError(err), 8058 } 8059 }, 8060 } 8061 } 8062 8063 while (true) { 8064 try current_thread.checkCancel(); 8065 const rc = ws2_32.listen(socket_handle, options.kernel_backlog); 8066 if (rc != ws2_32.SOCKET_ERROR) { 8067 current_thread.endSyscall(); 8068 return socket_handle; 8069 } 8070 switch (ws2_32.WSAGetLastError()) { 8071 .EINTR => continue, 8072 .NOTINITIALISED => { 8073 try initializeWsa(t); 8074 continue; 8075 }, 8076 else => |e| { 8077 current_thread.endSyscall(); 8078 switch (e) { 8079 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8080 .ENETDOWN => return error.NetworkDown, 8081 .EADDRINUSE => return error.AddressInUse, 8082 .EISCONN => |err| return wsaErrorBug(err), 8083 .EINVAL => |err| return wsaErrorBug(err), 8084 .EMFILE, .ENOBUFS => return error.SystemResources, 8085 .ENOTSOCK => |err| return wsaErrorBug(err), 8086 .EOPNOTSUPP => |err| return wsaErrorBug(err), 8087 .EINPROGRESS => |err| return wsaErrorBug(err), 8088 else => |err| return windows.unexpectedWSAError(err), 8089 } 8090 }, 8091 } 8092 } 8093 } 8094 8095 fn netListenUnixUnavailable( 8096 userdata: ?*anyopaque, 8097 address: *const net.UnixAddress, 8098 options: net.UnixAddress.ListenOptions, 8099 ) net.UnixAddress.ListenError!net.Socket.Handle { 8100 _ = userdata; 8101 _ = address; 8102 _ = options; 8103 return error.AddressFamilyUnsupported; 8104 } 8105 8106 fn posixBindUnix( 8107 current_thread: *Thread, 8108 fd: posix.socket_t, 8109 addr: *const posix.sockaddr, 8110 addr_len: posix.socklen_t, 8111 ) !void { 8112 try current_thread.beginSyscall(); 8113 while (true) { 8114 switch (posix.errno(posix.system.bind(fd, addr, addr_len))) { 8115 .SUCCESS => { 8116 current_thread.endSyscall(); 8117 break; 8118 }, 8119 .INTR => { 8120 try current_thread.checkCancel(); 8121 continue; 8122 }, 8123 .CANCELED => return current_thread.endSyscallCanceled(), 8124 else => |e| { 8125 current_thread.endSyscall(); 8126 switch (e) { 8127 .ACCES => return error.AccessDenied, 8128 .ADDRINUSE => return error.AddressInUse, 8129 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 8130 .ADDRNOTAVAIL => return error.AddressUnavailable, 8131 .NOMEM => return error.SystemResources, 8132 8133 .LOOP => return error.SymLinkLoop, 8134 .NOENT => return error.FileNotFound, 8135 .NOTDIR => return error.NotDir, 8136 .ROFS => return error.ReadOnlyFileSystem, 8137 .PERM => return error.PermissionDenied, 8138 8139 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8140 .INVAL => |err| return errnoBug(err), // invalid parameters 8141 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` 8142 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer 8143 .NAMETOOLONG => |err| return errnoBug(err), 8144 else => |err| return posix.unexpectedErrno(err), 8145 } 8146 }, 8147 } 8148 } 8149 } 8150 8151 fn posixBind( 8152 current_thread: *Thread, 8153 socket_fd: posix.socket_t, 8154 addr: *const posix.sockaddr, 8155 addr_len: posix.socklen_t, 8156 ) !void { 8157 try current_thread.beginSyscall(); 8158 while (true) { 8159 switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) { 8160 .SUCCESS => { 8161 current_thread.endSyscall(); 8162 break; 8163 }, 8164 .INTR => { 8165 try current_thread.checkCancel(); 8166 continue; 8167 }, 8168 .CANCELED => return current_thread.endSyscallCanceled(), 8169 else => |e| { 8170 current_thread.endSyscall(); 8171 switch (e) { 8172 .ADDRINUSE => return error.AddressInUse, 8173 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8174 .INVAL => |err| return errnoBug(err), // invalid parameters 8175 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` 8176 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 8177 .ADDRNOTAVAIL => return error.AddressUnavailable, 8178 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer 8179 .NOMEM => return error.SystemResources, 8180 else => |err| return posix.unexpectedErrno(err), 8181 } 8182 }, 8183 } 8184 } 8185 } 8186 8187 fn posixConnect( 8188 current_thread: *Thread, 8189 socket_fd: posix.socket_t, 8190 addr: *const posix.sockaddr, 8191 addr_len: posix.socklen_t, 8192 ) !void { 8193 try current_thread.beginSyscall(); 8194 while (true) { 8195 switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { 8196 .SUCCESS => { 8197 current_thread.endSyscall(); 8198 return; 8199 }, 8200 .INTR => { 8201 try current_thread.checkCancel(); 8202 continue; 8203 }, 8204 .CANCELED => return current_thread.endSyscallCanceled(), 8205 else => |e| { 8206 current_thread.endSyscall(); 8207 switch (e) { 8208 .ADDRNOTAVAIL => return error.AddressUnavailable, 8209 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 8210 .AGAIN, .INPROGRESS => return error.WouldBlock, 8211 .ALREADY => return error.ConnectionPending, 8212 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8213 .CONNREFUSED => return error.ConnectionRefused, 8214 .CONNRESET => return error.ConnectionResetByPeer, 8215 .FAULT => |err| return errnoBug(err), 8216 .ISCONN => |err| return errnoBug(err), 8217 .HOSTUNREACH => return error.HostUnreachable, 8218 .NETUNREACH => return error.NetworkUnreachable, 8219 .NOTSOCK => |err| return errnoBug(err), 8220 .PROTOTYPE => |err| return errnoBug(err), 8221 .TIMEDOUT => return error.Timeout, 8222 .CONNABORTED => |err| return errnoBug(err), 8223 .ACCES => return error.AccessDenied, 8224 .PERM => |err| return errnoBug(err), 8225 .NOENT => |err| return errnoBug(err), 8226 .NETDOWN => return error.NetworkDown, 8227 else => |err| return posix.unexpectedErrno(err), 8228 } 8229 }, 8230 } 8231 } 8232 } 8233 8234 fn posixConnectUnix( 8235 current_thread: *Thread, 8236 fd: posix.socket_t, 8237 addr: *const posix.sockaddr, 8238 addr_len: posix.socklen_t, 8239 ) !void { 8240 try current_thread.beginSyscall(); 8241 while (true) { 8242 switch (posix.errno(posix.system.connect(fd, addr, addr_len))) { 8243 .SUCCESS => { 8244 current_thread.endSyscall(); 8245 return; 8246 }, 8247 .INTR => { 8248 try current_thread.checkCancel(); 8249 continue; 8250 }, 8251 .CANCELED => return current_thread.endSyscallCanceled(), 8252 else => |e| { 8253 current_thread.endSyscall(); 8254 switch (e) { 8255 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 8256 .AGAIN => return error.WouldBlock, 8257 .INPROGRESS => return error.WouldBlock, 8258 .ACCES => return error.AccessDenied, 8259 8260 .LOOP => return error.SymLinkLoop, 8261 .NOENT => return error.FileNotFound, 8262 .NOTDIR => return error.NotDir, 8263 .ROFS => return error.ReadOnlyFileSystem, 8264 .PERM => return error.PermissionDenied, 8265 8266 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8267 .CONNABORTED => |err| return errnoBug(err), 8268 .FAULT => |err| return errnoBug(err), 8269 .ISCONN => |err| return errnoBug(err), 8270 .NOTSOCK => |err| return errnoBug(err), 8271 .PROTOTYPE => |err| return errnoBug(err), 8272 else => |err| return posix.unexpectedErrno(err), 8273 } 8274 }, 8275 } 8276 } 8277 } 8278 8279 fn posixGetSockName( 8280 current_thread: *Thread, 8281 socket_fd: posix.fd_t, 8282 addr: *posix.sockaddr, 8283 addr_len: *posix.socklen_t, 8284 ) !void { 8285 try current_thread.beginSyscall(); 8286 while (true) { 8287 switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) { 8288 .SUCCESS => { 8289 current_thread.endSyscall(); 8290 break; 8291 }, 8292 .INTR => { 8293 try current_thread.checkCancel(); 8294 continue; 8295 }, 8296 .CANCELED => return current_thread.endSyscallCanceled(), 8297 else => |e| { 8298 current_thread.endSyscall(); 8299 switch (e) { 8300 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8301 .FAULT => |err| return errnoBug(err), 8302 .INVAL => |err| return errnoBug(err), // invalid parameters 8303 .NOTSOCK => |err| return errnoBug(err), // always a race condition 8304 .NOBUFS => return error.SystemResources, 8305 else => |err| return posix.unexpectedErrno(err), 8306 } 8307 }, 8308 } 8309 } 8310 } 8311 8312 fn wsaGetSockName( 8313 t: *Threaded, 8314 current_thread: *Thread, 8315 handle: ws2_32.SOCKET, 8316 addr: *ws2_32.sockaddr, 8317 addr_len: *i32, 8318 ) !void { 8319 try current_thread.beginSyscall(); 8320 while (true) { 8321 const rc = ws2_32.getsockname(handle, addr, addr_len); 8322 if (rc != ws2_32.SOCKET_ERROR) { 8323 current_thread.endSyscall(); 8324 return; 8325 } 8326 switch (ws2_32.WSAGetLastError()) { 8327 .EINTR => { 8328 try current_thread.checkCancel(); 8329 continue; 8330 }, 8331 .NOTINITIALISED => { 8332 try initializeWsa(t); 8333 try current_thread.checkCancel(); 8334 continue; 8335 }, 8336 else => |e| { 8337 current_thread.endSyscall(); 8338 switch (e) { 8339 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8340 .ENETDOWN => return error.NetworkDown, 8341 .EFAULT => |err| return wsaErrorBug(err), 8342 .ENOTSOCK => |err| return wsaErrorBug(err), 8343 .EINVAL => |err| return wsaErrorBug(err), 8344 else => |err| return windows.unexpectedWSAError(err), 8345 } 8346 }, 8347 } 8348 } 8349 } 8350 8351 fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { 8352 const o: []const u8 = @ptrCast(&option); 8353 try current_thread.beginSyscall(); 8354 while (true) { 8355 switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) { 8356 .SUCCESS => { 8357 current_thread.endSyscall(); 8358 return; 8359 }, 8360 .INTR => { 8361 try current_thread.checkCancel(); 8362 continue; 8363 }, 8364 .CANCELED => return current_thread.endSyscallCanceled(), 8365 else => |e| { 8366 current_thread.endSyscall(); 8367 switch (e) { 8368 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8369 .NOTSOCK => |err| return errnoBug(err), 8370 .INVAL => |err| return errnoBug(err), 8371 .FAULT => |err| return errnoBug(err), 8372 else => |err| return posix.unexpectedErrno(err), 8373 } 8374 }, 8375 } 8376 } 8377 } 8378 8379 fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void { 8380 const o: []const u8 = @ptrCast(&option); 8381 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len)); 8382 while (true) { 8383 if (rc != ws2_32.SOCKET_ERROR) return; 8384 switch (ws2_32.WSAGetLastError()) { 8385 .EINTR => continue, 8386 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8387 .NOTINITIALISED => { 8388 try initializeWsa(t); 8389 continue; 8390 }, 8391 .ENETDOWN => return error.NetworkDown, 8392 .EFAULT => |err| return wsaErrorBug(err), 8393 .ENOTSOCK => |err| return wsaErrorBug(err), 8394 .EINVAL => |err| return wsaErrorBug(err), 8395 else => |err| return windows.unexpectedWSAError(err), 8396 } 8397 } 8398 } 8399 8400 fn netConnectIpPosix( 8401 userdata: ?*anyopaque, 8402 address: *const IpAddress, 8403 options: IpAddress.ConnectOptions, 8404 ) IpAddress.ConnectError!net.Stream { 8405 if (!have_networking) return error.NetworkDown; 8406 if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout"); 8407 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8408 const current_thread = Thread.getCurrent(t); 8409 const family = posixAddressFamily(address); 8410 const socket_fd = try openSocketPosix(current_thread, family, .{ 8411 .mode = options.mode, 8412 .protocol = options.protocol, 8413 }); 8414 errdefer posix.close(socket_fd); 8415 var storage: PosixAddress = undefined; 8416 var addr_len = addressToPosix(address, &storage); 8417 try posixConnect(current_thread, socket_fd, &storage.any, addr_len); 8418 try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); 8419 return .{ .socket = .{ 8420 .handle = socket_fd, 8421 .address = addressFromPosix(&storage), 8422 } }; 8423 } 8424 8425 fn netConnectIpWindows( 8426 userdata: ?*anyopaque, 8427 address: *const IpAddress, 8428 options: IpAddress.ConnectOptions, 8429 ) IpAddress.ConnectError!net.Stream { 8430 if (!have_networking) return error.NetworkDown; 8431 if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout"); 8432 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8433 const current_thread = Thread.getCurrent(t); 8434 const family = posixAddressFamily(address); 8435 const socket_handle = try openSocketWsa(t, current_thread, family, .{ 8436 .mode = options.mode, 8437 .protocol = options.protocol, 8438 }); 8439 errdefer closeSocketWindows(socket_handle); 8440 8441 var storage: WsaAddress = undefined; 8442 var addr_len = addressToWsa(address, &storage); 8443 8444 try current_thread.beginSyscall(); 8445 while (true) { 8446 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); 8447 if (rc != ws2_32.SOCKET_ERROR) { 8448 current_thread.endSyscall(); 8449 break; 8450 } 8451 switch (ws2_32.WSAGetLastError()) { 8452 .EINTR => { 8453 try current_thread.checkCancel(); 8454 continue; 8455 }, 8456 .NOTINITIALISED => { 8457 try initializeWsa(t); 8458 try current_thread.checkCancel(); 8459 continue; 8460 }, 8461 else => |e| { 8462 current_thread.endSyscall(); 8463 switch (e) { 8464 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8465 .EADDRNOTAVAIL => return error.AddressUnavailable, 8466 .ECONNREFUSED => return error.ConnectionRefused, 8467 .ECONNRESET => return error.ConnectionResetByPeer, 8468 .ETIMEDOUT => return error.Timeout, 8469 .EHOSTUNREACH => return error.HostUnreachable, 8470 .ENETUNREACH => return error.NetworkUnreachable, 8471 .EFAULT => |err| return wsaErrorBug(err), 8472 .EINVAL => |err| return wsaErrorBug(err), 8473 .EISCONN => |err| return wsaErrorBug(err), 8474 .ENOTSOCK => |err| return wsaErrorBug(err), 8475 .EWOULDBLOCK => return error.WouldBlock, 8476 .EACCES => return error.AccessDenied, 8477 .ENOBUFS => return error.SystemResources, 8478 .EAFNOSUPPORT => return error.AddressFamilyUnsupported, 8479 else => |err| return windows.unexpectedWSAError(err), 8480 } 8481 }, 8482 } 8483 } 8484 8485 try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); 8486 8487 return .{ .socket = .{ 8488 .handle = socket_handle, 8489 .address = addressFromWsa(&storage), 8490 } }; 8491 } 8492 8493 fn netConnectIpUnavailable( 8494 userdata: ?*anyopaque, 8495 address: *const IpAddress, 8496 options: IpAddress.ConnectOptions, 8497 ) IpAddress.ConnectError!net.Stream { 8498 _ = userdata; 8499 _ = address; 8500 _ = options; 8501 return error.NetworkDown; 8502 } 8503 8504 fn netConnectUnixPosix( 8505 userdata: ?*anyopaque, 8506 address: *const net.UnixAddress, 8507 ) net.UnixAddress.ConnectError!net.Socket.Handle { 8508 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 8509 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8510 const current_thread = Thread.getCurrent(t); 8511 const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { 8512 error.OptionUnsupported => return error.Unexpected, 8513 else => |e| return e, 8514 }; 8515 errdefer posix.close(socket_fd); 8516 var storage: UnixAddress = undefined; 8517 const addr_len = addressUnixToPosix(address, &storage); 8518 try posixConnectUnix(current_thread, socket_fd, &storage.any, addr_len); 8519 return socket_fd; 8520 } 8521 8522 fn netConnectUnixWindows( 8523 userdata: ?*anyopaque, 8524 address: *const net.UnixAddress, 8525 ) net.UnixAddress.ConnectError!net.Socket.Handle { 8526 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 8527 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8528 const current_thread = Thread.getCurrent(t); 8529 8530 const socket_handle = try openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream }); 8531 errdefer closeSocketWindows(socket_handle); 8532 var storage: WsaAddress = undefined; 8533 const addr_len = addressUnixToWsa(address, &storage); 8534 8535 while (true) { 8536 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); 8537 if (rc != ws2_32.SOCKET_ERROR) break; 8538 switch (ws2_32.WSAGetLastError()) { 8539 .EINTR => continue, 8540 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8541 .NOTINITIALISED => { 8542 try initializeWsa(t); 8543 continue; 8544 }, 8545 8546 .ECONNREFUSED => return error.FileNotFound, 8547 .EFAULT => |err| return wsaErrorBug(err), 8548 .EINVAL => |err| return wsaErrorBug(err), 8549 .EISCONN => |err| return wsaErrorBug(err), 8550 .ENOTSOCK => |err| return wsaErrorBug(err), 8551 .EWOULDBLOCK => return error.WouldBlock, 8552 .EACCES => return error.AccessDenied, 8553 .ENOBUFS => return error.SystemResources, 8554 .EAFNOSUPPORT => return error.AddressFamilyUnsupported, 8555 else => |err| return windows.unexpectedWSAError(err), 8556 } 8557 } 8558 8559 return socket_handle; 8560 } 8561 8562 fn netConnectUnixUnavailable( 8563 userdata: ?*anyopaque, 8564 address: *const net.UnixAddress, 8565 ) net.UnixAddress.ConnectError!net.Socket.Handle { 8566 _ = userdata; 8567 _ = address; 8568 return error.AddressFamilyUnsupported; 8569 } 8570 8571 fn netBindIpPosix( 8572 userdata: ?*anyopaque, 8573 address: *const IpAddress, 8574 options: IpAddress.BindOptions, 8575 ) IpAddress.BindError!net.Socket { 8576 if (!have_networking) return error.NetworkDown; 8577 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8578 const current_thread = Thread.getCurrent(t); 8579 const family = posixAddressFamily(address); 8580 const socket_fd = try openSocketPosix(current_thread, family, options); 8581 errdefer posix.close(socket_fd); 8582 var storage: PosixAddress = undefined; 8583 var addr_len = addressToPosix(address, &storage); 8584 try posixBind(current_thread, socket_fd, &storage.any, addr_len); 8585 try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); 8586 return .{ 8587 .handle = socket_fd, 8588 .address = addressFromPosix(&storage), 8589 }; 8590 } 8591 8592 fn netBindIpWindows( 8593 userdata: ?*anyopaque, 8594 address: *const IpAddress, 8595 options: IpAddress.BindOptions, 8596 ) IpAddress.BindError!net.Socket { 8597 if (!have_networking) return error.NetworkDown; 8598 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8599 const current_thread = Thread.getCurrent(t); 8600 const family = posixAddressFamily(address); 8601 const socket_handle = try openSocketWsa(t, current_thread, family, .{ 8602 .mode = options.mode, 8603 .protocol = options.protocol, 8604 }); 8605 errdefer closeSocketWindows(socket_handle); 8606 8607 var storage: WsaAddress = undefined; 8608 var addr_len = addressToWsa(address, &storage); 8609 8610 try current_thread.beginSyscall(); 8611 while (true) { 8612 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); 8613 if (rc != ws2_32.SOCKET_ERROR) { 8614 current_thread.endSyscall(); 8615 break; 8616 } 8617 switch (ws2_32.WSAGetLastError()) { 8618 .EINTR => { 8619 try current_thread.checkCancel(); 8620 continue; 8621 }, 8622 .NOTINITIALISED => { 8623 try initializeWsa(t); 8624 try current_thread.checkCancel(); 8625 continue; 8626 }, 8627 else => |e| { 8628 current_thread.endSyscall(); 8629 switch (e) { 8630 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8631 .EADDRINUSE => return error.AddressInUse, 8632 .EADDRNOTAVAIL => return error.AddressUnavailable, 8633 .ENOTSOCK => |err| return wsaErrorBug(err), 8634 .EFAULT => |err| return wsaErrorBug(err), 8635 .EINVAL => |err| return wsaErrorBug(err), 8636 .ENOBUFS => return error.SystemResources, 8637 .ENETDOWN => return error.NetworkDown, 8638 else => |err| return windows.unexpectedWSAError(err), 8639 } 8640 }, 8641 } 8642 } 8643 8644 try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); 8645 8646 return .{ 8647 .handle = socket_handle, 8648 .address = addressFromWsa(&storage), 8649 }; 8650 } 8651 8652 fn netBindIpUnavailable( 8653 userdata: ?*anyopaque, 8654 address: *const IpAddress, 8655 options: IpAddress.BindOptions, 8656 ) IpAddress.BindError!net.Socket { 8657 _ = userdata; 8658 _ = address; 8659 _ = options; 8660 return error.NetworkDown; 8661 } 8662 8663 fn openSocketPosix( 8664 current_thread: *Thread, 8665 family: posix.sa_family_t, 8666 options: IpAddress.BindOptions, 8667 ) error{ 8668 AddressFamilyUnsupported, 8669 ProtocolUnsupportedBySystem, 8670 ProcessFdQuotaExceeded, 8671 SystemFdQuotaExceeded, 8672 SystemResources, 8673 ProtocolUnsupportedByAddressFamily, 8674 SocketModeUnsupported, 8675 OptionUnsupported, 8676 Unexpected, 8677 Canceled, 8678 }!posix.socket_t { 8679 const mode = posixSocketMode(options.mode); 8680 const protocol = posixProtocol(options.protocol); 8681 try current_thread.beginSyscall(); 8682 const socket_fd = while (true) { 8683 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; 8684 const socket_rc = posix.system.socket(family, flags, protocol); 8685 switch (posix.errno(socket_rc)) { 8686 .SUCCESS => { 8687 const fd: posix.fd_t = @intCast(socket_rc); 8688 errdefer posix.close(fd); 8689 if (socket_flags_unsupported) while (true) { 8690 try current_thread.checkCancel(); 8691 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { 8692 .SUCCESS => break, 8693 .INTR => continue, 8694 .CANCELED => return current_thread.endSyscallCanceled(), 8695 else => |err| { 8696 current_thread.endSyscall(); 8697 return posix.unexpectedErrno(err); 8698 }, 8699 } 8700 }; 8701 current_thread.endSyscall(); 8702 break fd; 8703 }, 8704 .INTR => { 8705 try current_thread.checkCancel(); 8706 continue; 8707 }, 8708 .CANCELED => return current_thread.endSyscallCanceled(), 8709 else => |e| { 8710 current_thread.endSyscall(); 8711 switch (e) { 8712 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 8713 .INVAL => return error.ProtocolUnsupportedBySystem, 8714 .MFILE => return error.ProcessFdQuotaExceeded, 8715 .NFILE => return error.SystemFdQuotaExceeded, 8716 .NOBUFS => return error.SystemResources, 8717 .NOMEM => return error.SystemResources, 8718 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, 8719 .PROTOTYPE => return error.SocketModeUnsupported, 8720 else => |err| return posix.unexpectedErrno(err), 8721 } 8722 }, 8723 } 8724 }; 8725 errdefer posix.close(socket_fd); 8726 8727 if (options.ip6_only) { 8728 if (posix.IPV6 == void) return error.OptionUnsupported; 8729 try setSocketOption(current_thread, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0); 8730 } 8731 8732 return socket_fd; 8733 } 8734 8735 fn openSocketWsa( 8736 t: *Threaded, 8737 current_thread: *Thread, 8738 family: posix.sa_family_t, 8739 options: IpAddress.BindOptions, 8740 ) !ws2_32.SOCKET { 8741 const mode = posixSocketMode(options.mode); 8742 const protocol = posixProtocol(options.protocol); 8743 const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; 8744 try current_thread.beginSyscall(); 8745 while (true) { 8746 const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); 8747 if (rc != ws2_32.INVALID_SOCKET) { 8748 current_thread.endSyscall(); 8749 return rc; 8750 } 8751 switch (ws2_32.WSAGetLastError()) { 8752 .EINTR => { 8753 try current_thread.checkCancel(); 8754 continue; 8755 }, 8756 .NOTINITIALISED => { 8757 try initializeWsa(t); 8758 try current_thread.checkCancel(); 8759 continue; 8760 }, 8761 else => |e| { 8762 current_thread.endSyscall(); 8763 switch (e) { 8764 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8765 .EAFNOSUPPORT => return error.AddressFamilyUnsupported, 8766 .EMFILE => return error.ProcessFdQuotaExceeded, 8767 .ENOBUFS => return error.SystemResources, 8768 .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, 8769 else => |err| return windows.unexpectedWSAError(err), 8770 } 8771 }, 8772 } 8773 } 8774 } 8775 8776 fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream { 8777 if (!have_networking) return error.NetworkDown; 8778 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8779 const current_thread = Thread.getCurrent(t); 8780 var storage: PosixAddress = undefined; 8781 var addr_len: posix.socklen_t = @sizeOf(PosixAddress); 8782 try current_thread.beginSyscall(); 8783 const fd = while (true) { 8784 const rc = if (have_accept4) 8785 posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC) 8786 else 8787 posix.system.accept(listen_fd, &storage.any, &addr_len); 8788 switch (posix.errno(rc)) { 8789 .SUCCESS => { 8790 const fd: posix.fd_t = @intCast(rc); 8791 errdefer posix.close(fd); 8792 if (!have_accept4) while (true) { 8793 try current_thread.checkCancel(); 8794 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { 8795 .SUCCESS => break, 8796 .INTR => continue, 8797 else => |err| { 8798 current_thread.endSyscall(); 8799 return posix.unexpectedErrno(err); 8800 }, 8801 } 8802 }; 8803 current_thread.endSyscall(); 8804 break fd; 8805 }, 8806 .INTR => { 8807 try current_thread.checkCancel(); 8808 continue; 8809 }, 8810 .CANCELED => return current_thread.endSyscallCanceled(), 8811 else => |e| { 8812 current_thread.endSyscall(); 8813 switch (e) { 8814 .AGAIN => |err| return errnoBug(err), 8815 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8816 .CONNABORTED => return error.ConnectionAborted, 8817 .FAULT => |err| return errnoBug(err), 8818 .INVAL => return error.SocketNotListening, 8819 .NOTSOCK => |err| return errnoBug(err), 8820 .MFILE => return error.ProcessFdQuotaExceeded, 8821 .NFILE => return error.SystemFdQuotaExceeded, 8822 .NOBUFS => return error.SystemResources, 8823 .NOMEM => return error.SystemResources, 8824 .OPNOTSUPP => |err| return errnoBug(err), 8825 .PROTO => return error.ProtocolFailure, 8826 .PERM => return error.BlockedByFirewall, 8827 else => |err| return posix.unexpectedErrno(err), 8828 } 8829 }, 8830 } 8831 }; 8832 return .{ .socket = .{ 8833 .handle = fd, 8834 .address = addressFromPosix(&storage), 8835 } }; 8836 } 8837 8838 fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream { 8839 if (!have_networking) return error.NetworkDown; 8840 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8841 const current_thread = Thread.getCurrent(t); 8842 var storage: WsaAddress = undefined; 8843 var addr_len: i32 = @sizeOf(WsaAddress); 8844 try current_thread.beginSyscall(); 8845 while (true) { 8846 const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len); 8847 if (rc != ws2_32.INVALID_SOCKET) { 8848 current_thread.endSyscall(); 8849 return .{ .socket = .{ 8850 .handle = rc, 8851 .address = addressFromWsa(&storage), 8852 } }; 8853 } 8854 switch (ws2_32.WSAGetLastError()) { 8855 .EINTR => { 8856 try current_thread.checkCancel(); 8857 continue; 8858 }, 8859 .NOTINITIALISED => { 8860 try initializeWsa(t); 8861 try current_thread.checkCancel(); 8862 continue; 8863 }, 8864 else => |e| { 8865 current_thread.endSyscall(); 8866 switch (e) { 8867 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 8868 .ECONNRESET => return error.ConnectionAborted, 8869 .EFAULT => |err| return wsaErrorBug(err), 8870 .ENOTSOCK => |err| return wsaErrorBug(err), 8871 .EINVAL => |err| return wsaErrorBug(err), 8872 .EMFILE => return error.ProcessFdQuotaExceeded, 8873 .ENETDOWN => return error.NetworkDown, 8874 .ENOBUFS => return error.SystemResources, 8875 .EOPNOTSUPP => |err| return wsaErrorBug(err), 8876 else => |err| return windows.unexpectedWSAError(err), 8877 } 8878 }, 8879 } 8880 } 8881 } 8882 8883 fn netAcceptUnavailable(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream { 8884 _ = userdata; 8885 _ = listen_handle; 8886 return error.NetworkDown; 8887 } 8888 8889 fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { 8890 if (!have_networking) return error.NetworkDown; 8891 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8892 const current_thread = Thread.getCurrent(t); 8893 8894 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; 8895 var i: usize = 0; 8896 for (data) |buf| { 8897 if (iovecs_buffer.len - i == 0) break; 8898 if (buf.len != 0) { 8899 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; 8900 i += 1; 8901 } 8902 } 8903 const dest = iovecs_buffer[0..i]; 8904 assert(dest[0].len > 0); 8905 8906 if (native_os == .wasi and !builtin.link_libc) { 8907 try current_thread.beginSyscall(); 8908 while (true) { 8909 var n: usize = undefined; 8910 switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) { 8911 .SUCCESS => { 8912 current_thread.endSyscall(); 8913 return n; 8914 }, 8915 .INTR => { 8916 try current_thread.checkCancel(); 8917 continue; 8918 }, 8919 .CANCELED => return current_thread.endSyscallCanceled(), 8920 else => |e| { 8921 current_thread.endSyscall(); 8922 switch (e) { 8923 .INVAL => |err| return errnoBug(err), 8924 .FAULT => |err| return errnoBug(err), 8925 .AGAIN => |err| return errnoBug(err), 8926 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8927 .NOBUFS => return error.SystemResources, 8928 .NOMEM => return error.SystemResources, 8929 .NOTCONN => return error.SocketUnconnected, 8930 .CONNRESET => return error.ConnectionResetByPeer, 8931 .TIMEDOUT => return error.Timeout, 8932 .NOTCAPABLE => return error.AccessDenied, 8933 else => |err| return posix.unexpectedErrno(err), 8934 } 8935 }, 8936 } 8937 } 8938 } 8939 8940 try current_thread.beginSyscall(); 8941 while (true) { 8942 const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len)); 8943 switch (posix.errno(rc)) { 8944 .SUCCESS => { 8945 current_thread.endSyscall(); 8946 return @intCast(rc); 8947 }, 8948 .INTR => { 8949 try current_thread.checkCancel(); 8950 continue; 8951 }, 8952 .CANCELED => return current_thread.endSyscallCanceled(), 8953 else => |e| { 8954 current_thread.endSyscall(); 8955 switch (e) { 8956 .INVAL => |err| return errnoBug(err), 8957 .FAULT => |err| return errnoBug(err), 8958 .AGAIN => |err| return errnoBug(err), 8959 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 8960 .NOBUFS => return error.SystemResources, 8961 .NOMEM => return error.SystemResources, 8962 .NOTCONN => return error.SocketUnconnected, 8963 .CONNRESET => return error.ConnectionResetByPeer, 8964 .TIMEDOUT => return error.Timeout, 8965 .PIPE => return error.SocketUnconnected, 8966 .NETDOWN => return error.NetworkDown, 8967 else => |err| return posix.unexpectedErrno(err), 8968 } 8969 }, 8970 } 8971 } 8972 } 8973 8974 fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { 8975 if (!have_networking) return error.NetworkDown; 8976 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8977 const current_thread = Thread.getCurrent(t); 8978 8979 const bufs = b: { 8980 var iovec_buffer: [max_iovecs_len]ws2_32.WSABUF = undefined; 8981 var i: usize = 0; 8982 var n: usize = 0; 8983 for (data) |buf| { 8984 if (iovec_buffer.len - i == 0) break; 8985 if (buf.len == 0) continue; 8986 if (std.math.cast(u32, buf.len)) |len| { 8987 iovec_buffer[i] = .{ .buf = buf.ptr, .len = len }; 8988 i += 1; 8989 n += len; 8990 continue; 8991 } 8992 iovec_buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) }; 8993 i += 1; 8994 n += std.math.maxInt(u32); 8995 break; 8996 } 8997 8998 const bufs = iovec_buffer[0..i]; 8999 assert(bufs[0].len != 0); 9000 9001 break :b bufs; 9002 }; 9003 9004 while (true) { 9005 try current_thread.checkCancel(); 9006 9007 var flags: u32 = 0; 9008 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); 9009 var n: u32 = undefined; 9010 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, &overlapped, null); 9011 if (rc != ws2_32.SOCKET_ERROR) return n; 9012 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) { 9013 .IO_PENDING => e: { 9014 var result_flags: u32 = undefined; 9015 const overlapped_rc = ws2_32.WSAGetOverlappedResult( 9016 handle, 9017 &overlapped, 9018 &n, 9019 windows.TRUE, 9020 &result_flags, 9021 ); 9022 if (overlapped_rc == windows.FALSE) { 9023 break :e ws2_32.WSAGetLastError(); 9024 } else { 9025 return n; 9026 } 9027 }, 9028 else => |err| err, 9029 }; 9030 switch (wsa_error) { 9031 .EINTR => continue, 9032 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 9033 .NOTINITIALISED => { 9034 try initializeWsa(t); 9035 continue; 9036 }, 9037 9038 .ECONNRESET => return error.ConnectionResetByPeer, 9039 .EFAULT => unreachable, // a pointer is not completely contained in user address space. 9040 .EINVAL => |err| return wsaErrorBug(err), 9041 .EMSGSIZE => |err| return wsaErrorBug(err), 9042 .ENETDOWN => return error.NetworkDown, 9043 .ENETRESET => return error.ConnectionResetByPeer, 9044 .ENOTCONN => return error.SocketUnconnected, 9045 else => |err| return windows.unexpectedWSAError(err), 9046 } 9047 } 9048 } 9049 9050 fn netReadUnavailable(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { 9051 _ = userdata; 9052 _ = fd; 9053 _ = data; 9054 return error.NetworkDown; 9055 } 9056 9057 fn netSendPosix( 9058 userdata: ?*anyopaque, 9059 handle: net.Socket.Handle, 9060 messages: []net.OutgoingMessage, 9061 flags: net.SendFlags, 9062 ) struct { ?net.Socket.SendError, usize } { 9063 if (!have_networking) return .{ error.NetworkDown, 0 }; 9064 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9065 const current_thread = Thread.getCurrent(t); 9066 9067 const posix_flags: u32 = 9068 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) | 9069 @as(u32, if (@hasDecl(posix.MSG, "DONTROUTE") and flags.dont_route) posix.MSG.DONTROUTE else 0) | 9070 @as(u32, if (@hasDecl(posix.MSG, "EOR") and flags.eor) posix.MSG.EOR else 0) | 9071 @as(u32, if (@hasDecl(posix.MSG, "OOB") and flags.oob) posix.MSG.OOB else 0) | 9072 @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) | 9073 posix.MSG.NOSIGNAL; 9074 9075 var i: usize = 0; 9076 while (messages.len - i != 0) { 9077 if (have_sendmmsg) { 9078 i += netSendMany(current_thread, handle, messages[i..], posix_flags) catch |err| return .{ err, i }; 9079 continue; 9080 } 9081 netSendOne(t, current_thread, handle, &messages[i], posix_flags) catch |err| return .{ err, i }; 9082 i += 1; 9083 } 9084 return .{ null, i }; 9085 } 9086 9087 fn netSendWindows( 9088 userdata: ?*anyopaque, 9089 handle: net.Socket.Handle, 9090 messages: []net.OutgoingMessage, 9091 flags: net.SendFlags, 9092 ) struct { ?net.Socket.SendError, usize } { 9093 if (!have_networking) return .{ error.NetworkDown, 0 }; 9094 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9095 _ = t; 9096 _ = handle; 9097 _ = messages; 9098 _ = flags; 9099 @panic("TODO netSendWindows"); 9100 } 9101 9102 fn netSendUnavailable( 9103 userdata: ?*anyopaque, 9104 handle: net.Socket.Handle, 9105 messages: []net.OutgoingMessage, 9106 flags: net.SendFlags, 9107 ) struct { ?net.Socket.SendError, usize } { 9108 _ = userdata; 9109 _ = handle; 9110 _ = messages; 9111 _ = flags; 9112 return .{ error.NetworkDown, 0 }; 9113 } 9114 9115 fn netSendOne( 9116 t: *Threaded, 9117 current_thread: *Thread, 9118 handle: net.Socket.Handle, 9119 message: *net.OutgoingMessage, 9120 flags: u32, 9121 ) net.Socket.SendError!void { 9122 var addr: PosixAddress = undefined; 9123 var iovec: posix.iovec_const = .{ .base = @constCast(message.data_ptr), .len = message.data_len }; 9124 const msg: posix.msghdr_const = .{ 9125 .name = &addr.any, 9126 .namelen = addressToPosix(message.address, &addr), 9127 .iov = (&iovec)[0..1], 9128 .iovlen = 1, 9129 // OS returns EINVAL if this pointer is invalid even if controllen is zero. 9130 .control = if (message.control.len == 0) null else @constCast(message.control.ptr), 9131 .controllen = @intCast(message.control.len), 9132 .flags = 0, 9133 }; 9134 try current_thread.beginSyscall(); 9135 while (true) { 9136 const rc = posix.system.sendmsg(handle, &msg, flags); 9137 if (is_windows) { 9138 if (rc != ws2_32.SOCKET_ERROR) { 9139 current_thread.endSyscall(); 9140 message.data_len = @intCast(rc); 9141 return; 9142 } 9143 switch (ws2_32.WSAGetLastError()) { 9144 .EINTR => { 9145 try current_thread.checkCancel(); 9146 continue; 9147 }, 9148 .NOTINITIALISED => { 9149 try initializeWsa(t); 9150 try current_thread.checkCancel(); 9151 continue; 9152 }, 9153 else => |e| { 9154 current_thread.endSyscall(); 9155 switch (e) { 9156 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 9157 .EACCES => return error.AccessDenied, 9158 .EADDRNOTAVAIL => return error.AddressUnavailable, 9159 .ECONNRESET => return error.ConnectionResetByPeer, 9160 .EMSGSIZE => return error.MessageOversize, 9161 .ENOBUFS => return error.SystemResources, 9162 .ENOTSOCK => return error.FileDescriptorNotASocket, 9163 .EAFNOSUPPORT => return error.AddressFamilyUnsupported, 9164 .EDESTADDRREQ => unreachable, // A destination address is required. 9165 .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. 9166 .EHOSTUNREACH => return error.NetworkUnreachable, 9167 .EINVAL => unreachable, 9168 .ENETDOWN => return error.NetworkDown, 9169 .ENETRESET => return error.ConnectionResetByPeer, 9170 .ENETUNREACH => return error.NetworkUnreachable, 9171 .ENOTCONN => return error.SocketUnconnected, 9172 .ESHUTDOWN => |err| return wsaErrorBug(err), 9173 else => |err| return windows.unexpectedWSAError(err), 9174 } 9175 }, 9176 } 9177 } 9178 switch (posix.errno(rc)) { 9179 .SUCCESS => { 9180 current_thread.endSyscall(); 9181 message.data_len = @intCast(rc); 9182 return; 9183 }, 9184 .INTR => { 9185 try current_thread.checkCancel(); 9186 continue; 9187 }, 9188 .CANCELED => return current_thread.endSyscallCanceled(), 9189 else => |e| { 9190 current_thread.endSyscall(); 9191 switch (e) { 9192 .ACCES => return error.AccessDenied, 9193 .ALREADY => return error.FastOpenAlreadyInProgress, 9194 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 9195 .CONNRESET => return error.ConnectionResetByPeer, 9196 .DESTADDRREQ => |err| return errnoBug(err), 9197 .FAULT => |err| return errnoBug(err), 9198 .INVAL => |err| return errnoBug(err), 9199 .ISCONN => |err| return errnoBug(err), 9200 .MSGSIZE => return error.MessageOversize, 9201 .NOBUFS => return error.SystemResources, 9202 .NOMEM => return error.SystemResources, 9203 .NOTSOCK => |err| return errnoBug(err), 9204 .OPNOTSUPP => |err| return errnoBug(err), 9205 .PIPE => return error.SocketUnconnected, 9206 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 9207 .HOSTUNREACH => return error.HostUnreachable, 9208 .NETUNREACH => return error.NetworkUnreachable, 9209 .NOTCONN => return error.SocketUnconnected, 9210 .NETDOWN => return error.NetworkDown, 9211 else => |err| return posix.unexpectedErrno(err), 9212 } 9213 }, 9214 } 9215 } 9216 } 9217 9218 fn netSendMany( 9219 current_thread: *Thread, 9220 handle: net.Socket.Handle, 9221 messages: []net.OutgoingMessage, 9222 flags: u32, 9223 ) net.Socket.SendError!usize { 9224 var msg_buffer: [64]posix.system.mmsghdr = undefined; 9225 var addr_buffer: [msg_buffer.len]PosixAddress = undefined; 9226 var iovecs_buffer: [msg_buffer.len]posix.iovec = undefined; 9227 const min_len: usize = @min(messages.len, msg_buffer.len); 9228 const clamped_messages = messages[0..min_len]; 9229 const clamped_msgs = (&msg_buffer)[0..min_len]; 9230 const clamped_addrs = (&addr_buffer)[0..min_len]; 9231 const clamped_iovecs = (&iovecs_buffer)[0..min_len]; 9232 9233 for (clamped_messages, clamped_msgs, clamped_addrs, clamped_iovecs) |*message, *msg, *addr, *iovec| { 9234 iovec.* = .{ .base = @constCast(message.data_ptr), .len = message.data_len }; 9235 msg.* = .{ 9236 .hdr = .{ 9237 .name = &addr.any, 9238 .namelen = addressToPosix(message.address, addr), 9239 .iov = iovec[0..1], 9240 .iovlen = 1, 9241 .control = @constCast(message.control.ptr), 9242 .controllen = message.control.len, 9243 .flags = 0, 9244 }, 9245 .len = undefined, // Populated by calling sendmmsg below. 9246 }; 9247 } 9248 9249 try current_thread.beginSyscall(); 9250 while (true) { 9251 const rc = posix.system.sendmmsg(handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags); 9252 switch (posix.errno(rc)) { 9253 .SUCCESS => { 9254 current_thread.endSyscall(); 9255 const n: usize = @intCast(rc); 9256 for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| { 9257 message.data_len = msg.len; 9258 } 9259 return n; 9260 }, 9261 .INTR => { 9262 try current_thread.checkCancel(); 9263 continue; 9264 }, 9265 .CANCELED => return current_thread.endSyscallCanceled(), 9266 else => |e| { 9267 current_thread.endSyscall(); 9268 switch (e) { 9269 .AGAIN => |err| return errnoBug(err), 9270 .ALREADY => return error.FastOpenAlreadyInProgress, 9271 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 9272 .CONNRESET => return error.ConnectionResetByPeer, 9273 .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. 9274 .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. 9275 .INVAL => |err| return errnoBug(err), // Invalid argument passed. 9276 .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified 9277 .MSGSIZE => return error.MessageOversize, 9278 .NOBUFS => return error.SystemResources, 9279 .NOMEM => return error.SystemResources, 9280 .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. 9281 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. 9282 .PIPE => return error.SocketUnconnected, 9283 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 9284 .HOSTUNREACH => return error.HostUnreachable, 9285 .NETUNREACH => return error.NetworkUnreachable, 9286 .NOTCONN => return error.SocketUnconnected, 9287 .NETDOWN => return error.NetworkDown, 9288 else => |err| return posix.unexpectedErrno(err), 9289 } 9290 }, 9291 } 9292 } 9293 } 9294 9295 fn netReceivePosix( 9296 userdata: ?*anyopaque, 9297 handle: net.Socket.Handle, 9298 message_buffer: []net.IncomingMessage, 9299 data_buffer: []u8, 9300 flags: net.ReceiveFlags, 9301 timeout: Io.Timeout, 9302 ) struct { ?net.Socket.ReceiveTimeoutError, usize } { 9303 if (!have_networking) return .{ error.NetworkDown, 0 }; 9304 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9305 const current_thread = Thread.getCurrent(t); 9306 const t_io = io(t); 9307 9308 // recvmmsg is useless, here's why: 9309 // * [timeout bug](https://bugzilla.kernel.org/show_bug.cgi?id=75371) 9310 // * it wants iovecs for each message but we have a better API: one data 9311 // buffer to handle all the messages. The better API cannot be lowered to 9312 // the split vectors though because reducing the buffer size might make 9313 // some messages unreceivable. 9314 9315 // So the strategy instead is to use non-blocking recvmsg calls, calling 9316 // poll() with timeout if the first one returns EAGAIN. 9317 const posix_flags: u32 = 9318 @as(u32, if (flags.oob) posix.MSG.OOB else 0) | 9319 @as(u32, if (flags.peek) posix.MSG.PEEK else 0) | 9320 @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) | 9321 posix.MSG.DONTWAIT | posix.MSG.NOSIGNAL; 9322 9323 var poll_fds: [1]posix.pollfd = .{ 9324 .{ 9325 .fd = handle, 9326 .events = posix.POLL.IN, 9327 .revents = undefined, 9328 }, 9329 }; 9330 var message_i: usize = 0; 9331 var data_i: usize = 0; 9332 9333 const deadline = timeout.toDeadline(t_io) catch |err| return .{ err, message_i }; 9334 9335 recv: while (true) { 9336 if (message_buffer.len - message_i == 0) return .{ null, message_i }; 9337 const message = &message_buffer[message_i]; 9338 const remaining_data_buffer = data_buffer[data_i..]; 9339 var storage: PosixAddress = undefined; 9340 var iov: posix.iovec = .{ .base = remaining_data_buffer.ptr, .len = remaining_data_buffer.len }; 9341 var msg: posix.msghdr = .{ 9342 .name = &storage.any, 9343 .namelen = @sizeOf(PosixAddress), 9344 .iov = (&iov)[0..1], 9345 .iovlen = 1, 9346 .control = message.control.ptr, 9347 .controllen = @intCast(message.control.len), 9348 .flags = undefined, 9349 }; 9350 9351 current_thread.beginSyscall() catch |err| return .{ err, message_i }; 9352 const recv_rc = posix.system.recvmsg(handle, &msg, posix_flags); 9353 current_thread.endSyscall(); 9354 switch (posix.errno(recv_rc)) { 9355 .SUCCESS => { 9356 const data = remaining_data_buffer[0..@intCast(recv_rc)]; 9357 data_i += data.len; 9358 message.* = .{ 9359 .from = addressFromPosix(&storage), 9360 .data = data, 9361 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control, 9362 .flags = .{ 9363 .eor = (msg.flags & posix.MSG.EOR) != 0, 9364 .trunc = (msg.flags & posix.MSG.TRUNC) != 0, 9365 .ctrunc = (msg.flags & posix.MSG.CTRUNC) != 0, 9366 .oob = (msg.flags & posix.MSG.OOB) != 0, 9367 .errqueue = if (@hasDecl(posix.MSG, "ERRQUEUE")) (msg.flags & posix.MSG.ERRQUEUE) != 0 else false, 9368 }, 9369 }; 9370 message_i += 1; 9371 continue; 9372 }, 9373 .AGAIN => while (true) { 9374 if (message_i != 0) return .{ null, message_i }; 9375 9376 const max_poll_ms = std.math.maxInt(u31); 9377 const timeout_ms: u31 = if (deadline) |d| t: { 9378 const duration = d.durationFromNow(t_io) catch |err| return .{ err, message_i }; 9379 if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i }; 9380 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); 9381 } else max_poll_ms; 9382 9383 current_thread.beginSyscall() catch |err| return .{ err, message_i }; 9384 const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms); 9385 current_thread.endSyscall(); 9386 9387 switch (posix.errno(poll_rc)) { 9388 .SUCCESS => { 9389 if (poll_rc == 0) { 9390 // Although spurious timeouts are OK, when no deadline 9391 // is passed we must not return `error.Timeout`. 9392 if (deadline == null) continue; 9393 return .{ error.Timeout, message_i }; 9394 } 9395 continue :recv; 9396 }, 9397 .INTR => continue, 9398 .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, 9399 9400 .FAULT => |err| return .{ errnoBug(err), message_i }, 9401 .INVAL => |err| return .{ errnoBug(err), message_i }, 9402 .NOMEM => return .{ error.SystemResources, message_i }, 9403 else => |err| return .{ posix.unexpectedErrno(err), message_i }, 9404 } 9405 }, 9406 .INTR => continue, 9407 .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, 9408 9409 .BADF => |err| return .{ errnoBug(err), message_i }, 9410 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, 9411 .MFILE => return .{ error.ProcessFdQuotaExceeded, message_i }, 9412 .FAULT => |err| return .{ errnoBug(err), message_i }, 9413 .INVAL => |err| return .{ errnoBug(err), message_i }, 9414 .NOBUFS => return .{ error.SystemResources, message_i }, 9415 .NOMEM => return .{ error.SystemResources, message_i }, 9416 .NOTCONN => return .{ error.SocketUnconnected, message_i }, 9417 .NOTSOCK => |err| return .{ errnoBug(err), message_i }, 9418 .MSGSIZE => return .{ error.MessageOversize, message_i }, 9419 .PIPE => return .{ error.SocketUnconnected, message_i }, 9420 .OPNOTSUPP => |err| return .{ errnoBug(err), message_i }, 9421 .CONNRESET => return .{ error.ConnectionResetByPeer, message_i }, 9422 .NETDOWN => return .{ error.NetworkDown, message_i }, 9423 else => |err| return .{ posix.unexpectedErrno(err), message_i }, 9424 } 9425 } 9426 } 9427 9428 fn netReceiveWindows( 9429 userdata: ?*anyopaque, 9430 handle: net.Socket.Handle, 9431 message_buffer: []net.IncomingMessage, 9432 data_buffer: []u8, 9433 flags: net.ReceiveFlags, 9434 timeout: Io.Timeout, 9435 ) struct { ?net.Socket.ReceiveTimeoutError, usize } { 9436 if (!have_networking) return .{ error.NetworkDown, 0 }; 9437 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9438 _ = t; 9439 _ = handle; 9440 _ = message_buffer; 9441 _ = data_buffer; 9442 _ = flags; 9443 _ = timeout; 9444 @panic("TODO implement netReceiveWindows"); 9445 } 9446 9447 fn netReceiveUnavailable( 9448 userdata: ?*anyopaque, 9449 handle: net.Socket.Handle, 9450 message_buffer: []net.IncomingMessage, 9451 data_buffer: []u8, 9452 flags: net.ReceiveFlags, 9453 timeout: Io.Timeout, 9454 ) struct { ?net.Socket.ReceiveTimeoutError, usize } { 9455 _ = userdata; 9456 _ = handle; 9457 _ = message_buffer; 9458 _ = data_buffer; 9459 _ = flags; 9460 _ = timeout; 9461 return .{ error.NetworkDown, 0 }; 9462 } 9463 9464 fn netWritePosix( 9465 userdata: ?*anyopaque, 9466 fd: net.Socket.Handle, 9467 header: []const u8, 9468 data: []const []const u8, 9469 splat: usize, 9470 ) net.Stream.Writer.Error!usize { 9471 if (!have_networking) return error.NetworkDown; 9472 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9473 const current_thread = Thread.getCurrent(t); 9474 9475 var iovecs: [max_iovecs_len]posix.iovec_const = undefined; 9476 var msg: posix.msghdr_const = .{ 9477 .name = null, 9478 .namelen = 0, 9479 .iov = &iovecs, 9480 .iovlen = 0, 9481 .control = null, 9482 .controllen = 0, 9483 .flags = 0, 9484 }; 9485 addBuf(&iovecs, &msg.iovlen, header); 9486 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes); 9487 const pattern = data[data.len - 1]; 9488 if (iovecs.len - msg.iovlen != 0) switch (splat) { 9489 0 => {}, 9490 1 => addBuf(&iovecs, &msg.iovlen, pattern), 9491 else => switch (pattern.len) { 9492 0 => {}, 9493 1 => { 9494 var backup_buffer: [splat_buffer_size]u8 = undefined; 9495 const splat_buffer = &backup_buffer; 9496 const memset_len = @min(splat_buffer.len, splat); 9497 const buf = splat_buffer[0..memset_len]; 9498 @memset(buf, pattern[0]); 9499 addBuf(&iovecs, &msg.iovlen, buf); 9500 var remaining_splat = splat - buf.len; 9501 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) { 9502 assert(buf.len == splat_buffer.len); 9503 addBuf(&iovecs, &msg.iovlen, splat_buffer); 9504 remaining_splat -= splat_buffer.len; 9505 } 9506 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]); 9507 }, 9508 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| { 9509 addBuf(&iovecs, &msg.iovlen, pattern); 9510 }, 9511 }, 9512 }; 9513 const flags = posix.MSG.NOSIGNAL; 9514 9515 try current_thread.beginSyscall(); 9516 while (true) { 9517 const rc = posix.system.sendmsg(fd, &msg, flags); 9518 switch (posix.errno(rc)) { 9519 .SUCCESS => { 9520 current_thread.endSyscall(); 9521 return @intCast(rc); 9522 }, 9523 .INTR => { 9524 try current_thread.checkCancel(); 9525 continue; 9526 }, 9527 .CANCELED => return current_thread.endSyscallCanceled(), 9528 else => |e| { 9529 current_thread.endSyscall(); 9530 switch (e) { 9531 .ACCES => |err| return errnoBug(err), 9532 .AGAIN => |err| return errnoBug(err), 9533 .ALREADY => return error.FastOpenAlreadyInProgress, 9534 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 9535 .CONNRESET => return error.ConnectionResetByPeer, 9536 .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. 9537 .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. 9538 .INVAL => |err| return errnoBug(err), // Invalid argument passed. 9539 .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified 9540 .MSGSIZE => |err| return errnoBug(err), 9541 .NOBUFS => return error.SystemResources, 9542 .NOMEM => return error.SystemResources, 9543 .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. 9544 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. 9545 .PIPE => return error.SocketUnconnected, 9546 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 9547 .HOSTUNREACH => return error.HostUnreachable, 9548 .NETUNREACH => return error.NetworkUnreachable, 9549 .NOTCONN => return error.SocketUnconnected, 9550 .NETDOWN => return error.NetworkDown, 9551 else => |err| return posix.unexpectedErrno(err), 9552 } 9553 }, 9554 } 9555 } 9556 } 9557 9558 fn netWriteWindows( 9559 userdata: ?*anyopaque, 9560 handle: net.Socket.Handle, 9561 header: []const u8, 9562 data: []const []const u8, 9563 splat: usize, 9564 ) net.Stream.Writer.Error!usize { 9565 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9566 const current_thread = Thread.getCurrent(t); 9567 comptime assert(native_os == .windows); 9568 9569 var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined; 9570 var len: u32 = 0; 9571 addWsaBuf(&iovecs, &len, header); 9572 for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes); 9573 const pattern = data[data.len - 1]; 9574 if (iovecs.len - len != 0) switch (splat) { 9575 0 => {}, 9576 1 => addWsaBuf(&iovecs, &len, pattern), 9577 else => switch (pattern.len) { 9578 0 => {}, 9579 1 => { 9580 var backup_buffer: [64]u8 = undefined; 9581 const splat_buffer = &backup_buffer; 9582 const memset_len = @min(splat_buffer.len, splat); 9583 const buf = splat_buffer[0..memset_len]; 9584 @memset(buf, pattern[0]); 9585 addWsaBuf(&iovecs, &len, buf); 9586 var remaining_splat = splat - buf.len; 9587 while (remaining_splat > splat_buffer.len and len < iovecs.len) { 9588 addWsaBuf(&iovecs, &len, splat_buffer); 9589 remaining_splat -= splat_buffer.len; 9590 } 9591 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]); 9592 }, 9593 else => for (0..@min(splat, iovecs.len - len)) |_| { 9594 addWsaBuf(&iovecs, &len, pattern); 9595 }, 9596 }, 9597 }; 9598 9599 while (true) { 9600 try current_thread.checkCancel(); 9601 9602 var n: u32 = undefined; 9603 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); 9604 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, &overlapped, null); 9605 if (rc != ws2_32.SOCKET_ERROR) return n; 9606 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) { 9607 .IO_PENDING => e: { 9608 var result_flags: u32 = undefined; 9609 const overlapped_rc = ws2_32.WSAGetOverlappedResult( 9610 handle, 9611 &overlapped, 9612 &n, 9613 windows.TRUE, 9614 &result_flags, 9615 ); 9616 if (overlapped_rc == windows.FALSE) { 9617 break :e ws2_32.WSAGetLastError(); 9618 } else { 9619 return n; 9620 } 9621 }, 9622 else => |err| err, 9623 }; 9624 switch (wsa_error) { 9625 .EINTR => continue, 9626 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), 9627 .NOTINITIALISED => { 9628 try initializeWsa(t); 9629 continue; 9630 }, 9631 9632 .ECONNABORTED => return error.ConnectionResetByPeer, 9633 .ECONNRESET => return error.ConnectionResetByPeer, 9634 .EINVAL => return error.SocketUnconnected, 9635 .ENETDOWN => return error.NetworkDown, 9636 .ENETRESET => return error.ConnectionResetByPeer, 9637 .ENOBUFS => return error.SystemResources, 9638 .ENOTCONN => return error.SocketUnconnected, 9639 .ENOTSOCK => |err| return wsaErrorBug(err), 9640 .EOPNOTSUPP => |err| return wsaErrorBug(err), 9641 .ESHUTDOWN => |err| return wsaErrorBug(err), 9642 else => |err| return windows.unexpectedWSAError(err), 9643 } 9644 } 9645 } 9646 9647 fn addWsaBuf(v: []ws2_32.WSABUF, i: *u32, bytes: []const u8) void { 9648 const cap = std.math.maxInt(u32); 9649 var remaining = bytes; 9650 while (remaining.len > cap) { 9651 if (v.len - i.* == 0) return; 9652 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap }; 9653 i.* += 1; 9654 remaining = remaining[cap..]; 9655 } else { 9656 @branchHint(.likely); 9657 if (v.len - i.* == 0) return; 9658 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) }; 9659 i.* += 1; 9660 } 9661 } 9662 9663 fn netWriteUnavailable( 9664 userdata: ?*anyopaque, 9665 handle: net.Socket.Handle, 9666 header: []const u8, 9667 data: []const []const u8, 9668 splat: usize, 9669 ) net.Stream.Writer.Error!usize { 9670 _ = userdata; 9671 _ = handle; 9672 _ = header; 9673 _ = data; 9674 _ = splat; 9675 return error.NetworkDown; 9676 } 9677 9678 /// This is either usize or u32. Since, either is fine, let's use the same 9679 /// `addBuf` function for both writing to a file and sending network messages. 9680 const iovlen_t = @FieldType(posix.msghdr_const, "iovlen"); 9681 9682 fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { 9683 // OS checks ptr addr before length so zero length vectors must be omitted. 9684 if (bytes.len == 0) return; 9685 if (v.len - i.* == 0) return; 9686 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len }; 9687 i.* += 1; 9688 } 9689 9690 fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { 9691 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9692 _ = t; 9693 switch (native_os) { 9694 .windows => for (handles) |handle| closeSocketWindows(handle), 9695 else => for (handles) |handle| posix.close(handle), 9696 } 9697 } 9698 9699 fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { 9700 _ = userdata; 9701 _ = handles; 9702 unreachable; // How you gonna close something that was impossible to open? 9703 } 9704 9705 fn netInterfaceNameResolve( 9706 userdata: ?*anyopaque, 9707 name: *const net.Interface.Name, 9708 ) net.Interface.Name.ResolveError!net.Interface { 9709 if (!have_networking) return error.InterfaceNotFound; 9710 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9711 const current_thread = Thread.getCurrent(t); 9712 9713 if (native_os == .linux) { 9714 const sock_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) { 9715 error.ProcessFdQuotaExceeded => return error.SystemResources, 9716 error.SystemFdQuotaExceeded => return error.SystemResources, 9717 error.AddressFamilyUnsupported => return error.Unexpected, 9718 error.ProtocolUnsupportedBySystem => return error.Unexpected, 9719 error.ProtocolUnsupportedByAddressFamily => return error.Unexpected, 9720 error.SocketModeUnsupported => return error.Unexpected, 9721 error.OptionUnsupported => return error.Unexpected, 9722 else => |e| return e, 9723 }; 9724 defer posix.close(sock_fd); 9725 9726 var ifr: posix.ifreq = .{ 9727 .ifrn = .{ .name = @bitCast(name.bytes) }, 9728 .ifru = undefined, 9729 }; 9730 9731 try current_thread.beginSyscall(); 9732 while (true) { 9733 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { 9734 .SUCCESS => { 9735 current_thread.endSyscall(); 9736 return .{ .index = @bitCast(ifr.ifru.ivalue) }; 9737 }, 9738 .INTR => { 9739 try current_thread.checkCancel(); 9740 continue; 9741 }, 9742 .CANCELED => return current_thread.endSyscallCanceled(), 9743 else => |e| { 9744 current_thread.endSyscall(); 9745 switch (e) { 9746 .INVAL => |err| return errnoBug(err), // Bad parameters. 9747 .NOTTY => |err| return errnoBug(err), 9748 .NXIO => |err| return errnoBug(err), 9749 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 9750 .FAULT => |err| return errnoBug(err), // Bad pointer parameter. 9751 .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor 9752 .NODEV => return error.InterfaceNotFound, 9753 else => |err| return posix.unexpectedErrno(err), 9754 } 9755 }, 9756 } 9757 } 9758 } 9759 9760 if (native_os == .windows) { 9761 try current_thread.checkCancel(); 9762 @panic("TODO implement netInterfaceNameResolve for Windows"); 9763 } 9764 9765 if (builtin.link_libc) { 9766 try current_thread.checkCancel(); 9767 const index = std.c.if_nametoindex(&name.bytes); 9768 if (index == 0) return error.InterfaceNotFound; 9769 return .{ .index = @bitCast(index) }; 9770 } 9771 9772 @panic("unimplemented"); 9773 } 9774 9775 fn netInterfaceNameResolveUnavailable( 9776 userdata: ?*anyopaque, 9777 name: *const net.Interface.Name, 9778 ) net.Interface.Name.ResolveError!net.Interface { 9779 _ = userdata; 9780 _ = name; 9781 return error.InterfaceNotFound; 9782 } 9783 9784 fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { 9785 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9786 const current_thread = Thread.getCurrent(t); 9787 try current_thread.checkCancel(); 9788 9789 if (native_os == .linux) { 9790 _ = interface; 9791 @panic("TODO implement netInterfaceName for linux"); 9792 } 9793 9794 if (native_os == .windows) { 9795 @panic("TODO implement netInterfaceName for windows"); 9796 } 9797 9798 if (builtin.link_libc) { 9799 @panic("TODO implement netInterfaceName for libc"); 9800 } 9801 9802 @panic("unimplemented"); 9803 } 9804 9805 fn netInterfaceNameUnavailable(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { 9806 _ = userdata; 9807 _ = interface; 9808 return error.Unexpected; 9809 } 9810 9811 fn netLookup( 9812 userdata: ?*anyopaque, 9813 host_name: HostName, 9814 resolved: *Io.Queue(HostName.LookupResult), 9815 options: HostName.LookupOptions, 9816 ) net.HostName.LookupError!void { 9817 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9818 defer resolved.close(io(t)); 9819 netLookupFallible(t, host_name, resolved, options) catch |err| switch (err) { 9820 error.Closed => unreachable, // `resolved` must not be closed until `netLookup` returns 9821 else => |e| return e, 9822 }; 9823 } 9824 9825 fn netLookupUnavailable( 9826 userdata: ?*anyopaque, 9827 host_name: HostName, 9828 resolved: *Io.Queue(HostName.LookupResult), 9829 options: HostName.LookupOptions, 9830 ) net.HostName.LookupError!void { 9831 _ = host_name; 9832 _ = options; 9833 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9834 resolved.close(ioBasic(t)); 9835 return error.NetworkDown; 9836 } 9837 9838 fn netLookupFallible( 9839 t: *Threaded, 9840 host_name: HostName, 9841 resolved: *Io.Queue(HostName.LookupResult), 9842 options: HostName.LookupOptions, 9843 ) (net.HostName.LookupError || Io.QueueClosedError)!void { 9844 if (!have_networking) return error.NetworkDown; 9845 9846 const current_thread: *Thread = .getCurrent(t); 9847 const t_io = io(t); 9848 const name = host_name.bytes; 9849 assert(name.len <= HostName.max_len); 9850 9851 if (is_windows) { 9852 var name_buffer: [HostName.max_len + 1]u16 = undefined; 9853 const name_len = std.unicode.wtf8ToWtf16Le(&name_buffer, host_name.bytes) catch 9854 unreachable; // HostName is prevalidated. 9855 name_buffer[name_len] = 0; 9856 const name_w = name_buffer[0..name_len :0]; 9857 9858 var port_buffer: [8]u8 = undefined; 9859 var port_buffer_wide: [8]u16 = undefined; 9860 const port = std.fmt.bufPrint(&port_buffer, "{d}", .{options.port}) catch 9861 unreachable; // `port_buffer` is big enough for decimal u16. 9862 for (port, port_buffer_wide[0..port.len]) |byte, *wide| 9863 wide.* = std.mem.nativeToLittle(u16, byte); 9864 port_buffer_wide[port.len] = 0; 9865 const port_w = port_buffer_wide[0..port.len :0]; 9866 9867 const hints: ws2_32.ADDRINFOEXW = .{ 9868 .flags = .{ .NUMERICSERV = true }, 9869 .family = if (options.family) |f| switch (f) { 9870 .ip4 => posix.AF.INET, 9871 .ip6 => posix.AF.INET6, 9872 } else posix.AF.UNSPEC, 9873 .socktype = posix.SOCK.STREAM, 9874 .protocol = posix.IPPROTO.TCP, 9875 .canonname = null, 9876 .addr = null, 9877 .addrlen = 0, 9878 .blob = null, 9879 .bloblen = 0, 9880 .provider = null, 9881 .next = null, 9882 }; 9883 const cancel_handle: ?*windows.HANDLE = null; 9884 var res: *ws2_32.ADDRINFOEXW = undefined; 9885 const timeout: ?*ws2_32.timeval = null; 9886 while (true) { 9887 try current_thread.checkCancel(); // TODO make requestCancel call GetAddrInfoExCancel 9888 // TODO make this append to the queue eagerly rather than blocking until 9889 // the whole thing finishes 9890 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, cancel_handle)); 9891 switch (rc) { 9892 @as(ws2_32.WinsockError, @enumFromInt(0)) => break, 9893 .EINTR => continue, 9894 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, 9895 .NOTINITIALISED => { 9896 try initializeWsa(t); 9897 continue; 9898 }, 9899 .TRY_AGAIN => return error.NameServerFailure, 9900 .EINVAL => |err| return wsaErrorBug(err), 9901 .NO_RECOVERY => return error.NameServerFailure, 9902 .EAFNOSUPPORT => return error.AddressFamilyUnsupported, 9903 .NOT_ENOUGH_MEMORY => return error.SystemResources, 9904 .HOST_NOT_FOUND => return error.UnknownHostName, 9905 .TYPE_NOT_FOUND => return error.ProtocolUnsupportedByAddressFamily, 9906 .ESOCKTNOSUPPORT => return error.ProtocolUnsupportedBySystem, 9907 else => |err| return windows.unexpectedWSAError(err), 9908 } 9909 } 9910 defer ws2_32.FreeAddrInfoExW(res); 9911 9912 var it: ?*ws2_32.ADDRINFOEXW = res; 9913 var canon_name: ?[*:0]const u16 = null; 9914 while (it) |info| : (it = info.next) { 9915 const addr = info.addr orelse continue; 9916 const storage: WsaAddress = .{ .any = addr.* }; 9917 try resolved.putOne(t_io, .{ .address = addressFromWsa(&storage) }); 9918 9919 if (info.canonname) |n| { 9920 if (canon_name == null) { 9921 canon_name = n; 9922 } 9923 } 9924 } 9925 if (canon_name) |n| { 9926 const len = std.unicode.wtf16LeToWtf8(options.canonical_name_buffer, std.mem.sliceTo(n, 0)); 9927 try resolved.putOne(t_io, .{ .canonical_name = .{ 9928 .bytes = options.canonical_name_buffer[0..len], 9929 } }); 9930 } 9931 return; 9932 } 9933 9934 // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics. 9935 // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it. 9936 9937 if (builtin.target.isGnuLibC()) { 9938 // TODO use getaddrinfo_a / gai_cancel 9939 } 9940 9941 if (native_os == .linux) { 9942 if (options.family != .ip4) { 9943 if (IpAddress.parseIp6(name, options.port)) |addr| { 9944 try resolved.putAll(t_io, &.{ 9945 .{ .address = addr }, 9946 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, 9947 }); 9948 return; 9949 } else |_| {} 9950 } 9951 9952 if (options.family != .ip6) { 9953 if (IpAddress.parseIp4(name, options.port)) |addr| { 9954 try resolved.putAll(t_io, &.{ 9955 .{ .address = addr }, 9956 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, 9957 }); 9958 return; 9959 } else |_| {} 9960 } 9961 9962 lookupHosts(t, host_name, resolved, options) catch |err| switch (err) { 9963 error.UnknownHostName => {}, 9964 else => |e| return e, 9965 }; 9966 9967 // RFC 6761 Section 6.3.3 9968 // Name resolution APIs and libraries SHOULD recognize 9969 // localhost names as special and SHOULD always return the IP 9970 // loopback address for address queries and negative responses 9971 // for all other query types. 9972 9973 // Check for equal to "localhost(.)" or ends in ".localhost(.)" 9974 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; 9975 if (std.mem.endsWith(u8, name, localhost) and 9976 (name.len == localhost.len or name[name.len - localhost.len] == '.')) 9977 { 9978 var results_buffer: [3]HostName.LookupResult = undefined; 9979 var results_index: usize = 0; 9980 if (options.family != .ip4) { 9981 results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } }; 9982 results_index += 1; 9983 } 9984 if (options.family != .ip6) { 9985 results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } }; 9986 results_index += 1; 9987 } 9988 const canon_name = "localhost"; 9989 const canon_name_dest = options.canonical_name_buffer[0..canon_name.len]; 9990 canon_name_dest.* = canon_name.*; 9991 results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } }; 9992 results_index += 1; 9993 try resolved.putAll(t_io, results_buffer[0..results_index]); 9994 return; 9995 } 9996 9997 return lookupDnsSearch(t, host_name, resolved, options); 9998 } 9999 10000 if (native_os == .openbsd) { 10001 // TODO use getaddrinfo_async / asr_abort 10002 } 10003 10004 if (native_os == .freebsd) { 10005 // TODO use dnsres_getaddrinfo 10006 } 10007 10008 if (is_darwin) { 10009 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution 10010 } 10011 10012 if (builtin.link_libc) { 10013 // This operating system lacks a way to resolve asynchronously. We are 10014 // stuck with getaddrinfo. 10015 var name_buffer: [HostName.max_len + 1]u8 = undefined; 10016 @memcpy(name_buffer[0..host_name.bytes.len], host_name.bytes); 10017 name_buffer[host_name.bytes.len] = 0; 10018 const name_c = name_buffer[0..host_name.bytes.len :0]; 10019 10020 var port_buffer: [8]u8 = undefined; 10021 const port_c = std.fmt.bufPrintZ(&port_buffer, "{d}", .{options.port}) catch unreachable; 10022 10023 const hints: posix.addrinfo = .{ 10024 .flags = .{ .NUMERICSERV = true }, 10025 .family = posix.AF.UNSPEC, 10026 .socktype = posix.SOCK.STREAM, 10027 .protocol = posix.IPPROTO.TCP, 10028 .canonname = null, 10029 .addr = null, 10030 .addrlen = 0, 10031 .next = null, 10032 }; 10033 var res: ?*posix.addrinfo = null; 10034 try current_thread.beginSyscall(); 10035 while (true) { 10036 switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) { 10037 @as(posix.system.EAI, @enumFromInt(0)) => { 10038 current_thread.endSyscall(); 10039 break; 10040 }, 10041 .SYSTEM => switch (posix.errno(-1)) { 10042 .INTR => { 10043 try current_thread.checkCancel(); 10044 continue; 10045 }, 10046 .CANCELED => return current_thread.endSyscallCanceled(), 10047 else => |e| { 10048 current_thread.endSyscall(); 10049 return posix.unexpectedErrno(e); 10050 }, 10051 }, 10052 else => |e| { 10053 current_thread.endSyscall(); 10054 switch (e) { 10055 .ADDRFAMILY => return error.AddressFamilyUnsupported, 10056 .AGAIN => return error.NameServerFailure, 10057 .FAIL => return error.NameServerFailure, 10058 .FAMILY => return error.AddressFamilyUnsupported, 10059 .MEMORY => return error.SystemResources, 10060 .NODATA => return error.UnknownHostName, 10061 .NONAME => return error.UnknownHostName, 10062 else => return error.Unexpected, 10063 } 10064 }, 10065 } 10066 } 10067 defer if (res) |some| posix.system.freeaddrinfo(some); 10068 10069 var it = res; 10070 var canon_name: ?[*:0]const u8 = null; 10071 while (it) |info| : (it = info.next) { 10072 const addr = info.addr orelse continue; 10073 const storage: PosixAddress = .{ .any = addr.* }; 10074 try resolved.putOne(t_io, .{ .address = addressFromPosix(&storage) }); 10075 10076 if (info.canonname) |n| { 10077 if (canon_name == null) { 10078 canon_name = n; 10079 } 10080 } 10081 } 10082 if (canon_name) |n| { 10083 try resolved.putOne(t_io, .{ 10084 .canonical_name = copyCanon(options.canonical_name_buffer, std.mem.sliceTo(n, 0)), 10085 }); 10086 } 10087 return; 10088 } 10089 10090 return error.OptionUnsupported; 10091 } 10092 10093 fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer { 10094 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10095 // Only global mutex since this is Threaded. 10096 Io.stderr_thread_mutex.lock(); 10097 if (!t.stderr_writer_initialized) { 10098 const io_t = ioBasic(t); 10099 if (is_windows) t.stderr_writer.file = .stderr(); 10100 t.stderr_writer.io = io_t; 10101 t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple); 10102 t.stderr_writer_initialized = true; 10103 } 10104 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; 10105 t.stderr_writer.interface.flush() catch {}; 10106 t.stderr_writer.interface.buffer = buffer; 10107 return &t.stderr_writer; 10108 } 10109 10110 fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer { 10111 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10112 // Only global mutex since this is Threaded. 10113 if (!Io.stderr_thread_mutex.tryLock()) return null; 10114 if (!t.stderr_writer_initialized) { 10115 const io_t = ioBasic(t); 10116 if (is_windows) t.stderr_writer.file = .stderr(); 10117 t.stderr_writer.io = io_t; 10118 t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch 10119 return null; 10120 t.stderr_writer_initialized = true; 10121 } 10122 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; 10123 t.stderr_writer.interface.flush() catch {}; 10124 t.stderr_writer.interface.buffer = buffer; 10125 return &t.stderr_writer; 10126 } 10127 10128 fn unlockStderrWriter(userdata: ?*anyopaque) void { 10129 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10130 t.stderr_writer.interface.flush() catch {}; 10131 t.stderr_writer.interface.end = 0; 10132 t.stderr_writer.interface.buffer = &.{}; 10133 Io.stderr_thread_mutex.unlock(); 10134 } 10135 10136 pub const PosixAddress = extern union { 10137 any: posix.sockaddr, 10138 in: posix.sockaddr.in, 10139 in6: posix.sockaddr.in6, 10140 }; 10141 10142 const UnixAddress = extern union { 10143 any: posix.sockaddr, 10144 un: posix.sockaddr.un, 10145 }; 10146 10147 const WsaAddress = extern union { 10148 any: ws2_32.sockaddr, 10149 in: ws2_32.sockaddr.in, 10150 in6: ws2_32.sockaddr.in6, 10151 un: ws2_32.sockaddr.un, 10152 }; 10153 10154 pub fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t { 10155 return switch (a.*) { 10156 .ip4 => posix.AF.INET, 10157 .ip6 => posix.AF.INET6, 10158 }; 10159 } 10160 10161 pub fn addressFromPosix(posix_address: *const PosixAddress) IpAddress { 10162 return switch (posix_address.any.family) { 10163 posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) }, 10164 posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) }, 10165 else => .{ .ip4 = .loopback(0) }, 10166 }; 10167 } 10168 10169 fn addressFromWsa(wsa_address: *const WsaAddress) IpAddress { 10170 return switch (wsa_address.any.family) { 10171 posix.AF.INET => .{ .ip4 = address4FromWsa(&wsa_address.in) }, 10172 posix.AF.INET6 => .{ .ip6 = address6FromWsa(&wsa_address.in6) }, 10173 else => .{ .ip4 = .loopback(0) }, 10174 }; 10175 } 10176 10177 pub fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t { 10178 return switch (a.*) { 10179 .ip4 => |ip4| { 10180 storage.in = address4ToPosix(ip4); 10181 return @sizeOf(posix.sockaddr.in); 10182 }, 10183 .ip6 => |*ip6| { 10184 storage.in6 = address6ToPosix(ip6); 10185 return @sizeOf(posix.sockaddr.in6); 10186 }, 10187 }; 10188 } 10189 10190 fn addressToWsa(a: *const IpAddress, storage: *WsaAddress) i32 { 10191 return switch (a.*) { 10192 .ip4 => |ip4| { 10193 storage.in = address4ToPosix(ip4); 10194 return @sizeOf(posix.sockaddr.in); 10195 }, 10196 .ip6 => |*ip6| { 10197 storage.in6 = address6ToPosix(ip6); 10198 return @sizeOf(posix.sockaddr.in6); 10199 }, 10200 }; 10201 } 10202 10203 fn addressUnixToPosix(a: *const net.UnixAddress, storage: *UnixAddress) posix.socklen_t { 10204 @memcpy(storage.un.path[0..a.path.len], a.path); 10205 storage.un.family = posix.AF.UNIX; 10206 storage.un.path[a.path.len] = 0; 10207 return @sizeOf(posix.sockaddr.un); 10208 } 10209 10210 fn addressUnixToWsa(a: *const net.UnixAddress, storage: *WsaAddress) i32 { 10211 @memcpy(storage.un.path[0..a.path.len], a.path); 10212 storage.un.family = posix.AF.UNIX; 10213 storage.un.path[a.path.len] = 0; 10214 return @sizeOf(posix.sockaddr.un); 10215 } 10216 10217 fn address4FromPosix(in: *const posix.sockaddr.in) net.Ip4Address { 10218 return .{ 10219 .port = std.mem.bigToNative(u16, in.port), 10220 .bytes = @bitCast(in.addr), 10221 }; 10222 } 10223 10224 fn address6FromPosix(in6: *const posix.sockaddr.in6) net.Ip6Address { 10225 return .{ 10226 .port = std.mem.bigToNative(u16, in6.port), 10227 .bytes = in6.addr, 10228 .flow = in6.flowinfo, 10229 .interface = .{ .index = in6.scope_id }, 10230 }; 10231 } 10232 10233 fn address4FromWsa(in: *const ws2_32.sockaddr.in) net.Ip4Address { 10234 return .{ 10235 .port = std.mem.bigToNative(u16, in.port), 10236 .bytes = @bitCast(in.addr), 10237 }; 10238 } 10239 10240 fn address6FromWsa(in6: *const ws2_32.sockaddr.in6) net.Ip6Address { 10241 return .{ 10242 .port = std.mem.bigToNative(u16, in6.port), 10243 .bytes = in6.addr, 10244 .flow = in6.flowinfo, 10245 .interface = .{ .index = in6.scope_id }, 10246 }; 10247 } 10248 10249 fn address4ToPosix(a: net.Ip4Address) posix.sockaddr.in { 10250 return .{ 10251 .port = std.mem.nativeToBig(u16, a.port), 10252 .addr = @bitCast(a.bytes), 10253 }; 10254 } 10255 10256 fn address6ToPosix(a: *const net.Ip6Address) posix.sockaddr.in6 { 10257 return .{ 10258 .port = std.mem.nativeToBig(u16, a.port), 10259 .flowinfo = a.flow, 10260 .addr = a.bytes, 10261 .scope_id = a.interface.index, 10262 }; 10263 } 10264 10265 pub fn errnoBug(err: posix.E) Io.UnexpectedError { 10266 if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err}); 10267 return error.Unexpected; 10268 } 10269 10270 fn wsaErrorBug(err: ws2_32.WinsockError) Io.UnexpectedError { 10271 if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err}); 10272 return error.Unexpected; 10273 } 10274 10275 pub fn posixSocketMode(mode: net.Socket.Mode) u32 { 10276 return switch (mode) { 10277 .stream => posix.SOCK.STREAM, 10278 .dgram => posix.SOCK.DGRAM, 10279 .seqpacket => posix.SOCK.SEQPACKET, 10280 .raw => posix.SOCK.RAW, 10281 .rdm => posix.SOCK.RDM, 10282 }; 10283 } 10284 10285 pub fn posixProtocol(protocol: ?net.Protocol) u32 { 10286 return @intFromEnum(protocol orelse return 0); 10287 } 10288 10289 fn recoverableOsBugDetected() void { 10290 if (is_debug) unreachable; 10291 } 10292 10293 fn clockToPosix(clock: Io.Clock) posix.clockid_t { 10294 return switch (clock) { 10295 .real => posix.CLOCK.REALTIME, 10296 .awake => switch (native_os) { 10297 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW, 10298 else => posix.CLOCK.MONOTONIC, 10299 }, 10300 .boot => switch (native_os) { 10301 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.MONOTONIC_RAW, 10302 // On freebsd derivatives, use MONOTONIC_FAST as currently there's 10303 // no precision tradeoff. 10304 .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST, 10305 // On linux, use BOOTTIME instead of MONOTONIC as it ticks while 10306 // suspended. 10307 .linux => posix.CLOCK.BOOTTIME, 10308 // On other posix systems, MONOTONIC is generally the fastest and 10309 // ticks while suspended. 10310 else => posix.CLOCK.MONOTONIC, 10311 }, 10312 .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID, 10313 .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID, 10314 }; 10315 } 10316 10317 fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { 10318 return switch (clock) { 10319 .real => .REALTIME, 10320 .awake => .MONOTONIC, 10321 .boot => .MONOTONIC, 10322 .cpu_process => .PROCESS_CPUTIME_ID, 10323 .cpu_thread => .THREAD_CPUTIME_ID, 10324 }; 10325 } 10326 10327 fn statFromLinux(stx: *const std.os.linux.Statx) File.Stat { 10328 const atime = stx.atime; 10329 const mtime = stx.mtime; 10330 const ctime = stx.ctime; 10331 return .{ 10332 .inode = stx.ino, 10333 .size = stx.size, 10334 .permissions = .fromMode(stx.mode), 10335 .kind = switch (stx.mode & std.os.linux.S.IFMT) { 10336 std.os.linux.S.IFDIR => .directory, 10337 std.os.linux.S.IFCHR => .character_device, 10338 std.os.linux.S.IFBLK => .block_device, 10339 std.os.linux.S.IFREG => .file, 10340 std.os.linux.S.IFIFO => .named_pipe, 10341 std.os.linux.S.IFLNK => .sym_link, 10342 std.os.linux.S.IFSOCK => .unix_domain_socket, 10343 else => .unknown, 10344 }, 10345 .atime = .{ .nanoseconds = @intCast(@as(i128, atime.sec) * std.time.ns_per_s + atime.nsec) }, 10346 .mtime = .{ .nanoseconds = @intCast(@as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec) }, 10347 .ctime = .{ .nanoseconds = @intCast(@as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec) }, 10348 }; 10349 } 10350 10351 fn statFromPosix(st: *const posix.Stat) File.Stat { 10352 const atime = st.atime(); 10353 const mtime = st.mtime(); 10354 const ctime = st.ctime(); 10355 return .{ 10356 .inode = st.ino, 10357 .size = @bitCast(st.size), 10358 .permissions = .fromMode(st.mode), 10359 .kind = k: { 10360 const m = st.mode & posix.S.IFMT; 10361 switch (m) { 10362 posix.S.IFBLK => break :k .block_device, 10363 posix.S.IFCHR => break :k .character_device, 10364 posix.S.IFDIR => break :k .directory, 10365 posix.S.IFIFO => break :k .named_pipe, 10366 posix.S.IFLNK => break :k .sym_link, 10367 posix.S.IFREG => break :k .file, 10368 posix.S.IFSOCK => break :k .unix_domain_socket, 10369 else => {}, 10370 } 10371 if (native_os == .illumos) switch (m) { 10372 posix.S.IFDOOR => break :k .door, 10373 posix.S.IFPORT => break :k .event_port, 10374 else => {}, 10375 }; 10376 10377 break :k .unknown; 10378 }, 10379 .atime = timestampFromPosix(&atime), 10380 .mtime = timestampFromPosix(&mtime), 10381 .ctime = timestampFromPosix(&ctime), 10382 }; 10383 } 10384 10385 fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { 10386 return .{ 10387 .inode = st.ino, 10388 .size = @bitCast(st.size), 10389 .mode = 0, 10390 .kind = switch (st.filetype) { 10391 .BLOCK_DEVICE => .block_device, 10392 .CHARACTER_DEVICE => .character_device, 10393 .DIRECTORY => .directory, 10394 .SYMBOLIC_LINK => .sym_link, 10395 .REGULAR_FILE => .file, 10396 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, 10397 else => .unknown, 10398 }, 10399 .atime = .fromNanoseconds(st.atim), 10400 .mtime = .fromNanoseconds(st.mtim), 10401 .ctime = .fromNanoseconds(st.ctim), 10402 }; 10403 } 10404 10405 fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp { 10406 return .{ .nanoseconds = @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec) }; 10407 } 10408 10409 fn timestampToPosix(nanoseconds: i96) posix.timespec { 10410 return .{ 10411 .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)), 10412 .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)), 10413 }; 10414 } 10415 10416 fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { 10417 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName; 10418 // >= rather than > to make room for the null byte 10419 if (file_path.len >= buffer.len) return error.NameTooLong; 10420 @memcpy(buffer[0..file_path.len], file_path); 10421 buffer[file_path.len] = 0; 10422 return buffer[0..file_path.len :0]; 10423 } 10424 10425 fn lookupDnsSearch( 10426 t: *Threaded, 10427 host_name: HostName, 10428 resolved: *Io.Queue(HostName.LookupResult), 10429 options: HostName.LookupOptions, 10430 ) (HostName.LookupError || Io.QueueClosedError)!void { 10431 const t_io = io(t); 10432 const rc = HostName.ResolvConf.init(t_io) catch return error.ResolvConfParseFailed; 10433 10434 // Count dots, suppress search when >=ndots or name ends in 10435 // a dot, which is an explicit request for global scope. 10436 const dots = std.mem.countScalar(u8, host_name.bytes, '.'); 10437 const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len; 10438 const search = rc.search_buffer[0..search_len]; 10439 10440 var canon_name = host_name.bytes; 10441 10442 // Strip final dot for canon, fail if multiple trailing dots. 10443 if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; 10444 if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName; 10445 10446 // Name with search domain appended is set up in `canon_name`. This 10447 // both provides the desired default canonical name (if the requested 10448 // name is not a CNAME record) and serves as a buffer for passing the 10449 // full requested name to `lookupDns`. 10450 @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name); 10451 options.canonical_name_buffer[canon_name.len] = '.'; 10452 var it = std.mem.tokenizeAny(u8, search, " \t"); 10453 while (it.next()) |token| { 10454 @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token); 10455 const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len]; 10456 if (lookupDns(t, lookup_canon_name, &rc, resolved, options)) |result| { 10457 return result; 10458 } else |err| switch (err) { 10459 error.UnknownHostName => continue, 10460 else => |e| return e, 10461 } 10462 } 10463 10464 const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len]; 10465 return lookupDns(t, lookup_canon_name, &rc, resolved, options); 10466 } 10467 10468 fn lookupDns( 10469 t: *Threaded, 10470 lookup_canon_name: []const u8, 10471 rc: *const HostName.ResolvConf, 10472 resolved: *Io.Queue(HostName.LookupResult), 10473 options: HostName.LookupOptions, 10474 ) (HostName.LookupError || Io.QueueClosedError)!void { 10475 const t_io = io(t); 10476 const family_records: [2]struct { af: IpAddress.Family, rr: HostName.DnsRecord } = .{ 10477 .{ .af = .ip6, .rr = .A }, 10478 .{ .af = .ip4, .rr = .AAAA }, 10479 }; 10480 var query_buffers: [2][280]u8 = undefined; 10481 var answer_buffer: [2 * 512]u8 = undefined; 10482 var queries_buffer: [2][]const u8 = undefined; 10483 var answers_buffer: [2][]const u8 = undefined; 10484 var nq: usize = 0; 10485 var answer_buffer_i: usize = 0; 10486 10487 for (family_records) |fr| { 10488 if (options.family != fr.af) { 10489 const entropy = std.crypto.random.array(u8, 2); 10490 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy); 10491 queries_buffer[nq] = query_buffers[nq][0..len]; 10492 nq += 1; 10493 } 10494 } 10495 10496 var ip4_mapped_buffer: [HostName.ResolvConf.max_nameservers]IpAddress = undefined; 10497 const ip4_mapped = ip4_mapped_buffer[0..rc.nameservers_len]; 10498 var any_ip6 = false; 10499 for (rc.nameservers(), ip4_mapped) |*ns, *m| { 10500 m.* = .{ .ip6 = .fromAny(ns.*) }; 10501 any_ip6 = any_ip6 or ns.* == .ip6; 10502 } 10503 var socket = s: { 10504 if (any_ip6) ip6: { 10505 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; 10506 const socket = ip6_addr.bind(t_io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) { 10507 error.AddressFamilyUnsupported => break :ip6, 10508 else => |e| return e, 10509 }; 10510 break :s socket; 10511 } 10512 any_ip6 = false; 10513 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; 10514 const socket = try ip4_addr.bind(t_io, .{ .mode = .dgram }); 10515 break :s socket; 10516 }; 10517 defer socket.close(t_io); 10518 10519 const mapped_nameservers = if (any_ip6) ip4_mapped else rc.nameservers(); 10520 const queries = queries_buffer[0..nq]; 10521 const answers = answers_buffer[0..queries.len]; 10522 var answers_remaining = answers.len; 10523 for (answers) |*answer| answer.len = 0; 10524 10525 // boot clock is chosen because time the computer is suspended should count 10526 // against time spent waiting for external messages to arrive. 10527 const clock: Io.Clock = .boot; 10528 var now_ts = try clock.now(t_io); 10529 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); 10530 const attempt_duration: Io.Duration = .{ 10531 .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds), 10532 }; 10533 10534 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(t_io)) { 10535 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers; 10536 { 10537 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; 10538 var message_i: usize = 0; 10539 for (queries, answers) |query, *answer| { 10540 if (answer.len != 0) continue; 10541 for (mapped_nameservers) |*ns| { 10542 message_buffer[message_i] = .{ 10543 .address = ns, 10544 .data_ptr = query.ptr, 10545 .data_len = query.len, 10546 }; 10547 message_i += 1; 10548 } 10549 } 10550 _ = netSendPosix(t, socket.handle, message_buffer[0..message_i], .{}); 10551 } 10552 10553 const timeout: Io.Timeout = .{ .deadline = .{ 10554 .raw = now_ts.addDuration(attempt_duration), 10555 .clock = clock, 10556 } }; 10557 10558 while (true) { 10559 var message_buffer: [max_messages]Io.net.IncomingMessage = @splat(.init); 10560 const buf = answer_buffer[answer_buffer_i..]; 10561 const recv_err, const recv_n = socket.receiveManyTimeout(t_io, &message_buffer, buf, .{}, timeout); 10562 for (message_buffer[0..recv_n]) |*received_message| { 10563 const reply = received_message.data; 10564 // Ignore non-identifiable packets. 10565 if (reply.len < 4) continue; 10566 10567 // Ignore replies from addresses we didn't send to. 10568 const ns = for (mapped_nameservers) |*ns| { 10569 if (received_message.from.eql(ns)) break ns; 10570 } else { 10571 continue; 10572 }; 10573 10574 // Find which query this answer goes with, if any. 10575 const query, const answer = for (queries, answers) |query, *answer| { 10576 if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer }; 10577 } else { 10578 continue; 10579 }; 10580 if (answer.len != 0) continue; 10581 10582 // Only accept positive or negative responses; retry immediately on 10583 // server failure, and ignore all other codes such as refusal. 10584 switch (reply[3] & 15) { 10585 0, 3 => { 10586 answer.* = reply; 10587 answer_buffer_i += reply.len; 10588 answers_remaining -= 1; 10589 if (answer_buffer.len - answer_buffer_i == 0) break :send; 10590 if (answers_remaining == 0) break :send; 10591 }, 10592 2 => { 10593 var retry_message: Io.net.OutgoingMessage = .{ 10594 .address = ns, 10595 .data_ptr = query.ptr, 10596 .data_len = query.len, 10597 }; 10598 _ = netSendPosix(t, socket.handle, (&retry_message)[0..1], .{}); 10599 continue; 10600 }, 10601 else => continue, 10602 } 10603 } 10604 if (recv_err) |err| switch (err) { 10605 error.Canceled => return error.Canceled, 10606 error.Timeout => continue :send, 10607 else => continue, 10608 }; 10609 } 10610 } else { 10611 return error.NameServerFailure; 10612 } 10613 10614 var addresses_len: usize = 0; 10615 var canonical_name: ?HostName = null; 10616 10617 for (answers) |answer| { 10618 var it = HostName.DnsResponse.init(answer) catch { 10619 // Here we could potentially add diagnostics to the results queue. 10620 continue; 10621 }; 10622 while (it.next() catch { 10623 // Here we could potentially add diagnostics to the results queue. 10624 continue; 10625 }) |record| switch (record.rr) { 10626 .A => { 10627 const data = record.packet[record.data_off..][0..record.data_len]; 10628 if (data.len != 4) return error.InvalidDnsARecord; 10629 try resolved.putOne(t_io, .{ .address = .{ .ip4 = .{ 10630 .bytes = data[0..4].*, 10631 .port = options.port, 10632 } } }); 10633 addresses_len += 1; 10634 }, 10635 .AAAA => { 10636 const data = record.packet[record.data_off..][0..record.data_len]; 10637 if (data.len != 16) return error.InvalidDnsAAAARecord; 10638 try resolved.putOne(t_io, .{ .address = .{ .ip6 = .{ 10639 .bytes = data[0..16].*, 10640 .port = options.port, 10641 } } }); 10642 addresses_len += 1; 10643 }, 10644 .CNAME => { 10645 _, canonical_name = HostName.expand(record.packet, record.data_off, options.canonical_name_buffer) catch 10646 return error.InvalidDnsCnameRecord; 10647 }, 10648 _ => continue, 10649 }; 10650 } 10651 10652 try resolved.putOne(t_io, .{ .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name } }); 10653 if (addresses_len == 0) return error.NameServerFailure; 10654 } 10655 10656 fn lookupHosts( 10657 t: *Threaded, 10658 host_name: HostName, 10659 resolved: *Io.Queue(HostName.LookupResult), 10660 options: HostName.LookupOptions, 10661 ) !void { 10662 const t_io = io(t); 10663 const file = Dir.openFileAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) { 10664 error.FileNotFound, 10665 error.NotDir, 10666 error.AccessDenied, 10667 => return error.UnknownHostName, 10668 10669 error.Canceled => |e| return e, 10670 10671 else => { 10672 // Here we could add more detailed diagnostics to the results queue. 10673 return error.DetectingNetworkConfigurationFailed; 10674 }, 10675 }; 10676 defer file.close(t_io); 10677 10678 var line_buf: [512]u8 = undefined; 10679 var file_reader = file.reader(t_io, &line_buf); 10680 return lookupHostsReader(t, host_name, resolved, options, &file_reader.interface) catch |err| switch (err) { 10681 error.ReadFailed => switch (file_reader.err.?) { 10682 error.Canceled => |e| return e, 10683 else => { 10684 // Here we could add more detailed diagnostics to the results queue. 10685 return error.DetectingNetworkConfigurationFailed; 10686 }, 10687 }, 10688 error.Canceled, 10689 error.Closed, 10690 error.UnknownHostName, 10691 => |e| return e, 10692 }; 10693 } 10694 10695 fn lookupHostsReader( 10696 t: *Threaded, 10697 host_name: HostName, 10698 resolved: *Io.Queue(HostName.LookupResult), 10699 options: HostName.LookupOptions, 10700 reader: *Io.Reader, 10701 ) error{ ReadFailed, Canceled, UnknownHostName, Closed }!void { 10702 const t_io = io(t); 10703 var addresses_len: usize = 0; 10704 var canonical_name: ?HostName = null; 10705 while (true) { 10706 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { 10707 error.StreamTooLong => { 10708 // Skip lines that are too long. 10709 _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) { 10710 error.EndOfStream => break, 10711 error.ReadFailed => return error.ReadFailed, 10712 }; 10713 continue; 10714 }, 10715 error.ReadFailed => return error.ReadFailed, 10716 error.EndOfStream => break, 10717 }; 10718 reader.toss(1); 10719 var split_it = std.mem.splitScalar(u8, line, '#'); 10720 const no_comment_line = split_it.first(); 10721 10722 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); 10723 const ip_text = line_it.next() orelse continue; 10724 var first_name_text: ?[]const u8 = null; 10725 while (line_it.next()) |name_text| { 10726 if (std.mem.eql(u8, name_text, host_name.bytes)) { 10727 if (first_name_text == null) first_name_text = name_text; 10728 break; 10729 } 10730 } else continue; 10731 10732 if (canonical_name == null) { 10733 if (HostName.init(first_name_text.?)) |name_text| { 10734 if (name_text.bytes.len <= options.canonical_name_buffer.len) { 10735 const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len]; 10736 @memcpy(canonical_name_dest, name_text.bytes); 10737 canonical_name = .{ .bytes = canonical_name_dest }; 10738 } 10739 } else |_| {} 10740 } 10741 10742 if (options.family != .ip6) { 10743 if (IpAddress.parseIp4(ip_text, options.port)) |addr| { 10744 try resolved.putOne(t_io, .{ .address = addr }); 10745 addresses_len += 1; 10746 } else |_| {} 10747 } 10748 if (options.family != .ip4) { 10749 if (IpAddress.parseIp6(ip_text, options.port)) |addr| { 10750 try resolved.putOne(t_io, .{ .address = addr }); 10751 addresses_len += 1; 10752 } else |_| {} 10753 } 10754 } 10755 10756 if (canonical_name) |canon_name| try resolved.putOne(t_io, .{ .canonical_name = canon_name }); 10757 if (addresses_len == 0) return error.UnknownHostName; 10758 } 10759 10760 /// Writes DNS resolution query packet data to `w`; at most 280 bytes. 10761 fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: HostName.DnsRecord, entropy: [2]u8) usize { 10762 // This implementation is ported from musl libc. 10763 // A more idiomatic "ziggy" implementation would be welcome. 10764 var name = dname; 10765 if (std.mem.endsWith(u8, name, ".")) name.len -= 1; 10766 assert(name.len <= 253); 10767 const n = 17 + name.len + @intFromBool(name.len != 0); 10768 10769 // Construct query template - ID will be filled later 10770 q[0..2].* = entropy; 10771 @memset(q[2..n], 0); 10772 q[2] = @as(u8, op) * 8 + 1; 10773 q[5] = 1; 10774 @memcpy(q[13..][0..name.len], name); 10775 var i: usize = 13; 10776 var j: usize = undefined; 10777 while (q[i] != 0) : (i = j + 1) { 10778 j = i; 10779 while (q[j] != 0 and q[j] != '.') : (j += 1) {} 10780 // TODO determine the circumstances for this and whether or 10781 // not this should be an error. 10782 if (j - i - 1 > 62) unreachable; 10783 q[i - 1] = @intCast(j - i); 10784 } 10785 q[i + 1] = @intFromEnum(ty); 10786 q[i + 3] = class; 10787 return n; 10788 } 10789 10790 fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) HostName { 10791 const dest = canonical_name_buffer[0..name.len]; 10792 @memcpy(dest, name); 10793 return .{ .bytes = dest }; 10794 } 10795 10796 /// Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it: 10797 /// https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6 10798 /// 10799 /// This XNU version appears to correspond to 11.0.1: 10800 /// https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html 10801 /// 10802 /// ulock_wait() uses 32-bit micro-second timeouts where 0 = INFINITE or no-timeout 10803 /// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) 10804 const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11; 10805 10806 fn closeSocketWindows(s: ws2_32.SOCKET) void { 10807 const rc = ws2_32.closesocket(s); 10808 if (is_debug) switch (rc) { 10809 0 => {}, 10810 ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) { 10811 else => recoverableOsBugDetected(), 10812 }, 10813 else => recoverableOsBugDetected(), 10814 }; 10815 } 10816 10817 const Wsa = struct { 10818 status: Status = .uninitialized, 10819 mutex: Io.Mutex = .init, 10820 init_error: ?Wsa.InitError = null, 10821 10822 const Status = enum { uninitialized, initialized, failure }; 10823 10824 const InitError = error{ 10825 ProcessFdQuotaExceeded, 10826 NetworkDown, 10827 VersionUnsupported, 10828 BlockingOperationInProgress, 10829 } || Io.UnexpectedError; 10830 }; 10831 10832 fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { 10833 const t_io = io(t); 10834 const wsa = &t.wsa; 10835 try wsa.mutex.lock(t_io); 10836 defer wsa.mutex.unlock(t_io); 10837 switch (wsa.status) { 10838 .uninitialized => { 10839 var wsa_data: ws2_32.WSADATA = undefined; 10840 const minor_version = 2; 10841 const major_version = 2; 10842 switch (ws2_32.WSAStartup((@as(windows.WORD, minor_version) << 8) | major_version, &wsa_data)) { 10843 0 => { 10844 wsa.status = .initialized; 10845 return; 10846 }, 10847 else => |err_int| { 10848 wsa.status = .failure; 10849 wsa.init_error = switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) { 10850 .SYSNOTREADY => error.NetworkDown, 10851 .VERNOTSUPPORTED => error.VersionUnsupported, 10852 .EINPROGRESS => error.BlockingOperationInProgress, 10853 .EPROCLIM => error.ProcessFdQuotaExceeded, 10854 else => |err| windows.unexpectedWSAError(err), 10855 }; 10856 }, 10857 } 10858 }, 10859 .initialized => return, 10860 .failure => {}, 10861 } 10862 return error.NetworkDown; 10863 } 10864 10865 fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} 10866 10867 test { 10868 _ = @import("Threaded/test.zig"); 10869 }