blob bd40bdf4 (775880B) - 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 process = std.process; 17 const Allocator = std.mem.Allocator; 18 const Alignment = std.mem.Alignment; 19 const assert = std.debug.assert; 20 const posix = std.posix; 21 const windows = std.os.windows; 22 const ws2_32 = windows.ws2_32; 23 24 /// Thread-safe. 25 /// 26 /// Used for: 27 /// * allocating `Io.Future` and `Io.Group` closures. 28 /// * formatting spawning child processes 29 /// * scanning environment variables on some targets 30 /// * memory-mapping when mmap or equivalent is not available 31 allocator: Allocator, 32 mutex: Io.Mutex = .init, 33 cond: Io.Condition = .init, 34 run_queue: std.SinglyLinkedList = .{}, 35 join_requested: bool = false, 36 stack_size: usize, 37 /// All threads are spawned detached; this is how we wait until they all exit. 38 wait_group: WaitGroup = .init, 39 async_limit: Io.Limit, 40 concurrent_limit: Io.Limit = .unlimited, 41 /// Error from calling `std.Thread.getCpuCount` in `init`. 42 cpu_count_error: ?std.Thread.CpuCountError, 43 /// Number of threads that are unavailable to take tasks. To calculate 44 /// available count, subtract this from either `async_limit` or 45 /// `concurrent_limit`. 46 busy_count: usize = 0, 47 worker_threads: std.atomic.Value(?*Thread), 48 pid: Pid = .unknown, 49 50 wsa: if (is_windows) Wsa else struct {} = .{}, 51 52 have_signal_handler: bool, 53 old_sig_io: if (have_sig_io) posix.Sigaction else void, 54 old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, 55 56 use_sendfile: UseSendfile = .default, 57 use_copy_file_range: UseCopyFileRange = .default, 58 use_fcopyfile: UseFcopyfile = .default, 59 use_fchmodat2: UseFchmodat2 = .default, 60 disable_memory_mapping: bool, 61 62 stderr_writer: File.Writer = .{ 63 .io = undefined, 64 .interface = File.Writer.initInterface(&.{}), 65 .file = if (is_windows) undefined else .stderr(), 66 .mode = .streaming, 67 }, 68 stderr_mode: Io.Terminal.Mode = .no_color, 69 stderr_writer_initialized: bool = false, 70 stderr_mutex: Io.Mutex = .init, 71 stderr_mutex_locker: std.Thread.Id = Thread.invalid_id, 72 stderr_mutex_lock_count: usize = 0, 73 74 argv0: Argv0, 75 /// Protected by `mutex`. Determines whether `environ` has been 76 /// memoized based on `process_environ`. 77 environ_initialized: bool, 78 environ: Environ, 79 80 null_file: NullFile = .{}, 81 random_file: RandomFile = .{}, 82 pipe_file: PipeFile = .{}, 83 84 csprng: Csprng = .uninitialized, 85 86 system_basic_information: SystemBasicInformation = .{}, 87 88 const SystemBasicInformation = if (!is_windows) struct {} else struct { 89 buffer: windows.SYSTEM.BASIC_INFORMATION = undefined, 90 initialized: std.atomic.Value(bool) = .{ .raw = false }, 91 }; 92 93 pub const Csprng = struct { 94 rng: std.Random.DefaultCsprng, 95 96 pub const uninitialized: Csprng = .{ .rng = .{ 97 .state = undefined, 98 .offset = std.math.maxInt(usize), 99 } }; 100 101 pub const seed_len = std.Random.DefaultCsprng.secret_seed_length; 102 103 pub fn isInitialized(c: *const Csprng) bool { 104 return c.rng.offset != std.math.maxInt(usize); 105 } 106 }; 107 108 pub const Argv0 = switch (native_os) { 109 .openbsd, .haiku => struct { 110 value: ?[*:0]const u8, 111 112 pub const empty: Argv0 = .{ .value = null }; 113 114 pub fn init(args: process.Args) Argv0 { 115 return .{ .value = args.vector[0] }; 116 } 117 }, 118 else => struct { 119 pub const empty: Argv0 = .{}; 120 121 pub fn init(args: process.Args) Argv0 { 122 _ = args; 123 return .{}; 124 } 125 }, 126 }; 127 128 pub const Environ = struct { 129 /// Unmodified data directly from the OS. 130 process_environ: process.Environ, 131 /// Protected by `mutex`. Memoized based on `process_environ`. Tracks whether the 132 /// environment variables are present, ignoring their value. 133 exist: Exist = .{}, 134 /// Protected by `mutex`. Memoized based on `process_environ`. 135 string: String = .{}, 136 /// ZIG_PROGRESS 137 zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing, 138 /// Protected by `mutex`. Tracks the problem, if any, that occurred when 139 /// trying to scan environment variables. 140 /// 141 /// Errors are only possible on WASI. 142 err: ?Error = null, 143 144 pub const empty: Environ = .{ .process_environ = .empty }; 145 146 pub const Error = Allocator.Error || Io.UnexpectedError; 147 148 pub const Exist = struct { 149 NO_COLOR: bool = false, 150 CLICOLOR_FORCE: bool = false, 151 }; 152 153 pub const String = switch (native_os) { 154 .windows, .wasi => struct {}, 155 else => struct { 156 PATH: ?[:0]const u8 = null, 157 DEBUGINFOD_CACHE_PATH: ?[:0]const u8 = null, 158 XDG_CACHE_HOME: ?[:0]const u8 = null, 159 HOME: ?[:0]const u8 = null, 160 }, 161 }; 162 163 pub fn scan(environ: *Environ, allocator: Allocator) void { 164 if (is_windows) { 165 // This value expires with any call that modifies the environment, 166 // which is outside of this Io implementation's control, so references 167 // must be short-lived. 168 const peb = windows.peb(); 169 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); 170 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); 171 const ptr = peb.ProcessParameters.Environment; 172 173 var i: usize = 0; 174 while (ptr[i] != 0) { 175 // There are some special environment variables that start with =, 176 // so we need a special case to not treat = as a key/value separator 177 // if it's the first character. 178 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 179 const key_start = i; 180 if (ptr[i] == '=') i += 1; 181 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} 182 const key_w = ptr[key_start..i]; 183 184 const value_start = i + 1; 185 while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value 186 const value_w = ptr[value_start..i]; 187 i += 1; // skip over null byte 188 189 if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) { 190 environ.exist.NO_COLOR = true; 191 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) { 192 environ.exist.CLICOLOR_FORCE = true; 193 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) { 194 environ.zig_progress_file = file: { 195 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; 196 const len = std.unicode.calcWtf8Len(value_w); 197 if (len > value_buf.len) break :file error.UnrecognizedFormat; 198 assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len); 199 break :file .{ 200 .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch 201 break :file error.UnrecognizedFormat), 202 .flags = .{ .nonblocking = true }, 203 }; 204 }; 205 } 206 comptime assert(@sizeOf(String) == 0); 207 } 208 } else if (native_os == .wasi and !builtin.link_libc) { 209 var environ_size: usize = undefined; 210 var environ_buf_size: usize = undefined; 211 212 switch (std.os.wasi.environ_sizes_get(&environ_size, &environ_buf_size)) { 213 .SUCCESS => {}, 214 else => |err| { 215 environ.err = posix.unexpectedErrno(err); 216 return; 217 }, 218 } 219 if (environ_size == 0) return; 220 221 const wasi_environ = allocator.alloc([*:0]u8, environ_size) catch |err| { 222 environ.err = err; 223 return; 224 }; 225 defer allocator.free(wasi_environ); 226 const wasi_environ_buf = allocator.alloc(u8, environ_buf_size) catch |err| { 227 environ.err = err; 228 return; 229 }; 230 defer allocator.free(wasi_environ_buf); 231 232 switch (std.os.wasi.environ_get(wasi_environ.ptr, wasi_environ_buf.ptr)) { 233 .SUCCESS => {}, 234 else => |err| { 235 environ.err = posix.unexpectedErrno(err); 236 return; 237 }, 238 } 239 240 for (wasi_environ) |env| { 241 const pair = std.mem.sliceTo(env, 0); 242 var parts = std.mem.splitScalar(u8, pair, '='); 243 const key = parts.first(); 244 if (std.mem.eql(u8, key, "NO_COLOR")) { 245 environ.exist.NO_COLOR = true; 246 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { 247 environ.exist.CLICOLOR_FORCE = true; 248 } 249 comptime assert(@sizeOf(String) == 0); 250 } 251 } else { 252 for (environ.process_environ.block.slice) |opt_entry| { 253 const entry = opt_entry.?; 254 var entry_i: usize = 0; 255 while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {} 256 const key = entry[0..entry_i]; 257 258 var end_i: usize = entry_i; 259 while (entry[end_i] != 0) : (end_i += 1) {} 260 const value = entry[entry_i + 1 .. end_i :0]; 261 262 if (std.mem.eql(u8, key, "NO_COLOR")) { 263 environ.exist.NO_COLOR = true; 264 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { 265 environ.exist.CLICOLOR_FORCE = true; 266 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { 267 environ.zig_progress_file = file: { 268 break :file .{ 269 .handle = std.fmt.parseInt(u31, value, 10) catch 270 break :file error.UnrecognizedFormat, 271 .flags = .{ .nonblocking = true }, 272 }; 273 }; 274 } else inline for (@typeInfo(String).@"struct".fields) |field| { 275 if (std.mem.eql(u8, key, field.name)) @field(environ.string, field.name) = value; 276 } 277 } 278 } 279 } 280 }; 281 282 pub const NullFile = switch (native_os) { 283 .windows => struct { 284 handle: ?windows.HANDLE = null, 285 286 fn deinit(this: *@This()) void { 287 if (this.handle) |handle| { 288 windows.CloseHandle(handle); 289 this.handle = null; 290 } 291 } 292 }, 293 .wasi, .ios, .tvos, .visionos, .watchos => struct { 294 fn deinit(this: @This()) void { 295 _ = this; 296 } 297 }, 298 else => struct { 299 fd: posix.fd_t = -1, 300 301 fn deinit(this: *@This()) void { 302 if (this.fd >= 0) { 303 closeFd(this.fd); 304 this.fd = -1; 305 } 306 } 307 }, 308 }; 309 310 pub const RandomFile = switch (native_os) { 311 .windows => NullFile, 312 else => if (use_dev_urandom) NullFile else struct { 313 fn deinit(this: @This()) void { 314 _ = this; 315 } 316 }, 317 }; 318 319 pub const PipeFile = switch (native_os) { 320 .windows => struct { 321 handle: ?windows.HANDLE = null, 322 323 fn deinit(this: *@This()) void { 324 if (this.handle) |handle| { 325 windows.CloseHandle(handle); 326 this.handle = null; 327 } 328 } 329 }, 330 else => struct { 331 fn deinit(this: @This()) void { 332 _ = this; 333 } 334 }, 335 }; 336 337 pub const Pid = if (native_os == .linux) enum(posix.pid_t) { 338 unknown = 0, 339 _, 340 } else enum(u0) { unknown = 0 }; 341 342 pub const UseSendfile = if (have_sendfile) enum { 343 enabled, 344 disabled, 345 pub const default: UseSendfile = .enabled; 346 } else enum { 347 disabled, 348 pub const default: UseSendfile = .disabled; 349 }; 350 351 pub const UseCopyFileRange = if (have_copy_file_range) enum { 352 enabled, 353 disabled, 354 pub const default: UseCopyFileRange = .enabled; 355 } else enum { 356 disabled, 357 pub const default: UseCopyFileRange = .disabled; 358 }; 359 360 pub const UseFcopyfile = if (have_fcopyfile) enum { 361 enabled, 362 disabled, 363 pub const default: UseFcopyfile = .enabled; 364 } else enum { 365 disabled, 366 pub const default: UseFcopyfile = .disabled; 367 }; 368 369 pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum { 370 enabled, 371 disabled, 372 pub const default: UseFchmodat2 = .enabled; 373 } else enum { 374 disabled, 375 pub const default: UseFchmodat2 = .disabled; 376 }; 377 378 const Runnable = struct { 379 node: std.SinglyLinkedList.Node, 380 startFn: *const fn (*Runnable, *Thread, *Threaded) void, 381 }; 382 383 const Group = struct { 384 ptr: *Io.Group, 385 386 /// Returns a correctly-typed pointer to the `Io.Group.token` field. 387 /// 388 /// The status indicates how many pending tasks are in the group, whether the group has been 389 /// canceled, and whether the group has been awaited. 390 /// 391 /// Note that the zero value of `Status` intentionally represents the initial group state (empty 392 /// with no awaiters). This is a requirement of `Io.Group`. 393 fn status(g: Group) *std.atomic.Value(Status) { 394 return @ptrCast(&g.ptr.token); 395 } 396 /// Returns a correctly-typed pointer to the `Io.Group.state` field. The double-pointer here is 397 /// intentional, because the `state` field itself stores a pointer, and this function returns a 398 /// pointer to that field. 399 /// 400 /// On completion of the whole group, if `status` indicates that there is an awaiter, the last 401 /// task must increment this `u32` and do a futex wake on it to signal that awaiter. 402 fn awaiter(g: Group) **std.atomic.Value(u32) { 403 return @ptrCast(&g.ptr.state); 404 } 405 406 const Status = packed struct(usize) { 407 num_running: @Int(.unsigned, @bitSizeOf(usize) - 2), 408 have_awaiter: bool, 409 canceled: bool, 410 }; 411 412 const Task = struct { 413 runnable: Runnable, 414 group: *Io.Group, 415 func: *const fn (context: *const anyopaque) void, 416 context_alignment: Alignment, 417 alloc_len: usize, 418 419 /// `Task.runnable.node` is `undefined` in the created `Task`. 420 fn create( 421 gpa: Allocator, 422 group: Group, 423 context: []const u8, 424 context_alignment: Alignment, 425 func: *const fn (context: *const anyopaque) void, 426 ) Allocator.Error!*Task { 427 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task); 428 const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment); 429 const alloc_len = worst_case_context_offset + context.len; 430 431 const task: *Task = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Task), alloc_len))); 432 errdefer comptime unreachable; 433 434 task.* = .{ 435 .runnable = .{ 436 .node = undefined, 437 .startFn = &start, 438 }, 439 .group = group.ptr, 440 .func = func, 441 .context_alignment = context_alignment, 442 .alloc_len = alloc_len, 443 }; 444 @memcpy(task.contextPointer()[0..context.len], context); 445 return task; 446 } 447 448 fn destroy(task: *Task, gpa: Allocator) void { 449 const base: [*]align(@alignOf(Task)) u8 = @ptrCast(task); 450 gpa.free(base[0..task.alloc_len]); 451 } 452 453 fn contextPointer(task: *Task) [*]u8 { 454 const base: [*]u8 = @ptrCast(task); 455 const offset = task.context_alignment.forward(@intFromPtr(base) + @sizeOf(Task)) - @intFromPtr(base); 456 return base + offset; 457 } 458 459 fn start(r: *Runnable, thread: *Thread, t: *Threaded) void { 460 const task: *Task = @fieldParentPtr("runnable", r); 461 const group: Group = .{ .ptr = task.group }; 462 463 // This would be a simple store, but it's upgraded to an RMW so we can use `.acquire` to 464 // enforce the ordering between this and the `group.status().load` below. Paired with 465 // the `.release` rmw on `Thread.status` in `cancelThreads`, this creates a StoreLoad 466 // barrier which guarantees that when a group is canceled, either we see the cancelation 467 // in the group status, or the canceler sees our thread status so can directly notify us 468 // of the cancelation. 469 _ = thread.status.swap(.{ 470 .cancelation = .none, 471 .awaitable = .fromGroup(group.ptr), 472 }, .acquire); 473 if (group.status().load(.monotonic).canceled) { 474 thread.status.store(.{ 475 .cancelation = .canceling, 476 .awaitable = .fromGroup(group.ptr), 477 }, .monotonic); 478 } 479 480 task.func(task.contextPointer()); 481 482 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); 483 const old_status = group.status().fetchSub(.{ 484 .num_running = 1, 485 .have_awaiter = false, 486 .canceled = false, 487 }, .acq_rel); // acquire `group.awaiter()`, release task results 488 assert(old_status.num_running > 0); 489 if (old_status.have_awaiter and old_status.num_running == 1) { 490 const to_signal = group.awaiter().*; 491 // `awaiter` should only be modified by us. For another thread to see `num_running` 492 // drop to 0 after this point would indicate that another task started up, meaning 493 // `async`/`cancel` was racing with awaited group completion. 494 group.awaiter().* = undefined; 495 _ = to_signal.fetchAdd(1, .release); // release results 496 Thread.futexWake(&to_signal.raw, 1); 497 } 498 499 // Task completed. Self-destruct sequence initiated. 500 task.destroy(t.allocator); 501 } 502 }; 503 504 /// Assumes the caller has already atomically updated the group status to indicate cancelation, 505 /// and notifies any already-running threads of this cancelation. 506 fn cancelThreads(g: Group, t: *Threaded) bool { 507 var any_blocked = false; 508 var it = t.worker_threads.load(.acquire); // acquire `Thread` values 509 while (it) |thread| : (it = thread.next) { 510 // This non-mutating RMW exists for ordering reasons: see comment in `Group.Task.start` for reasons. 511 _ = thread.status.fetchOr(.{ .cancelation = @enumFromInt(0), .awaitable = .null }, .release); 512 if (thread.cancelAwaitable(.fromGroup(g.ptr))) any_blocked = true; 513 } 514 return any_blocked; 515 } 516 517 /// Uses `Thread.signalCanceledSyscall` to signal any threads which are still blocked in a 518 /// syscall for this group and have not observed a cancelation request yet. Returns `true` if 519 /// more signals may be necessary, in which case the caller must call this again after a delay. 520 fn signalAllCanceledSyscalls(g: Group, t: *Threaded) bool { 521 var any_signaled = false; 522 var it = t.worker_threads.load(.acquire); // acquire `Thread` values 523 while (it) |thread| : (it = thread.next) { 524 if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr))) any_signaled = true; 525 } 526 return any_signaled; 527 } 528 529 /// The caller has canceled `g`. Inform any threads working on that group of the cancelation if 530 /// necessary, and wait for `g` to finish (indicated by `num_completed` being incremented from 0 531 /// to 1), while sending regular signals to threads if necessary for them to unblock from any 532 /// cancelable syscalls. 533 /// 534 /// `skip_signals` means it is already known that no threads are currently working on the group 535 /// so no notifications or signals are necessary. 536 fn waitForCancelWithSignaling( 537 g: Group, 538 t: *Threaded, 539 num_completed: *std.atomic.Value(u32), 540 skip_signals: bool, 541 ) void { 542 var need_signal: bool = !skip_signals and g.cancelThreads(t); 543 var timeout_ns: u64 = 1 << 10; 544 while (true) { 545 need_signal = need_signal and g.signalAllCanceledSyscalls(t); 546 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); 547 switch (num_completed.load(.acquire)) { // acquire task results 548 0 => {}, 549 1 => break, 550 else => unreachable, 551 } 552 timeout_ns <<|= 1; 553 } 554 } 555 }; 556 557 /// Trailing data: 558 /// 1. context 559 /// 2. result 560 const Future = struct { 561 runnable: Runnable, 562 func: *const fn (context: *const anyopaque, result: *anyopaque) void, 563 status: std.atomic.Value(Status), 564 /// On completion, increment this `u32` and do a futex wake on it. 565 awaiter: *std.atomic.Value(u32), 566 context_alignment: Alignment, 567 result_offset: usize, 568 alloc_len: usize, 569 570 const Status = packed struct(usize) { 571 /// The values of this enum are chosen so that await/cancel can just OR with 0b01 and 0b11 572 /// respectively. That *does* clobber `.done`, but that's actually fine, because if the tag 573 /// is `.done` then only the awaiter is referencing this `Future` anyway. 574 tag: enum(u2) { 575 /// The future is queued or running (depending on whether `thread` is set). 576 pending = 0b00, 577 /// Like `pending`, but the future is being awaited. `Future.awaiter` is populated. 578 pending_awaited = 0b01, 579 /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated. 580 pending_canceled = 0b11, 581 /// The future has already completed. `thread` is `.null`, unless the future terminated 582 /// with an acknowledged cancel request, in which case `thread` is `.all_ones`. 583 done = 0b10, 584 }, 585 /// When the future begins execution, this is atomically updated from `null` to the thread running the 586 /// `Future`, so that cancelation knows which thread to cancel. 587 thread: Thread.PackedPtr, 588 }; 589 590 /// `Future.runnable.node` is `undefined` in the created `Future`. 591 fn create( 592 gpa: Allocator, 593 result_len: usize, 594 result_alignment: Alignment, 595 context: []const u8, 596 context_alignment: Alignment, 597 func: *const fn (context: *const anyopaque, result: *anyopaque) void, 598 ) Allocator.Error!*Future { 599 const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Future); 600 const worst_case_context_offset = context_alignment.forward(@sizeOf(Future) + max_context_misalignment); 601 const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len); 602 const alloc_len = worst_case_result_offset + result_len; 603 604 const future: *Future = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Future), alloc_len))); 605 errdefer comptime unreachable; 606 607 const actual_context_addr = context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)); 608 const actual_result_addr = result_alignment.forward(actual_context_addr + context.len); 609 const actual_result_offset = actual_result_addr - @intFromPtr(future); 610 future.* = .{ 611 .runnable = .{ 612 .node = undefined, 613 .startFn = &start, 614 }, 615 .func = func, 616 .status = .init(.{ 617 .tag = .pending, 618 .thread = .null, 619 }), 620 .awaiter = undefined, 621 .context_alignment = context_alignment, 622 .result_offset = actual_result_offset, 623 .alloc_len = alloc_len, 624 }; 625 @memcpy(future.contextPointer()[0..context.len], context); 626 return future; 627 } 628 629 fn destroy(future: *Future, gpa: Allocator) void { 630 const base: [*]align(@alignOf(Future)) u8 = @ptrCast(future); 631 gpa.free(base[0..future.alloc_len]); 632 } 633 634 fn resultPointer(future: *Future) [*]u8 { 635 const base: [*]u8 = @ptrCast(future); 636 return base + future.result_offset; 637 } 638 639 fn contextPointer(future: *Future) [*]u8 { 640 const base: [*]u8 = @ptrCast(future); 641 const context_offset = future.context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)) - @intFromPtr(future); 642 return base + context_offset; 643 } 644 645 fn start(r: *Runnable, thread: *Thread, t: *Threaded) void { 646 _ = t; 647 const future: *Future = @fieldParentPtr("runnable", r); 648 649 thread.status.store(.{ 650 .cancelation = .none, 651 .awaitable = .fromFuture(future), 652 }, .monotonic); 653 { 654 const old_status = future.status.fetchOr(.{ 655 .tag = .pending, 656 .thread = .pack(thread), 657 }, .release); 658 assert(old_status.thread == .null); 659 switch (old_status.tag) { 660 .pending, .pending_awaited => {}, 661 .pending_canceled => thread.status.store(.{ 662 .cancelation = .canceling, 663 .awaitable = .fromFuture(future), 664 }, .monotonic), 665 .done => unreachable, 666 } 667 } 668 669 future.func(future.contextPointer(), future.resultPointer()); 670 671 const had_acknowledged_cancel = switch (thread.status.load(.monotonic).cancelation) { 672 .none, .canceling => false, 673 .canceled => true, 674 .parked => unreachable, 675 .blocked => unreachable, 676 .blocked_alertable => unreachable, 677 .blocked_alertable_canceling => unreachable, 678 .blocked_canceling => unreachable, 679 }; 680 thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); 681 const old_status = future.status.swap(.{ 682 .tag = .done, 683 .thread = if (had_acknowledged_cancel) .all_ones else .null, 684 }, .acq_rel); // acquire `future.awaiter`, release results 685 switch (old_status.tag) { 686 .pending => {}, 687 .pending_awaited, .pending_canceled => { 688 const to_signal = future.awaiter; 689 _ = to_signal.fetchAdd(1, .release); // release results 690 Thread.futexWake(&to_signal.raw, 1); 691 }, 692 .done => unreachable, 693 } 694 } 695 696 /// The caller has canceled `future`. `thread` is the thread currently running that future. 697 /// Inform `thread` of the cancelation if necessary, and wait for `future` to finish (indicated 698 /// by `num_completed` being incremented from 0 to 1), while sending regular signals to `thread` 699 /// if necessary for it to unblock from a cancelable syscall. 700 fn waitForCancelWithSignaling( 701 future: *Future, 702 t: *Threaded, 703 num_completed: *std.atomic.Value(u32), 704 thread: ?*Thread, 705 ) void { 706 var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false; 707 var timeout_ns: u64 = 1 << 10; 708 while (true) { 709 need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)); 710 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); 711 switch (num_completed.load(.acquire)) { // acquire task results 712 0 => {}, 713 1 => break, 714 else => unreachable, 715 } 716 timeout_ns <<|= 1; 717 } 718 } 719 }; 720 721 /// A sequence of (ptr_bit_width - 3) bits which uniquely identifies a group or future. The bits are 722 /// the MSBs of the `*Io.Group` or `*Future`. These things do not necessarily have 3 zero bits at 723 /// the end (they are pointer-aligned, so on 32-bit targets only have 2), but because they both have 724 /// a *size* of at least 8 bytes, no two groups/futures in memory at the same time will have the 725 /// same value for all of these bits. In other words, given a group/future pointer, the next group 726 /// or future must be at least 8 bytes later, so its address will have a different value for one of 727 /// the top (ptr_bit_width - 3) bits. 728 const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) { 729 comptime { 730 assert(@sizeOf(Future) >= 8); 731 assert(@sizeOf(Io.Group) >= 8); 732 } 733 null = 0, 734 all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 3)), 735 _, 736 const Split = packed struct(usize) { low: u3, high: AwaitableId }; 737 fn fromGroup(g: *Io.Group) AwaitableId { 738 const split: Split = @bitCast(@intFromPtr(g)); 739 return split.high; 740 } 741 fn fromFuture(f: *Future) AwaitableId { 742 const split: Split = @bitCast(@intFromPtr(f)); 743 return split.high; 744 } 745 }; 746 747 const Thread = struct { 748 next: ?*Thread, 749 750 id: std.Thread.Id, 751 handle: Handle, 752 753 status: std.atomic.Value(Status), 754 755 cancel_protection: Io.CancelProtection, 756 /// Always released when `Status.cancelation` is set to `.parked`. 757 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, 758 unpark_flag: UnparkFlag, 759 760 csprng: Csprng, 761 762 const Handle = Handle: { 763 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; 764 if (is_windows) break :Handle windows.HANDLE; 765 break :Handle void; 766 }; 767 768 const Status = packed struct(usize) { 769 /// The specific values of these enum fields are chosen to simplify the implementation of 770 /// the transformations we need to apply to this state. 771 cancelation: enum(u3) { 772 /// The thread has not yet been canceled, and is not in a cancelable operation. 773 /// To request cancelation, just set the status to `.canceling`. 774 none = 0b000, 775 776 /// The thread is parked in a cancelable futex wait or sleep. 777 /// Only applicable if `use_parking_futex` or `use_parking_sleep`. 778 /// To request cancelation, set the status to `.canceling` and unpark the thread. 779 /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread. 780 parked = 0b001, 781 782 /// The thread is blocked in a cancelable system call. 783 /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. 784 blocked = 0b011, 785 786 /// Windows-only: the thread is blocked in an alertable wait via 787 /// `NtDelayExecution`. To request cancelation, set the status to 788 /// `blocked_alertable_canceling` and repeatedly alert the thread 789 /// until the status changes. 790 blocked_alertable = 0b010, 791 792 /// The thread has an outstanding cancelation request but is not in a cancelable operation. 793 /// When it acknowledges the cancelation, it will set the status to `.canceled`. 794 canceling = 0b110, 795 796 /// The thread has received and acknowledged a cancelation request. 797 /// If `recancel` is called, the status will revert to `.canceling`, but otherwise, the status 798 /// will not change for the remainder of this task's execution. 799 canceled = 0b111, 800 801 /// The thread is blocked in a cancelable system call, and is being 802 /// canceled. The thread which triggered the cancelation will send 803 /// signals to this thread until its status changes. 804 blocked_canceling = 0b101, 805 806 /// Windows-only: the thread is blocked in an alertable wait via 807 /// `NtDelayExecution`, and is being canceled. The thread which 808 /// triggered the cancelation will send signals to this thread 809 /// until its status changes. 810 blocked_alertable_canceling = 0b100, 811 }, 812 813 /// We cannot turn this value back into a pointer. Instead, it exists so that a task can be 814 /// canceled by a cmpxchg on thread status: if it is running the task we want to cancel, 815 /// then update the `cancelation` field. 816 awaitable: AwaitableId, 817 }; 818 819 const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; 820 821 threadlocal var current: ?*Thread = null; 822 823 /// A value that does not alias any other thread id. 824 const invalid_id: std.Thread.Id = std.math.maxInt(std.Thread.Id); 825 826 fn currentId() std.Thread.Id { 827 return if (current) |t| t.id else std.Thread.getCurrentId(); 828 } 829 830 /// The thread is neither in a syscall nor entering one, but we want to check for cancelation 831 /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`. 832 fn checkCancel() Io.Cancelable!void { 833 const thread = Thread.current orelse return; 834 switch (thread.cancel_protection) { 835 .blocked => return, 836 .unblocked => {}, 837 } 838 // Here, unlike `Syscall.checkCancel`, it's not particularly likely that we're canceled, so 839 // it seems preferable to do a cheap atomic load and, in the unlikely case, a separate store 840 // to acknowledge. Besides, the state transitions we need here can't be done with one atomic 841 // OR/AND/XOR on `Status.cancelation`, so we don't actually have any other option. 842 const status = thread.status.load(.monotonic); 843 switch (status.cancelation) { 844 .parked => unreachable, 845 .blocked => unreachable, 846 .blocked_alertable => unreachable, 847 .blocked_alertable_canceling => unreachable, 848 .blocked_canceling => unreachable, 849 .none, .canceled => {}, 850 .canceling => { 851 thread.status.store(.{ 852 .cancelation = .canceled, 853 .awaitable = status.awaitable, 854 }, .monotonic); 855 return error.Canceled; 856 }, 857 } 858 } 859 860 fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void { 861 return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable; 862 } 863 864 fn futexWait(ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void { 865 return Thread.futexWaitInner(ptr, expect, false, timeout_ns); 866 } 867 868 fn futexWaitInner(ptr: *const u32, expect: u32, uncancelable: bool, timeout_ns: ?u64) Io.Cancelable!void { 869 @branchHint(.cold); 870 871 if (builtin.single_threaded) unreachable; // nobody would ever wake us 872 873 if (use_parking_futex) { 874 return parking_futex.wait( 875 ptr, 876 expect, 877 uncancelable, 878 if (timeout_ns) |ns| .{ .duration = .{ 879 .raw = .fromNanoseconds(ns), 880 .clock = .boot, 881 } } else .none, 882 ); 883 } else if (builtin.cpu.arch.isWasm()) { 884 comptime assert(builtin.cpu.has(.wasm, .atomics)); 885 // TODO implement cancelation for WASM futex waits by signaling the futex 886 if (!uncancelable) try Thread.checkCancel(); 887 const to: i64 = if (timeout_ns) |ns| ns else -1; 888 const signed_expect: i32 = @bitCast(expect); 889 const result = asm volatile ( 890 \\local.get %[ptr] 891 \\local.get %[expected] 892 \\local.get %[timeout] 893 \\memory.atomic.wait32 0 894 \\local.set %[ret] 895 : [ret] "=r" (-> u32), 896 : [ptr] "r" (ptr), 897 [expected] "r" (signed_expect), 898 [timeout] "r" (to), 899 ); 900 switch (result) { 901 0 => {}, // ok 902 1 => {}, // expected != loaded 903 2 => {}, // timeout 904 else => assert(!is_debug), 905 } 906 } else switch (native_os) { 907 .linux => { 908 const linux = std.os.linux; 909 var ts_buffer: linux.timespec = undefined; 910 const ts: ?*linux.timespec = if (timeout_ns) |ns| ts: { 911 ts_buffer = timestampToPosix(ns); 912 break :ts &ts_buffer; 913 } else null; 914 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); 915 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts); 916 syscall.finish(); 917 switch (linux.errno(rc)) { 918 .SUCCESS => {}, // notified by `wake()` 919 .INTR => {}, // caller's responsibility to retry 920 .AGAIN => {}, // ptr.* != expect 921 .INVAL => {}, // possibly timeout overflow 922 .TIMEDOUT => {}, 923 .FAULT => recoverableOsBugDetected(), // ptr was invalid 924 else => recoverableOsBugDetected(), 925 } 926 }, 927 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 928 const c = std.c; 929 const flags: c.UL = .{ 930 .op = .COMPARE_AND_WAIT, 931 .NO_ERRNO = true, 932 }; 933 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); 934 const status = switch (darwin_supports_ulock_wait2) { 935 true => c.__ulock_wait2(flags, ptr, expect, ns: { 936 const ns = timeout_ns orelse break :ns 0; 937 if (ns == 0) break :ns 1; 938 break :ns ns; 939 }, 0), 940 false => c.__ulock_wait(flags, ptr, expect, us: { 941 const ns = timeout_ns orelse break :us 0; 942 const us = std.math.lossyCast(u32, ns / std.time.ns_per_us); 943 if (us == 0) break :us 1; 944 break :us us; 945 }), 946 }; 947 syscall.finish(); 948 if (status >= 0) return; 949 switch (@as(c.E, @enumFromInt(-status))) { 950 .INTR => {}, // spurious wake 951 // Address of the futex was paged out. This is unlikely, but possible in theory, and 952 // pthread/libdispatch on darwin bother to handle it. In this case we'll return 953 // without waiting, but the caller should retry anyway. 954 .FAULT => {}, 955 .TIMEDOUT => {}, // timeout 956 else => recoverableOsBugDetected(), 957 } 958 }, 959 .freebsd => { 960 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); 961 var tm_size: usize = 0; 962 var tm: std.c._umtx_time = undefined; 963 var tm_ptr: ?*const std.c._umtx_time = null; 964 if (timeout_ns) |ns| { 965 tm_ptr = &tm; 966 tm_size = @sizeOf(@TypeOf(tm)); 967 tm.flags = 0; // use relative time not UMTX_ABSTIME 968 tm.clockid = .MONOTONIC; 969 tm.timeout = timestampToPosix(ns); 970 } 971 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); 972 const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr)); 973 syscall.finish(); 974 if (is_debug) switch (posix.errno(rc)) { 975 .SUCCESS => {}, 976 .FAULT => unreachable, // one of the args points to invalid memory 977 .INVAL => unreachable, // arguments should be correct 978 .TIMEDOUT => {}, // timeout 979 .INTR => {}, // spurious wake 980 else => unreachable, 981 }; 982 }, 983 .openbsd => { 984 var tm: std.c.timespec = undefined; 985 var tm_ptr: ?*const std.c.timespec = null; 986 if (timeout_ns) |ns| { 987 tm_ptr = &tm; 988 tm = timestampToPosix(ns); 989 } 990 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); 991 const rc = std.c.futex( 992 ptr, 993 std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG, 994 @as(c_int, @bitCast(expect)), 995 tm_ptr, 996 null, // uaddr2 is ignored 997 ); 998 syscall.finish(); 999 if (is_debug) switch (posix.errno(rc)) { 1000 .SUCCESS => {}, 1001 .NOSYS => unreachable, // constant op known good value 1002 .AGAIN => {}, // contents of uaddr != val 1003 .INVAL => unreachable, // invalid timeout 1004 .TIMEDOUT => {}, // timeout 1005 .INTR => {}, // a signal arrived 1006 .CANCELED => {}, // a signal arrived and SA_RESTART was set 1007 else => unreachable, 1008 }; 1009 }, 1010 .dragonfly => { 1011 var timeout_us: c_int = undefined; 1012 if (timeout_ns) |ns| { 1013 timeout_us = std.math.cast(c_int, ns / std.time.ns_per_us) orelse std.math.maxInt(c_int); 1014 } else { 1015 timeout_us = 0; 1016 } 1017 const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); 1018 const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us); 1019 syscall.finish(); 1020 if (is_debug) switch (std.posix.errno(rc)) { 1021 .SUCCESS => {}, 1022 .BUSY => {}, // ptr != expect 1023 .AGAIN => {}, // maybe timed out, or paged out, or hit 2s kernel refresh 1024 .INTR => {}, // spurious wake 1025 .INVAL => unreachable, // invalid timeout 1026 else => unreachable, 1027 }; 1028 }, 1029 else => @compileError("unimplemented: futexWait"), 1030 } 1031 } 1032 1033 fn futexWake(ptr: *const u32, max_waiters: u32) void { 1034 @branchHint(.cold); 1035 assert(max_waiters != 0); 1036 1037 if (builtin.single_threaded) return; // nothing to wake up 1038 1039 if (use_parking_futex) { 1040 return parking_futex.wake(ptr, max_waiters); 1041 } else if (builtin.cpu.arch.isWasm()) { 1042 comptime assert(builtin.cpu.has(.wasm, .atomics)); 1043 const woken_count = asm volatile ( 1044 \\local.get %[ptr] 1045 \\local.get %[waiters] 1046 \\memory.atomic.notify 0 1047 \\local.set %[ret] 1048 : [ret] "=r" (-> u32), 1049 : [ptr] "r" (ptr), 1050 [waiters] "r" (max_waiters), 1051 ); 1052 _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled 1053 } else switch (native_os) { 1054 .linux => { 1055 const linux = std.os.linux; 1056 switch (linux.errno(linux.futex_3arg( 1057 ptr, 1058 .{ .cmd = .WAKE, .private = true }, 1059 @min(max_waiters, std.math.maxInt(i32)), 1060 ))) { 1061 .SUCCESS => return, // successful wake up 1062 .INVAL => return, // invalid futex_wait() on ptr done elsewhere 1063 .FAULT => return, // pointer became invalid while doing the wake 1064 else => return recoverableOsBugDetected(), // deadlock due to operating system bug 1065 } 1066 }, 1067 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 1068 const c = std.c; 1069 const flags: c.UL = .{ 1070 .op = .COMPARE_AND_WAIT, 1071 .NO_ERRNO = true, 1072 .WAKE_ALL = max_waiters > 1, 1073 }; 1074 while (true) { 1075 const status = c.__ulock_wake(flags, ptr, 0); 1076 if (status >= 0) return; 1077 switch (@as(c.E, @enumFromInt(-status))) { 1078 .INTR, .CANCELED => continue, // spurious wake() 1079 .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t 1080 .NOENT => return, // nothing was woken up 1081 .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD 1082 else => unreachable, // deadlock due to operating system bug 1083 } 1084 } 1085 }, 1086 .freebsd => { 1087 const rc = std.c._umtx_op( 1088 @intFromPtr(ptr), 1089 @intFromEnum(std.c.UMTX_OP.WAKE_PRIVATE), 1090 @as(c_ulong, @min(max_waiters, std.math.maxInt(c_int))), 1091 0, // there is no timeout struct 1092 0, // there is no timeout struct pointer 1093 ); 1094 switch (posix.errno(rc)) { 1095 .SUCCESS => {}, 1096 .FAULT => {}, // it's ok if the ptr doesn't point to valid memory 1097 .INVAL => unreachable, // arguments should be correct 1098 else => unreachable, // deadlock due to operating system bug 1099 } 1100 }, 1101 .openbsd => { 1102 const rc = std.c.futex( 1103 ptr, 1104 std.c.FUTEX.WAKE | std.c.FUTEX.PRIVATE_FLAG, 1105 @min(max_waiters, std.math.maxInt(c_int)), 1106 null, // timeout is ignored 1107 null, // uaddr2 is ignored 1108 ); 1109 assert(rc >= 0); 1110 }, 1111 .dragonfly => { 1112 // will generally return 0 unless the address is bad 1113 _ = std.c.umtx_wakeup( 1114 @ptrCast(ptr), 1115 @min(max_waiters, std.math.maxInt(c_int)), 1116 ); 1117 }, 1118 else => @compileError("unimplemented: futexWake"), 1119 } 1120 } 1121 1122 /// Cancels `thread` if it is working on `awaitable`. 1123 /// 1124 /// It is possible that `thread` gets canceled by this function, but is blocked in a syscall. In 1125 /// that case, the thread may need to be sent a signal to interrupt the call. This function will 1126 /// return `true` to indicate this, in which case the caller must call `signalCanceledSyscall`. 1127 fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) bool { 1128 var status = thread.status.load(.monotonic); 1129 while (true) { 1130 if (status.awaitable != awaitable) return false; // thread is working on something else 1131 status = switch (status.cancelation) { 1132 .none => thread.status.cmpxchgWeak( 1133 .{ .cancelation = .none, .awaitable = awaitable }, 1134 .{ .cancelation = .canceling, .awaitable = awaitable }, 1135 .monotonic, 1136 .monotonic, 1137 ) orelse return false, 1138 1139 .parked => thread.status.cmpxchgWeak( 1140 .{ .cancelation = .parked, .awaitable = awaitable }, 1141 .{ .cancelation = .canceling, .awaitable = awaitable }, 1142 .acquire, // acquire `thread.futex_waiter` 1143 .monotonic, 1144 ) orelse { 1145 if (!use_parking_futex and !use_parking_sleep) unreachable; 1146 if (thread.futex_waiter) |futex_waiter| { 1147 parking_futex.removeCanceledWaiter(futex_waiter); 1148 } 1149 if (need_unpark_flag) setUnparkFlag(&thread.unpark_flag); 1150 unpark(&.{thread.id}, null); 1151 return false; 1152 }, 1153 1154 .blocked => thread.status.cmpxchgWeak( 1155 .{ .cancelation = .blocked, .awaitable = awaitable }, 1156 .{ .cancelation = .blocked_canceling, .awaitable = awaitable }, 1157 .monotonic, 1158 .monotonic, 1159 ) orelse return true, 1160 1161 .blocked_alertable => thread.status.cmpxchgWeak( 1162 .{ .cancelation = .blocked_alertable, .awaitable = awaitable }, 1163 .{ .cancelation = .blocked_alertable_canceling, .awaitable = awaitable }, 1164 .monotonic, 1165 .monotonic, 1166 ) orelse { 1167 if (!is_windows) unreachable; 1168 return true; 1169 }, 1170 1171 .canceling, .canceled => { 1172 // This can happen when the task start raced with the cancelation, so the thread 1173 // saw the cancelation on the future/group *and* we are trying to signal the 1174 // thread here. 1175 return false; 1176 }, 1177 1178 .blocked_canceling => unreachable, // `awaitable` has not been canceled before now 1179 .blocked_alertable_canceling => unreachable, // `awaitable` has not been canceled before now 1180 }; 1181 } 1182 } 1183 1184 /// Sends a signal to `thread` if it is still blocked in a syscall (i.e. has not yet observed 1185 /// the cancelation request from `cancelAwaitable`). 1186 /// 1187 /// Unfortunately, the signal could arrive before the syscall actually starts, so the interrupt 1188 /// is missed. To handle this, we may need to send multiple signals. As such, if this function 1189 /// returns `true`, then it should be called again after a short delay to send another signal if 1190 /// the thread is still blocked. For the implementation, `Future.waitForCancelWithSignaling` and 1191 /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and 1192 /// doubling each call. In practice, it is rare to send more than one signal. 1193 fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool { 1194 const status = thread.status.load(.monotonic); 1195 if (status.awaitable != awaitable) { 1196 // The thread has moved on and is working on something totally different. 1197 return false; 1198 } 1199 1200 // The thread ID and/or handle can be read non-atomically because they never change and were 1201 // released by the store that made `thread` available to us. 1202 1203 switch (status.cancelation) { 1204 .blocked_canceling => if (std.Thread.use_pthreads) { 1205 return switch (std.c.pthread_kill(thread.handle, .IO)) { 1206 0 => true, 1207 else => false, 1208 }; 1209 } else switch (native_os) { 1210 .linux => { 1211 const pid: posix.pid_t = pid: { 1212 const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); 1213 if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid); 1214 const pid = std.os.linux.getpid(); 1215 @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); 1216 break :pid pid; 1217 }; 1218 return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) { 1219 0 => true, 1220 else => false, 1221 }; 1222 }, 1223 .windows => { 1224 var iosb: windows.IO_STATUS_BLOCK = undefined; 1225 return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) { 1226 .NOT_FOUND => true, // this might mean the operation hasn't started yet 1227 .SUCCESS => false, // the OS confirmed that our cancelation worked 1228 else => false, 1229 }; 1230 }, 1231 else => return false, 1232 }, 1233 1234 .blocked_alertable_canceling => { 1235 if (!is_windows) unreachable; 1236 return switch (windows.ntdll.NtAlertThread(thread.handle)) { 1237 .SUCCESS => true, 1238 else => false, 1239 }; 1240 }, 1241 1242 else => { 1243 // The thread is working on `awaitable`, but no longer needs signaling (they already 1244 // woke up and saw the cancelation). 1245 return false; 1246 }, 1247 } 1248 } 1249 1250 /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to 1251 /// alignment) so that those two bits can be used in a `packed struct`. 1252 const PackedPtr = enum(@Int(.unsigned, @bitSizeOf(usize) - 2)) { 1253 null = 0, 1254 all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 2)), 1255 _, 1256 1257 const Split = packed struct(usize) { low: u2, high: PackedPtr }; 1258 fn pack(ptr: *Thread) PackedPtr { 1259 const split: Split = @bitCast(@intFromPtr(ptr)); 1260 assert(split.low == 0); 1261 return split.high; 1262 } 1263 fn unpack(ptr: PackedPtr) ?*Thread { 1264 const split: Split = .{ .low = 0, .high = ptr }; 1265 return @ptrFromInt(@as(usize, @bitCast(split))); 1266 } 1267 }; 1268 1269 /// Same as `Io.Mutex.lock` but avoids the VTable. 1270 fn mutexLock(m: *Io.Mutex) Io.Cancelable!void { 1271 const initial_state = m.state.cmpxchgWeak( 1272 .unlocked, 1273 .locked_once, 1274 .acquire, 1275 .monotonic, 1276 ) orelse { 1277 @branchHint(.likely); 1278 return; 1279 }; 1280 if (initial_state == .contended) { 1281 try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); 1282 } 1283 while (m.state.swap(.contended, .acquire) != .unlocked) { 1284 try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); 1285 } 1286 } 1287 }; 1288 1289 const Syscall = struct { 1290 thread: ?*Thread, 1291 /// Marks entry to a syscall region. This should be tightly scoped around the actual syscall 1292 /// to minimize races. The syscall must be marked as "finished" by `checkCancel`, `finish`, 1293 /// or one of the wrappers of `finish`. 1294 fn start() Io.Cancelable!Syscall { 1295 const thread = Thread.current orelse return .{ .thread = null }; 1296 switch (thread.cancel_protection) { 1297 .blocked => return .{ .thread = null }, 1298 .unblocked => {}, 1299 } 1300 switch (thread.status.fetchOr(.{ 1301 .cancelation = @enumFromInt(0b011), 1302 .awaitable = .null, 1303 }, .monotonic).cancelation) { 1304 .parked => unreachable, 1305 .blocked => unreachable, 1306 .blocked_alertable => unreachable, 1307 .blocked_alertable_canceling => unreachable, 1308 .blocked_canceling => unreachable, 1309 .none => return .{ .thread = thread }, // new status is `.blocked` 1310 .canceling => return error.Canceled, // new status is `.canceled` 1311 .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged) 1312 } 1313 } 1314 /// Checks whether this syscall has been canceled. This should be called when a syscall is 1315 /// interrupted through a mechanism which may indicate cancelation, or may be spurious. If 1316 /// the syscall was canceled, it is finished and `error.Canceled` is returned. Otherwise, 1317 /// the syscall is not marked finished, and the caller should retry. 1318 fn checkCancel(s: Syscall) Io.Cancelable!void { 1319 const thread = s.thread orelse return; 1320 switch (thread.status.fetchOr(.{ 1321 .cancelation = @enumFromInt(0b010), 1322 .awaitable = .null, 1323 }, .monotonic).cancelation) { 1324 .none => unreachable, 1325 .parked => unreachable, 1326 .blocked_alertable => unreachable, 1327 .blocked_alertable_canceling => unreachable, 1328 .canceling => unreachable, 1329 .canceled => unreachable, 1330 .blocked => {}, // new status is `.blocked` (unchanged) 1331 .blocked_canceling => return error.Canceled, // new status is `.canceled` 1332 } 1333 } 1334 /// Marks this syscall as finished. 1335 fn finish(s: Syscall) void { 1336 const thread = s.thread orelse return; 1337 switch (thread.status.fetchXor(.{ 1338 .cancelation = @enumFromInt(0b011), 1339 .awaitable = .null, 1340 }, .monotonic).cancelation) { 1341 .none => unreachable, 1342 .parked => unreachable, 1343 .blocked_alertable => unreachable, 1344 .blocked_alertable_canceling => unreachable, 1345 .canceling => unreachable, 1346 .canceled => unreachable, 1347 .blocked => {}, // new status is `.none` 1348 .blocked_canceling => {}, // new status is `.canceling` 1349 } 1350 } 1351 /// Indicates instead of `NtCancelSynchronousIoFile` we need to use 1352 /// `NtAlertThread` to interrupt the wait. 1353 /// 1354 /// Windows only, called from blocked state only. 1355 fn toAlertable(s: Syscall) Io.Cancelable!AlertableSyscall { 1356 comptime assert(is_windows); 1357 const thread = s.thread orelse return .{ .thread = null }; 1358 var prev = thread.status.load(.monotonic); 1359 while (true) prev = switch (prev.cancelation) { 1360 .none => unreachable, 1361 .parked => unreachable, 1362 .blocked_alertable => unreachable, 1363 .blocked_alertable_canceling => unreachable, 1364 .canceling => unreachable, 1365 .canceled => unreachable, 1366 1367 .blocked => thread.status.cmpxchgWeak(prev, .{ 1368 .cancelation = .blocked_alertable, 1369 .awaitable = prev.awaitable, 1370 }, .monotonic, .monotonic) orelse return .{ .thread = thread }, 1371 1372 .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ 1373 .cancelation = .canceled, 1374 .awaitable = prev.awaitable, 1375 }, .monotonic, .monotonic) orelse return error.Canceled, 1376 }; 1377 } 1378 /// Convenience wrapper which calls `finish`, then returns `err`. 1379 fn fail(s: Syscall, err: anytype) @TypeOf(err) { 1380 s.finish(); 1381 return err; 1382 } 1383 /// Convenience wrapper which calls `finish`, then calls `Threaded.errnoBug`. 1384 fn errnoBug(s: Syscall, err: posix.E) Io.UnexpectedError { 1385 @branchHint(.cold); 1386 s.finish(); 1387 return Threaded.errnoBug(err); 1388 } 1389 /// Convenience wrapper which calls `finish`, then calls `posix.unexpectedErrno`. 1390 fn unexpectedErrno(s: Syscall, err: posix.E) Io.UnexpectedError { 1391 @branchHint(.cold); 1392 s.finish(); 1393 return posix.unexpectedErrno(err); 1394 } 1395 /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`. 1396 fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { 1397 @branchHint(.cold); 1398 s.finish(); 1399 return windows.statusBug(status); 1400 } 1401 /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`. 1402 fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { 1403 @branchHint(.cold); 1404 s.finish(); 1405 return windows.unexpectedStatus(status); 1406 } 1407 }; 1408 1409 const AlertableSyscall = struct { 1410 thread: ?*Thread, 1411 1412 comptime { 1413 assert(is_windows); 1414 } 1415 1416 fn start() Io.Cancelable!AlertableSyscall { 1417 const thread = Thread.current orelse return .{ .thread = null }; 1418 switch (thread.cancel_protection) { 1419 .blocked => return .{ .thread = null }, 1420 .unblocked => {}, 1421 } 1422 const old_status = thread.status.fetchOr(.{ 1423 .cancelation = @enumFromInt(0b010), 1424 .awaitable = .null, 1425 }, .monotonic); 1426 switch (old_status.cancelation) { 1427 .parked => unreachable, 1428 .blocked => unreachable, 1429 .blocked_alertable => unreachable, 1430 .blocked_canceling => unreachable, 1431 .blocked_alertable_canceling => unreachable, 1432 .none => return .{ .thread = thread }, // new status is `.blocked_alertable` 1433 .canceling => { 1434 // Status is unchanged (still `.canceling`)---change to `.canceled` before return. 1435 thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic); 1436 return error.Canceled; 1437 }, 1438 .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged) 1439 } 1440 } 1441 1442 fn checkCancel(s: AlertableSyscall) Io.Cancelable!void { 1443 comptime assert(is_windows); 1444 const thread = s.thread orelse return; 1445 const old_status = thread.status.fetchOr(.{ 1446 .cancelation = @enumFromInt(0b010), 1447 .awaitable = .null, 1448 }, .monotonic); 1449 switch (old_status.cancelation) { 1450 .none => unreachable, 1451 .parked => unreachable, 1452 .blocked => unreachable, 1453 .blocked_canceling => unreachable, 1454 .canceling => unreachable, 1455 .canceled => unreachable, 1456 .blocked_alertable => {}, // new status is `.blocked_alertable` (unchanged) 1457 .blocked_alertable_canceling => { 1458 // New status is `.canceling`---change to `.canceled` before return. 1459 thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic); 1460 return error.Canceled; 1461 }, 1462 } 1463 } 1464 1465 fn finish(s: AlertableSyscall) void { 1466 comptime assert(is_windows); 1467 const thread = s.thread orelse return; 1468 switch (thread.status.fetchXor(.{ 1469 .cancelation = @enumFromInt(0b010), 1470 .awaitable = .null, 1471 }, .monotonic).cancelation) { 1472 .none => unreachable, 1473 .parked => unreachable, 1474 .blocked => unreachable, 1475 .blocked_canceling => unreachable, 1476 .canceling => unreachable, 1477 .canceled => unreachable, 1478 .blocked_alertable => {}, // new status is `.none` 1479 .blocked_alertable_canceling => {}, // new status is `.canceling` 1480 } 1481 } 1482 1483 fn fail(s: AlertableSyscall, err: anytype) @TypeOf(err) { 1484 s.finish(); 1485 return err; 1486 } 1487 1488 fn ntstatusBug(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError { 1489 @branchHint(.cold); 1490 s.finish(); 1491 return windows.statusBug(status); 1492 } 1493 1494 fn unexpectedNtstatus(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError { 1495 @branchHint(.cold); 1496 s.finish(); 1497 return windows.unexpectedStatus(status); 1498 } 1499 1500 fn unexpectedWsaError(s: AlertableSyscall, err: ws2_32.WinsockError) Io.UnexpectedError { 1501 @branchHint(.cold); 1502 s.finish(); 1503 return windows.unexpectedWsaError(err); 1504 } 1505 1506 fn wsaErrorBug(s: AlertableSyscall, err: ws2_32.WinsockError) Io.UnexpectedError { 1507 @branchHint(.cold); 1508 s.finish(); 1509 return windows.wsaErrorBug(err); 1510 } 1511 }; 1512 1513 pub fn waitForApcOrAlert() void { 1514 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); 1515 _ = windows.ntdll.NtDelayExecution(windows.TRUE, &infinite_timeout); 1516 } 1517 1518 pub const max_iovecs_len = 8; 1519 pub const splat_buffer_size = 64; 1520 /// Happens to be the same number that matches maximum number of handles that 1521 /// NtWaitForMultipleObjects accepts. We use this value also for poll() on 1522 /// posix systems. 1523 const poll_buffer_len = 64; 1524 pub const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; 1525 /// There are multiple kernel bugs being worked around with retries. 1526 const max_windows_kernel_bug_retries = 13; 1527 1528 comptime { 1529 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX); 1530 } 1531 1532 pub const InitOptions = struct { 1533 /// Affects how many bytes are memory-mapped for threads. 1534 stack_size: usize = std.Thread.SpawnConfig.default_stack_size, 1535 /// Maximum thread pool size (excluding main thread) when dispatching async 1536 /// tasks. Until this limit, calls to `Io.async` when all threads are busy will 1537 /// cause a new thread to be spawned and permanently added to the pool. After 1538 /// this limit, calls to `Io.async` when all threads are busy run the task 1539 /// immediately. 1540 /// 1541 /// Defaults to one less than the number of logical CPU cores. 1542 /// 1543 /// Protected by `Threaded.mutex` once the I/O instance is already in use. See 1544 /// `setAsyncLimit`. 1545 async_limit: ?Io.Limit = null, 1546 /// Maximum thread pool size (excluding main thread) for dispatching concurrent 1547 /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread 1548 /// pool size. 1549 /// 1550 /// After this number, calls to `Io.concurrent` return `error.ConcurrencyUnavailable`. 1551 concurrent_limit: Io.Limit = .unlimited, 1552 /// Affects the following operations: 1553 /// * `processExecutablePath` on OpenBSD and Haiku. 1554 argv0: Argv0 = .empty, 1555 /// Affects the following operations: 1556 /// * `fileIsTty` 1557 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH"). 1558 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath` 1559 environ: process.Environ = .empty, 1560 /// If set to `true`, `File.MemoryMap` APIs will always take the fallback path. 1561 disable_memory_mapping: bool = false, 1562 }; 1563 1564 /// Related: 1565 /// * `init_single_threaded` 1566 pub fn init( 1567 /// Must be threadsafe. Only used for the following functions: 1568 /// * `Io.VTable.async` 1569 /// * `Io.VTable.concurrent` 1570 /// * `Io.VTable.groupAsync` 1571 /// * `Io.VTable.groupConcurrent` 1572 /// If these functions are avoided, then `Allocator.failing` may be passed 1573 /// here. 1574 gpa: Allocator, 1575 options: InitOptions, 1576 ) Threaded { 1577 if (builtin.single_threaded) return .{ 1578 .allocator = gpa, 1579 .stack_size = options.stack_size, 1580 .async_limit = options.async_limit orelse init_single_threaded.async_limit, 1581 .cpu_count_error = init_single_threaded.cpu_count_error, 1582 .concurrent_limit = options.concurrent_limit, 1583 .old_sig_io = undefined, 1584 .old_sig_pipe = undefined, 1585 .have_signal_handler = init_single_threaded.have_signal_handler, 1586 .argv0 = options.argv0, 1587 .environ_initialized = options.environ.block.isEmpty(), 1588 .environ = .{ .process_environ = options.environ }, 1589 .worker_threads = init_single_threaded.worker_threads, 1590 .disable_memory_mapping = options.disable_memory_mapping, 1591 }; 1592 1593 const cpu_count = std.Thread.getCpuCount(); 1594 1595 var t: Threaded = .{ 1596 .allocator = gpa, 1597 .stack_size = options.stack_size, 1598 .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing, 1599 .concurrent_limit = options.concurrent_limit, 1600 .cpu_count_error = if (cpu_count) |_| null else |e| e, 1601 .old_sig_io = undefined, 1602 .old_sig_pipe = undefined, 1603 .have_signal_handler = false, 1604 .argv0 = options.argv0, 1605 .environ_initialized = options.environ.block.isEmpty(), 1606 .environ = .{ .process_environ = options.environ }, 1607 .worker_threads = .init(null), 1608 .disable_memory_mapping = options.disable_memory_mapping, 1609 }; 1610 1611 if (posix.Sigaction != void) { 1612 // This causes sending `posix.SIG.IO` to thread to interrupt blocking 1613 // syscalls, returning `posix.E.INTR`. 1614 const act: posix.Sigaction = .{ 1615 .handler = .{ .handler = doNothingSignalHandler }, 1616 .mask = posix.sigemptyset(), 1617 .flags = 0, 1618 }; 1619 if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); 1620 if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe); 1621 t.have_signal_handler = true; 1622 } 1623 1624 return t; 1625 } 1626 1627 /// Statically initialize such that calls to `Io.VTable.concurrent` will fail 1628 /// with `error.ConcurrencyUnavailable`. 1629 /// 1630 /// When initialized this way: 1631 /// * cancel requests have no effect. 1632 /// * `deinit` is safe, but unnecessary to call. 1633 pub const init_single_threaded: Threaded = .{ 1634 .allocator = .failing, 1635 .stack_size = std.Thread.SpawnConfig.default_stack_size, 1636 .async_limit = .nothing, 1637 .cpu_count_error = null, 1638 .concurrent_limit = .nothing, 1639 .old_sig_io = undefined, 1640 .old_sig_pipe = undefined, 1641 .have_signal_handler = false, 1642 .argv0 = .empty, 1643 .environ_initialized = true, 1644 .environ = .empty, 1645 .worker_threads = .init(null), 1646 .disable_memory_mapping = false, 1647 }; 1648 1649 var global_single_threaded_instance: Threaded = .init_single_threaded; 1650 1651 /// In general, the application is responsible for choosing the `Io` 1652 /// implementation and library code should accept an `Io` parameter rather than 1653 /// accessing this declaration. Most code should avoid referencing this 1654 /// declaration entirely. 1655 /// 1656 /// However, in some cases such as debugging, it is desirable to hardcode a 1657 /// reference to this `Io` implementation. 1658 /// 1659 /// This instance does not support concurrency or cancelation. 1660 pub const global_single_threaded: *Threaded = &global_single_threaded_instance; 1661 1662 pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { 1663 mutexLock(&t.mutex); 1664 defer mutexUnlock(&t.mutex); 1665 t.async_limit = new_limit; 1666 } 1667 1668 pub fn deinit(t: *Threaded) void { 1669 t.join(); 1670 if (is_windows and t.wsa.status == .initialized) { 1671 if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); 1672 } 1673 if (posix.Sigaction != void and t.have_signal_handler) { 1674 if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); 1675 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); 1676 } 1677 t.null_file.deinit(); 1678 t.random_file.deinit(); 1679 t.pipe_file.deinit(); 1680 t.* = undefined; 1681 } 1682 1683 fn join(t: *Threaded) void { 1684 if (builtin.single_threaded) return; 1685 { 1686 mutexLock(&t.mutex); 1687 defer mutexUnlock(&t.mutex); 1688 t.join_requested = true; 1689 } 1690 condBroadcast(&t.cond); 1691 t.wait_group.wait(); 1692 } 1693 1694 fn worker(t: *Threaded) void { 1695 var thread: Thread = .{ 1696 .next = undefined, 1697 .id = std.Thread.getCurrentId(), 1698 .handle = handle: { 1699 if (std.Thread.use_pthreads) break :handle std.c.pthread_self(); 1700 if (is_windows) break :handle undefined; // populated below 1701 }, 1702 .status = .init(.{ 1703 .cancelation = .none, 1704 .awaitable = .null, 1705 }), 1706 .cancel_protection = .unblocked, 1707 .futex_waiter = undefined, 1708 .unpark_flag = unpark_flag_init, 1709 .csprng = .uninitialized, 1710 }; 1711 Thread.current = &thread; 1712 1713 if (is_windows) { 1714 assert(windows.ntdll.NtOpenThread( 1715 &thread.handle, 1716 .{ 1717 .SPECIFIC = .{ 1718 .THREAD = .{ 1719 .TERMINATE = true, // for `NtCancelSynchronousIoFile` 1720 }, 1721 }, 1722 }, 1723 &.{ .ObjectName = null }, 1724 &windows.teb().ClientId, 1725 ) == .SUCCESS); 1726 } 1727 defer if (is_windows) { 1728 windows.CloseHandle(thread.handle); 1729 }; 1730 1731 { 1732 var head = t.worker_threads.load(.monotonic); 1733 while (true) { 1734 thread.next = head; 1735 head = t.worker_threads.cmpxchgWeak( 1736 head, 1737 &thread, 1738 .release, 1739 .monotonic, 1740 ) orelse break; 1741 } 1742 } 1743 1744 defer t.wait_group.finish(); 1745 1746 mutexLock(&t.mutex); 1747 defer mutexUnlock(&t.mutex); 1748 1749 while (true) { 1750 while (t.run_queue.popFirst()) |runnable_node| { 1751 mutexUnlock(&t.mutex); 1752 thread.cancel_protection = .unblocked; 1753 const runnable: *Runnable = @fieldParentPtr("node", runnable_node); 1754 runnable.startFn(runnable, &thread, t); 1755 mutexLock(&t.mutex); 1756 t.busy_count -= 1; 1757 } 1758 if (t.join_requested) break; 1759 condWait(&t.cond, &t.mutex); 1760 } 1761 } 1762 1763 pub fn io(t: *Threaded) Io { 1764 return .{ 1765 .userdata = t, 1766 .vtable = &.{ 1767 .crashHandler = crashHandler, 1768 1769 .async = async, 1770 .concurrent = concurrent, 1771 .await = await, 1772 .cancel = cancel, 1773 1774 .groupAsync = groupAsync, 1775 .groupConcurrent = groupConcurrent, 1776 .groupAwait = groupAwait, 1777 .groupCancel = groupCancel, 1778 1779 .recancel = recancel, 1780 .swapCancelProtection = swapCancelProtection, 1781 .checkCancel = checkCancel, 1782 1783 .futexWait = futexWait, 1784 .futexWaitUncancelable = futexWaitUncancelable, 1785 .futexWake = futexWake, 1786 1787 .operate = operate, 1788 .batchAwaitAsync = batchAwaitAsync, 1789 .batchAwaitConcurrent = batchAwaitConcurrent, 1790 .batchCancel = batchCancel, 1791 1792 .dirCreateDir = dirCreateDir, 1793 .dirCreateDirPath = dirCreateDirPath, 1794 .dirCreateDirPathOpen = dirCreateDirPathOpen, 1795 .dirStat = dirStat, 1796 .dirStatFile = dirStatFile, 1797 .dirAccess = dirAccess, 1798 .dirCreateFile = dirCreateFile, 1799 .dirCreateFileAtomic = dirCreateFileAtomic, 1800 .dirOpenFile = dirOpenFile, 1801 .dirOpenDir = dirOpenDir, 1802 .dirClose = dirClose, 1803 .dirRead = dirRead, 1804 .dirRealPath = dirRealPath, 1805 .dirRealPathFile = dirRealPathFile, 1806 .dirDeleteFile = dirDeleteFile, 1807 .dirDeleteDir = dirDeleteDir, 1808 .dirRename = dirRename, 1809 .dirRenamePreserve = dirRenamePreserve, 1810 .dirSymLink = dirSymLink, 1811 .dirReadLink = dirReadLink, 1812 .dirSetOwner = dirSetOwner, 1813 .dirSetFileOwner = dirSetFileOwner, 1814 .dirSetPermissions = dirSetPermissions, 1815 .dirSetFilePermissions = dirSetFilePermissions, 1816 .dirSetTimestamps = dirSetTimestamps, 1817 .dirHardLink = dirHardLink, 1818 1819 .fileStat = fileStat, 1820 .fileLength = fileLength, 1821 .fileClose = fileClose, 1822 .fileWritePositional = fileWritePositional, 1823 .fileWriteFileStreaming = fileWriteFileStreaming, 1824 .fileWriteFilePositional = fileWriteFilePositional, 1825 .fileReadPositional = fileReadPositional, 1826 .fileSeekBy = fileSeekBy, 1827 .fileSeekTo = fileSeekTo, 1828 .fileSync = fileSync, 1829 .fileIsTty = fileIsTty, 1830 .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, 1831 .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, 1832 .fileSetLength = fileSetLength, 1833 .fileSetOwner = fileSetOwner, 1834 .fileSetPermissions = fileSetPermissions, 1835 .fileSetTimestamps = fileSetTimestamps, 1836 .fileLock = fileLock, 1837 .fileTryLock = fileTryLock, 1838 .fileUnlock = fileUnlock, 1839 .fileDowngradeLock = fileDowngradeLock, 1840 .fileRealPath = fileRealPath, 1841 .fileHardLink = fileHardLink, 1842 1843 .fileMemoryMapCreate = fileMemoryMapCreate, 1844 .fileMemoryMapDestroy = fileMemoryMapDestroy, 1845 .fileMemoryMapSetLength = fileMemoryMapSetLength, 1846 .fileMemoryMapRead = fileMemoryMapRead, 1847 .fileMemoryMapWrite = fileMemoryMapWrite, 1848 1849 .processExecutableOpen = processExecutableOpen, 1850 .processExecutablePath = processExecutablePath, 1851 .lockStderr = lockStderr, 1852 .tryLockStderr = tryLockStderr, 1853 .unlockStderr = unlockStderr, 1854 .processCurrentPath = processCurrentPath, 1855 .processSetCurrentDir = processSetCurrentDir, 1856 .processSetCurrentPath = processSetCurrentPath, 1857 .processReplace = processReplace, 1858 .processReplacePath = processReplacePath, 1859 .processSpawn = processSpawn, 1860 .processSpawnPath = processSpawnPath, 1861 .childWait = childWait, 1862 .childKill = childKill, 1863 1864 .progressParentFile = progressParentFile, 1865 1866 .now = now, 1867 .clockResolution = clockResolution, 1868 .sleep = sleep, 1869 1870 .random = random, 1871 .randomSecure = randomSecure, 1872 1873 .netListenIp = switch (native_os) { 1874 .windows => netListenIpWindows, 1875 else => netListenIpPosix, 1876 }, 1877 .netListenUnix = switch (native_os) { 1878 .windows => netListenUnixWindows, 1879 else => netListenUnixPosix, 1880 }, 1881 .netAccept = switch (native_os) { 1882 .windows => netAcceptWindows, 1883 else => netAcceptPosix, 1884 }, 1885 .netBindIp = switch (native_os) { 1886 .windows => netBindIpWindows, 1887 else => netBindIpPosix, 1888 }, 1889 .netConnectIp = switch (native_os) { 1890 .windows => netConnectIpWindows, 1891 else => netConnectIpPosix, 1892 }, 1893 .netConnectUnix = switch (native_os) { 1894 .windows => netConnectUnixWindows, 1895 else => netConnectUnixPosix, 1896 }, 1897 .netSocketCreatePair = netSocketCreatePair, 1898 .netClose = netClose, 1899 .netShutdown = switch (native_os) { 1900 .windows => netShutdownWindows, 1901 else => netShutdownPosix, 1902 }, 1903 .netRead = switch (native_os) { 1904 .windows => netReadWindows, 1905 else => netReadPosix, 1906 }, 1907 .netWrite = switch (native_os) { 1908 .windows => netWriteWindows, 1909 else => netWritePosix, 1910 }, 1911 .netWriteFile = netWriteFile, 1912 .netSend = switch (native_os) { 1913 .windows => netSendWindows, 1914 else => netSendPosix, 1915 }, 1916 .netInterfaceNameResolve = netInterfaceNameResolve, 1917 .netInterfaceName = netInterfaceName, 1918 .netLookup = netLookup, 1919 }, 1920 }; 1921 } 1922 1923 pub const socket_flags_unsupported = is_darwin or native_os == .haiku; 1924 const have_accept4 = !socket_flags_unsupported; 1925 const have_flock_open_flags = @hasField(posix.O, "EXLOCK"); 1926 const have_networking = std.options.networking and native_os != .wasi; 1927 const have_flock = @TypeOf(posix.system.flock) != void; 1928 const have_sendmmsg = native_os == .linux; 1929 const have_futex = switch (builtin.cpu.arch) { 1930 .wasm32, .wasm64 => builtin.cpu.has(.wasm, .atomics), 1931 else => true, 1932 }; 1933 const have_preadv = switch (native_os) { 1934 .windows, .haiku => false, 1935 else => true, 1936 }; 1937 const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO"); 1938 const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE"); 1939 const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux; 1940 const have_copy_file_range = switch (native_os) { 1941 .linux, .freebsd => true, 1942 else => false, 1943 }; 1944 const have_fcopyfile = is_darwin; 1945 const have_fchmodat2 = native_os == .linux and 1946 (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and 1947 (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); 1948 const have_fchmodat_flags = native_os != .linux or 1949 (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); 1950 1951 const have_fchown = switch (native_os) { 1952 .wasi, .windows => false, 1953 else => true, 1954 }; 1955 1956 const have_fchmod = switch (native_os) { 1957 .windows => false, 1958 .wasi => builtin.link_libc, 1959 else => true, 1960 }; 1961 1962 const have_waitid = switch (native_os) { 1963 .linux => @hasField(std.os.linux.SYS, "waitid"), 1964 else => false, 1965 }; 1966 1967 const have_wait4 = switch (native_os) { 1968 .linux => @hasField(std.os.linux.SYS, "wait4"), 1969 .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, 1970 else => false, 1971 }; 1972 1973 const have_mmap = switch (native_os) { 1974 .wasi, .windows => false, 1975 else => true, 1976 }; 1977 const have_poll = switch (native_os) { 1978 .wasi, .windows => false, 1979 else => true, 1980 }; 1981 1982 const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open; 1983 const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; 1984 const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; 1985 const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; 1986 const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; 1987 const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; 1988 const pread_sym = if (posix.lfs64_abi) posix.system.pread64 else posix.system.pread; 1989 const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; 1990 const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev; 1991 const pwrite_sym = if (posix.lfs64_abi) posix.system.pwrite64 else posix.system.pwrite; 1992 const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile; 1993 const mmap_sym = if (posix.lfs64_abi) posix.system.mmap64 else posix.system.mmap; 1994 1995 const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ 1996 .major = 34, 1997 .minor = 0, 1998 .patch = 0, 1999 } else .{ 2000 .major = 2, 2001 .minor = 27, 2002 .patch = 0, 2003 }); 2004 const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; 2005 2006 const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) 2007 .{ .major = 30, .minor = 0, .patch = 0 } 2008 else 2009 .{ .major = 2, .minor = 28, .patch = 0 }); 2010 2011 const use_libc_getrandom = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ 2012 .major = 28, 2013 .minor = 0, 2014 .patch = 0, 2015 } else .{ 2016 .major = 2, 2017 .minor = 25, 2018 .patch = 0, 2019 }); 2020 2021 const use_dev_urandom = @TypeOf(posix.system.getrandom) == void and native_os == .linux; 2022 2023 fn crashHandler(userdata: ?*anyopaque) void { 2024 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2025 _ = t; 2026 const thread = Thread.current orelse return; 2027 thread.status.store(.{ .cancelation = .canceled, .awaitable = .null }, .monotonic); 2028 thread.cancel_protection = .blocked; 2029 } 2030 2031 fn async( 2032 userdata: ?*anyopaque, 2033 result: []u8, 2034 result_alignment: Alignment, 2035 context: []const u8, 2036 context_alignment: Alignment, 2037 start: *const fn (context: *const anyopaque, result: *anyopaque) void, 2038 ) ?*Io.AnyFuture { 2039 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2040 if (builtin.single_threaded) { 2041 start(context.ptr, result.ptr); 2042 return null; 2043 } 2044 2045 const gpa = t.allocator; 2046 const future = Future.create(gpa, result.len, result_alignment, context, context_alignment, start) catch |err| switch (err) { 2047 error.OutOfMemory => { 2048 start(context.ptr, result.ptr); 2049 return null; 2050 }, 2051 }; 2052 2053 mutexLock(&t.mutex); 2054 2055 const busy_count = t.busy_count; 2056 2057 if (busy_count >= @intFromEnum(t.async_limit)) { 2058 mutexUnlock(&t.mutex); 2059 future.destroy(gpa); 2060 start(context.ptr, result.ptr); 2061 return null; 2062 } 2063 2064 t.busy_count = busy_count + 1; 2065 2066 const pool_size = t.wait_group.value(); 2067 if (pool_size - busy_count == 0) { 2068 t.wait_group.start(); 2069 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { 2070 t.wait_group.finish(); 2071 t.busy_count = busy_count; 2072 mutexUnlock(&t.mutex); 2073 future.destroy(gpa); 2074 start(context.ptr, result.ptr); 2075 return null; 2076 }; 2077 thread.detach(); 2078 } 2079 2080 t.run_queue.prepend(&future.runnable.node); 2081 2082 mutexUnlock(&t.mutex); 2083 condSignal(&t.cond); 2084 return @ptrCast(future); 2085 } 2086 2087 fn concurrent( 2088 userdata: ?*anyopaque, 2089 result_len: usize, 2090 result_alignment: Alignment, 2091 context: []const u8, 2092 context_alignment: Alignment, 2093 start: *const fn (context: *const anyopaque, result: *anyopaque) void, 2094 ) Io.ConcurrentError!*Io.AnyFuture { 2095 if (builtin.single_threaded) return error.ConcurrencyUnavailable; 2096 2097 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2098 2099 const gpa = t.allocator; 2100 const future = Future.create(gpa, result_len, result_alignment, context, context_alignment, start) catch |err| switch (err) { 2101 error.OutOfMemory => return error.ConcurrencyUnavailable, 2102 }; 2103 errdefer future.destroy(gpa); 2104 2105 mutexLock(&t.mutex); 2106 defer mutexUnlock(&t.mutex); 2107 2108 const busy_count = t.busy_count; 2109 2110 if (busy_count >= @intFromEnum(t.concurrent_limit)) 2111 return error.ConcurrencyUnavailable; 2112 2113 t.busy_count = busy_count + 1; 2114 errdefer t.busy_count = busy_count; 2115 2116 const pool_size = t.wait_group.value(); 2117 if (pool_size - busy_count == 0) { 2118 t.wait_group.start(); 2119 errdefer t.wait_group.finish(); 2120 2121 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch 2122 return error.ConcurrencyUnavailable; 2123 2124 thread.detach(); 2125 } 2126 2127 t.run_queue.prepend(&future.runnable.node); 2128 2129 condSignal(&t.cond); 2130 return @ptrCast(future); 2131 } 2132 2133 fn groupAsync( 2134 userdata: ?*anyopaque, 2135 type_erased: *Io.Group, 2136 context: []const u8, 2137 context_alignment: Alignment, 2138 start: *const fn (context: *const anyopaque) void, 2139 ) void { 2140 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2141 const g: Group = .{ .ptr = type_erased }; 2142 2143 if (builtin.single_threaded) return groupAsyncEager(start, context.ptr); 2144 2145 const gpa = t.allocator; 2146 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) { 2147 error.OutOfMemory => return groupAsyncEager(start, context.ptr), 2148 }; 2149 2150 mutexLock(&t.mutex); 2151 2152 const busy_count = t.busy_count; 2153 2154 if (busy_count >= @intFromEnum(t.async_limit)) { 2155 mutexUnlock(&t.mutex); 2156 task.destroy(gpa); 2157 return groupAsyncEager(start, context.ptr); 2158 } 2159 2160 t.busy_count = busy_count + 1; 2161 2162 const pool_size = t.wait_group.value(); 2163 if (pool_size - busy_count == 0) { 2164 t.wait_group.start(); 2165 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { 2166 t.wait_group.finish(); 2167 t.busy_count = busy_count; 2168 mutexUnlock(&t.mutex); 2169 task.destroy(gpa); 2170 return groupAsyncEager(start, context.ptr); 2171 }; 2172 thread.detach(); 2173 } 2174 2175 // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue 2176 // prepend so that the task doesn't finish without observing this and try to decrement the count 2177 // below zero. 2178 _ = g.status().fetchAdd(.{ 2179 .num_running = 1, 2180 .have_awaiter = false, 2181 .canceled = false, 2182 }, .monotonic); 2183 t.run_queue.prepend(&task.runnable.node); 2184 2185 mutexUnlock(&t.mutex); 2186 condSignal(&t.cond); 2187 } 2188 fn groupAsyncEager( 2189 start: *const fn (context: *const anyopaque) void, 2190 context: *const anyopaque, 2191 ) void { 2192 start(context); 2193 } 2194 2195 fn groupConcurrent( 2196 userdata: ?*anyopaque, 2197 type_erased: *Io.Group, 2198 context: []const u8, 2199 context_alignment: Alignment, 2200 start: *const fn (context: *const anyopaque) void, 2201 ) Io.ConcurrentError!void { 2202 if (builtin.single_threaded) return error.ConcurrencyUnavailable; 2203 2204 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2205 const g: Group = .{ .ptr = type_erased }; 2206 2207 const gpa = t.allocator; 2208 const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) { 2209 error.OutOfMemory => return error.ConcurrencyUnavailable, 2210 }; 2211 errdefer task.destroy(gpa); 2212 2213 mutexLock(&t.mutex); 2214 defer mutexUnlock(&t.mutex); 2215 2216 const busy_count = t.busy_count; 2217 2218 if (busy_count >= @intFromEnum(t.concurrent_limit)) 2219 return error.ConcurrencyUnavailable; 2220 2221 t.busy_count = busy_count + 1; 2222 errdefer t.busy_count = busy_count; 2223 2224 const pool_size = t.wait_group.value(); 2225 if (pool_size - busy_count == 0) { 2226 t.wait_group.start(); 2227 errdefer t.wait_group.finish(); 2228 2229 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch 2230 return error.ConcurrencyUnavailable; 2231 2232 thread.detach(); 2233 } 2234 2235 // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue 2236 // prepend so that the task doesn't finish without observing this and try to decrement the count 2237 // below zero. 2238 _ = g.status().fetchAdd(.{ 2239 .num_running = 1, 2240 .have_awaiter = false, 2241 .canceled = false, 2242 }, .monotonic); 2243 t.run_queue.prepend(&task.runnable.node); 2244 2245 condSignal(&t.cond); 2246 } 2247 2248 fn groupAwait(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void { 2249 _ = initial_token; // we need to load `token` *after* the group finishes 2250 if (builtin.single_threaded) unreachable; // nothing to await 2251 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2252 const g: Group = .{ .ptr = type_erased }; 2253 2254 var num_completed: std.atomic.Value(u32) = .init(0); 2255 g.awaiter().* = &num_completed; 2256 2257 const pre_await_status = g.status().fetchOr(.{ 2258 .num_running = 0, 2259 .have_awaiter = true, 2260 .canceled = false, 2261 }, .acq_rel); // acquire results if complete; release `g.awaiter()` 2262 2263 assert(!pre_await_status.have_awaiter); 2264 assert(!pre_await_status.canceled); 2265 if (pre_await_status.num_running == 0) { 2266 // Already done. Since the group is finished, it's illegal to spawn more tasks in it 2267 // until we return, so we can access `g.status()` non-atomically. 2268 g.status().raw.have_awaiter = false; 2269 return; 2270 } 2271 2272 while (Thread.futexWait(&num_completed.raw, 0, null)) { 2273 switch (num_completed.load(.acquire)) { // acquire task results 2274 0 => continue, 2275 1 => break, 2276 else => unreachable, // group was reused before `await` returned 2277 } 2278 } else |err| switch (err) { 2279 error.Canceled => { 2280 const pre_cancel_status = g.status().fetchOr(.{ 2281 .num_running = 0, 2282 .have_awaiter = false, 2283 .canceled = true, 2284 }, .acq_rel); // acquire results if complete; release `g.awaiter()` 2285 assert(pre_cancel_status.have_awaiter); 2286 assert(!pre_cancel_status.canceled); 2287 2288 // Even if `pre_cancel_status.num_running == 0`, we still need to wait for the signal, 2289 // because in that case the last member of the group is already trying to modify it. 2290 // However, if we know everything is done, we *can* skip signaling blocked threads. 2291 const skip_signals = pre_cancel_status.num_running == 0; 2292 g.waitForCancelWithSignaling(t, &num_completed, skip_signals); 2293 2294 // The group is finished, so it's illegal to spawn more tasks in it until we return, so 2295 // we can access `g.status()` non-atomically. 2296 g.status().raw.canceled = false; 2297 g.status().raw.have_awaiter = false; 2298 return error.Canceled; 2299 }, 2300 } 2301 2302 // The group is finished, so it's illegal to spawn more tasks in it until we return, so 2303 // we can access `g.status()` non-atomically. 2304 g.status().raw.have_awaiter = false; 2305 } 2306 2307 fn groupCancel(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) void { 2308 _ = initial_token; 2309 if (builtin.single_threaded) unreachable; // nothing to cancel 2310 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2311 const g: Group = .{ .ptr = type_erased }; 2312 2313 var num_completed: std.atomic.Value(u32) = .init(0); 2314 g.awaiter().* = &num_completed; 2315 2316 const pre_cancel_status = g.status().fetchOr(.{ 2317 .num_running = 0, 2318 .have_awaiter = true, 2319 .canceled = true, 2320 }, .acq_rel); // acquire results if complete; release `g.awaiter()` 2321 2322 assert(!pre_cancel_status.have_awaiter); 2323 assert(!pre_cancel_status.canceled); 2324 if (pre_cancel_status.num_running == 0) { 2325 // Already done. Since the group is finished, it's illegal to spawn more tasks in it 2326 // until we return, so we can access `g.status()` non-atomically. 2327 g.status().raw.have_awaiter = false; 2328 g.status().raw.canceled = false; 2329 return; 2330 } 2331 2332 g.waitForCancelWithSignaling(t, &num_completed, false); 2333 2334 g.status().raw = .{ .num_running = 0, .have_awaiter = false, .canceled = false }; 2335 } 2336 2337 fn recancel(userdata: ?*anyopaque) void { 2338 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2339 _ = t; 2340 recancelInner(); 2341 } 2342 fn recancelInner() void { 2343 const thread = Thread.current.?; // called `recancel` but was not canceled 2344 switch (thread.status.fetchXor(.{ 2345 .cancelation = @enumFromInt(0b001), 2346 .awaitable = .null, 2347 }, .monotonic).cancelation) { 2348 .canceled => {}, 2349 .none => unreachable, // called `recancel` but was not canceled 2350 .canceling => unreachable, // called `recancel` but cancelation was already pending 2351 .parked => unreachable, 2352 .blocked => unreachable, 2353 .blocked_alertable => unreachable, 2354 .blocked_alertable_canceling => unreachable, 2355 .blocked_canceling => unreachable, 2356 } 2357 } 2358 2359 fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection { 2360 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2361 _ = t; 2362 const thread = Thread.current orelse return .unblocked; 2363 const old = thread.cancel_protection; 2364 thread.cancel_protection = new; 2365 return old; 2366 } 2367 2368 fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void { 2369 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2370 _ = t; 2371 return Thread.checkCancel(); 2372 } 2373 2374 fn await( 2375 userdata: ?*anyopaque, 2376 any_future: *Io.AnyFuture, 2377 result: []u8, 2378 result_alignment: Alignment, 2379 ) void { 2380 _ = result_alignment; 2381 if (builtin.single_threaded) unreachable; // nothing to await 2382 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2383 const future: *Future = @ptrCast(@alignCast(any_future)); 2384 2385 var num_completed: std.atomic.Value(u32) = .init(0); 2386 future.awaiter = &num_completed; 2387 2388 const pre_await_status = future.status.fetchOr(.{ 2389 .tag = .pending_awaited, 2390 .thread = .null, 2391 }, .acq_rel); // acquire results if complete; release `future.awaiter` 2392 switch (pre_await_status.tag) { 2393 .pending => while (Thread.futexWait(&num_completed.raw, 0, null)) { 2394 switch (num_completed.load(.acquire)) { // acquire task results 2395 0 => continue, 2396 1 => break, 2397 else => unreachable, // group was reused before `await` returned 2398 } 2399 } else |err| switch (err) { 2400 error.Canceled => { 2401 const pre_cancel_status = future.status.fetchOr(.{ 2402 .tag = .pending_canceled, 2403 .thread = .null, 2404 }, .acq_rel); // acquire results if complete; release `future.awaiter` 2405 const done_status = switch (pre_cancel_status.tag) { 2406 .pending => unreachable, // invalid state: we already awaited 2407 .pending_awaited => done_status: { 2408 const working_thread = pre_cancel_status.thread.unpack(); 2409 future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread)); 2410 break :done_status future.status.load(.monotonic); 2411 }, 2412 .pending_canceled => unreachable, // `await` raced with `cancel` 2413 .done => done_status: { 2414 // The task just finished, but we still need to wait for the signal, because the 2415 // task thread already figured out that they need to update `future.awaiter`. 2416 future.waitForCancelWithSignaling(t, &num_completed, null); 2417 // Also, we have clobbered `future.status.tag` to `.pending_canceled`, but that's 2418 // not actually a problem for the logic below. 2419 break :done_status pre_cancel_status; 2420 }, 2421 }; 2422 // If the future did not acknowledge the cancelation, we need to mark it outstanding 2423 // for us. Because `done_status.tag == .done`, the information about whether there 2424 // was an acknowledged cancelation is encoded in `done_status.thread`. 2425 assert(done_status.tag == .done); 2426 switch (done_status.thread) { 2427 .null => recancelInner(), // cancelation was not acknowledged, so it's ours 2428 .all_ones => {}, // cancelation was acknowledged, so it was this task's job to propagate it 2429 _ => unreachable, 2430 } 2431 }, 2432 }, 2433 .pending_awaited => unreachable, // `await` raced with `await` 2434 .pending_canceled => unreachable, // `await` raced with `cancel` 2435 .done => {}, 2436 } 2437 @memcpy(result, future.resultPointer()); 2438 future.destroy(t.allocator); 2439 } 2440 2441 fn cancel( 2442 userdata: ?*anyopaque, 2443 any_future: *Io.AnyFuture, 2444 result: []u8, 2445 result_alignment: Alignment, 2446 ) void { 2447 _ = result_alignment; 2448 if (builtin.single_threaded) unreachable; // nothing to cancel 2449 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2450 const future: *Future = @ptrCast(@alignCast(any_future)); 2451 2452 var num_completed: std.atomic.Value(u32) = .init(0); 2453 future.awaiter = &num_completed; 2454 2455 const pre_cancel_status = future.status.fetchOr(.{ 2456 .tag = .pending_canceled, 2457 .thread = .null, 2458 }, .acq_rel); // acquire results if complete; release `future.awaiter` 2459 switch (pre_cancel_status.tag) { 2460 .pending => { 2461 const working_thread = pre_cancel_status.thread.unpack(); 2462 future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread)); 2463 }, 2464 .pending_awaited => unreachable, // `await` raced with `await` 2465 .pending_canceled => unreachable, // `await` raced with `cancel` 2466 .done => {}, 2467 } 2468 @memcpy(result, future.resultPointer()); 2469 future.destroy(t.allocator); 2470 } 2471 2472 fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void { 2473 if (builtin.single_threaded) { 2474 assert(timeout != .none); // Deadlock. 2475 return; 2476 } 2477 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2478 const t_io = io(t); 2479 const timeout_ns: ?u64 = ns: { 2480 const d = timeout.toDurationFromNow(t_io) orelse break :ns null; 2481 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); 2482 }; 2483 return Thread.futexWait(ptr, expected, timeout_ns); 2484 } 2485 2486 fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { 2487 if (builtin.single_threaded) unreachable; // Deadlock. 2488 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2489 _ = t; 2490 Thread.futexWaitUncancelable(ptr, expected, null); 2491 } 2492 2493 fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { 2494 if (builtin.single_threaded) return; // Nothing to wake up. 2495 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2496 _ = t; 2497 Thread.futexWake(ptr, max_waiters); 2498 } 2499 2500 fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result { 2501 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2502 switch (operation) { 2503 .file_read_streaming => |o| return .{ 2504 .file_read_streaming = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { 2505 error.Canceled => |e| return e, 2506 else => |e| e, 2507 }, 2508 }, 2509 .file_write_streaming => |o| return .{ 2510 .file_write_streaming = fileWriteStreaming(t, o.file, o.header, o.data, o.splat) catch |err| switch (err) { 2511 error.Canceled => |e| return e, 2512 else => |e| e, 2513 }, 2514 }, 2515 .device_io_control => |*o| return .{ .device_io_control = try deviceIoControl(o) }, 2516 .net_receive => |*o| return .{ .net_receive = o: { 2517 if (!have_networking) break :o .{ error.NetworkDown, 0 }; 2518 if (is_windows) break :o netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags); 2519 netReceivePosix(o.socket_handle, &o.message_buffer[0], o.data_buffer, o.flags, false) catch |err| switch (err) { 2520 error.Canceled => |e| return e, 2521 error.WouldBlock => unreachable, 2522 else => |e| break :o .{ e, 0 }, 2523 }; 2524 break :o .{ null, 1 }; 2525 } }, 2526 } 2527 } 2528 2529 fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { 2530 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2531 if (is_windows) { 2532 batchDrainSubmittedWindows(t, b, false) catch |err| switch (err) { 2533 error.ConcurrencyUnavailable => unreachable, // passed concurrency=false 2534 else => |e| return e, 2535 }; 2536 const alertable_syscall = try AlertableSyscall.start(); 2537 while (b.pending.head != .none and b.completed.head == .none) waitForApcOrAlert(); 2538 alertable_syscall.finish(); 2539 return; 2540 } 2541 if (have_poll) { 2542 var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; 2543 var poll_len: u32 = 0; 2544 { 2545 var index = b.submitted.head; 2546 while (index != .none and poll_len < poll_buffer_len) { 2547 const submission = &b.storage[index.toIndex()].submission; 2548 switch (submission.operation) { 2549 .file_read_streaming => |o| { 2550 poll_buffer[poll_len] = .{ 2551 .fd = o.file.handle, 2552 .events = posix.POLL.IN | posix.POLL.ERR, 2553 .revents = 0, 2554 }; 2555 poll_len += 1; 2556 }, 2557 .file_write_streaming => |o| { 2558 poll_buffer[poll_len] = .{ 2559 .fd = o.file.handle, 2560 .events = posix.POLL.OUT | posix.POLL.ERR, 2561 .revents = 0, 2562 }; 2563 poll_len += 1; 2564 }, 2565 .device_io_control => |o| { 2566 poll_buffer[poll_len] = .{ 2567 .fd = o.file.handle, 2568 .events = posix.POLL.OUT | posix.POLL.IN | posix.POLL.ERR, 2569 .revents = 0, 2570 }; 2571 poll_len += 1; 2572 }, 2573 .net_receive => |*o| { 2574 poll_buffer[poll_len] = .{ 2575 .fd = o.socket_handle, 2576 .events = posix.POLL.IN | posix.POLL.ERR, 2577 .revents = 0, 2578 }; 2579 poll_len += 1; 2580 }, 2581 } 2582 index = submission.node.next; 2583 } 2584 } 2585 switch (poll_len) { 2586 0 => return, 2587 1 => {}, 2588 else => while (true) { 2589 const timeout_ms: i32 = t: { 2590 if (b.completed.head != .none) { 2591 // It is legal to call batchWait with already completed 2592 // operations in the ring. In such case, we need to avoid 2593 // blocking in the poll syscall, but we can still take this 2594 // opportunity to find additional ready operations. 2595 break :t 0; 2596 } 2597 break :t std.math.maxInt(i32); 2598 }; 2599 const syscall = try Syscall.start(); 2600 const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); 2601 syscall.finish(); 2602 switch (posix.errno(rc)) { 2603 .SUCCESS => { 2604 if (rc == 0) { 2605 if (b.completed.head != .none) { 2606 // Since there are already completions available in the 2607 // queue, this is neither a timeout nor a case for 2608 // retrying. 2609 return; 2610 } 2611 continue; 2612 } 2613 var prev_index: Io.Operation.OptionalIndex = .none; 2614 var index = b.submitted.head; 2615 for (poll_buffer[0..poll_len]) |poll_entry| { 2616 const storage = &b.storage[index.toIndex()]; 2617 const submission = &storage.submission; 2618 const next_index = submission.node.next; 2619 if (poll_entry.revents != 0) { 2620 const result = try operate(t, submission.operation); 2621 2622 switch (prev_index) { 2623 .none => b.submitted.head = next_index, 2624 else => b.storage[prev_index.toIndex()].submission.node.next = next_index, 2625 } 2626 if (next_index == .none) b.submitted.tail = prev_index; 2627 2628 switch (b.completed.tail) { 2629 .none => b.completed.head = index, 2630 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, 2631 } 2632 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; 2633 b.completed.tail = index; 2634 } else prev_index = index; 2635 index = next_index; 2636 } 2637 assert(index == .none); 2638 return; 2639 }, 2640 .INTR => continue, 2641 else => break, 2642 } 2643 }, 2644 } 2645 } 2646 2647 var tail_index = b.completed.tail; 2648 defer b.completed.tail = tail_index; 2649 var index = b.submitted.head; 2650 errdefer b.submitted.head = index; 2651 while (index != .none) { 2652 const storage = &b.storage[index.toIndex()]; 2653 const submission = &storage.submission; 2654 const next_index = submission.node.next; 2655 const result = try operate(t, submission.operation); 2656 2657 switch (tail_index) { 2658 .none => b.completed.head = index, 2659 else => b.storage[tail_index.toIndex()].completion.node.next = index, 2660 } 2661 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; 2662 tail_index = index; 2663 index = next_index; 2664 } 2665 b.submitted = .{ .head = .none, .tail = .none }; 2666 } 2667 2668 fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { 2669 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2670 if (is_windows) { 2671 const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(io(t)); 2672 try batchDrainSubmittedWindows(t, b, true); 2673 while (b.pending.head != .none and b.completed.head == .none) { 2674 var delay_interval: windows.LARGE_INTEGER = interval: { 2675 const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); 2676 break :interval timeoutToWindowsInterval(.{ .deadline = d }).?; 2677 }; 2678 const alertable_syscall = try AlertableSyscall.start(); 2679 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); 2680 alertable_syscall.finish(); 2681 switch (delay_rc) { 2682 .SUCCESS, .TIMEOUT => { 2683 // The thread woke due to the timeout. Although spurious 2684 // timeouts are OK, when no deadline is passed we must not 2685 // return `error.Timeout`. 2686 if (timeout != .none and b.completed.head == .none) return error.Timeout; 2687 }, 2688 else => {}, 2689 } 2690 } 2691 return; 2692 } 2693 if (native_os == .wasi) { 2694 // TODO call poll_oneoff 2695 return error.ConcurrencyUnavailable; 2696 } 2697 if (!have_poll) return error.ConcurrencyUnavailable; 2698 var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; 2699 var poll_storage: struct { 2700 gpa: Allocator, 2701 batch: *Io.Batch, 2702 slice: []posix.pollfd, 2703 len: u32, 2704 2705 fn add(storage: *@This(), fd: File.Handle, events: @FieldType(posix.pollfd, "events")) Io.ConcurrentError!void { 2706 const len = storage.len; 2707 if (len == poll_buffer_len) { 2708 const slice: []posix.pollfd = if (storage.batch.userdata) |batch_userdata| 2709 @as([*]posix.pollfd, @ptrCast(@alignCast(batch_userdata)))[0..storage.batch.storage.len] 2710 else allocation: { 2711 const allocation = storage.gpa.alloc(posix.pollfd, storage.batch.storage.len) catch 2712 return error.ConcurrencyUnavailable; 2713 storage.batch.userdata = allocation.ptr; 2714 break :allocation allocation; 2715 }; 2716 @memcpy(slice[0..poll_buffer_len], storage.slice); 2717 storage.slice = slice; 2718 } 2719 storage.slice[len] = .{ 2720 .fd = fd, 2721 .events = events, 2722 .revents = 0, 2723 }; 2724 storage.len = len + 1; 2725 } 2726 } = .{ .gpa = t.allocator, .batch = b, .slice = &poll_buffer, .len = 0 }; 2727 { 2728 var index = b.submitted.head; 2729 while (index != .none) { 2730 const storage = &b.storage[index.toIndex()]; 2731 const submission = storage.submission; 2732 switch (submission.operation) { 2733 .file_read_streaming => |o| try poll_storage.add(o.file.handle, posix.POLL.IN | posix.POLL.ERR), 2734 .file_write_streaming => |o| try poll_storage.add(o.file.handle, posix.POLL.OUT | posix.POLL.ERR), 2735 .device_io_control => |o| try poll_storage.add(o.file.handle, posix.POLL.IN | posix.POLL.OUT | posix.POLL.ERR), 2736 .net_receive => |*o| nb: { 2737 var data_i: usize = 0; 2738 const result: Io.Operation.Result = .{ .net_receive = for (o.message_buffer, 0..) |*msg, msg_i| { 2739 const remaining_data_buffer = o.data_buffer[data_i..]; 2740 netReceivePosix(o.socket_handle, msg, remaining_data_buffer, o.flags, true) catch |err| switch (err) { 2741 error.Canceled => |e| return e, 2742 error.WouldBlock => { 2743 if (msg_i != 0) break .{ null, msg_i }; 2744 try poll_storage.add(o.socket_handle, posix.POLL.IN | posix.POLL.ERR); 2745 break :nb; 2746 }, 2747 else => |e| break .{ e, 0 }, 2748 }; 2749 data_i += msg.data.len; 2750 } else .{ null, o.message_buffer.len } }; 2751 switch (b.completed.tail) { 2752 .none => b.completed.head = index, 2753 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, 2754 } 2755 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; 2756 b.completed.tail = index; 2757 }, 2758 } 2759 index = submission.node.next; 2760 } 2761 } 2762 switch (poll_storage.len) { 2763 0 => return, 2764 1 => if (timeout == .none and b.completed.head == .none) { 2765 const index = b.submitted.head; 2766 const storage = &b.storage[index.toIndex()]; 2767 const result = try operate(t, storage.submission.operation); 2768 2769 b.submitted = .{ .head = .none, .tail = .none }; 2770 2771 switch (b.completed.tail) { 2772 .none => b.completed.head = index, 2773 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, 2774 } 2775 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; 2776 b.completed.tail = index; 2777 return; 2778 }, 2779 else => {}, 2780 } 2781 const t_io = io(t); 2782 const deadline = timeout.toTimestamp(t_io); 2783 while (true) { 2784 const timeout_ms: i32 = t: { 2785 if (b.completed.head != .none) { 2786 // It is legal to call batchWait with already completed 2787 // operations in the ring. In such case, we need to avoid 2788 // blocking in the poll syscall, but we can still take this 2789 // opportunity to find additional ready operations. 2790 break :t 0; 2791 } 2792 const d = deadline orelse break :t -1; 2793 const duration = d.durationFromNow(t_io); 2794 break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32)); 2795 }; 2796 const syscall = try Syscall.start(); 2797 const rc = posix.system.poll(poll_storage.slice.ptr, poll_storage.len, timeout_ms); 2798 syscall.finish(); 2799 switch (posix.errno(rc)) { 2800 .SUCCESS => { 2801 if (rc == 0) { 2802 if (b.completed.head != .none) { 2803 // Since there are already completions available in the 2804 // queue, this is neither a timeout nor a case for 2805 // retrying. 2806 return; 2807 } 2808 // Although spurious timeouts are OK, when no deadline is 2809 // passed we must not return `error.Timeout`. 2810 if (deadline == null) continue; 2811 return error.Timeout; 2812 } 2813 var prev_index: Io.Operation.OptionalIndex = .none; 2814 var index = b.submitted.head; 2815 for (poll_storage.slice[0..poll_storage.len]) |poll_entry| { 2816 const submission = &b.storage[index.toIndex()].submission; 2817 const next_index = submission.node.next; 2818 if (poll_entry.revents != 0) { 2819 const result = try operate(t, submission.operation); 2820 2821 switch (prev_index) { 2822 .none => b.submitted.head = next_index, 2823 else => b.storage[prev_index.toIndex()].submission.node.next = next_index, 2824 } 2825 if (next_index == .none) b.submitted.tail = prev_index; 2826 2827 switch (b.completed.tail) { 2828 .none => b.completed.head = index, 2829 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, 2830 } 2831 b.completed.tail = index; 2832 b.storage[index.toIndex()] = .{ .completion = .{ 2833 .node = .{ .next = .none }, 2834 .result = result, 2835 } }; 2836 } else prev_index = index; 2837 index = next_index; 2838 } 2839 assert(index == .none); 2840 return; 2841 }, 2842 .INTR => continue, 2843 else => return error.ConcurrencyUnavailable, 2844 } 2845 } 2846 } 2847 2848 const WindowsBatchOperationUserdata = extern struct { 2849 file: windows.HANDLE, 2850 iosb: windows.IO_STATUS_BLOCK, 2851 2852 const Erased = Io.Operation.Storage.Pending.Userdata; 2853 2854 comptime { 2855 assert(@sizeOf(WindowsBatchOperationUserdata) <= @sizeOf(Erased)); 2856 } 2857 2858 fn toErased(userdata: *WindowsBatchOperationUserdata) *Erased { 2859 return @ptrCast(userdata); 2860 } 2861 2862 fn fromErased(erased: *Erased) *WindowsBatchOperationUserdata { 2863 return @ptrCast(erased); 2864 } 2865 }; 2866 2867 fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { 2868 const t: *Threaded = @ptrCast(@alignCast(userdata)); 2869 if (is_windows) { 2870 if (b.pending.head == .none) return; 2871 waitForApcOrAlert(); 2872 var index = b.pending.head; 2873 while (index != .none) { 2874 const pending = &b.storage[index.toIndex()].pending; 2875 const operation_userdata: *WindowsBatchOperationUserdata = .fromErased(&pending.userdata); 2876 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; 2877 _ = windows.ntdll.NtCancelIoFileEx(operation_userdata.file, &operation_userdata.iosb, &cancel_iosb); 2878 index = pending.node.next; 2879 } 2880 while (b.pending.head != .none) waitForApcOrAlert(); 2881 } else if (b.userdata) |batch_userdata| { 2882 const poll_storage: [*]posix.pollfd = @ptrCast(@alignCast(batch_userdata)); 2883 t.allocator.free(poll_storage[0..b.storage.len]); 2884 b.userdata = null; 2885 } 2886 } 2887 2888 fn batchCompleteBlockingWindows( 2889 b: *Io.Batch, 2890 operation_userdata: *WindowsBatchOperationUserdata, 2891 result: Io.Operation.Result, 2892 ) void { 2893 const erased_userdata = operation_userdata.toErased(); 2894 const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("userdata", erased_userdata); 2895 switch (pending.node.prev) { 2896 .none => b.pending.head = pending.node.next, 2897 else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next, 2898 } 2899 switch (pending.node.next) { 2900 .none => b.pending.tail = pending.node.prev, 2901 else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev, 2902 } 2903 const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending); 2904 const index: Io.Operation.OptionalIndex = .fromIndex(storage - b.storage.ptr); 2905 switch (b.completed.tail) { 2906 .none => b.completed.head = index, 2907 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, 2908 } 2909 b.completed.tail = index; 2910 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; 2911 } 2912 2913 fn batchApc( 2914 apc_context: ?*anyopaque, 2915 iosb: *windows.IO_STATUS_BLOCK, 2916 _: windows.ULONG, 2917 ) callconv(.winapi) void { 2918 const b: *Io.Batch = @ptrCast(@alignCast(apc_context)); 2919 const operation_userdata: *WindowsBatchOperationUserdata = @fieldParentPtr("iosb", iosb); 2920 const erased_userdata = operation_userdata.toErased(); 2921 const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("userdata", erased_userdata); 2922 switch (pending.node.prev) { 2923 .none => b.pending.head = pending.node.next, 2924 else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next, 2925 } 2926 switch (pending.node.next) { 2927 .none => b.pending.tail = pending.node.prev, 2928 else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev, 2929 } 2930 const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending); 2931 const index: Io.Operation.OptionalIndex = .fromIndex(storage - b.storage.ptr); 2932 switch (iosb.u.Status) { 2933 .CANCELLED => { 2934 const tail_index = b.unused.tail; 2935 switch (tail_index) { 2936 .none => b.unused.head = index, 2937 else => b.storage[tail_index.toIndex()].unused.next = index, 2938 } 2939 storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } }; 2940 b.unused.tail = index; 2941 }, 2942 else => { 2943 switch (b.completed.tail) { 2944 .none => b.completed.head = index, 2945 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, 2946 } 2947 b.completed.tail = index; 2948 const result: Io.Operation.Result = switch (pending.tag) { 2949 .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, 2950 .file_write_streaming => .{ .file_write_streaming = ntWriteFileResult(iosb) }, 2951 .device_io_control => .{ .device_io_control = iosb.* }, 2952 .net_receive => unreachable, 2953 }; 2954 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; 2955 }, 2956 } 2957 } 2958 2959 /// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable. 2960 fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io.ConcurrentError || Io.Cancelable)!void { 2961 var index = b.submitted.head; 2962 errdefer b.submitted.head = index; 2963 while (index != .none) { 2964 const storage = &b.storage[index.toIndex()]; 2965 const submission = storage.submission; 2966 storage.* = .{ .pending = .{ 2967 .node = .{ .prev = b.pending.tail, .next = .none }, 2968 .tag = submission.operation, 2969 .userdata = undefined, 2970 } }; 2971 switch (b.pending.tail) { 2972 .none => b.pending.head = index, 2973 else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index, 2974 } 2975 b.pending.tail = index; 2976 const operation_userdata: *WindowsBatchOperationUserdata = .fromErased(&storage.pending.userdata); 2977 errdefer { 2978 operation_userdata.iosb = .{ .u = .{ .Status = .CANCELLED }, .Information = undefined }; 2979 batchApc(b, &operation_userdata.iosb, 0); 2980 } 2981 switch (submission.operation) { 2982 .file_read_streaming => |o| o: { 2983 var data_index: usize = 0; 2984 while (o.data.len - data_index != 0 and o.data[data_index].len == 0) data_index += 1; 2985 if (o.data.len - data_index == 0) { 2986 operation_userdata.iosb = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; 2987 batchApc(b, &operation_userdata.iosb, 0); 2988 break :o; 2989 } 2990 const buffer = o.data[data_index]; 2991 const short_buffer_len = std.math.lossyCast(u32, buffer.len); 2992 2993 if (o.file.flags.nonblocking) { 2994 operation_userdata.file = o.file.handle; 2995 switch (windows.ntdll.NtReadFile( 2996 o.file.handle, 2997 null, // event 2998 &batchApc, 2999 b, 3000 &operation_userdata.iosb, 3001 buffer.ptr, 3002 short_buffer_len, 3003 null, // byte offset 3004 null, // key 3005 )) { 3006 .PENDING, .SUCCESS => {}, 3007 .CANCELLED => unreachable, 3008 else => |status| { 3009 operation_userdata.iosb.u.Status = status; 3010 batchApc(b, &operation_userdata.iosb, 0); 3011 }, 3012 } 3013 } else { 3014 if (concurrency) return error.ConcurrencyUnavailable; 3015 3016 const syscall: Syscall = try .start(); 3017 while (true) switch (windows.ntdll.NtReadFile( 3018 o.file.handle, 3019 null, // event 3020 null, // APC routine 3021 null, // APC context 3022 &operation_userdata.iosb, 3023 buffer.ptr, 3024 short_buffer_len, 3025 null, // byte offset 3026 null, // key 3027 )) { 3028 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag 3029 .CANCELLED => { 3030 try syscall.checkCancel(); 3031 continue; 3032 }, 3033 else => |status| { 3034 syscall.finish(); 3035 operation_userdata.iosb.u.Status = status; 3036 batchApc(b, &operation_userdata.iosb, 0); 3037 break; 3038 }, 3039 }; 3040 } 3041 }, 3042 .file_write_streaming => |o| o: { 3043 const buffer = windowsWriteBuffer(o.header, o.data, o.splat); 3044 if (buffer.len == 0) { 3045 operation_userdata.iosb = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; 3046 batchApc(b, &operation_userdata.iosb, 0); 3047 break :o; 3048 } 3049 if (o.file.flags.nonblocking) { 3050 operation_userdata.file = o.file.handle; 3051 switch (windows.ntdll.NtWriteFile( 3052 o.file.handle, 3053 null, // event 3054 &batchApc, 3055 b, 3056 &operation_userdata.iosb, 3057 buffer.ptr, 3058 @intCast(buffer.len), 3059 null, // byte offset 3060 null, // key 3061 )) { 3062 .PENDING, .SUCCESS => {}, 3063 .CANCELLED => unreachable, 3064 else => |status| { 3065 operation_userdata.iosb.u.Status = status; 3066 batchApc(b, &operation_userdata.iosb, 0); 3067 }, 3068 } 3069 } else { 3070 if (concurrency) return error.ConcurrencyUnavailable; 3071 3072 const syscall: Syscall = try .start(); 3073 while (true) switch (windows.ntdll.NtWriteFile( 3074 o.file.handle, 3075 null, // event 3076 null, // APC routine 3077 null, // APC context 3078 &operation_userdata.iosb, 3079 buffer.ptr, 3080 @intCast(buffer.len), 3081 null, // byte offset 3082 null, // key 3083 )) { 3084 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag 3085 .CANCELLED => { 3086 try syscall.checkCancel(); 3087 continue; 3088 }, 3089 else => |status| { 3090 syscall.finish(); 3091 operation_userdata.iosb.u.Status = status; 3092 batchApc(b, &operation_userdata.iosb, 0); 3093 break; 3094 }, 3095 }; 3096 } 3097 }, 3098 .device_io_control => |o| { 3099 const NtControlFile = switch (o.code.DeviceType) { 3100 .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, 3101 else => &windows.ntdll.NtDeviceIoControlFile, 3102 }; 3103 if (o.file.flags.nonblocking) { 3104 operation_userdata.file = o.file.handle; 3105 switch (NtControlFile( 3106 o.file.handle, 3107 null, // event 3108 &batchApc, 3109 b, 3110 &operation_userdata.iosb, 3111 o.code, 3112 if (o.in.len > 0) o.in.ptr else null, 3113 @intCast(o.in.len), 3114 if (o.out.len > 0) o.out.ptr else null, 3115 @intCast(o.out.len), 3116 )) { 3117 .PENDING, .SUCCESS => {}, 3118 .CANCELLED => unreachable, 3119 else => |status| { 3120 operation_userdata.iosb.u.Status = status; 3121 batchApc(b, &operation_userdata.iosb, 0); 3122 }, 3123 } 3124 } else { 3125 if (concurrency) return error.ConcurrencyUnavailable; 3126 3127 const syscall: Syscall = try .start(); 3128 while (true) switch (NtControlFile( 3129 o.file.handle, 3130 null, // event 3131 null, // APC routine 3132 null, // APC context 3133 &operation_userdata.iosb, 3134 o.code, 3135 if (o.in.len > 0) o.in.ptr else null, 3136 @intCast(o.in.len), 3137 if (o.out.len > 0) o.out.ptr else null, 3138 @intCast(o.out.len), 3139 )) { 3140 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag 3141 .CANCELLED => { 3142 try syscall.checkCancel(); 3143 continue; 3144 }, 3145 else => |status| { 3146 syscall.finish(); 3147 operation_userdata.iosb.u.Status = status; 3148 batchApc(b, &operation_userdata.iosb, 0); 3149 break; 3150 }, 3151 }; 3152 } 3153 }, 3154 .net_receive => |*o| { 3155 // TODO integrate with overlapped I/O or equivalent to avoid this error 3156 if (concurrency) return error.ConcurrencyUnavailable; 3157 batchCompleteBlockingWindows(b, operation_userdata, .{ 3158 .net_receive = netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags), 3159 }); 3160 }, 3161 } 3162 index = submission.node.next; 3163 } 3164 b.submitted = .{ .head = .none, .tail = .none }; 3165 } 3166 3167 /// Since Windows only supports writing one contiguous buffer, returns the 3168 /// first one, while also limiting it to a length representable by 32-bit 3169 /// unsigned integer. 3170 fn windowsWriteBuffer(header: []const u8, data: []const []const u8, splat: usize) []const u8 { 3171 const buffer = b: { 3172 if (header.len != 0) break :b header; 3173 for (data[0 .. data.len - 1]) |buffer| { 3174 if (buffer.len != 0) break :b buffer; 3175 } 3176 if (splat == 0) return &.{}; 3177 break :b data[data.len - 1]; 3178 }; 3179 return buffer[0..std.math.lossyCast(u32, buffer.len)]; 3180 } 3181 3182 fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { 3183 const ct = complete_tail.*; 3184 const len: u31 = @intCast(ring.len); 3185 ring[ct.index(len)] = op; 3186 complete_tail.* = ct.next(len); 3187 } 3188 3189 const dirCreateDir = switch (native_os) { 3190 .windows => dirCreateDirWindows, 3191 .wasi => dirCreateDirWasi, 3192 else => dirCreateDirPosix, 3193 }; 3194 3195 fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { 3196 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3197 _ = t; 3198 3199 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3200 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3201 3202 const syscall: Syscall = try .start(); 3203 while (true) { 3204 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) { 3205 .SUCCESS => { 3206 syscall.finish(); 3207 return; 3208 }, 3209 .INTR => { 3210 try syscall.checkCancel(); 3211 continue; 3212 }, 3213 .ACCES => return syscall.fail(error.AccessDenied), 3214 .PERM => return syscall.fail(error.PermissionDenied), 3215 .DQUOT => return syscall.fail(error.DiskQuota), 3216 .EXIST => return syscall.fail(error.PathAlreadyExists), 3217 .LOOP => return syscall.fail(error.SymLinkLoop), 3218 .MLINK => return syscall.fail(error.LinkQuotaExceeded), 3219 .NAMETOOLONG => return syscall.fail(error.NameTooLong), 3220 .NOENT => return syscall.fail(error.FileNotFound), 3221 .NOMEM => return syscall.fail(error.SystemResources), 3222 .NOSPC => return syscall.fail(error.NoSpaceLeft), 3223 .NOTDIR => return syscall.fail(error.NotDir), 3224 .ROFS => return syscall.fail(error.ReadOnlyFileSystem), 3225 // dragonfly: when dir_fd is unlinked from filesystem 3226 .NOTCONN => return syscall.fail(error.FileNotFound), 3227 .ILSEQ => return syscall.fail(error.BadPathName), 3228 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. 3229 .FAULT => |err| return syscall.errnoBug(err), 3230 else => |err| return syscall.unexpectedErrno(err), 3231 } 3232 } 3233 } 3234 3235 fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { 3236 if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions); 3237 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3238 _ = t; 3239 const syscall: Syscall = try .start(); 3240 while (true) { 3241 switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) { 3242 .SUCCESS => { 3243 syscall.finish(); 3244 return; 3245 }, 3246 .INTR => { 3247 try syscall.checkCancel(); 3248 continue; 3249 }, 3250 else => |e| { 3251 syscall.finish(); 3252 switch (e) { 3253 .ACCES => return error.AccessDenied, 3254 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3255 .PERM => return error.PermissionDenied, 3256 .DQUOT => return error.DiskQuota, 3257 .EXIST => return error.PathAlreadyExists, 3258 .FAULT => |err| return errnoBug(err), 3259 .LOOP => return error.SymLinkLoop, 3260 .MLINK => return error.LinkQuotaExceeded, 3261 .NAMETOOLONG => return error.NameTooLong, 3262 .NOENT => return error.FileNotFound, 3263 .NOMEM => return error.SystemResources, 3264 .NOSPC => return error.NoSpaceLeft, 3265 .NOTDIR => return error.NotDir, 3266 .ROFS => return error.ReadOnlyFileSystem, 3267 .NOTCAPABLE => return error.AccessDenied, 3268 .ILSEQ => return error.BadPathName, 3269 else => |err| return posix.unexpectedErrno(err), 3270 } 3271 }, 3272 } 3273 } 3274 } 3275 3276 fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { 3277 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3278 _ = t; 3279 _ = permissions; // TODO use this value 3280 3281 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); 3282 const attr: windows.OBJECT.ATTRIBUTES = .{ 3283 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, 3284 .Attributes = .{ .INHERIT = false }, 3285 .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w.span())), 3286 .SecurityDescriptor = null, 3287 .SecurityQualityOfService = null, 3288 }; 3289 3290 var sub_dir_handle: windows.HANDLE = undefined; 3291 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 3292 var attempt: u5 = 0; 3293 var syscall: Syscall = try .start(); 3294 while (true) switch (windows.ntdll.NtCreateFile( 3295 &sub_dir_handle, 3296 .{ 3297 .GENERIC = .{ .READ = true }, 3298 .STANDARD = .{ .SYNCHRONIZE = true }, 3299 }, 3300 &attr, 3301 &io_status_block, 3302 null, 3303 .{ .NORMAL = true }, 3304 .VALID_FLAGS, 3305 .CREATE, 3306 .{ 3307 .DIRECTORY_FILE = true, 3308 .NON_DIRECTORY_FILE = false, 3309 .IO = .SYNCHRONOUS_NONALERT, 3310 .OPEN_REPARSE_POINT = false, 3311 }, 3312 null, 3313 0, 3314 )) { 3315 .SUCCESS => { 3316 syscall.finish(); 3317 windows.CloseHandle(sub_dir_handle); 3318 return; 3319 }, 3320 .CANCELLED => { 3321 try syscall.checkCancel(); 3322 continue; 3323 }, 3324 .SHARING_VIOLATION => { 3325 // This occurs if the file attempting to be opened is a running 3326 // executable. However, there's a kernel bug: the error may be 3327 // incorrectly returned for an indeterminate amount of time 3328 // after an executable file is closed. Here we work around the 3329 // kernel bug with retry attempts. 3330 syscall.finish(); 3331 if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected; 3332 try parking_sleep.sleep(.{ .duration = .{ 3333 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 3334 .clock = .awake, 3335 } }); 3336 attempt += 1; 3337 syscall = try .start(); 3338 continue; 3339 }, 3340 .DELETE_PENDING => { 3341 // This error means that there *was* a file in this location on 3342 // the file system, but it was deleted. However, the OS is not 3343 // finished with the deletion operation, and so this CreateFile 3344 // call has failed. There is not really a sane way to handle 3345 // this other than retrying the creation after the OS finishes 3346 // the deletion. 3347 syscall.finish(); 3348 if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected; 3349 try parking_sleep.sleep(.{ .duration = .{ 3350 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 3351 .clock = .awake, 3352 } }); 3353 attempt += 1; 3354 syscall = try .start(); 3355 continue; 3356 }, 3357 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 3358 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 3359 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 3360 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found 3361 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't 3362 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 3363 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), 3364 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 3365 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 3366 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 3367 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), 3368 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), 3369 else => |status| return syscall.unexpectedNtstatus(status), 3370 }; 3371 } 3372 3373 fn dirCreateDirPath( 3374 userdata: ?*anyopaque, 3375 dir: Dir, 3376 sub_path: []const u8, 3377 permissions: Dir.Permissions, 3378 ) Dir.CreateDirPathError!Dir.CreatePathStatus { 3379 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3380 3381 var it = Dir.path.componentIterator(sub_path); 3382 var status: Dir.CreatePathStatus = .existed; 3383 var component = it.last() orelse return error.BadPathName; 3384 while (true) { 3385 if (dirCreateDir(t, dir, component.path, permissions)) |_| { 3386 status = .created; 3387 } else |err| switch (err) { 3388 error.PathAlreadyExists => { 3389 // It is important to return an error if it's not a directory 3390 // because otherwise a dangling symlink could cause an infinite 3391 // loop. 3392 const kind = try filePathKind(t, dir, component.path); 3393 if (kind != .directory) return error.NotDir; 3394 }, 3395 error.FileNotFound => |e| { 3396 component = it.previous() orelse return e; 3397 continue; 3398 }, 3399 else => |e| return e, 3400 } 3401 component = it.next() orelse return status; 3402 } 3403 } 3404 3405 const dirCreateDirPathOpen = switch (native_os) { 3406 .windows => dirCreateDirPathOpenWindows, 3407 .wasi => dirCreateDirPathOpenWasi, 3408 else => dirCreateDirPathOpenPosix, 3409 }; 3410 3411 fn dirCreateDirPathOpenPosix( 3412 userdata: ?*anyopaque, 3413 dir: Dir, 3414 sub_path: []const u8, 3415 permissions: Dir.Permissions, 3416 options: Dir.OpenOptions, 3417 ) Dir.CreateDirPathOpenError!Dir { 3418 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3419 const t_io = io(t); 3420 return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { 3421 error.FileNotFound => { 3422 _ = try dir.createDirPathStatus(t_io, sub_path, permissions); 3423 return dirOpenDirPosix(t, dir, sub_path, options); 3424 }, 3425 else => |e| return e, 3426 }; 3427 } 3428 3429 fn dirCreateDirPathOpenWindows( 3430 userdata: ?*anyopaque, 3431 dir: Dir, 3432 sub_path: []const u8, 3433 permissions: Dir.Permissions, 3434 options: Dir.OpenOptions, 3435 ) Dir.CreateDirPathOpenError!Dir { 3436 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3437 const w = windows; 3438 3439 _ = permissions; // TODO apply these permissions 3440 3441 var it = Dir.path.componentIterator(sub_path); 3442 // If there are no components in the path, then create a dummy component with the full path. 3443 var component: Dir.path.NativeComponentIterator.Component = it.last() orelse .{ 3444 .name = "", 3445 .path = sub_path, 3446 }; 3447 3448 components: while (true) { 3449 const sub_path_w = try sliceToPrefixedFileW(dir.handle, component.path); 3450 const attr: windows.OBJECT.ATTRIBUTES = .{ 3451 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, 3452 .ObjectName = @constCast(&sub_path_w.string()), 3453 }; 3454 const is_last = it.peekNext() == null; 3455 var result: Dir = .{ .handle = undefined }; 3456 var iosb: w.IO_STATUS_BLOCK = undefined; 3457 const syscall: Syscall = try .start(); 3458 while (true) switch (w.ntdll.NtCreateFile( 3459 &result.handle, 3460 .{ 3461 .SPECIFIC = .{ .FILE_DIRECTORY = .{ 3462 .LIST = options.iterate, 3463 .READ_EA = true, 3464 .READ_ATTRIBUTES = true, 3465 .TRAVERSE = true, 3466 } }, 3467 .STANDARD = .{ 3468 .RIGHTS = .READ, 3469 .SYNCHRONIZE = true, 3470 }, 3471 }, 3472 &attr, 3473 &iosb, 3474 null, 3475 .{ .NORMAL = true }, 3476 .VALID_FLAGS, 3477 if (is_last) .OPEN_IF else .CREATE, 3478 .{ 3479 .DIRECTORY_FILE = true, 3480 .IO = .SYNCHRONOUS_NONALERT, 3481 .OPEN_FOR_BACKUP_INTENT = true, 3482 .OPEN_REPARSE_POINT = !options.follow_symlinks, 3483 }, 3484 null, 3485 0, 3486 )) { 3487 .SUCCESS => { 3488 syscall.finish(); 3489 component = it.next() orelse return result; 3490 w.CloseHandle(result.handle); 3491 continue :components; 3492 }, 3493 .CANCELLED => { 3494 try syscall.checkCancel(); 3495 continue; 3496 }, 3497 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 3498 .OBJECT_NAME_COLLISION => { 3499 syscall.finish(); 3500 assert(!is_last); 3501 // stat the file and return an error if it's not a directory 3502 // this is important because otherwise a dangling symlink 3503 // could cause an infinite loop 3504 const fstat = try dirStatFileWindows(t, dir, component.path, .{ 3505 .follow_symlinks = options.follow_symlinks, 3506 }); 3507 if (fstat.kind != .directory) return error.NotDir; 3508 3509 component = it.next().?; 3510 continue :components; 3511 }, 3512 3513 .OBJECT_NAME_NOT_FOUND, 3514 .OBJECT_PATH_NOT_FOUND, 3515 => { 3516 syscall.finish(); 3517 component = it.previous() orelse return error.FileNotFound; 3518 continue :components; 3519 }, 3520 3521 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 3522 // This can happen if the directory has 'List folder contents' permission set to 'Deny' 3523 // and the directory is trying to be opened for iteration. 3524 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 3525 .DISK_FULL => return syscall.fail(error.NoSpaceLeft), 3526 .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s), 3527 else => |s| return syscall.unexpectedNtstatus(s), 3528 }; 3529 } 3530 } 3531 3532 fn dirCreateDirPathOpenWasi( 3533 userdata: ?*anyopaque, 3534 dir: Dir, 3535 sub_path: []const u8, 3536 permissions: Dir.Permissions, 3537 options: Dir.OpenOptions, 3538 ) Dir.CreateDirPathOpenError!Dir { 3539 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3540 const t_io = io(t); 3541 return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) { 3542 error.FileNotFound => { 3543 _ = try dir.createDirPathStatus(t_io, sub_path, permissions); 3544 return dirOpenDirWasi(t, dir, sub_path, options); 3545 }, 3546 else => |e| return e, 3547 }; 3548 } 3549 3550 fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { 3551 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3552 return fileStat(t, .{ 3553 .handle = dir.handle, 3554 .flags = .{ .nonblocking = false }, 3555 }); 3556 } 3557 3558 const dirStatFile = switch (native_os) { 3559 .linux => dirStatFileLinux, 3560 .windows => dirStatFileWindows, 3561 .wasi => dirStatFileWasi, 3562 else => dirStatFilePosix, 3563 }; 3564 3565 fn dirStatFileLinux( 3566 userdata: ?*anyopaque, 3567 dir: Dir, 3568 sub_path: []const u8, 3569 options: Dir.StatFileOptions, 3570 ) Dir.StatFileError!File.Stat { 3571 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3572 _ = t; 3573 const linux = std.os.linux; 3574 const sys = if (statx_use_c) std.c else std.os.linux; 3575 3576 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3577 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3578 3579 const flags: u32 = linux.AT.NO_AUTOMOUNT | 3580 @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0); 3581 3582 const syscall: Syscall = try .start(); 3583 while (true) { 3584 var statx = std.mem.zeroes(linux.Statx); 3585 switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_request, &statx))) { 3586 .SUCCESS => { 3587 syscall.finish(); 3588 return statFromLinux(&statx); 3589 }, 3590 .INTR => { 3591 try syscall.checkCancel(); 3592 continue; 3593 }, 3594 else => |e| { 3595 syscall.finish(); 3596 switch (e) { 3597 .ACCES => return error.AccessDenied, 3598 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3599 .FAULT => |err| return errnoBug(err), 3600 .INVAL => |err| return errnoBug(err), 3601 .LOOP => return error.SymLinkLoop, 3602 .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above. 3603 .NOENT => return error.FileNotFound, 3604 .NOTDIR => return error.NotDir, 3605 .NOMEM => return error.SystemResources, 3606 else => |err| return posix.unexpectedErrno(err), 3607 } 3608 }, 3609 } 3610 } 3611 } 3612 3613 fn dirStatFilePosix( 3614 userdata: ?*anyopaque, 3615 dir: Dir, 3616 sub_path: []const u8, 3617 options: Dir.StatFileOptions, 3618 ) Dir.StatFileError!File.Stat { 3619 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3620 _ = t; 3621 3622 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3623 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3624 3625 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; 3626 3627 return posixStatFile(dir.handle, sub_path_posix, flags); 3628 } 3629 3630 fn posixStatFile(dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat { 3631 const syscall: Syscall = try .start(); 3632 while (true) { 3633 var stat = std.mem.zeroes(posix.Stat); 3634 switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) { 3635 .SUCCESS => { 3636 syscall.finish(); 3637 return statFromPosix(&stat); 3638 }, 3639 .INTR => { 3640 try syscall.checkCancel(); 3641 continue; 3642 }, 3643 else => |e| { 3644 syscall.finish(); 3645 switch (e) { 3646 .INVAL => |err| return errnoBug(err), 3647 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3648 .NOMEM => return error.SystemResources, 3649 .ACCES => return error.AccessDenied, 3650 .PERM => return error.PermissionDenied, 3651 .FAULT => |err| return errnoBug(err), 3652 .NAMETOOLONG => return error.NameTooLong, 3653 .LOOP => return error.SymLinkLoop, 3654 .NOENT => return error.FileNotFound, 3655 .NOTDIR => return error.FileNotFound, 3656 .ILSEQ => return error.BadPathName, 3657 else => |err| return posix.unexpectedErrno(err), 3658 } 3659 }, 3660 } 3661 } 3662 } 3663 3664 fn dirStatFileWindows( 3665 userdata: ?*anyopaque, 3666 dir: Dir, 3667 sub_path: []const u8, 3668 options: Dir.StatFileOptions, 3669 ) Dir.StatFileError!File.Stat { 3670 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3671 const file = try dirOpenFileWindows(t, dir, sub_path, .{ 3672 .follow_symlinks = options.follow_symlinks, 3673 }); 3674 defer windows.CloseHandle(file.handle); 3675 return fileStatWindows(t, file); 3676 } 3677 3678 fn dirStatFileWasi( 3679 userdata: ?*anyopaque, 3680 dir: Dir, 3681 sub_path: []const u8, 3682 options: Dir.StatFileOptions, 3683 ) Dir.StatFileError!File.Stat { 3684 if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options); 3685 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3686 _ = t; 3687 const wasi = std.os.wasi; 3688 const flags: wasi.lookupflags_t = .{ 3689 .SYMLINK_FOLLOW = options.follow_symlinks, 3690 }; 3691 var stat: wasi.filestat_t = undefined; 3692 const syscall: Syscall = try .start(); 3693 while (true) { 3694 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { 3695 .SUCCESS => { 3696 syscall.finish(); 3697 return statFromWasi(&stat); 3698 }, 3699 .INTR => { 3700 try syscall.checkCancel(); 3701 continue; 3702 }, 3703 else => |e| { 3704 syscall.finish(); 3705 switch (e) { 3706 .INVAL => |err| return errnoBug(err), 3707 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3708 .NOMEM => return error.SystemResources, 3709 .ACCES => return error.AccessDenied, 3710 .FAULT => |err| return errnoBug(err), 3711 .NAMETOOLONG => return error.NameTooLong, 3712 .NOENT => return error.FileNotFound, 3713 .NOTDIR => return error.FileNotFound, 3714 .NOTCAPABLE => return error.AccessDenied, 3715 .ILSEQ => return error.BadPathName, 3716 else => |err| return posix.unexpectedErrno(err), 3717 } 3718 }, 3719 } 3720 } 3721 } 3722 3723 fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind { 3724 if (native_os == .linux) { 3725 var path_buffer: [posix.PATH_MAX]u8 = undefined; 3726 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 3727 3728 const linux = std.os.linux; 3729 const syscall: Syscall = try .start(); 3730 while (true) { 3731 var statx = std.mem.zeroes(linux.Statx); 3732 switch (linux.errno(linux.statx( 3733 dir.handle, 3734 sub_path_posix, 3735 linux.AT.NO_AUTOMOUNT | linux.AT.SYMLINK_NOFOLLOW, 3736 .{ .TYPE = true }, 3737 &statx, 3738 ))) { 3739 .SUCCESS => { 3740 syscall.finish(); 3741 if (!statx.mask.TYPE) return error.Unexpected; 3742 return statxKind(statx.mode); 3743 }, 3744 .INTR => { 3745 try syscall.checkCancel(); 3746 continue; 3747 }, 3748 .NOMEM => return syscall.fail(error.SystemResources), 3749 else => |err| return syscall.unexpectedErrno(err), 3750 } 3751 } 3752 } 3753 3754 const stat = try dirStatFile(t, dir, sub_path, .{ .follow_symlinks = false }); 3755 return stat.kind; 3756 } 3757 3758 fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { 3759 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3760 3761 if (native_os == .linux) { 3762 const linux = std.os.linux; 3763 3764 const syscall: Syscall = try .start(); 3765 while (true) { 3766 var statx = std.mem.zeroes(linux.Statx); 3767 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) { 3768 .SUCCESS => { 3769 syscall.finish(); 3770 if (!statx.mask.SIZE) return error.Unexpected; 3771 return statx.size; 3772 }, 3773 .INTR => { 3774 try syscall.checkCancel(); 3775 continue; 3776 }, 3777 else => |e| { 3778 syscall.finish(); 3779 switch (e) { 3780 .ACCES => |err| return errnoBug(err), 3781 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3782 .FAULT => |err| return errnoBug(err), 3783 .INVAL => |err| return errnoBug(err), 3784 .LOOP => |err| return errnoBug(err), 3785 .NAMETOOLONG => |err| return errnoBug(err), 3786 .NOENT => |err| return errnoBug(err), 3787 .NOMEM => return error.SystemResources, 3788 .NOTDIR => |err| return errnoBug(err), 3789 else => |err| return posix.unexpectedErrno(err), 3790 } 3791 }, 3792 } 3793 } 3794 } else if (is_windows) { 3795 // TODO call NtQueryInformationFile and ask for only the size instead of "all" 3796 } 3797 3798 const stat = try fileStat(t, file); 3799 return stat.size; 3800 } 3801 3802 const fileStat = switch (native_os) { 3803 .linux => fileStatLinux, 3804 .windows => fileStatWindows, 3805 .wasi => fileStatWasi, 3806 else => fileStatPosix, 3807 }; 3808 3809 fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 3810 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3811 _ = t; 3812 3813 if (posix.Stat == void) return error.Streaming; 3814 3815 const syscall: Syscall = try .start(); 3816 while (true) { 3817 var stat = std.mem.zeroes(posix.Stat); 3818 switch (posix.errno(fstat_sym(file.handle, &stat))) { 3819 .SUCCESS => { 3820 syscall.finish(); 3821 return statFromPosix(&stat); 3822 }, 3823 .INTR => { 3824 try syscall.checkCancel(); 3825 continue; 3826 }, 3827 else => |e| { 3828 syscall.finish(); 3829 switch (e) { 3830 .INVAL => |err| return errnoBug(err), 3831 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3832 .NOMEM => return error.SystemResources, 3833 .ACCES => return error.AccessDenied, 3834 else => |err| return posix.unexpectedErrno(err), 3835 } 3836 }, 3837 } 3838 } 3839 } 3840 3841 fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 3842 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3843 _ = t; 3844 const linux = std.os.linux; 3845 const sys = if (statx_use_c) std.c else std.os.linux; 3846 3847 const syscall: Syscall = try .start(); 3848 while (true) { 3849 var statx = std.mem.zeroes(linux.Statx); 3850 switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_request, &statx))) { 3851 .SUCCESS => { 3852 syscall.finish(); 3853 return statFromLinux(&statx); 3854 }, 3855 .INTR => { 3856 try syscall.checkCancel(); 3857 continue; 3858 }, 3859 else => |e| { 3860 syscall.finish(); 3861 switch (e) { 3862 .ACCES => |err| return errnoBug(err), 3863 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3864 .FAULT => |err| return errnoBug(err), 3865 .INVAL => |err| return errnoBug(err), 3866 .LOOP => |err| return errnoBug(err), 3867 .NAMETOOLONG => |err| return errnoBug(err), 3868 .NOENT => |err| return errnoBug(err), 3869 .NOMEM => return error.SystemResources, 3870 .NOTDIR => |err| return errnoBug(err), 3871 else => |err| return posix.unexpectedErrno(err), 3872 } 3873 }, 3874 } 3875 } 3876 } 3877 3878 fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 3879 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3880 3881 const block_size: u32 = if (t.systemBasicInformation()) |sbi| 3882 @intCast(@max(sbi.PageSize, sbi.AllocationGranularity)) 3883 else 3884 std.heap.page_size_max; 3885 3886 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 3887 var info: windows.FILE.ALL_INFORMATION = undefined; 3888 { 3889 const syscall: Syscall = try .start(); 3890 while (true) switch (windows.ntdll.NtQueryInformationFile( 3891 file.handle, 3892 &io_status_block, 3893 &info, 3894 @sizeOf(windows.FILE.ALL_INFORMATION), 3895 .All, 3896 )) { 3897 .SUCCESS => break syscall.finish(), 3898 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer 3899 // size provided. This is treated as success because the type of variable-length information that this would be relevant for 3900 // (name, volume name, etc) we don't care about. 3901 .BUFFER_OVERFLOW => break syscall.finish(), 3902 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), 3903 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 3904 .CANCELLED => { 3905 try syscall.checkCancel(); 3906 continue; 3907 }, 3908 else => |s| return syscall.unexpectedNtstatus(s), 3909 }; 3910 } 3911 return .{ 3912 .inode = info.InternalInformation.IndexNumber, 3913 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)), 3914 .permissions = .default_file, 3915 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { 3916 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; 3917 const syscall: Syscall = try .start(); 3918 while (true) switch (windows.ntdll.NtQueryInformationFile( 3919 file.handle, 3920 &io_status_block, 3921 &tag_info, 3922 @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), 3923 .AttributeTag, 3924 )) { 3925 .SUCCESS => break syscall.finish(), 3926 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors 3927 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e 3928 .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err), 3929 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 3930 .CANCELLED => { 3931 try syscall.checkCancel(); 3932 continue; 3933 }, 3934 else => |s| return syscall.unexpectedNtstatus(s), 3935 }; 3936 if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link; 3937 // Unknown reparse point 3938 break :reparse_point .unknown; 3939 } else if (info.BasicInformation.FileAttributes.DIRECTORY) 3940 .directory 3941 else 3942 .file, 3943 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), 3944 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), 3945 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), 3946 .nlink = info.StandardInformation.NumberOfLinks, 3947 .block_size = block_size, 3948 }; 3949 } 3950 3951 fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION { 3952 if (!t.system_basic_information.initialized.load(.acquire)) { 3953 mutexLock(&t.mutex); 3954 defer mutexUnlock(&t.mutex); 3955 3956 switch (windows.ntdll.NtQuerySystemInformation( 3957 .Basic, 3958 &t.system_basic_information.buffer, 3959 @sizeOf(windows.SYSTEM.BASIC_INFORMATION), 3960 null, 3961 )) { 3962 .SUCCESS => {}, 3963 else => return null, 3964 } 3965 3966 t.system_basic_information.initialized.store(true, .release); 3967 } 3968 return &t.system_basic_information.buffer; 3969 } 3970 3971 fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { 3972 if (builtin.link_libc) return fileStatPosix(userdata, file); 3973 3974 const t: *Threaded = @ptrCast(@alignCast(userdata)); 3975 _ = t; 3976 3977 const syscall: Syscall = try .start(); 3978 while (true) { 3979 var stat: std.os.wasi.filestat_t = undefined; 3980 switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) { 3981 .SUCCESS => { 3982 syscall.finish(); 3983 return statFromWasi(&stat); 3984 }, 3985 .INTR => { 3986 try syscall.checkCancel(); 3987 continue; 3988 }, 3989 else => |e| { 3990 syscall.finish(); 3991 switch (e) { 3992 .INVAL => |err| return errnoBug(err), 3993 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 3994 .NOMEM => return error.SystemResources, 3995 .ACCES => return error.AccessDenied, 3996 .NOTCAPABLE => return error.AccessDenied, 3997 else => |err| return posix.unexpectedErrno(err), 3998 } 3999 }, 4000 } 4001 } 4002 } 4003 4004 const dirAccess = switch (native_os) { 4005 .windows => dirAccessWindows, 4006 .wasi => dirAccessWasi, 4007 else => dirAccessPosix, 4008 }; 4009 4010 fn dirAccessPosix( 4011 userdata: ?*anyopaque, 4012 dir: Dir, 4013 sub_path: []const u8, 4014 options: Dir.AccessOptions, 4015 ) Dir.AccessError!void { 4016 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4017 _ = t; 4018 4019 var path_buffer: [posix.PATH_MAX]u8 = undefined; 4020 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 4021 4022 const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0); 4023 4024 const mode: u32 = 4025 @as(u32, if (options.read) posix.R_OK else 0) | 4026 @as(u32, if (options.write) posix.W_OK else 0) | 4027 @as(u32, if (options.execute) posix.X_OK else 0); 4028 4029 const syscall: Syscall = try .start(); 4030 while (true) { 4031 switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) { 4032 .SUCCESS => { 4033 syscall.finish(); 4034 return; 4035 }, 4036 .INTR => { 4037 try syscall.checkCancel(); 4038 continue; 4039 }, 4040 else => |e| { 4041 syscall.finish(); 4042 switch (e) { 4043 .ACCES => return error.AccessDenied, 4044 .PERM => return error.PermissionDenied, 4045 .ROFS => return error.ReadOnlyFileSystem, 4046 .LOOP => return error.SymLinkLoop, 4047 .TXTBSY => return error.FileBusy, 4048 .NOTDIR => return error.FileNotFound, 4049 .NOENT => return error.FileNotFound, 4050 .NAMETOOLONG => return error.NameTooLong, 4051 .INVAL => |err| return errnoBug(err), 4052 .FAULT => |err| return errnoBug(err), 4053 .IO => return error.InputOutput, 4054 .NOMEM => return error.SystemResources, 4055 .ILSEQ => return error.BadPathName, 4056 else => |err| return posix.unexpectedErrno(err), 4057 } 4058 }, 4059 } 4060 } 4061 } 4062 4063 fn dirAccessWasi( 4064 userdata: ?*anyopaque, 4065 dir: Dir, 4066 sub_path: []const u8, 4067 options: Dir.AccessOptions, 4068 ) Dir.AccessError!void { 4069 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options); 4070 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4071 _ = t; 4072 const wasi = std.os.wasi; 4073 const flags: wasi.lookupflags_t = .{ 4074 .SYMLINK_FOLLOW = options.follow_symlinks, 4075 }; 4076 var stat: wasi.filestat_t = undefined; 4077 4078 const syscall: Syscall = try .start(); 4079 while (true) { 4080 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { 4081 .SUCCESS => { 4082 syscall.finish(); 4083 break; 4084 }, 4085 .INTR => { 4086 try syscall.checkCancel(); 4087 continue; 4088 }, 4089 else => |e| { 4090 syscall.finish(); 4091 switch (e) { 4092 .INVAL => |err| return errnoBug(err), 4093 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4094 .NOMEM => return error.SystemResources, 4095 .ACCES => return error.AccessDenied, 4096 .FAULT => |err| return errnoBug(err), 4097 .NAMETOOLONG => return error.NameTooLong, 4098 .NOENT => return error.FileNotFound, 4099 .NOTDIR => return error.FileNotFound, 4100 .NOTCAPABLE => return error.AccessDenied, 4101 .ILSEQ => return error.BadPathName, 4102 else => |err| return posix.unexpectedErrno(err), 4103 } 4104 }, 4105 } 4106 } 4107 4108 if (!options.read and !options.write and !options.execute) 4109 return; 4110 4111 var directory: wasi.fdstat_t = undefined; 4112 if (wasi.fd_fdstat_get(dir.handle, &directory) != .SUCCESS) 4113 return error.AccessDenied; 4114 4115 var rights: wasi.rights_t = .{}; 4116 if (options.read) { 4117 if (stat.filetype == .DIRECTORY) { 4118 rights.FD_READDIR = true; 4119 } else { 4120 rights.FD_READ = true; 4121 } 4122 } 4123 if (options.write) 4124 rights.FD_WRITE = true; 4125 4126 // No validation for execution. 4127 4128 // https://github.com/ziglang/zig/issues/18882 4129 const rights_int: u64 = @bitCast(rights); 4130 const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting); 4131 if ((rights_int & inheriting_int) != rights_int) 4132 return error.AccessDenied; 4133 } 4134 4135 fn dirAccessWindows( 4136 userdata: ?*anyopaque, 4137 dir: Dir, 4138 sub_path: []const u8, 4139 options: Dir.AccessOptions, 4140 ) Dir.AccessError!void { 4141 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4142 _ = t; 4143 4144 _ = options; // TODO 4145 4146 if (std.mem.eql(u8, sub_path, ".") or std.mem.eql(u8, sub_path, "..")) return; 4147 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); 4148 const attr: windows.OBJECT.ATTRIBUTES = .{ 4149 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, 4150 .ObjectName = @constCast(&sub_path_w.string()), 4151 }; 4152 var basic_info: windows.FILE.BASIC_INFORMATION = undefined; 4153 const syscall: Syscall = try .start(); 4154 while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { 4155 .SUCCESS => return syscall.finish(), 4156 .CANCELLED => { 4157 try syscall.checkCancel(); 4158 continue; 4159 }, 4160 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 4161 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 4162 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err), 4163 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), 4164 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 4165 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), 4166 else => |rc| return syscall.unexpectedNtstatus(rc), 4167 }; 4168 } 4169 4170 const dirCreateFile = switch (native_os) { 4171 .windows => dirCreateFileWindows, 4172 .wasi => dirCreateFileWasi, 4173 else => dirCreateFilePosix, 4174 }; 4175 4176 fn dirCreateFilePosix( 4177 userdata: ?*anyopaque, 4178 dir: Dir, 4179 sub_path: []const u8, 4180 flags: File.CreateFlags, 4181 ) File.OpenError!File { 4182 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4183 _ = t; 4184 4185 var path_buffer: [posix.PATH_MAX]u8 = undefined; 4186 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 4187 4188 var os_flags: posix.O = .{ 4189 .ACCMODE = if (flags.read) .RDWR else .WRONLY, 4190 .CREAT = true, 4191 .TRUNC = flags.truncate, 4192 .EXCL = flags.exclusive, 4193 }; 4194 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; 4195 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; 4196 4197 // Use the O locking flags if the os supports them to acquire the lock 4198 // atomically. Note that the NONBLOCK flag is removed after the openat() 4199 // call is successful. 4200 if (have_flock_open_flags) switch (flags.lock) { 4201 .none => {}, 4202 .shared => { 4203 os_flags.SHLOCK = true; 4204 os_flags.NONBLOCK = flags.lock_nonblocking; 4205 }, 4206 .exclusive => { 4207 os_flags.EXLOCK = true; 4208 os_flags.NONBLOCK = flags.lock_nonblocking; 4209 }, 4210 }; 4211 4212 const fd: posix.fd_t = fd: { 4213 const syscall: Syscall = try .start(); 4214 while (true) { 4215 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode()); 4216 switch (posix.errno(rc)) { 4217 .SUCCESS => { 4218 syscall.finish(); 4219 break :fd @intCast(rc); 4220 }, 4221 .INTR => { 4222 try syscall.checkCancel(); 4223 continue; 4224 }, 4225 else => |e| { 4226 syscall.finish(); 4227 switch (e) { 4228 .FAULT => |err| return errnoBug(err), 4229 .INVAL => return error.BadPathName, 4230 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4231 .ACCES => return error.AccessDenied, 4232 .FBIG => return error.FileTooBig, 4233 .OVERFLOW => return error.FileTooBig, 4234 .ISDIR => return error.IsDir, 4235 .LOOP => return error.SymLinkLoop, 4236 .MFILE => return error.ProcessFdQuotaExceeded, 4237 .NAMETOOLONG => return error.NameTooLong, 4238 .NFILE => return error.SystemFdQuotaExceeded, 4239 .NODEV => return error.NoDevice, 4240 .NOENT => return error.FileNotFound, 4241 .SRCH => return error.FileNotFound, // Linux when accessing procfs. 4242 .NOMEM => return error.SystemResources, 4243 .NOSPC => return error.NoSpaceLeft, 4244 .NOTDIR => return error.NotDir, 4245 .PERM => return error.PermissionDenied, 4246 .EXIST => return error.PathAlreadyExists, 4247 .BUSY => return error.DeviceBusy, 4248 .OPNOTSUPP => return error.FileLocksUnsupported, 4249 .AGAIN => return error.WouldBlock, 4250 .TXTBSY => return error.FileBusy, 4251 .NXIO => return error.NoDevice, 4252 .ILSEQ => return error.BadPathName, 4253 else => |err| return posix.unexpectedErrno(err), 4254 } 4255 }, 4256 } 4257 } 4258 }; 4259 errdefer closeFd(fd); 4260 4261 if (have_flock and !have_flock_open_flags and flags.lock != .none) { 4262 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0; 4263 const lock_flags = switch (flags.lock) { 4264 .none => unreachable, 4265 .shared => posix.LOCK.SH | lock_nonblocking, 4266 .exclusive => posix.LOCK.EX | lock_nonblocking, 4267 }; 4268 4269 const syscall: Syscall = try .start(); 4270 while (true) { 4271 switch (posix.errno(posix.system.flock(fd, lock_flags))) { 4272 .SUCCESS => { 4273 syscall.finish(); 4274 break; 4275 }, 4276 .INTR => { 4277 try syscall.checkCancel(); 4278 continue; 4279 }, 4280 else => |e| { 4281 syscall.finish(); 4282 switch (e) { 4283 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4284 .INVAL => |err| return errnoBug(err), // invalid parameters 4285 .NOLCK => return error.SystemResources, 4286 .AGAIN => return error.WouldBlock, 4287 .OPNOTSUPP => return error.FileLocksUnsupported, 4288 else => |err| return posix.unexpectedErrno(err), 4289 } 4290 }, 4291 } 4292 } 4293 } 4294 4295 if (have_flock_open_flags and flags.lock_nonblocking) { 4296 var fl_flags: usize = fl: { 4297 const syscall: Syscall = try .start(); 4298 while (true) { 4299 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); 4300 switch (posix.errno(rc)) { 4301 .SUCCESS => { 4302 syscall.finish(); 4303 break :fl @intCast(rc); 4304 }, 4305 .INTR => { 4306 try syscall.checkCancel(); 4307 continue; 4308 }, 4309 else => |err| { 4310 syscall.finish(); 4311 return posix.unexpectedErrno(err); 4312 }, 4313 } 4314 } 4315 }; 4316 4317 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); 4318 4319 const syscall: Syscall = try .start(); 4320 while (true) { 4321 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { 4322 .SUCCESS => { 4323 syscall.finish(); 4324 break; 4325 }, 4326 .INTR => { 4327 try syscall.checkCancel(); 4328 continue; 4329 }, 4330 else => |err| { 4331 syscall.finish(); 4332 return posix.unexpectedErrno(err); 4333 }, 4334 } 4335 } 4336 } 4337 4338 return .{ 4339 .handle = fd, 4340 .flags = .{ .nonblocking = false }, 4341 }; 4342 } 4343 4344 fn dirCreateFileWindows( 4345 userdata: ?*anyopaque, 4346 dir: Dir, 4347 sub_path: []const u8, 4348 flags: File.CreateFlags, 4349 ) File.OpenError!File { 4350 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4351 _ = t; 4352 4353 if (std.mem.eql(u8, sub_path, ".")) return error.IsDir; 4354 if (std.mem.eql(u8, sub_path, "..")) return error.IsDir; 4355 4356 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); 4357 const attr: windows.OBJECT.ATTRIBUTES = .{ 4358 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, 4359 .ObjectName = @constCast(&sub_path_w.string()), 4360 }; 4361 const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) 4362 .CREATE 4363 else if (flags.truncate) 4364 .OVERWRITE_IF 4365 else 4366 .OPEN_IF; 4367 4368 const access_mask: windows.ACCESS_MASK = .{ 4369 .STANDARD = .{ .SYNCHRONIZE = true }, 4370 .GENERIC = .{ 4371 .WRITE = true, 4372 .READ = flags.read, 4373 }, 4374 }; 4375 4376 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 4377 var attempt: u5 = 0; 4378 var handle: windows.HANDLE = undefined; 4379 var syscall: Syscall = try .start(); 4380 while (true) switch (windows.ntdll.NtCreateFile( 4381 &handle, 4382 access_mask, 4383 &attr, 4384 &io_status_block, 4385 null, 4386 .{ .NORMAL = true }, 4387 .VALID_FLAGS, // share access 4388 create_disposition, 4389 .{ 4390 .NON_DIRECTORY_FILE = true, 4391 .IO = .SYNCHRONOUS_NONALERT, 4392 }, 4393 null, 4394 0, 4395 )) { 4396 .SUCCESS => { 4397 syscall.finish(); 4398 break; 4399 }, 4400 .CANCELLED => { 4401 try syscall.checkCancel(); 4402 continue; 4403 }, 4404 .SHARING_VIOLATION => { 4405 // This occurs if the file attempting to be opened is a running 4406 // executable. However, there's a kernel bug: the error may be 4407 // incorrectly returned for an indeterminate amount of time 4408 // after an executable file is closed. Here we work around the 4409 // kernel bug with retry attempts. 4410 syscall.finish(); 4411 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 4412 try parking_sleep.sleep(.{ .duration = .{ 4413 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 4414 .clock = .awake, 4415 } }); 4416 attempt += 1; 4417 syscall = try .start(); 4418 continue; 4419 }, 4420 .DELETE_PENDING => { 4421 // This error means that there *was* a file in this location on 4422 // the file system, but it was deleted. However, the OS is not 4423 // finished with the deletion operation, and so this CreateFile 4424 // call has failed. Here, we simulate the kernel bug being 4425 // fixed by sleeping and retrying until the error goes away. 4426 syscall.finish(); 4427 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 4428 try parking_sleep.sleep(.{ .duration = .{ 4429 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 4430 .clock = .awake, 4431 } }); 4432 attempt += 1; 4433 syscall = try .start(); 4434 continue; 4435 }, 4436 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 4437 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 4438 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 4439 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found 4440 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't 4441 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), 4442 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 4443 .PIPE_BUSY => return syscall.fail(error.PipeBusy), 4444 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), 4445 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), 4446 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), 4447 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 4448 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 4449 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), 4450 .DISK_FULL => return syscall.fail(error.NoSpaceLeft), 4451 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 4452 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), 4453 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), 4454 else => |status| return syscall.unexpectedNtstatus(status), 4455 }; 4456 errdefer windows.CloseHandle(handle); 4457 4458 const exclusive = switch (flags.lock) { 4459 .none => return .{ 4460 .handle = handle, 4461 .flags = .{ .nonblocking = false }, 4462 }, 4463 .shared => false, 4464 .exclusive => true, 4465 }; 4466 4467 syscall = try .start(); 4468 while (true) switch (windows.ntdll.NtLockFile( 4469 handle, 4470 null, 4471 null, 4472 null, 4473 &io_status_block, 4474 &windows_lock_range_off, 4475 &windows_lock_range_len, 4476 null, 4477 @intFromBool(flags.lock_nonblocking), 4478 @intFromBool(exclusive), 4479 )) { 4480 .SUCCESS => { 4481 syscall.finish(); 4482 return .{ 4483 .handle = handle, 4484 .flags = .{ .nonblocking = false }, 4485 }; 4486 }, 4487 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), 4488 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), 4489 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer 4490 else => |status| return syscall.unexpectedNtstatus(status), 4491 }; 4492 } 4493 4494 fn dirCreateFileWasi( 4495 userdata: ?*anyopaque, 4496 dir: Dir, 4497 sub_path: []const u8, 4498 flags: File.CreateFlags, 4499 ) File.OpenError!File { 4500 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4501 _ = t; 4502 const wasi = std.os.wasi; 4503 const lookup_flags: wasi.lookupflags_t = .{}; 4504 const oflags: wasi.oflags_t = .{ 4505 .CREAT = true, 4506 .TRUNC = flags.truncate, 4507 .EXCL = flags.exclusive, 4508 }; 4509 const fdflags: wasi.fdflags_t = .{}; 4510 const base: wasi.rights_t = .{ 4511 .FD_READ = flags.read, 4512 .FD_WRITE = true, 4513 .FD_DATASYNC = true, 4514 .FD_SEEK = true, 4515 .FD_TELL = true, 4516 .FD_FDSTAT_SET_FLAGS = true, 4517 .FD_SYNC = true, 4518 .FD_ALLOCATE = true, 4519 .FD_ADVISE = true, 4520 .FD_FILESTAT_SET_TIMES = true, 4521 .FD_FILESTAT_SET_SIZE = true, 4522 .FD_FILESTAT_GET = true, 4523 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or 4524 // FD_WRITE is also set. 4525 .POLL_FD_READWRITE = true, 4526 }; 4527 const inheriting: wasi.rights_t = .{}; 4528 var fd: posix.fd_t = undefined; 4529 const syscall: Syscall = try .start(); 4530 while (true) { 4531 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { 4532 .SUCCESS => { 4533 syscall.finish(); 4534 return .{ 4535 .handle = fd, 4536 .flags = .{ .nonblocking = false }, 4537 }; 4538 }, 4539 .INTR => { 4540 try syscall.checkCancel(); 4541 continue; 4542 }, 4543 else => |e| { 4544 syscall.finish(); 4545 switch (e) { 4546 .FAULT => |err| return errnoBug(err), 4547 .INVAL => return error.BadPathName, 4548 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4549 .ACCES => return error.AccessDenied, 4550 .FBIG => return error.FileTooBig, 4551 .OVERFLOW => return error.FileTooBig, 4552 .ISDIR => return error.IsDir, 4553 .LOOP => return error.SymLinkLoop, 4554 .MFILE => return error.ProcessFdQuotaExceeded, 4555 .NAMETOOLONG => return error.NameTooLong, 4556 .NFILE => return error.SystemFdQuotaExceeded, 4557 .NODEV => return error.NoDevice, 4558 .NOENT => return error.FileNotFound, 4559 .NOMEM => return error.SystemResources, 4560 .NOSPC => return error.NoSpaceLeft, 4561 .NOTDIR => return error.NotDir, 4562 .PERM => return error.PermissionDenied, 4563 .EXIST => return error.PathAlreadyExists, 4564 .BUSY => return error.DeviceBusy, 4565 .NOTCAPABLE => return error.AccessDenied, 4566 .ILSEQ => return error.BadPathName, 4567 else => |err| return posix.unexpectedErrno(err), 4568 } 4569 }, 4570 } 4571 } 4572 } 4573 4574 fn dirCreateFileAtomic( 4575 userdata: ?*anyopaque, 4576 dir: Dir, 4577 dest_path: []const u8, 4578 options: Dir.CreateFileAtomicOptions, 4579 ) Dir.CreateFileAtomicError!File.Atomic { 4580 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4581 const t_io = io(t); 4582 4583 // Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's 4584 // useless when we have to make up a bogus path name to do the rename() 4585 // anyway. 4586 if (native_os == .linux and !options.replace) tmpfile: { 4587 const flags: posix.O = if (@hasField(posix.O, "TMPFILE")) .{ 4588 .ACCMODE = .RDWR, 4589 .TMPFILE = true, 4590 .DIRECTORY = true, 4591 .CLOEXEC = true, 4592 } else if (@hasField(posix.O, "TMPFILE0") and !@hasField(posix.O, "TMPFILE2")) .{ 4593 .ACCMODE = .RDWR, 4594 .TMPFILE0 = true, 4595 .TMPFILE1 = true, 4596 .DIRECTORY = true, 4597 .CLOEXEC = true, 4598 } else break :tmpfile; 4599 4600 const dest_dirname = Dir.path.dirname(dest_path); 4601 if (dest_dirname) |dirname| { 4602 // This has a nice side effect of preemptively triggering EISDIR or 4603 // ENOENT, avoiding the ambiguity below. 4604 if (options.make_path) dir.createDirPath(t_io, dirname) catch |err| switch (err) { 4605 // None of these make sense in this context. 4606 error.IsDir, 4607 error.Streaming, 4608 error.DiskQuota, 4609 error.PathAlreadyExists, 4610 error.LinkQuotaExceeded, 4611 error.PipeBusy, 4612 error.FileTooBig, 4613 error.DeviceBusy, 4614 error.FileLocksUnsupported, 4615 error.FileBusy, 4616 => return error.Unexpected, 4617 4618 else => |e| return e, 4619 }; 4620 } 4621 4622 var path_buffer: [posix.PATH_MAX]u8 = undefined; 4623 const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer); 4624 4625 const syscall: Syscall = try .start(); 4626 while (true) { 4627 const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode()); 4628 switch (posix.errno(rc)) { 4629 .SUCCESS => { 4630 syscall.finish(); 4631 return .{ 4632 .file = .{ 4633 .handle = @intCast(rc), 4634 .flags = .{ .nonblocking = false }, 4635 }, 4636 .file_basename_hex = 0, 4637 .dest_sub_path = dest_path, 4638 .file_open = true, 4639 .file_exists = false, 4640 .close_dir_on_deinit = false, 4641 .dir = dir, 4642 }; 4643 }, 4644 .INTR => { 4645 try syscall.checkCancel(); 4646 continue; 4647 }, 4648 .ISDIR, .NOENT => { 4649 // Ambiguous error code. It might mean the file system 4650 // does not support O_TMPFILE. Therefore, we must fall 4651 // back to not using O_TMPFILE. 4652 syscall.finish(); 4653 break :tmpfile; 4654 }, 4655 .INVAL => return syscall.fail(error.BadPathName), 4656 .ACCES => return syscall.fail(error.AccessDenied), 4657 .LOOP => return syscall.fail(error.SymLinkLoop), 4658 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 4659 .NAMETOOLONG => return syscall.fail(error.NameTooLong), 4660 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), 4661 .NODEV => return syscall.fail(error.NoDevice), 4662 .NOMEM => return syscall.fail(error.SystemResources), 4663 .NOSPC => return syscall.fail(error.NoSpaceLeft), 4664 .NOTDIR => return syscall.fail(error.NotDir), 4665 .PERM => return syscall.fail(error.PermissionDenied), 4666 .AGAIN => return syscall.fail(error.WouldBlock), 4667 .NXIO => return syscall.fail(error.NoDevice), 4668 .ILSEQ => return syscall.fail(error.BadPathName), 4669 else => |err| return syscall.unexpectedErrno(err), 4670 } 4671 } 4672 } 4673 4674 if (Dir.path.dirname(dest_path)) |dirname| { 4675 const new_dir = if (options.make_path) 4676 dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) { 4677 // None of these make sense in this context. 4678 error.IsDir, 4679 error.Streaming, 4680 error.DiskQuota, 4681 error.PathAlreadyExists, 4682 error.LinkQuotaExceeded, 4683 error.PipeBusy, 4684 error.FileTooBig, 4685 error.FileLocksUnsupported, 4686 error.DeviceBusy, 4687 => return error.Unexpected, 4688 4689 else => |e| return e, 4690 } 4691 else 4692 try dir.openDir(t_io, dirname, .{}); 4693 4694 return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true); 4695 } 4696 4697 return atomicFileInit(t_io, dest_path, options.permissions, dir, false); 4698 } 4699 4700 fn atomicFileInit( 4701 t_io: Io, 4702 dest_basename: []const u8, 4703 permissions: File.Permissions, 4704 dir: Dir, 4705 close_dir_on_deinit: bool, 4706 ) Dir.CreateFileAtomicError!File.Atomic { 4707 while (true) { 4708 var random_integer: u64 = undefined; 4709 t_io.random(@ptrCast(&random_integer)); 4710 const tmp_sub_path = std.fmt.hex(random_integer); 4711 const file = dir.createFile(t_io, &tmp_sub_path, .{ 4712 .permissions = permissions, 4713 .exclusive = true, 4714 }) catch |err| switch (err) { 4715 error.PathAlreadyExists => continue, 4716 error.DeviceBusy => continue, 4717 error.FileBusy => continue, 4718 4719 error.IsDir => return error.Unexpected, // No path components. 4720 error.FileTooBig => return error.Unexpected, // Creating, not opening. 4721 error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks. 4722 error.PipeBusy => return error.Unexpected, // Not opening a pipe. 4723 4724 else => |e| return e, 4725 }; 4726 return .{ 4727 .file = file, 4728 .file_basename_hex = random_integer, 4729 .dest_sub_path = dest_basename, 4730 .file_open = true, 4731 .file_exists = true, 4732 .close_dir_on_deinit = close_dir_on_deinit, 4733 .dir = dir, 4734 }; 4735 } 4736 } 4737 4738 const dirOpenFile = switch (native_os) { 4739 .windows => dirOpenFileWindows, 4740 .wasi => dirOpenFileWasi, 4741 else => dirOpenFilePosix, 4742 }; 4743 4744 fn dirOpenFilePosix( 4745 userdata: ?*anyopaque, 4746 dir: Dir, 4747 sub_path: []const u8, 4748 flags: File.OpenFlags, 4749 ) File.OpenError!File { 4750 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4751 4752 var path_buffer: [posix.PATH_MAX]u8 = undefined; 4753 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 4754 4755 var os_flags: posix.O = switch (native_os) { 4756 .wasi => .{ 4757 .read = flags.mode != .write_only, 4758 .write = flags.mode != .read_only, 4759 .NOFOLLOW = !flags.follow_symlinks, 4760 }, 4761 else => .{ 4762 .ACCMODE = switch (flags.mode) { 4763 .read_only => .RDONLY, 4764 .write_only => .WRONLY, 4765 .read_write => .RDWR, 4766 }, 4767 .NOFOLLOW = !flags.follow_symlinks, 4768 }, 4769 }; 4770 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; 4771 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; 4772 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty; 4773 if (@hasField(posix.O, "PATH") and flags.path_only) os_flags.PATH = true; 4774 4775 // Use the O locking flags if the os supports them to acquire the lock 4776 // atomically. Note that the NONBLOCK flag is removed after the openat() 4777 // call is successful. 4778 if (have_flock_open_flags) switch (flags.lock) { 4779 .none => {}, 4780 .shared => { 4781 os_flags.SHLOCK = true; 4782 os_flags.NONBLOCK = flags.lock_nonblocking; 4783 }, 4784 .exclusive => { 4785 os_flags.EXLOCK = true; 4786 os_flags.NONBLOCK = flags.lock_nonblocking; 4787 }, 4788 }; 4789 4790 const mode: posix.mode_t = 0; 4791 4792 const fd: posix.fd_t = fd: { 4793 const syscall: Syscall = try .start(); 4794 while (true) { 4795 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, mode); 4796 switch (posix.errno(rc)) { 4797 .SUCCESS => { 4798 syscall.finish(); 4799 break :fd @intCast(rc); 4800 }, 4801 .INTR => { 4802 try syscall.checkCancel(); 4803 continue; 4804 }, 4805 else => |e| { 4806 syscall.finish(); 4807 switch (e) { 4808 .FAULT => |err| return errnoBug(err), 4809 .INVAL => return error.BadPathName, 4810 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4811 .ACCES => return error.AccessDenied, 4812 .FBIG => return error.FileTooBig, 4813 .OVERFLOW => return error.FileTooBig, 4814 .ISDIR => return error.IsDir, 4815 .LOOP => return error.SymLinkLoop, 4816 .MFILE => return error.ProcessFdQuotaExceeded, 4817 .NAMETOOLONG => return error.NameTooLong, 4818 .NFILE => return error.SystemFdQuotaExceeded, 4819 .NODEV => return error.NoDevice, 4820 .NOENT => return error.FileNotFound, 4821 .SRCH => return error.FileNotFound, // Linux when opening procfs files. 4822 .NOMEM => return error.SystemResources, 4823 .NOSPC => return error.NoSpaceLeft, 4824 .NOTDIR => return error.NotDir, 4825 .PERM => return error.PermissionDenied, 4826 .EXIST => return error.PathAlreadyExists, 4827 .BUSY => return error.DeviceBusy, 4828 .OPNOTSUPP => return error.FileLocksUnsupported, 4829 .AGAIN => return error.WouldBlock, 4830 .TXTBSY => return error.FileBusy, 4831 .NXIO => return error.NoDevice, 4832 .ILSEQ => return error.BadPathName, 4833 else => |err| return posix.unexpectedErrno(err), 4834 } 4835 }, 4836 } 4837 } 4838 }; 4839 errdefer closeFd(fd); 4840 4841 if (!flags.allow_directory) { 4842 const is_dir = is_dir: { 4843 const stat = fileStat(t, .{ 4844 .handle = fd, 4845 .flags = .{ .nonblocking = false }, 4846 }) catch |err| switch (err) { 4847 // The directory-ness is either unknown or unknowable 4848 error.Streaming => break :is_dir false, 4849 else => |e| return e, 4850 }; 4851 break :is_dir stat.kind == .directory; 4852 }; 4853 if (is_dir) return error.IsDir; 4854 } 4855 4856 if (have_flock and !have_flock_open_flags and flags.lock != .none) { 4857 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0; 4858 const lock_flags = switch (flags.lock) { 4859 .none => unreachable, 4860 .shared => posix.LOCK.SH | lock_nonblocking, 4861 .exclusive => posix.LOCK.EX | lock_nonblocking, 4862 }; 4863 const syscall: Syscall = try .start(); 4864 while (true) { 4865 switch (posix.errno(posix.system.flock(fd, lock_flags))) { 4866 .SUCCESS => { 4867 syscall.finish(); 4868 break; 4869 }, 4870 .INTR => { 4871 try syscall.checkCancel(); 4872 continue; 4873 }, 4874 else => |e| { 4875 syscall.finish(); 4876 switch (e) { 4877 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 4878 .INVAL => |err| return errnoBug(err), // invalid parameters 4879 .NOLCK => return error.SystemResources, 4880 .AGAIN => return error.WouldBlock, 4881 .OPNOTSUPP => return error.FileLocksUnsupported, 4882 else => |err| return posix.unexpectedErrno(err), 4883 } 4884 }, 4885 } 4886 } 4887 } 4888 4889 if (have_flock_open_flags and flags.lock_nonblocking) { 4890 var fl_flags: usize = fl: { 4891 const syscall: Syscall = try .start(); 4892 while (true) { 4893 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); 4894 switch (posix.errno(rc)) { 4895 .SUCCESS => { 4896 syscall.finish(); 4897 break :fl @intCast(rc); 4898 }, 4899 .INTR => { 4900 try syscall.checkCancel(); 4901 continue; 4902 }, 4903 else => |err| { 4904 syscall.finish(); 4905 return posix.unexpectedErrno(err); 4906 }, 4907 } 4908 } 4909 }; 4910 4911 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); 4912 4913 const syscall: Syscall = try .start(); 4914 while (true) { 4915 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { 4916 .SUCCESS => { 4917 syscall.finish(); 4918 break; 4919 }, 4920 .INTR => { 4921 try syscall.checkCancel(); 4922 continue; 4923 }, 4924 else => |err| { 4925 syscall.finish(); 4926 return posix.unexpectedErrno(err); 4927 }, 4928 } 4929 } 4930 } 4931 4932 return .{ 4933 .handle = fd, 4934 .flags = .{ .nonblocking = false }, 4935 }; 4936 } 4937 4938 fn dirOpenFileWindows( 4939 userdata: ?*anyopaque, 4940 dir: Dir, 4941 sub_path: []const u8, 4942 flags: File.OpenFlags, 4943 ) File.OpenError!File { 4944 const t: *Threaded = @ptrCast(@alignCast(userdata)); 4945 _ = t; 4946 const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); 4947 const sub_path_w = sub_path_w_array.span(); 4948 const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; 4949 return dirOpenFileWtf16(dir_handle, sub_path_w, flags); 4950 } 4951 4952 pub fn dirOpenFileWtf16( 4953 dir_handle: ?windows.HANDLE, 4954 sub_path_w: []const u16, 4955 flags: File.OpenFlags, 4956 ) File.OpenError!File { 4957 const allow_directory = flags.allow_directory and !flags.isWrite(); 4958 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; 4959 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; 4960 const w = windows; 4961 4962 var io_status_block: w.IO_STATUS_BLOCK = undefined; 4963 var attempt: u5 = 0; 4964 var syscall: Syscall = try .start(); 4965 const handle = while (true) { 4966 var result: w.HANDLE = undefined; 4967 switch (w.ntdll.NtCreateFile( 4968 &result, 4969 .{ 4970 .STANDARD = .{ .SYNCHRONIZE = true }, 4971 .GENERIC = .{ 4972 .READ = flags.isRead(), 4973 .WRITE = flags.isWrite(), 4974 }, 4975 }, 4976 &.{ 4977 .RootDirectory = dir_handle, 4978 .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)), 4979 }, 4980 &io_status_block, 4981 null, 4982 .{ .NORMAL = true }, 4983 .VALID_FLAGS, 4984 .OPEN, 4985 .{ 4986 .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, 4987 .NON_DIRECTORY_FILE = !allow_directory, 4988 .OPEN_REPARSE_POINT = !flags.follow_symlinks, 4989 }, 4990 null, 4991 0, 4992 )) { 4993 .SUCCESS => { 4994 syscall.finish(); 4995 break result; 4996 }, 4997 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 4998 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 4999 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 5000 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found 5001 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't 5002 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), 5003 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), 5004 .CANCELLED => { 5005 try syscall.checkCancel(); 5006 continue; 5007 }, 5008 .SHARING_VIOLATION => { 5009 // This occurs if the file attempting to be opened is a running 5010 // executable. However, there's a kernel bug: the error may be 5011 // incorrectly returned for an indeterminate amount of time 5012 // after an executable file is closed. Here we work around the 5013 // kernel bug with retry attempts. 5014 syscall.finish(); 5015 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 5016 try parking_sleep.sleep(.{ .duration = .{ 5017 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 5018 .clock = .awake, 5019 } }); 5020 attempt += 1; 5021 syscall = try .start(); 5022 continue; 5023 }, 5024 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 5025 .PIPE_BUSY => return syscall.fail(error.PipeBusy), 5026 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), 5027 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), 5028 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), 5029 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), 5030 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 5031 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 5032 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), 5033 .DELETE_PENDING => { 5034 // This error means that there *was* a file in this location on 5035 // the file system, but it was deleted. However, the OS is not 5036 // finished with the deletion operation, and so this CreateFile 5037 // call has failed. Here, we simulate the kernel bug being 5038 // fixed by sleeping and retrying until the error goes away. 5039 syscall.finish(); 5040 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 5041 try parking_sleep.sleep(.{ .duration = .{ 5042 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 5043 .clock = .awake, 5044 } }); 5045 attempt += 1; 5046 syscall = try .start(); 5047 continue; 5048 }, 5049 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), 5050 else => |rc| return syscall.unexpectedNtstatus(rc), 5051 } 5052 }; 5053 errdefer w.CloseHandle(handle); 5054 5055 const exclusive = switch (flags.lock) { 5056 .none => return .{ 5057 .handle = handle, 5058 .flags = .{ .nonblocking = false }, 5059 }, 5060 .shared => false, 5061 .exclusive => true, 5062 }; 5063 syscall = try .start(); 5064 while (true) switch (w.ntdll.NtLockFile( 5065 handle, 5066 null, 5067 null, 5068 null, 5069 &io_status_block, 5070 &windows_lock_range_off, 5071 &windows_lock_range_len, 5072 null, 5073 @intFromBool(flags.lock_nonblocking), 5074 @intFromBool(exclusive), 5075 )) { 5076 .SUCCESS => break syscall.finish(), 5077 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), 5078 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), 5079 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer 5080 else => |status| return syscall.unexpectedNtstatus(status), 5081 }; 5082 return .{ 5083 .handle = handle, 5084 .flags = .{ .nonblocking = false }, 5085 }; 5086 } 5087 5088 fn dirOpenFileWasi( 5089 userdata: ?*anyopaque, 5090 dir: Dir, 5091 sub_path: []const u8, 5092 flags: File.OpenFlags, 5093 ) File.OpenError!File { 5094 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags); 5095 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5096 const wasi = std.os.wasi; 5097 var base: std.os.wasi.rights_t = .{}; 5098 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE 5099 // is also set. 5100 if (flags.isRead()) { 5101 base.FD_READ = true; 5102 base.FD_TELL = true; 5103 base.FD_SEEK = true; 5104 base.FD_FILESTAT_GET = true; 5105 base.POLL_FD_READWRITE = true; 5106 } 5107 if (flags.isWrite()) { 5108 base.FD_WRITE = true; 5109 base.FD_TELL = true; 5110 base.FD_SEEK = true; 5111 base.FD_DATASYNC = true; 5112 base.FD_FDSTAT_SET_FLAGS = true; 5113 base.FD_SYNC = true; 5114 base.FD_ALLOCATE = true; 5115 base.FD_ADVISE = true; 5116 base.FD_FILESTAT_SET_TIMES = true; 5117 base.FD_FILESTAT_SET_SIZE = true; 5118 base.POLL_FD_READWRITE = true; 5119 } 5120 const lookup_flags: wasi.lookupflags_t = .{}; 5121 const oflags: wasi.oflags_t = .{}; 5122 const inheriting: wasi.rights_t = .{}; 5123 const fdflags: wasi.fdflags_t = .{}; 5124 var fd: posix.fd_t = undefined; 5125 const syscall: Syscall = try .start(); 5126 while (true) { 5127 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { 5128 .SUCCESS => { 5129 syscall.finish(); 5130 break; 5131 }, 5132 .INTR => { 5133 try syscall.checkCancel(); 5134 continue; 5135 }, 5136 else => |e| { 5137 syscall.finish(); 5138 switch (e) { 5139 .FAULT => |err| return errnoBug(err), 5140 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 5141 .ACCES => return error.AccessDenied, 5142 .FBIG => return error.FileTooBig, 5143 .OVERFLOW => return error.FileTooBig, 5144 .ISDIR => return error.IsDir, 5145 .LOOP => return error.SymLinkLoop, 5146 .MFILE => return error.ProcessFdQuotaExceeded, 5147 .NFILE => return error.SystemFdQuotaExceeded, 5148 .NODEV => return error.NoDevice, 5149 .NOENT => return error.FileNotFound, 5150 .NOMEM => return error.SystemResources, 5151 .NOTDIR => return error.NotDir, 5152 .PERM => return error.PermissionDenied, 5153 .BUSY => return error.DeviceBusy, 5154 .NOTCAPABLE => return error.AccessDenied, 5155 .NAMETOOLONG => return error.NameTooLong, 5156 .INVAL => return error.BadPathName, 5157 .ILSEQ => return error.BadPathName, 5158 else => |err| return posix.unexpectedErrno(err), 5159 } 5160 }, 5161 } 5162 } 5163 errdefer closeFd(fd); 5164 5165 if (!flags.allow_directory) { 5166 const is_dir = is_dir: { 5167 const stat = fileStat(t, .{ .handle = fd, .flags = .{ .nonblocking = false } }) catch |err| switch (err) { 5168 // The directory-ness is either unknown or unknowable 5169 error.Streaming => break :is_dir false, 5170 else => |e| return e, 5171 }; 5172 break :is_dir stat.kind == .directory; 5173 }; 5174 if (is_dir) return error.IsDir; 5175 } 5176 5177 return .{ 5178 .handle = fd, 5179 .flags = .{ .nonblocking = false }, 5180 }; 5181 } 5182 5183 const dirOpenDir = switch (native_os) { 5184 .wasi => dirOpenDirWasi, 5185 .haiku => dirOpenDirHaiku, 5186 else => dirOpenDirPosix, 5187 }; 5188 5189 /// This function is also used for WASI when libc is linked. 5190 fn dirOpenDirPosix( 5191 userdata: ?*anyopaque, 5192 dir: Dir, 5193 sub_path: []const u8, 5194 options: Dir.OpenOptions, 5195 ) Dir.OpenError!Dir { 5196 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5197 _ = t; 5198 5199 if (is_windows) { 5200 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); 5201 return dirOpenDirWindows(dir, sub_path_w.span(), options); 5202 } 5203 5204 var path_buffer: [posix.PATH_MAX]u8 = undefined; 5205 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 5206 5207 var flags: posix.O = switch (native_os) { 5208 .wasi => .{ 5209 .read = true, 5210 .NOFOLLOW = !options.follow_symlinks, 5211 .DIRECTORY = true, 5212 }, 5213 else => .{ 5214 .ACCMODE = .RDONLY, 5215 .NOFOLLOW = !options.follow_symlinks, 5216 .DIRECTORY = true, 5217 .CLOEXEC = true, 5218 }, 5219 }; 5220 5221 if (@hasField(posix.O, "PATH") and !options.iterate) 5222 flags.PATH = true; 5223 5224 const mode: posix.mode_t = 0; 5225 5226 const syscall: Syscall = try .start(); 5227 while (true) { 5228 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); 5229 switch (posix.errno(rc)) { 5230 .SUCCESS => { 5231 syscall.finish(); 5232 return .{ .handle = @intCast(rc) }; 5233 }, 5234 .INTR => { 5235 try syscall.checkCancel(); 5236 continue; 5237 }, 5238 .INVAL => return syscall.fail(error.BadPathName), 5239 .ACCES => return syscall.fail(error.AccessDenied), 5240 .LOOP => return syscall.fail(error.SymLinkLoop), 5241 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 5242 .NAMETOOLONG => return syscall.fail(error.NameTooLong), 5243 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), 5244 .NODEV => return syscall.fail(error.NoDevice), 5245 .NOENT => return syscall.fail(error.FileNotFound), 5246 .NOMEM => return syscall.fail(error.SystemResources), 5247 .NOTDIR => return syscall.fail(error.NotDir), 5248 .PERM => return syscall.fail(error.PermissionDenied), 5249 .NXIO => return syscall.fail(error.NoDevice), 5250 .ILSEQ => return syscall.fail(error.BadPathName), 5251 .FAULT => |err| return syscall.errnoBug(err), 5252 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. 5253 .BUSY => |err| return syscall.errnoBug(err), // O_EXCL not passed 5254 else => |err| return syscall.unexpectedErrno(err), 5255 } 5256 } 5257 } 5258 5259 fn dirOpenDirHaiku( 5260 userdata: ?*anyopaque, 5261 dir: Dir, 5262 sub_path: []const u8, 5263 options: Dir.OpenOptions, 5264 ) Dir.OpenError!Dir { 5265 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5266 _ = t; 5267 5268 var path_buffer: [posix.PATH_MAX]u8 = undefined; 5269 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 5270 5271 _ = options; 5272 5273 const syscall: Syscall = try .start(); 5274 while (true) { 5275 const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix); 5276 if (rc >= 0) { 5277 syscall.finish(); 5278 return .{ .handle = rc }; 5279 } 5280 switch (@as(posix.E, @enumFromInt(rc))) { 5281 .INTR => { 5282 try syscall.checkCancel(); 5283 continue; 5284 }, 5285 else => |e| { 5286 syscall.finish(); 5287 switch (e) { 5288 .FAULT => |err| return errnoBug(err), 5289 .INVAL => |err| return errnoBug(err), 5290 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 5291 .ACCES => return error.AccessDenied, 5292 .LOOP => return error.SymLinkLoop, 5293 .MFILE => return error.ProcessFdQuotaExceeded, 5294 .NAMETOOLONG => return error.NameTooLong, 5295 .NFILE => return error.SystemFdQuotaExceeded, 5296 .NODEV => return error.NoDevice, 5297 .NOENT => return error.FileNotFound, 5298 .NOMEM => return error.SystemResources, 5299 .NOTDIR => return error.NotDir, 5300 .PERM => return error.PermissionDenied, 5301 .BUSY => return error.DeviceBusy, 5302 else => |err| return posix.unexpectedErrno(err), 5303 } 5304 }, 5305 } 5306 } 5307 } 5308 5309 pub fn dirOpenDirWindows( 5310 dir: Dir, 5311 sub_path_w: []const u16, 5312 options: Dir.OpenOptions, 5313 ) Dir.OpenError!Dir { 5314 const w = windows; 5315 5316 var io_status_block: w.IO_STATUS_BLOCK = undefined; 5317 var result: Dir = .{ .handle = undefined }; 5318 5319 const attr: w.OBJECT.ATTRIBUTES = .{ 5320 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, 5321 .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)), 5322 }; 5323 5324 const syscall: Syscall = try .start(); 5325 while (true) switch (w.ntdll.NtCreateFile( 5326 &result.handle, 5327 // TODO remove some of these flags if options.access_sub_paths is false 5328 .{ 5329 .SPECIFIC = .{ .FILE_DIRECTORY = .{ 5330 .LIST = options.iterate, 5331 .READ_EA = true, 5332 .TRAVERSE = true, 5333 .READ_ATTRIBUTES = true, 5334 } }, 5335 .STANDARD = .{ 5336 .RIGHTS = .READ, 5337 .SYNCHRONIZE = true, 5338 }, 5339 }, 5340 &attr, 5341 &io_status_block, 5342 null, 5343 .{ .NORMAL = true }, 5344 .VALID_FLAGS, 5345 .OPEN, 5346 .{ 5347 .DIRECTORY_FILE = true, 5348 .IO = .SYNCHRONOUS_NONALERT, 5349 .OPEN_FOR_BACKUP_INTENT = true, 5350 .OPEN_REPARSE_POINT = !options.follow_symlinks, 5351 }, 5352 null, 5353 0, 5354 )) { 5355 .SUCCESS => { 5356 syscall.finish(); 5357 return result; 5358 }, 5359 .CANCELLED => { 5360 try syscall.checkCancel(); 5361 continue; 5362 }, 5363 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 5364 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 5365 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err), 5366 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 5367 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 5368 // This can happen if the directory has 'List folder contents' permission set to 'Deny' 5369 // and the directory is trying to be opened for iteration. 5370 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 5371 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), 5372 else => |rc| return syscall.unexpectedNtstatus(rc), 5373 }; 5374 } 5375 5376 fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { 5377 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5378 _ = t; 5379 for (dirs) |dir| { 5380 if (is_windows) { 5381 windows.CloseHandle(dir.handle); 5382 } else { 5383 closeFd(dir.handle); 5384 } 5385 } 5386 } 5387 5388 const dirRead = switch (native_os) { 5389 .linux => dirReadLinux, 5390 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin, 5391 .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd, 5392 .illumos => dirReadIllumos, 5393 .haiku => dirReadHaiku, 5394 .windows => dirReadWindows, 5395 .wasi => dirReadWasi, 5396 else => dirReadUnimplemented, 5397 }; 5398 5399 fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 5400 const linux = std.os.linux; 5401 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5402 _ = t; 5403 var buffer_index: usize = 0; 5404 while (buffer.len - buffer_index != 0) { 5405 if (dr.end - dr.index == 0) { 5406 // Refill the buffer, unless we've already created references to 5407 // buffered data. 5408 if (buffer_index != 0) break; 5409 if (dr.state == .reset) { 5410 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { 5411 error.Unseekable => return error.Unexpected, 5412 else => |e| return e, 5413 }; 5414 dr.state = .reading; 5415 } 5416 const syscall: Syscall = try .start(); 5417 const n = while (true) { 5418 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); 5419 switch (linux.errno(rc)) { 5420 .SUCCESS => { 5421 syscall.finish(); 5422 break rc; 5423 }, 5424 .INTR => { 5425 try syscall.checkCancel(); 5426 continue; 5427 }, 5428 else => |e| { 5429 syscall.finish(); 5430 switch (e) { 5431 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. 5432 .FAULT => |err| return errnoBug(err), 5433 .NOTDIR => |err| return errnoBug(err), 5434 // To be consistent across platforms, iteration 5435 // ends if the directory being iterated is deleted 5436 // during iteration. This matches the behavior of 5437 // non-Linux, non-WASI UNIX platforms. 5438 .NOENT => { 5439 dr.state = .finished; 5440 return 0; 5441 }, 5442 // This can occur when reading /proc/$PID/net, or 5443 // if the provided buffer is too small. Neither 5444 // scenario is intended to be handled by this API. 5445 .INVAL => return error.Unexpected, 5446 .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory. 5447 else => |err| return posix.unexpectedErrno(err), 5448 } 5449 }, 5450 } 5451 }; 5452 if (n == 0) { 5453 dr.state = .finished; 5454 return 0; 5455 } 5456 dr.index = 0; 5457 dr.end = n; 5458 } 5459 // Linux aligns the header by padding after the null byte of the name 5460 // to align the next entry. This means we can find the end of the name 5461 // by looking at only the 8 bytes before the next record. However since 5462 // file names are usually short it's better to keep the machine code 5463 // simpler. 5464 // 5465 // Furthermore, I observed qemu user mode to not align this struct, so 5466 // this code makes the conservative choice to not assume alignment. 5467 const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); 5468 const next_index = dr.index + linux_entry.reclen; 5469 dr.index = next_index; 5470 const name_ptr: [*]u8 = &linux_entry.name; 5471 const padded_name = name_ptr[0 .. linux_entry.reclen - @offsetOf(linux.dirent64, "name")]; 5472 const name_len = std.mem.findScalar(u8, padded_name, 0).?; 5473 const name = name_ptr[0..name_len :0]; 5474 5475 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; 5476 5477 const entry_kind: File.Kind = switch (linux_entry.type) { 5478 linux.DT.BLK => .block_device, 5479 linux.DT.CHR => .character_device, 5480 linux.DT.DIR => .directory, 5481 linux.DT.FIFO => .named_pipe, 5482 linux.DT.LNK => .sym_link, 5483 linux.DT.REG => .file, 5484 linux.DT.SOCK => .unix_domain_socket, 5485 else => .unknown, 5486 }; 5487 buffer[buffer_index] = .{ 5488 .name = name, 5489 .kind = entry_kind, 5490 .inode = linux_entry.ino, 5491 }; 5492 buffer_index += 1; 5493 } 5494 return buffer_index; 5495 } 5496 5497 fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 5498 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5499 _ = t; 5500 const Header = extern struct { 5501 seek: i64, 5502 }; 5503 const header: *Header = @ptrCast(dr.buffer.ptr); 5504 const header_end: usize = @sizeOf(Header); 5505 if (dr.index < header_end) { 5506 // Initialize header. 5507 dr.index = header_end; 5508 dr.end = header_end; 5509 header.* = .{ .seek = 0 }; 5510 } 5511 var buffer_index: usize = 0; 5512 while (buffer.len - buffer_index != 0) { 5513 if (dr.end - dr.index == 0) { 5514 // Refill the buffer, unless we've already created references to 5515 // buffered data. 5516 if (buffer_index != 0) break; 5517 if (dr.state == .reset) { 5518 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { 5519 error.Unseekable => return error.Unexpected, 5520 else => |e| return e, 5521 }; 5522 dr.state = .reading; 5523 } 5524 const dents_buffer = dr.buffer[header_end..]; 5525 const syscall: Syscall = try .start(); 5526 const n: usize = while (true) { 5527 const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek); 5528 switch (posix.errno(rc)) { 5529 .SUCCESS => { 5530 syscall.finish(); 5531 break @intCast(rc); 5532 }, 5533 .INTR => { 5534 try syscall.checkCancel(); 5535 continue; 5536 }, 5537 else => |e| { 5538 syscall.finish(); 5539 switch (e) { 5540 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. 5541 .FAULT => |err| return errnoBug(err), 5542 .NOTDIR => |err| return errnoBug(err), 5543 .INVAL => |err| return errnoBug(err), 5544 else => |err| return posix.unexpectedErrno(err), 5545 } 5546 }, 5547 } 5548 }; 5549 if (n == 0) { 5550 dr.state = .finished; 5551 return 0; 5552 } 5553 dr.index = header_end; 5554 dr.end = header_end + n; 5555 } 5556 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); 5557 const next_index = dr.index + darwin_entry.reclen; 5558 dr.index = next_index; 5559 5560 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen]; 5561 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) 5562 continue; 5563 5564 const entry_kind: File.Kind = switch (darwin_entry.type) { 5565 posix.DT.BLK => .block_device, 5566 posix.DT.CHR => .character_device, 5567 posix.DT.DIR => .directory, 5568 posix.DT.FIFO => .named_pipe, 5569 posix.DT.LNK => .sym_link, 5570 posix.DT.REG => .file, 5571 posix.DT.SOCK => .unix_domain_socket, 5572 posix.DT.WHT => .whiteout, 5573 else => .unknown, 5574 }; 5575 buffer[buffer_index] = .{ 5576 .name = name, 5577 .kind = entry_kind, 5578 .inode = darwin_entry.ino, 5579 }; 5580 buffer_index += 1; 5581 } 5582 return buffer_index; 5583 } 5584 5585 fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 5586 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5587 _ = t; 5588 var buffer_index: usize = 0; 5589 while (buffer.len - buffer_index != 0) { 5590 if (dr.end - dr.index == 0) { 5591 // Refill the buffer, unless we've already created references to 5592 // buffered data. 5593 if (buffer_index != 0) break; 5594 if (dr.state == .reset) { 5595 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { 5596 error.Unseekable => return error.Unexpected, 5597 else => |e| return e, 5598 }; 5599 dr.state = .reading; 5600 } 5601 const syscall: Syscall = try .start(); 5602 const n: usize = while (true) { 5603 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); 5604 switch (posix.errno(rc)) { 5605 .SUCCESS => { 5606 syscall.finish(); 5607 break @intCast(rc); 5608 }, 5609 .INTR => { 5610 try syscall.checkCancel(); 5611 continue; 5612 }, 5613 else => |e| { 5614 syscall.finish(); 5615 switch (e) { 5616 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability 5617 .FAULT => |err| return errnoBug(err), 5618 .NOTDIR => |err| return errnoBug(err), 5619 .INVAL => |err| return errnoBug(err), 5620 // Introduced in freebsd 13.2: directory unlinked 5621 // but still open. To be consistent, iteration ends 5622 // if the directory being iterated is deleted 5623 // during iteration. 5624 .NOENT => { 5625 dr.state = .finished; 5626 return 0; 5627 }, 5628 else => |err| return posix.unexpectedErrno(err), 5629 } 5630 }, 5631 } 5632 }; 5633 if (n == 0) { 5634 dr.state = .finished; 5635 return 0; 5636 } 5637 dr.index = 0; 5638 dr.end = n; 5639 } 5640 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); 5641 const next_index = dr.index + 5642 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen(); 5643 dr.index = next_index; 5644 5645 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen]; 5646 5647 const skip_zero_fileno = switch (native_os) { 5648 // fileno=0 is used to mark invalid entries or deleted files. 5649 .openbsd, .netbsd => true, 5650 else => false, 5651 }; 5652 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or 5653 (skip_zero_fileno and bsd_entry.fileno == 0)) 5654 { 5655 continue; 5656 } 5657 5658 const entry_kind: File.Kind = switch (bsd_entry.type) { 5659 posix.DT.BLK => .block_device, 5660 posix.DT.CHR => .character_device, 5661 posix.DT.DIR => .directory, 5662 posix.DT.FIFO => .named_pipe, 5663 posix.DT.LNK => .sym_link, 5664 posix.DT.REG => .file, 5665 posix.DT.SOCK => .unix_domain_socket, 5666 posix.DT.WHT => .whiteout, 5667 else => .unknown, 5668 }; 5669 buffer[buffer_index] = .{ 5670 .name = name, 5671 .kind = entry_kind, 5672 .inode = bsd_entry.fileno, 5673 }; 5674 buffer_index += 1; 5675 } 5676 return buffer_index; 5677 } 5678 5679 fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 5680 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5681 _ = t; 5682 var buffer_index: usize = 0; 5683 while (buffer.len - buffer_index != 0) { 5684 if (dr.end - dr.index == 0) { 5685 // Refill the buffer, unless we've already created references to 5686 // buffered data. 5687 if (buffer_index != 0) break; 5688 if (dr.state == .reset) { 5689 posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { 5690 error.Unseekable => return error.Unexpected, 5691 else => |e| return e, 5692 }; 5693 dr.state = .reading; 5694 } 5695 const syscall: Syscall = try .start(); 5696 const n: usize = while (true) { 5697 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); 5698 switch (posix.errno(rc)) { 5699 .SUCCESS => { 5700 syscall.finish(); 5701 break rc; 5702 }, 5703 .INTR => { 5704 try syscall.checkCancel(); 5705 continue; 5706 }, 5707 else => |e| { 5708 syscall.finish(); 5709 switch (e) { 5710 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability 5711 .FAULT => |err| return errnoBug(err), 5712 .NOTDIR => |err| return errnoBug(err), 5713 .INVAL => |err| return errnoBug(err), 5714 else => |err| return posix.unexpectedErrno(err), 5715 } 5716 }, 5717 } 5718 }; 5719 if (n == 0) { 5720 dr.state = .finished; 5721 return 0; 5722 } 5723 dr.index = 0; 5724 dr.end = n; 5725 } 5726 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); 5727 const next_index = dr.index + entry.reclen; 5728 dr.index = next_index; 5729 5730 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0); 5731 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; 5732 5733 // illumos dirent doesn't expose type, so we have to call stat to get it. 5734 const stat = try posixStatFile(dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW); 5735 5736 buffer[buffer_index] = .{ 5737 .name = name, 5738 .kind = stat.kind, 5739 .inode = entry.ino, 5740 }; 5741 buffer_index += 1; 5742 } 5743 return buffer_index; 5744 } 5745 5746 fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 5747 _ = userdata; 5748 _ = dr; 5749 _ = buffer; 5750 @panic("TODO implement dirReadHaiku"); 5751 } 5752 5753 fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 5754 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5755 _ = t; 5756 const w = windows; 5757 5758 // We want to be able to use the `dr.buffer` for both the NtQueryDirectoryFile call (which 5759 // returns WTF-16 names) *and* as a buffer for storing those WTF-16 names as WTF-8 to be able 5760 // to return them in `Dir.Entry.name`. However, the problem that needs to be overcome in order to do 5761 // that is that each WTF-16 code unit can be encoded as a maximum of 3 WTF-8 bytes, which means 5762 // that it's not guaranteed that the memory used for the WTF-16 name will be sufficient 5763 // for the WTF-8 encoding of the same name (for example, € is encoded as one WTF-16 code unit, 5764 // [2 bytes] but encoded in WTF-8 as 3 bytes). 5765 // 5766 // The approach taken here is to "reserve" enough space in the `dr.buffer` to ensure that 5767 // at least one entry with the maximum possible WTF-8 name length can be stored without clobbering 5768 // any entries that follow it. That is, we determine how much space is needed to allow that, 5769 // and then only provide the remaining portion of `dr.buffer` to the NtQueryDirectoryFile 5770 // call. The WTF-16 names can then be safely converted using the full `dr.buffer` slice, making 5771 // sure that each name can only potentially overwrite the data of its own entry. 5772 // 5773 // The worst case, where an entry's name is both the maximum length of a component and 5774 // made up entirely of code points that are encoded as one WTF-16 code unit/three WTF-8 bytes, 5775 // would therefore look like the diagram below, and only one entry would be able to be returned: 5776 // 5777 // | reserved | remaining unreserved buffer | 5778 // | entry 1 | entry 2 | ... | 5779 // | wtf-8 name of entry 1 | 5780 // 5781 // However, in the average case we will be able to store more than one WTF-8 name at a time in the 5782 // available buffer and therefore we will be able to populate more than one `Dir.Entry` at a time. 5783 // That might look something like this (where name 1, name 2, etc are the converted WTF-8 names): 5784 // 5785 // | reserved | remaining unreserved buffer | 5786 // | entry 1 | entry 2 | ... | 5787 // | name 1 | name 2 | name 3 | name 4 | ... | 5788 // 5789 // Note: More than the minimum amount of space could be reserved to make the "worst case" 5790 // less likely, but since the worst-case also requires a maximum length component to matter, 5791 // it's unlikely for it to become a problem in normal scenarios even if all names on the filesystem 5792 // are made up of non-ASCII characters that have the "one WTF-16 code unit <-> three WTF-8 bytes" 5793 // property (e.g. code points >= U+0800 and <= U+FFFF), as it's unlikely for a significant 5794 // number of components to be maximum length. 5795 5796 // We need `3 * NAME_MAX` bytes to store a max-length component as WTF-8 safely. 5797 // Because needing to store a max-length component depends on a `FileName` *with* the maximum 5798 // component length, we know that the corresponding populated `FILE_BOTH_DIR_INFORMATION` will 5799 // be of size `@sizeOf(w.FILE_BOTH_DIR_INFORMATION) + 2 * NAME_MAX` bytes, so we only need to 5800 // reserve enough to get us to up to having `3 * NAME_MAX` bytes available when taking into account 5801 // that we have the ability to write over top of the reserved memory + the full footprint of that 5802 // particular `FILE_BOTH_DIR_INFORMATION`. 5803 const max_info_len = @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2; 5804 const info_align = @alignOf(w.FILE_BOTH_DIR_INFORMATION); 5805 const reserve_needed = std.mem.alignForward(usize, Dir.max_name_bytes, info_align) - max_info_len; 5806 const unreserved_start = std.mem.alignForward(usize, reserve_needed, info_align); 5807 const unreserved_buffer = dr.buffer[unreserved_start..]; 5808 // This is enforced by `Dir.Reader` 5809 assert(unreserved_buffer.len >= max_info_len); 5810 5811 var name_index: usize = 0; 5812 var buffer_index: usize = 0; 5813 while (buffer.len - buffer_index != 0) { 5814 if (dr.end - dr.index == 0) { 5815 // Refill the buffer, unless we've already created references to 5816 // buffered data. 5817 if (buffer_index != 0) break; 5818 5819 var io_status_block: w.IO_STATUS_BLOCK = undefined; 5820 const syscall: Syscall = try .start(); 5821 const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile( 5822 dr.dir.handle, 5823 null, 5824 null, 5825 null, 5826 &io_status_block, 5827 unreserved_buffer.ptr, 5828 std.math.lossyCast(w.ULONG, unreserved_buffer.len), 5829 .BothDirectory, 5830 w.FALSE, 5831 null, 5832 @intFromBool(dr.state == .reset), 5833 )) { 5834 .CANCELLED => { 5835 try syscall.checkCancel(); 5836 continue; 5837 }, 5838 else => |rc| { 5839 syscall.finish(); 5840 break rc; 5841 }, 5842 }; 5843 dr.state = .reading; 5844 if (io_status_block.Information == 0) { 5845 dr.state = .finished; 5846 return 0; 5847 } 5848 dr.index = 0; 5849 dr.end = io_status_block.Information; 5850 switch (rc) { 5851 .SUCCESS => {}, 5852 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability 5853 else => return w.unexpectedStatus(rc), 5854 } 5855 } 5856 5857 // While the official API docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly 5858 // this may not always be the case (e.g. due to faulty VM/sandboxing tools) 5859 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&unreserved_buffer[dr.index])); 5860 const backtrack_index = dr.index; 5861 if (dir_info.NextEntryOffset != 0) { 5862 dr.index += dir_info.NextEntryOffset; 5863 } else { 5864 dr.index = dr.end; 5865 } 5866 5867 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; 5868 5869 if (std.mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or std.mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) { 5870 continue; 5871 } 5872 5873 // Read any relevant information from the `dir_info` now since it's possible the WTF-8 5874 // name will overwrite it. 5875 const kind: File.Kind = blk: { 5876 const attrs = dir_info.FileAttributes; 5877 if (attrs.REPARSE_POINT) break :blk .sym_link; 5878 if (attrs.DIRECTORY) break :blk .directory; 5879 break :blk .file; 5880 }; 5881 const inode: File.INode = dir_info.FileIndex; 5882 5883 // If there's no more space for WTF-8 names without bleeding over into 5884 // the remaining unprocessed entries, then backtrack and return what we have so far. 5885 if (name_index + std.unicode.calcWtf8Len(name_wtf16le) > unreserved_start + dr.index) { 5886 // We should always be able to fit at least one entry into the buffer no matter what 5887 assert(buffer_index != 0); 5888 dr.index = backtrack_index; 5889 break; 5890 } 5891 5892 const name_buf = dr.buffer[name_index..]; 5893 const name_wtf8_len = std.unicode.wtf16LeToWtf8(name_buf, name_wtf16le); 5894 const name_wtf8 = name_buf[0..name_wtf8_len]; 5895 name_index += name_wtf8_len; 5896 5897 buffer[buffer_index] = .{ 5898 .name = name_wtf8, 5899 .kind = kind, 5900 .inode = inode, 5901 }; 5902 buffer_index += 1; 5903 } 5904 5905 return buffer_index; 5906 } 5907 5908 fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 5909 // We intentinally use fd_readdir even when linked with libc, since its 5910 // implementation is exactly the same as below, and we avoid the code 5911 // complexity here. 5912 const wasi = std.os.wasi; 5913 const t: *Threaded = @ptrCast(@alignCast(userdata)); 5914 _ = t; 5915 const Header = extern struct { 5916 cookie: u64, 5917 }; 5918 const header: *align(@alignOf(usize)) Header = @ptrCast(dr.buffer.ptr); 5919 const header_end: usize = @sizeOf(Header); 5920 if (dr.index < header_end) { 5921 // Initialize header. 5922 dr.index = header_end; 5923 dr.end = header_end; 5924 header.* = .{ .cookie = wasi.DIRCOOKIE_START }; 5925 } 5926 var buffer_index: usize = 0; 5927 while (buffer.len - buffer_index != 0) { 5928 // According to the WASI spec, the last entry might be truncated, so we 5929 // need to check if the remaining buffer contains the whole dirent. 5930 if (dr.end - dr.index < @sizeOf(wasi.dirent_t)) { 5931 // Refill the buffer, unless we've already created references to 5932 // buffered data. 5933 if (buffer_index != 0) break; 5934 if (dr.state == .reset) { 5935 header.* = .{ .cookie = wasi.DIRCOOKIE_START }; 5936 dr.state = .reading; 5937 } 5938 const dents_buffer = dr.buffer[header_end..]; 5939 var n: usize = undefined; 5940 const syscall: Syscall = try .start(); 5941 while (true) { 5942 switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) { 5943 .SUCCESS => { 5944 syscall.finish(); 5945 break; 5946 }, 5947 .INTR => { 5948 try syscall.checkCancel(); 5949 continue; 5950 }, 5951 else => |e| { 5952 syscall.finish(); 5953 switch (e) { 5954 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. 5955 .FAULT => |err| return errnoBug(err), 5956 .NOTDIR => |err| return errnoBug(err), 5957 .INVAL => |err| return errnoBug(err), 5958 // To be consistent across platforms, iteration 5959 // ends if the directory being iterated is deleted 5960 // during iteration. This matches the behavior of 5961 // non-Linux, non-WASI UNIX platforms. 5962 .NOENT => { 5963 dr.state = .finished; 5964 return 0; 5965 }, 5966 .NOTCAPABLE => return error.AccessDenied, 5967 else => |err| return posix.unexpectedErrno(err), 5968 } 5969 }, 5970 } 5971 } 5972 if (n == 0) { 5973 dr.state = .finished; 5974 return 0; 5975 } 5976 dr.index = header_end; 5977 dr.end = header_end + n; 5978 } 5979 const entry: *align(1) wasi.dirent_t = @ptrCast(&dr.buffer[dr.index]); 5980 const entry_size = @sizeOf(wasi.dirent_t); 5981 const name_index = dr.index + entry_size; 5982 if (name_index + entry.namlen > dr.end) { 5983 // This case, the name is truncated, so we need to call readdir to store the entire name. 5984 dr.end = dr.index; // Force fd_readdir in the next loop. 5985 continue; 5986 } 5987 const name = dr.buffer[name_index..][0..entry.namlen]; 5988 const next_index = name_index + entry.namlen; 5989 dr.index = next_index; 5990 header.cookie = entry.next; 5991 5992 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) 5993 continue; 5994 5995 const entry_kind: File.Kind = switch (entry.type) { 5996 .BLOCK_DEVICE => .block_device, 5997 .CHARACTER_DEVICE => .character_device, 5998 .DIRECTORY => .directory, 5999 .SYMBOLIC_LINK => .sym_link, 6000 .REGULAR_FILE => .file, 6001 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, 6002 else => .unknown, 6003 }; 6004 buffer[buffer_index] = .{ 6005 .name = name, 6006 .kind = entry_kind, 6007 .inode = entry.ino, 6008 }; 6009 buffer_index += 1; 6010 } 6011 return buffer_index; 6012 } 6013 6014 fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { 6015 _ = userdata; 6016 _ = dir_reader; 6017 _ = buffer; 6018 return error.Unexpected; 6019 } 6020 6021 const dirRealPathFile = switch (native_os) { 6022 .windows => dirRealPathFileWindows, 6023 else => dirRealPathFilePosix, 6024 }; 6025 6026 fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize { 6027 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6028 _ = t; 6029 6030 var path_name_w = try sliceToPrefixedFileW(dir.handle, sub_path); 6031 6032 const h_file = handle: { 6033 if (OpenFile(path_name_w.span(), .{ 6034 .dir = dir.handle, 6035 .access_mask = .{ 6036 .GENERIC = .{ .READ = true }, 6037 .STANDARD = .{ .SYNCHRONIZE = true }, 6038 }, 6039 .creation = .OPEN, 6040 .filter = .any, 6041 })) |handle| { 6042 break :handle handle; 6043 } else |err| switch (err) { 6044 error.WouldBlock => unreachable, 6045 else => |e| return e, 6046 } 6047 }; 6048 defer windows.CloseHandle(h_file); 6049 return realPathWindows(h_file, out_buffer); 6050 } 6051 6052 fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { 6053 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; 6054 const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, &wide_buf); 6055 6056 const len = std.unicode.calcWtf8Len(wide_slice); 6057 if (len > out_buffer.len) 6058 return error.NameTooLong; 6059 6060 return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); 6061 } 6062 6063 /// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. 6064 /// Defaults to DOS volume names. 6065 pub const GetFinalPathNameByHandleFormat = struct { 6066 volume_name: enum { 6067 /// Format as DOS volume name 6068 Dos, 6069 /// Format as NT volume name 6070 Nt, 6071 } = .Dos, 6072 }; 6073 6074 pub const GetFinalPathNameByHandleError = error{ 6075 AccessDenied, 6076 FileNotFound, 6077 NameTooLong, 6078 /// The volume does not contain a recognized file system. File system 6079 /// drivers might not be loaded, or the volume may be corrupt. 6080 UnrecognizedVolume, 6081 } || Io.Cancelable || Io.UnexpectedError; 6082 6083 /// Returns canonical (normalized) path of handle. 6084 /// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include 6085 /// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). 6086 /// If DOS volume name format is selected, note that this function does *not* prepend 6087 /// `\\?\` prefix to the resultant path. 6088 pub fn GetFinalPathNameByHandle( 6089 hFile: windows.HANDLE, 6090 fmt: GetFinalPathNameByHandleFormat, 6091 out_buffer: []u16, 6092 ) GetFinalPathNameByHandleError![]u16 { 6093 const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) { 6094 // we assume InvalidHandle is close enough to FileNotFound in semantics 6095 // to not further complicate the error set 6096 error.InvalidHandle => return error.FileNotFound, 6097 else => |e| return e, 6098 }; 6099 6100 switch (fmt.volume_name) { 6101 .Nt => { 6102 // the returned path is already in .Nt format 6103 return final_path; 6104 }, 6105 .Dos => { 6106 // parse the string to separate volume path from file path 6107 const device_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); 6108 6109 // We aren't entirely sure of the structure of the path returned by 6110 // QueryObjectName in all contexts/environments. 6111 // This code is written to cover the various cases that have 6112 // been encountered and solved appropriately. But note that there's 6113 // no easy way to verify that they have all been tackled! 6114 // (Unless you, the reader knows of one then please do action that!) 6115 if (!std.mem.startsWith(u16, final_path, device_prefix)) { 6116 // Wine seems to return NT namespaced paths starting with \??\ from QueryObjectName 6117 // (e.g. `\??\Z:\some\path\to\a\file.txt`), in which case we can just strip the 6118 // prefix to turn it into an absolute path. 6119 // https://github.com/ziglang/zig/issues/26029 6120 // https://bugs.winehq.org/show_bug.cgi?id=39569 6121 return windows.ntToWin32Namespace(final_path, out_buffer) catch |err| switch (err) { 6122 error.NotNtPath => return error.Unexpected, 6123 error.NameTooLong => |e| return e, 6124 }; 6125 } 6126 6127 const file_path_begin_index = std.mem.findPos(u16, final_path, device_prefix.len, &[_]u16{'\\'}) orelse unreachable; 6128 const volume_name_u16 = final_path[0..file_path_begin_index]; 6129 const device_name_u16 = volume_name_u16[device_prefix.len..]; 6130 const file_name_u16 = final_path[file_path_begin_index..]; 6131 6132 // MUP is Multiple UNC Provider, and indicates that the path is a UNC 6133 // path. In this case, the canonical UNC path can be gotten by just 6134 // dropping the \Device\Mup\ and making sure the path begins with \\ 6135 if (std.mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) { 6136 out_buffer[0] = '\\'; 6137 @memmove(out_buffer[1..][0..file_name_u16.len], file_name_u16); 6138 return out_buffer[0 .. 1 + file_name_u16.len]; 6139 } 6140 6141 // Get DOS volume name. DOS volume names are actually symbolic link objects to the 6142 // actual NT volume. For example: 6143 // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: 6144 const MIN_SIZE = @sizeOf(windows.MOUNTMGR_MOUNT_POINT) + windows.MAX_PATH; 6145 // We initialize the input buffer to all zeros for convenience since 6146 // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this. 6147 var input_buf: [MIN_SIZE]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINT)) = [_]u8{0} ** MIN_SIZE; 6148 var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINTS)) = undefined; 6149 6150 // This surprising path is a filesystem path to the mount manager on Windows. 6151 // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points 6152 // This is the NT namespaced version of \\.\MountPointManager 6153 const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager"); 6154 const mgmt_handle = OpenFile(mgmt_path_u16, .{ 6155 .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } }, 6156 .creation = .OPEN, 6157 }) catch |err| switch (err) { 6158 error.IsDir => return error.Unexpected, 6159 error.NotDir => return error.Unexpected, 6160 error.NoDevice => return error.Unexpected, 6161 error.AccessDenied => return error.Unexpected, 6162 error.PipeBusy => return error.Unexpected, 6163 error.FileBusy => return error.Unexpected, 6164 error.PathAlreadyExists => return error.Unexpected, 6165 error.WouldBlock => return error.Unexpected, 6166 error.NetworkNotFound => return error.Unexpected, 6167 error.AntivirusInterference => return error.Unexpected, 6168 error.BadPathName => return error.Unexpected, 6169 else => |e| return e, 6170 }; 6171 defer windows.CloseHandle(mgmt_handle); 6172 6173 var input_struct: *windows.MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]); 6174 input_struct.DeviceNameOffset = @sizeOf(windows.MOUNTMGR_MOUNT_POINT); 6175 input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2); 6176 @memcpy(input_buf[@sizeOf(windows.MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr))); 6177 6178 switch ((try deviceIoControl(&.{ 6179 .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = false } }, 6180 .code = windows.IOCTL.MOUNTMGR.QUERY_POINTS, 6181 .in = &input_buf, 6182 .out = &output_buf, 6183 })).u.Status) { 6184 .SUCCESS => {}, 6185 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, 6186 else => |status| return windows.unexpectedStatus(status), 6187 } 6188 const mount_points_struct: *const windows.MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]); 6189 6190 const mount_points = @as( 6191 [*]const windows.MOUNTMGR_MOUNT_POINT, 6192 @ptrCast(&mount_points_struct.MountPoints[0]), 6193 )[0..mount_points_struct.NumberOfMountPoints]; 6194 6195 for (mount_points) |mount_point| { 6196 const symlink = @as( 6197 [*]const u16, 6198 @ptrCast(@alignCast(&output_buf[mount_point.SymbolicLinkNameOffset])), 6199 )[0 .. mount_point.SymbolicLinkNameLength / 2]; 6200 6201 // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks 6202 // with traditional DOS drive letters, so pick the first one available. 6203 var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); 6204 const prefix = prefix_buf[0..prefix_buf.len]; 6205 6206 if (std.mem.startsWith(u16, symlink, prefix)) { 6207 const drive_letter = symlink[prefix.len..]; 6208 6209 if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; 6210 6211 @memcpy(out_buffer[0..drive_letter.len], drive_letter); 6212 @memmove(out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16); 6213 const total_len = drive_letter.len + file_name_u16.len; 6214 6215 // Validate that DOS does not contain any spurious nul bytes. 6216 assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null); 6217 6218 return out_buffer[0..total_len]; 6219 } else if (mountmgrIsVolumeName(symlink)) { 6220 // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d}, 6221 // then it is a volume mounted as a path rather than a drive letter. We need to 6222 // query the mount manager again to get the DOS path for the volume. 6223 6224 // 49 is the maximum length accepted by mountmgrIsVolumeName 6225 const vol_input_size = @sizeOf(windows.MOUNTMGR_TARGET_NAME) + (49 * 2); 6226 var vol_input_buf: [vol_input_size]u8 align(@alignOf(windows.MOUNTMGR_TARGET_NAME)) = [_]u8{0} ** vol_input_size; 6227 // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path, 6228 // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>). 6229 // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here. 6230 const min_output_size = @sizeOf(windows.MOUNTMGR_VOLUME_PATHS) + (windows.PATH_MAX_WIDE * 2); 6231 var vol_output_buf: [min_output_size]u8 align(@alignOf(windows.MOUNTMGR_VOLUME_PATHS)) = undefined; 6232 6233 var vol_input_struct: *windows.MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]); 6234 vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); 6235 @memcpy(@as([*]windows.WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); 6236 6237 switch ((try deviceIoControl(&.{ 6238 .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = true } }, 6239 .code = windows.IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, 6240 .in = &vol_input_buf, 6241 .out = &vol_output_buf, 6242 })).u.Status) { 6243 .SUCCESS => {}, 6244 .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume, 6245 else => |status| return windows.unexpectedStatus(status), 6246 } 6247 const volume_paths_struct: *const windows.MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); 6248 const volume_path = std.mem.sliceTo(@as( 6249 [*]const u16, 6250 &volume_paths_struct.MultiSz, 6251 )[0 .. volume_paths_struct.MultiSzLength / 2], 0); 6252 6253 if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong; 6254 6255 // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where 6256 // we want to place the filename before returning. Here are the possible overlapping cases: 6257 // 6258 // out_buffer: [filename] 6259 // dest: [___(a)___] [___(b)___] 6260 // 6261 // In the case of (a), we need to copy forwards, and in the case of (b) we need 6262 // to copy backwards. We also need to do this before copying the volume path because 6263 // it could overwrite the file_name_u16 memory. 6264 const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len]; 6265 @memmove(file_name_dest, file_name_u16); 6266 @memcpy(out_buffer[0..volume_path.len], volume_path); 6267 const total_len = volume_path.len + file_name_u16.len; 6268 6269 // Validate that DOS does not contain any spurious nul bytes. 6270 assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null); 6271 6272 return out_buffer[0..total_len]; 6273 } 6274 } 6275 6276 // If we've ended up here, then something went wrong/is corrupted in the OS, 6277 // so error out! 6278 return error.FileNotFound; 6279 }, 6280 } 6281 } 6282 6283 test GetFinalPathNameByHandle { 6284 if (builtin.os.tag != .windows) 6285 return; 6286 6287 //any file will do 6288 var tmp = std.testing.tmpDir(.{}); 6289 defer tmp.cleanup(); 6290 const handle = tmp.dir.handle; 6291 var buffer: [windows.PATH_MAX_WIDE]u16 = undefined; 6292 6293 //check with sufficient size 6294 const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer); 6295 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer); 6296 6297 const required_len_in_u16 = nt_path.len + @divExact(@intFromPtr(nt_path.ptr) - @intFromPtr(&buffer), 2) + 1; 6298 //check with insufficient size 6299 try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); 6300 try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1])); 6301 6302 //check with exactly-sufficient size 6303 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]); 6304 _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); 6305 } 6306 6307 /// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h 6308 fn mountmgrIsVolumeName(name: []const u16) bool { 6309 return (name.len == 48 or (name.len == 49 and name[48] == std.mem.nativeToLittle(u16, '\\'))) and 6310 name[0] == std.mem.nativeToLittle(u16, '\\') and 6311 (name[1] == std.mem.nativeToLittle(u16, '?') or name[1] == std.mem.nativeToLittle(u16, '\\')) and 6312 name[2] == std.mem.nativeToLittle(u16, '?') and 6313 name[3] == std.mem.nativeToLittle(u16, '\\') and 6314 std.mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and 6315 name[19] == std.mem.nativeToLittle(u16, '-') and 6316 name[24] == std.mem.nativeToLittle(u16, '-') and 6317 name[29] == std.mem.nativeToLittle(u16, '-') and 6318 name[34] == std.mem.nativeToLittle(u16, '-') and 6319 name[47] == std.mem.nativeToLittle(u16, '}'); 6320 } 6321 6322 test mountmgrIsVolumeName { 6323 @setEvalBranchQuota(2000); 6324 const L = std.unicode.utf8ToUtf16LeStringLiteral; 6325 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); 6326 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); 6327 try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); 6328 try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); 6329 try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); 6330 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo"))); 6331 try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}"))); 6332 } 6333 6334 pub const QueryObjectNameError = error{ 6335 AccessDenied, 6336 InvalidHandle, 6337 NameTooLong, 6338 Unexpected, 6339 }; 6340 6341 pub fn QueryObjectName(handle: windows.HANDLE, out_buffer: []u16) QueryObjectNameError![]u16 { 6342 const out_buffer_aligned = std.mem.alignInSlice(out_buffer, @alignOf(windows.OBJECT.NAME_INFORMATION)) orelse return error.NameTooLong; 6343 6344 const info: *windows.OBJECT.NAME_INFORMATION = @ptrCast(out_buffer_aligned); 6345 // buffer size is specified in bytes 6346 const out_buffer_len = std.math.cast(windows.ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(windows.ULONG); 6347 // last argument would return the length required for full_buffer, not exposed here 6348 return switch (windows.ntdll.NtQueryObject(handle, .Name, info, out_buffer_len, null)) { 6349 .SUCCESS => { 6350 // info.Name from ObQueryNameString is documented to be empty if the object 6351 // was "unnamed", not sure if this can happen for file handles 6352 return if (info.Name.isEmpty()) error.Unexpected else info.Name.slice(); 6353 }, 6354 .ACCESS_DENIED => error.AccessDenied, 6355 .INVALID_HANDLE => error.InvalidHandle, 6356 // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), 6357 // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) 6358 .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, 6359 else => |e| windows.unexpectedStatus(e), 6360 }; 6361 } 6362 6363 test QueryObjectName { 6364 if (builtin.os.tag != .windows) 6365 return; 6366 6367 //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. 6368 var tmp = std.testing.tmpDir(.{}); 6369 defer tmp.cleanup(); 6370 const handle = tmp.dir.handle; 6371 var out_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; 6372 6373 const result_path = try QueryObjectName(handle, &out_buffer); 6374 const required_len_in_u16 = result_path.len + @divExact(@intFromPtr(result_path.ptr) - @intFromPtr(&out_buffer), 2) + 1; 6375 //insufficient size 6376 try std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); 6377 //exactly-sufficient size 6378 _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); 6379 } 6380 6381 const Wtf16ToPrefixedFileWError = error{ 6382 AccessDenied, 6383 FileNotFound, 6384 } || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; 6385 6386 /// Converts the `path` to WTF16, null-terminated. If the path contains any 6387 /// namespace prefix, or is anything but a relative path (rooted, drive relative, 6388 /// etc) the result will have the NT-style prefix `\??\`. 6389 /// 6390 /// Similar to RtlDosPathNameToNtPathName_U with a few differences: 6391 /// - Does not allocate on the heap. 6392 /// - Relative paths are kept as relative unless they contain too many .. 6393 /// components, in which case they are resolved against the `dir` if it 6394 /// is non-null, or the CWD if it is null. 6395 /// - Special case device names like COM1, NUL, etc are not handled specially (TODO) 6396 /// - . and space are not stripped from the end of relative paths (potential TODO) 6397 pub fn wToPrefixedFileW(dir: ?windows.HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!WindowsPathSpace { 6398 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' }; 6399 if (windows.hasCommonNtPrefix(u16, path)) { 6400 // TODO: Figure out a way to design an API that can avoid the copy for NT, 6401 // since it is always returned fully unmodified. 6402 var path_space: WindowsPathSpace = undefined; 6403 path_space.data[0..nt_prefix.len].* = nt_prefix; 6404 const len_after_prefix = path.len - nt_prefix.len; 6405 @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); 6406 path_space.len = path.len; 6407 path_space.data[path_space.len] = 0; 6408 return path_space; 6409 } else { 6410 const path_type = Dir.path.getWin32PathType(u16, path); 6411 var path_space: WindowsPathSpace = undefined; 6412 if (path_type == .local_device) { 6413 switch (getLocalDevicePathType(u16, path)) { 6414 .verbatim => { 6415 path_space.data[0..nt_prefix.len].* = nt_prefix; 6416 const len_after_prefix = path.len - nt_prefix.len; 6417 @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); 6418 path_space.len = path.len; 6419 path_space.data[path_space.len] = 0; 6420 return path_space; 6421 }, 6422 .local_device, .fake_verbatim => { 6423 const path_byte_len = windows.ntdll.RtlGetFullPathName_U( 6424 path.ptr, 6425 path_space.data.len * 2, 6426 &path_space.data, 6427 null, 6428 ); 6429 if (path_byte_len == 0) { 6430 // TODO: This may not be the right error 6431 return error.BadPathName; 6432 } else if (path_byte_len / 2 > path_space.data.len) { 6433 return error.NameTooLong; 6434 } 6435 path_space.len = path_byte_len / 2; 6436 // Both prefixes will be normalized but retained, so all 6437 // we need to do now is replace them with the NT prefix 6438 path_space.data[0..nt_prefix.len].* = nt_prefix; 6439 return path_space; 6440 }, 6441 } 6442 } 6443 relative: { 6444 if (path_type == .relative) { 6445 // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc. 6446 // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html 6447 6448 // TODO: Potentially strip all trailing . and space characters from the 6449 // end of the path. This is something that both RtlDosPathNameToNtPathName_U 6450 // and RtlGetFullPathName_U do. Technically, trailing . and spaces 6451 // are allowed, but such paths may not interact well with Windows (i.e. 6452 // files with these paths can't be deleted from explorer.exe, etc). 6453 // This could be something that normalizePath may want to do. 6454 6455 @memcpy(path_space.data[0..path.len], path); 6456 // Try to normalize, but if we get too many parent directories, 6457 // then we need to start over and use RtlGetFullPathName_U instead. 6458 path_space.len = windows.normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) { 6459 error.TooManyParentDirs => break :relative, 6460 }; 6461 path_space.data[path_space.len] = 0; 6462 return path_space; 6463 } 6464 } 6465 // We now know we are going to return an absolute NT path, so 6466 // we can unconditionally prefix it with the NT prefix. 6467 path_space.data[0..nt_prefix.len].* = nt_prefix; 6468 if (path_type == .root_local_device) { 6469 // `\\.` and `\\?` always get converted to `\??\` exactly, so 6470 // we can just stop here 6471 path_space.len = nt_prefix.len; 6472 path_space.data[path_space.len] = 0; 6473 return path_space; 6474 } 6475 const path_buf_offset = switch (path_type) { 6476 // UNC paths will always start with `\\`. However, we want to 6477 // end up with something like `\??\UNC\server\share`, so to get 6478 // RtlGetFullPathName to write into the spot we want the `server` 6479 // part to end up, we need to provide an offset such that 6480 // the `\\` part gets written where the `C\` of `UNC\` will be 6481 // in the final NT path. 6482 .unc_absolute => nt_prefix.len + 2, 6483 else => nt_prefix.len, 6484 }; 6485 const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset); 6486 const path_to_get: [:0]const u16 = path_to_get: { 6487 // If dir is null, then we don't need to bother with GetFinalPathNameByHandle because 6488 // RtlGetFullPathName_U will resolve relative paths against the CWD for us. 6489 if (path_type != .relative or dir == null) { 6490 break :path_to_get path; 6491 } 6492 // We can also skip GetFinalPathNameByHandle if the handle matches 6493 // the handle returned by Io.Dir.cwd() 6494 if (dir.? == Io.Dir.cwd().handle) { 6495 break :path_to_get path; 6496 } 6497 // At this point, we know we have a relative path that had too many 6498 // `..` components to be resolved by normalizePath, so we need to 6499 // convert it into an absolute path and let RtlGetFullPathName_U 6500 // canonicalize it. We do this by getting the path of the `dir` 6501 // and appending the relative path to it. 6502 var dir_path_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; 6503 const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) { 6504 // This mapping is not correct; it is actually expected 6505 // that calling GetFinalPathNameByHandle might return 6506 // error.UnrecognizedVolume, and in fact has been observed 6507 // in the wild. The problem is that wToPrefixedFileW was 6508 // never intended to make *any* OS syscall APIs. It's only 6509 // supposed to convert a string to one that is eligible to 6510 // be used in the ntdll syscalls. 6511 // 6512 // To solve this, this function needs to no longer call 6513 // GetFinalPathNameByHandle under any conditions, or the 6514 // calling function needs to get reworked to not need to 6515 // call this function. 6516 // 6517 // This may involve making breaking API changes. 6518 error.UnrecognizedVolume => return error.Unexpected, 6519 else => |e| return e, 6520 }; 6521 if (dir_path.len + 1 + path.len > windows.PATH_MAX_WIDE) { 6522 return error.NameTooLong; 6523 } 6524 // We don't have to worry about potentially doubling up path separators 6525 // here since RtlGetFullPathName_U will handle canonicalizing it. 6526 dir_path_buf[dir_path.len] = '\\'; 6527 @memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path); 6528 const full_len = dir_path.len + 1 + path.len; 6529 dir_path_buf[full_len] = 0; 6530 break :path_to_get dir_path_buf[0..full_len :0]; 6531 }; 6532 const path_byte_len = windows.ntdll.RtlGetFullPathName_U( 6533 path_to_get.ptr, 6534 buf_len * 2, 6535 path_space.data[path_buf_offset..].ptr, 6536 null, 6537 ); 6538 if (path_byte_len == 0) { 6539 // TODO: This may not be the right error 6540 return error.BadPathName; 6541 } else if (path_byte_len / 2 > buf_len) { 6542 return error.NameTooLong; 6543 } 6544 path_space.len = path_buf_offset + (path_byte_len / 2); 6545 if (path_type == .unc_absolute) { 6546 // Now add in the UNC, the `C` should overwrite the first `\` of the 6547 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>` 6548 assert(path_space.data[path_buf_offset] == '\\'); 6549 assert(path_space.data[path_buf_offset + 1] == '\\'); 6550 const unc = [_]u16{ 'U', 'N', 'C' }; 6551 path_space.data[nt_prefix.len..][0..unc.len].* = unc; 6552 } 6553 return path_space; 6554 } 6555 } 6556 6557 const LocalDevicePathType = enum { 6558 /// `\\.\` (path separators can be `\` or `/`) 6559 local_device, 6560 /// `\\?\` 6561 /// When converted to an NT path, everything past the prefix is left 6562 /// untouched and `\\?\` is replaced by `\??\`. 6563 verbatim, 6564 /// `\\?\` without all path separators being `\`. 6565 /// This seems to be recognized as a prefix, but the 'verbatim' aspect 6566 /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path, 6567 /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't 6568 /// be treated as part of the final path]) 6569 fake_verbatim, 6570 }; 6571 6572 /// Only relevant for Win32 -> NT path conversion. 6573 /// Asserts `path` is of type `Dir.path.Win32PathType.local_device`. 6574 fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType { 6575 if (std.debug.runtime_safety) { 6576 assert(Dir.path.getWin32PathType(T, path) == .local_device); 6577 } 6578 6579 const backslash = std.mem.nativeToLittle(T, '\\'); 6580 const all_backslash = path[0] == backslash and 6581 path[1] == backslash and 6582 path[3] == backslash; 6583 return switch (path[2]) { 6584 std.mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim, 6585 std.mem.nativeToLittle(T, '.') => .local_device, 6586 else => unreachable, 6587 }; 6588 } 6589 6590 pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError; 6591 6592 /// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path. 6593 /// https://wtf-8.codeberg.page/ 6594 pub fn sliceToPrefixedFileW(dir: ?windows.HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!WindowsPathSpace { 6595 var temp_path: WindowsPathSpace = undefined; 6596 temp_path.len = std.unicode.wtf8ToWtf16Le(&temp_path.data, path) catch |err| switch (err) { 6597 error.InvalidWtf8 => return error.BadPathName, 6598 }; 6599 temp_path.data[temp_path.len] = 0; 6600 return wToPrefixedFileW(dir, temp_path.span()); 6601 } 6602 6603 pub const WindowsPathSpace = struct { 6604 data: [windows.PATH_MAX_WIDE:0]u16, 6605 len: usize, 6606 6607 pub fn span(wps: *const WindowsPathSpace) [:0]const u16 { 6608 return wps.data[0..wps.len :0]; 6609 } 6610 6611 pub fn string(wps: *const WindowsPathSpace) windows.UNICODE_STRING { 6612 return .init(wps.span()); 6613 } 6614 }; 6615 6616 fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize { 6617 if (native_os == .wasi) return error.OperationUnsupported; 6618 6619 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6620 _ = t; 6621 6622 var path_buffer: [posix.PATH_MAX]u8 = undefined; 6623 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 6624 6625 if (builtin.link_libc and dir.handle == posix.AT.FDCWD) { 6626 if (out_buffer.len < posix.PATH_MAX) return error.NameTooLong; 6627 const syscall: Syscall = try .start(); 6628 while (true) { 6629 if (std.c.realpath(sub_path_posix, out_buffer.ptr)) |redundant_pointer| { 6630 syscall.finish(); 6631 assert(redundant_pointer == out_buffer.ptr); 6632 return std.mem.indexOfScalar(u8, out_buffer, 0) orelse out_buffer.len; 6633 } 6634 const err: posix.E = @enumFromInt(std.c._errno().*); 6635 if (err == .INTR) { 6636 try syscall.checkCancel(); 6637 continue; 6638 } 6639 syscall.finish(); 6640 switch (err) { 6641 .INVAL => return errnoBug(err), 6642 .BADF => return errnoBug(err), 6643 .FAULT => return errnoBug(err), 6644 .ACCES => return error.AccessDenied, 6645 .NOENT => return error.FileNotFound, 6646 .OPNOTSUPP => return error.OperationUnsupported, 6647 .NOTDIR => return error.NotDir, 6648 .NAMETOOLONG => return error.NameTooLong, 6649 .LOOP => return error.SymLinkLoop, 6650 .IO => return error.InputOutput, 6651 else => return posix.unexpectedErrno(err), 6652 } 6653 } 6654 } 6655 6656 var flags: posix.O = .{}; 6657 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true; 6658 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; 6659 if (@hasField(posix.O, "PATH")) flags.PATH = true; 6660 6661 const mode: posix.mode_t = 0; 6662 6663 const syscall: Syscall = try .start(); 6664 const fd: posix.fd_t = while (true) { 6665 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); 6666 switch (posix.errno(rc)) { 6667 .SUCCESS => { 6668 syscall.finish(); 6669 break @intCast(rc); 6670 }, 6671 .INTR => { 6672 try syscall.checkCancel(); 6673 continue; 6674 }, 6675 else => |e| { 6676 syscall.finish(); 6677 switch (e) { 6678 .FAULT => |err| return errnoBug(err), 6679 .INVAL => return error.BadPathName, 6680 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6681 .ACCES => return error.AccessDenied, 6682 .FBIG => return error.FileTooBig, 6683 .OVERFLOW => return error.FileTooBig, 6684 .ISDIR => return error.IsDir, 6685 .LOOP => return error.SymLinkLoop, 6686 .MFILE => return error.ProcessFdQuotaExceeded, 6687 .NAMETOOLONG => return error.NameTooLong, 6688 .NFILE => return error.SystemFdQuotaExceeded, 6689 .NODEV => return error.NoDevice, 6690 .NOENT => return error.FileNotFound, 6691 .SRCH => return error.FileNotFound, // Linux when accessing procfs. 6692 .NOMEM => return error.SystemResources, 6693 .NOSPC => return error.NoSpaceLeft, 6694 .NOTDIR => return error.NotDir, 6695 .PERM => return error.PermissionDenied, 6696 .EXIST => return error.PathAlreadyExists, 6697 .BUSY => return error.DeviceBusy, 6698 .NXIO => return error.NoDevice, 6699 .ILSEQ => return error.BadPathName, 6700 else => |err| return posix.unexpectedErrno(err), 6701 } 6702 }, 6703 } 6704 }; 6705 defer closeFd(fd); 6706 return realPathPosix(fd, out_buffer); 6707 } 6708 6709 const dirRealPath = switch (native_os) { 6710 .windows => dirRealPathWindows, 6711 else => dirRealPathPosix, 6712 }; 6713 6714 fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize { 6715 if (native_os == .wasi) return error.OperationUnsupported; 6716 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6717 _ = t; 6718 return realPathPosix(dir.handle, out_buffer); 6719 } 6720 6721 fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize { 6722 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6723 _ = t; 6724 return realPathWindows(dir.handle, out_buffer); 6725 } 6726 6727 const fileRealPath = switch (native_os) { 6728 .windows => fileRealPathWindows, 6729 else => fileRealPathPosix, 6730 }; 6731 6732 fn fileRealPathWindows(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { 6733 if (native_os == .wasi) return error.OperationUnsupported; 6734 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6735 _ = t; 6736 return realPathWindows(file.handle, out_buffer); 6737 } 6738 6739 fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { 6740 if (native_os == .wasi) return error.OperationUnsupported; 6741 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6742 _ = t; 6743 return realPathPosix(file.handle, out_buffer); 6744 } 6745 6746 fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { 6747 switch (native_os) { 6748 .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 6749 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; 6750 @memset(&sufficient_buffer, 0); 6751 const syscall: Syscall = try .start(); 6752 while (true) { 6753 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) { 6754 .SUCCESS => { 6755 syscall.finish(); 6756 break; 6757 }, 6758 .INTR => { 6759 try syscall.checkCancel(); 6760 continue; 6761 }, 6762 else => |e| { 6763 syscall.finish(); 6764 switch (e) { 6765 .ACCES => return error.AccessDenied, 6766 .BADF => return error.FileNotFound, 6767 .NOENT => return error.FileNotFound, 6768 .NOMEM => return error.SystemResources, 6769 .NOSPC => return error.NameTooLong, 6770 .RANGE => return error.NameTooLong, 6771 else => |err| return posix.unexpectedErrno(err), 6772 } 6773 }, 6774 } 6775 } 6776 const n = std.mem.indexOfScalar(u8, &sufficient_buffer, 0) orelse sufficient_buffer.len; 6777 if (n > out_buffer.len) return error.NameTooLong; 6778 @memcpy(out_buffer[0..n], sufficient_buffer[0..n]); 6779 return n; 6780 }, 6781 .linux, .serenity, .illumos => { 6782 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; 6783 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}"; 6784 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable; 6785 const syscall: Syscall = try .start(); 6786 while (true) { 6787 const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len); 6788 switch (posix.errno(rc)) { 6789 .SUCCESS => { 6790 syscall.finish(); 6791 const len: usize = @bitCast(rc); 6792 return len; 6793 }, 6794 .INTR => { 6795 try syscall.checkCancel(); 6796 continue; 6797 }, 6798 else => |e| { 6799 syscall.finish(); 6800 switch (e) { 6801 .ACCES => return error.AccessDenied, 6802 .FAULT => |err| return errnoBug(err), 6803 .IO => return error.FileSystem, 6804 .LOOP => return error.SymLinkLoop, 6805 .NAMETOOLONG => return error.NameTooLong, 6806 .NOENT => return error.FileNotFound, 6807 .NOMEM => return error.SystemResources, 6808 .NOTDIR => return error.NotDir, 6809 .ILSEQ => |err| return errnoBug(err), 6810 else => |err| return posix.unexpectedErrno(err), 6811 } 6812 }, 6813 } 6814 } 6815 }, 6816 .freebsd => { 6817 var k_file: std.c.kinfo_file = undefined; 6818 k_file.structsize = std.c.KINFO_FILE_SIZE; 6819 const syscall: Syscall = try .start(); 6820 while (true) { 6821 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&k_file)))) { 6822 .SUCCESS => { 6823 syscall.finish(); 6824 break; 6825 }, 6826 .INTR => { 6827 try syscall.checkCancel(); 6828 continue; 6829 }, 6830 .BADF => { 6831 syscall.finish(); 6832 return error.FileNotFound; 6833 }, 6834 else => |err| { 6835 syscall.finish(); 6836 return posix.unexpectedErrno(err); 6837 }, 6838 } 6839 } 6840 const len = std.mem.findScalar(u8, &k_file.path, 0) orelse k_file.path.len; 6841 if (len == 0) return error.NameTooLong; 6842 @memcpy(out_buffer[0..len], k_file.path[0..len]); 6843 return len; 6844 }, 6845 else => return error.OperationUnsupported, 6846 } 6847 comptime unreachable; 6848 } 6849 6850 fn fileHardLink( 6851 userdata: ?*anyopaque, 6852 file: File, 6853 new_dir: Dir, 6854 new_sub_path: []const u8, 6855 options: File.HardLinkOptions, 6856 ) File.HardLinkError!void { 6857 _ = userdata; 6858 if (native_os != .linux) return error.OperationUnsupported; 6859 6860 var new_path_buffer: [posix.PATH_MAX]u8 = undefined; 6861 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); 6862 6863 const flags: u32 = if (options.follow_symlinks) 6864 posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH 6865 else 6866 posix.AT.EMPTY_PATH; 6867 6868 return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) { 6869 error.FileNotFound => { 6870 if (options.follow_symlinks) return error.FileNotFound; 6871 var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; 6872 const proc_path = std.fmt.bufPrintSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch 6873 unreachable; 6874 return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW); 6875 }, 6876 else => |e| return e, 6877 }; 6878 } 6879 6880 fn linkat( 6881 old_dir: posix.fd_t, 6882 old_path: [*:0]const u8, 6883 new_dir: posix.fd_t, 6884 new_path: [*:0]const u8, 6885 flags: u32, 6886 ) File.HardLinkError!void { 6887 const syscall: Syscall = try .start(); 6888 while (true) { 6889 switch (posix.errno(posix.system.linkat(old_dir, old_path, new_dir, new_path, flags))) { 6890 .SUCCESS => return syscall.finish(), 6891 .INTR => { 6892 try syscall.checkCancel(); 6893 continue; 6894 }, 6895 .ACCES => return syscall.fail(error.AccessDenied), 6896 .DQUOT => return syscall.fail(error.DiskQuota), 6897 .EXIST => return syscall.fail(error.PathAlreadyExists), 6898 .IO => return syscall.fail(error.HardwareFailure), 6899 .LOOP => return syscall.fail(error.SymLinkLoop), 6900 .MLINK => return syscall.fail(error.LinkQuotaExceeded), 6901 .NAMETOOLONG => return syscall.fail(error.NameTooLong), 6902 .NOENT => return syscall.fail(error.FileNotFound), 6903 .NOMEM => return syscall.fail(error.SystemResources), 6904 .NOSPC => return syscall.fail(error.NoSpaceLeft), 6905 .NOTDIR => return syscall.fail(error.NotDir), 6906 .PERM => return syscall.fail(error.PermissionDenied), 6907 .ROFS => return syscall.fail(error.ReadOnlyFileSystem), 6908 .XDEV => return syscall.fail(error.CrossDevice), 6909 .ILSEQ => return syscall.fail(error.BadPathName), 6910 .FAULT => |err| return syscall.errnoBug(err), 6911 .INVAL => |err| return syscall.errnoBug(err), 6912 else => |err| return syscall.unexpectedErrno(err), 6913 } 6914 } 6915 } 6916 6917 const dirDeleteFile = switch (native_os) { 6918 .windows => dirDeleteFileWindows, 6919 .wasi => dirDeleteFileWasi, 6920 else => dirDeleteFilePosix, 6921 }; 6922 6923 fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { 6924 return dirDeleteWindows(userdata, dir, sub_path, false) catch |err| switch (err) { 6925 error.DirNotEmpty => unreachable, 6926 else => |e| return e, 6927 }; 6928 } 6929 6930 fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { 6931 if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path); 6932 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6933 _ = t; 6934 const syscall: Syscall = try .start(); 6935 while (true) { 6936 const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len); 6937 switch (res) { 6938 .SUCCESS => { 6939 syscall.finish(); 6940 return; 6941 }, 6942 .INTR => { 6943 try syscall.checkCancel(); 6944 continue; 6945 }, 6946 else => |e| { 6947 syscall.finish(); 6948 switch (e) { 6949 .ACCES => return error.AccessDenied, 6950 .PERM => return error.PermissionDenied, 6951 .BUSY => return error.FileBusy, 6952 .FAULT => |err| return errnoBug(err), 6953 .IO => return error.FileSystem, 6954 .ISDIR => return error.IsDir, 6955 .LOOP => return error.SymLinkLoop, 6956 .NAMETOOLONG => return error.NameTooLong, 6957 .NOENT => return error.FileNotFound, 6958 .NOTDIR => return error.NotDir, 6959 .NOMEM => return error.SystemResources, 6960 .ROFS => return error.ReadOnlyFileSystem, 6961 .NOTCAPABLE => return error.AccessDenied, 6962 .ILSEQ => return error.BadPathName, 6963 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 6964 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 6965 else => |err| return posix.unexpectedErrno(err), 6966 } 6967 }, 6968 } 6969 } 6970 } 6971 6972 fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { 6973 const t: *Threaded = @ptrCast(@alignCast(userdata)); 6974 _ = t; 6975 6976 var path_buffer: [posix.PATH_MAX]u8 = undefined; 6977 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 6978 6979 const syscall: Syscall = try .start(); 6980 while (true) { 6981 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) { 6982 .SUCCESS => { 6983 syscall.finish(); 6984 return; 6985 }, 6986 .INTR => { 6987 try syscall.checkCancel(); 6988 continue; 6989 }, 6990 // Some systems return permission errors when trying to delete a 6991 // directory, so we need to handle that case specifically and 6992 // translate the error. 6993 .PERM => switch (native_os) { 6994 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => { 6995 6996 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them). 6997 var st = std.mem.zeroes(posix.Stat); 6998 while (true) { 6999 try syscall.checkCancel(); 7000 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) { 7001 .SUCCESS => { 7002 syscall.finish(); 7003 break; 7004 }, 7005 .INTR => continue, 7006 else => { 7007 syscall.finish(); 7008 return error.PermissionDenied; 7009 }, 7010 } 7011 } 7012 const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR; 7013 if (is_dir) 7014 return error.IsDir 7015 else 7016 return error.PermissionDenied; 7017 }, 7018 else => { 7019 syscall.finish(); 7020 return error.PermissionDenied; 7021 }, 7022 }, 7023 else => |e| { 7024 syscall.finish(); 7025 switch (e) { 7026 .ACCES => return error.AccessDenied, 7027 .BUSY => return error.FileBusy, 7028 .FAULT => |err| return errnoBug(err), 7029 .IO => return error.FileSystem, 7030 .ISDIR => return error.IsDir, 7031 .LOOP => return error.SymLinkLoop, 7032 .NAMETOOLONG => return error.NameTooLong, 7033 .NOENT => return error.FileNotFound, 7034 .NOTDIR => return error.NotDir, 7035 .NOMEM => return error.SystemResources, 7036 .ROFS => return error.ReadOnlyFileSystem, 7037 .EXIST => |err| return errnoBug(err), 7038 .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR 7039 .ILSEQ => return error.BadPathName, 7040 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 7041 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 7042 else => |err| return posix.unexpectedErrno(err), 7043 } 7044 }, 7045 } 7046 } 7047 } 7048 7049 const dirDeleteDir = switch (native_os) { 7050 .windows => dirDeleteDirWindows, 7051 .wasi => dirDeleteDirWasi, 7052 else => dirDeleteDirPosix, 7053 }; 7054 7055 fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { 7056 return dirDeleteWindows(userdata, dir, sub_path, true) catch |err| switch (err) { 7057 error.IsDir => unreachable, 7058 else => |e| return e, 7059 }; 7060 } 7061 7062 fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) (Dir.DeleteDirError || Dir.DeleteFileError)!void { 7063 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7064 _ = t; 7065 const w = windows; 7066 7067 if (std.mem.eql(u8, sub_path, "..")) { 7068 // Can't remove the parent directory with an open handle. 7069 return error.FileBusy; 7070 } 7071 var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); 7072 if (std.mem.eql(u8, sub_path, ".")) { 7073 // Windows does not recognize this, but it does work with empty string. 7074 sub_path_w.len = 0; 7075 } 7076 const attr: w.OBJECT.ATTRIBUTES = .{ 7077 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, 7078 .ObjectName = @constCast(&sub_path_w.string()), 7079 }; 7080 7081 var io_status_block: w.IO_STATUS_BLOCK = undefined; 7082 var tmp_handle: w.HANDLE = undefined; 7083 { 7084 const syscall: Syscall = try .start(); 7085 while (true) switch (w.ntdll.NtCreateFile( 7086 &tmp_handle, 7087 .{ .STANDARD = .{ 7088 .RIGHTS = .{ .DELETE = true }, 7089 .SYNCHRONIZE = true, 7090 } }, 7091 &attr, 7092 &io_status_block, 7093 null, 7094 .{}, 7095 .VALID_FLAGS, 7096 .OPEN, 7097 .{ 7098 .DIRECTORY_FILE = remove_dir, 7099 .IO = .SYNCHRONOUS_NONALERT, 7100 .NON_DIRECTORY_FILE = !remove_dir, 7101 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? 7102 }, 7103 null, 7104 0, 7105 )) { 7106 .SUCCESS => break syscall.finish(), 7107 .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err), 7108 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 7109 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 7110 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found 7111 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't 7112 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), 7113 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), 7114 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 7115 .SHARING_VIOLATION => return syscall.fail(error.FileBusy), 7116 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 7117 .DELETE_PENDING => return syscall.finish(), 7118 else => |rc| return syscall.unexpectedNtstatus(rc), 7119 }; 7120 } 7121 defer w.CloseHandle(tmp_handle); 7122 7123 // FileDispositionInformationEx has varying levels of support: 7124 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1 7125 // (INVALID_INFO_CLASS is returned if not supported) 7126 // - Requires the NTFS filesystem 7127 // (on filesystems like FAT32, INVALID_PARAMETER is returned) 7128 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1 7129 // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 7130 // (NOT_SUPPORTED is returned if a flag is unsupported) 7131 // 7132 // The strategy here is just to try using FileDispositionInformationEx and fall back to 7133 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. 7134 const rc = rc: { 7135 // Deletion with posix semantics if the filesystem supports it. 7136 var info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{ 7137 .DELETE = true, 7138 .POSIX_SEMANTICS = true, 7139 .IGNORE_READONLY_ATTRIBUTE = true, 7140 } }; 7141 7142 const syscall: Syscall = try .start(); 7143 while (true) switch (w.ntdll.NtSetInformationFile( 7144 tmp_handle, 7145 &io_status_block, 7146 &info, 7147 @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX), 7148 .DispositionEx, 7149 )) { 7150 .CANCELLED => { 7151 try syscall.checkCancel(); 7152 continue; 7153 }, 7154 // The filesystem does not support FileDispositionInformationEx 7155 .INVALID_PARAMETER, 7156 // The operating system does not support FileDispositionInformationEx 7157 .INVALID_INFO_CLASS, 7158 // The operating system does not support one of the flags 7159 .NOT_SUPPORTED, 7160 => break, // use fallback path below; `syscall` still active 7161 7162 // For all other statuses, fall down to the switch below to handle them. 7163 else => |rc| { 7164 syscall.finish(); 7165 break :rc rc; 7166 }, 7167 }; 7168 7169 // Deletion with file pending semantics, which requires waiting or moving 7170 // files to get them removed (from here). 7171 var file_dispo: w.FILE.DISPOSITION.INFORMATION = .{ 7172 .DeleteFile = w.TRUE, 7173 }; 7174 7175 while (true) switch (w.ntdll.NtSetInformationFile( 7176 tmp_handle, 7177 &io_status_block, 7178 &file_dispo, 7179 @sizeOf(w.FILE.DISPOSITION.INFORMATION), 7180 .Disposition, 7181 )) { 7182 .CANCELLED => { 7183 try syscall.checkCancel(); 7184 continue; 7185 }, 7186 else => |rc| { 7187 syscall.finish(); 7188 break :rc rc; 7189 }, 7190 }; 7191 }; 7192 switch (rc) { 7193 .SUCCESS => {}, 7194 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, 7195 .INVALID_PARAMETER => |err| return w.statusBug(err), 7196 .CANNOT_DELETE => return error.AccessDenied, 7197 .MEDIA_WRITE_PROTECTED => return error.AccessDenied, 7198 .ACCESS_DENIED => return error.AccessDenied, 7199 else => return w.unexpectedStatus(rc), 7200 } 7201 } 7202 7203 fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { 7204 if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path); 7205 7206 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7207 _ = t; 7208 7209 const syscall: Syscall = try .start(); 7210 while (true) { 7211 const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len); 7212 switch (res) { 7213 .SUCCESS => { 7214 syscall.finish(); 7215 return; 7216 }, 7217 .INTR => { 7218 try syscall.checkCancel(); 7219 continue; 7220 }, 7221 else => |e| { 7222 syscall.finish(); 7223 switch (e) { 7224 .ACCES => return error.AccessDenied, 7225 .PERM => return error.PermissionDenied, 7226 .BUSY => return error.FileBusy, 7227 .FAULT => |err| return errnoBug(err), 7228 .IO => return error.FileSystem, 7229 .LOOP => return error.SymLinkLoop, 7230 .NAMETOOLONG => return error.NameTooLong, 7231 .NOENT => return error.FileNotFound, 7232 .NOTDIR => return error.NotDir, 7233 .NOMEM => return error.SystemResources, 7234 .ROFS => return error.ReadOnlyFileSystem, 7235 .NOTEMPTY => return error.DirNotEmpty, 7236 .NOTCAPABLE => return error.AccessDenied, 7237 .ILSEQ => return error.BadPathName, 7238 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 7239 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 7240 else => |err| return posix.unexpectedErrno(err), 7241 } 7242 }, 7243 } 7244 } 7245 } 7246 7247 fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { 7248 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7249 _ = t; 7250 7251 var path_buffer: [posix.PATH_MAX]u8 = undefined; 7252 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 7253 7254 const syscall: Syscall = try .start(); 7255 while (true) { 7256 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) { 7257 .SUCCESS => { 7258 syscall.finish(); 7259 return; 7260 }, 7261 .INTR => { 7262 try syscall.checkCancel(); 7263 continue; 7264 }, 7265 else => |e| { 7266 syscall.finish(); 7267 switch (e) { 7268 .ACCES => return error.AccessDenied, 7269 .PERM => return error.PermissionDenied, 7270 .BUSY => return error.FileBusy, 7271 .FAULT => |err| return errnoBug(err), 7272 .IO => return error.FileSystem, 7273 .ISDIR => |err| return errnoBug(err), 7274 .LOOP => return error.SymLinkLoop, 7275 .NAMETOOLONG => return error.NameTooLong, 7276 .NOENT => return error.FileNotFound, 7277 .NOTDIR => return error.NotDir, 7278 .NOMEM => return error.SystemResources, 7279 .ROFS => return error.ReadOnlyFileSystem, 7280 .EXIST => |err| return errnoBug(err), 7281 .NOTEMPTY => return error.DirNotEmpty, 7282 .ILSEQ => return error.BadPathName, 7283 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component 7284 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 7285 else => |err| return posix.unexpectedErrno(err), 7286 } 7287 }, 7288 } 7289 } 7290 } 7291 7292 const dirRename = switch (native_os) { 7293 .windows => dirRenameWindows, 7294 .wasi => dirRenameWasi, 7295 else => dirRenamePosix, 7296 }; 7297 7298 fn dirRenameWindows( 7299 userdata: ?*anyopaque, 7300 old_dir: Dir, 7301 old_sub_path: []const u8, 7302 new_dir: Dir, 7303 new_sub_path: []const u8, 7304 ) Dir.RenameError!void { 7305 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7306 _ = t; 7307 return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) { 7308 error.PathAlreadyExists => return error.Unexpected, 7309 error.OperationUnsupported => return error.Unexpected, 7310 else => |e| return e, 7311 }; 7312 } 7313 7314 fn dirRenamePreserve( 7315 userdata: ?*anyopaque, 7316 old_dir: Dir, 7317 old_sub_path: []const u8, 7318 new_dir: Dir, 7319 new_sub_path: []const u8, 7320 ) Dir.RenamePreserveError!void { 7321 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7322 if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false); 7323 if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path); 7324 // Make a hard link then delete the original. 7325 try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false }); 7326 const prev = swapCancelProtection(t, .blocked); 7327 defer _ = swapCancelProtection(t, prev); 7328 dirDeleteFile(t, old_dir, old_sub_path) catch {}; 7329 } 7330 7331 fn dirRenameWindowsInner( 7332 old_dir: Dir, 7333 old_sub_path: []const u8, 7334 new_dir: Dir, 7335 new_sub_path: []const u8, 7336 replace_if_exists: bool, 7337 ) Dir.RenamePreserveError!void { 7338 const w = windows; 7339 const old_path_w_buf = try sliceToPrefixedFileW(old_dir.handle, old_sub_path); 7340 const old_path_w = old_path_w_buf.span(); 7341 const new_path_w_buf = try sliceToPrefixedFileW(new_dir.handle, new_sub_path); 7342 const new_path_w = new_path_w_buf.span(); 7343 7344 const src_fd = src_fd: { 7345 if (OpenFile(old_path_w, .{ 7346 .dir = old_dir.handle, 7347 .access_mask = .{ 7348 .GENERIC = .{ .WRITE = true }, 7349 .STANDARD = .{ 7350 .RIGHTS = .{ .DELETE = true }, 7351 .SYNCHRONIZE = true, 7352 }, 7353 }, 7354 .creation = .OPEN, 7355 .filter = .any, // This function is supposed to rename both files and directories. 7356 .follow_symlinks = false, 7357 })) |handle| { 7358 break :src_fd handle; 7359 } else |err| switch (err) { 7360 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. 7361 else => |e| return e, 7362 } 7363 }; 7364 defer w.CloseHandle(src_fd); 7365 7366 var rc: w.NTSTATUS = undefined; 7367 // FileRenameInformationEx has varying levels of support: 7368 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 7369 // (INVALID_INFO_CLASS is returned if not supported) 7370 // - Requires the NTFS filesystem 7371 // (on filesystems like FAT32, INVALID_PARAMETER is returned) 7372 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 7373 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 7374 // (NOT_SUPPORTED is returned if a flag is unsupported) 7375 // 7376 // The strategy here is just to try using FileRenameInformationEx and fall back to 7377 // FileRenameInformation if the return value lets us know that some aspect of it is not supported. 7378 const need_fallback = need_fallback: { 7379 var rename_info: w.FILE.RENAME_INFORMATION = .init(.{ 7380 .Flags = .{ 7381 .REPLACE_IF_EXISTS = replace_if_exists, 7382 .POSIX_SEMANTICS = true, 7383 .IGNORE_READONLY_ATTRIBUTE = true, 7384 }, 7385 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, 7386 .FileName = new_path_w, 7387 }); 7388 var io_status_block: w.IO_STATUS_BLOCK = undefined; 7389 const rename_info_buf = rename_info.toBuffer(); 7390 rc = w.ntdll.NtSetInformationFile( 7391 src_fd, 7392 &io_status_block, 7393 rename_info_buf.ptr, 7394 @intCast(rename_info_buf.len), 7395 .RenameEx, 7396 ); 7397 switch (rc) { 7398 .SUCCESS => return, 7399 // The filesystem does not support FileDispositionInformationEx 7400 .INVALID_PARAMETER, 7401 // The operating system does not support FileDispositionInformationEx 7402 .INVALID_INFO_CLASS, 7403 // The operating system does not support one of the flags 7404 .NOT_SUPPORTED, 7405 => break :need_fallback true, 7406 // For all other statuses, fall down to the switch below to handle them. 7407 else => break :need_fallback false, 7408 } 7409 }; 7410 7411 if (need_fallback) { 7412 var rename_info: w.FILE.RENAME_INFORMATION = .init(.{ 7413 .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists }, 7414 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, 7415 .FileName = new_path_w, 7416 }); 7417 var io_status_block: w.IO_STATUS_BLOCK = undefined; 7418 const rename_info_buf = rename_info.toBuffer(); 7419 rc = w.ntdll.NtSetInformationFile( 7420 src_fd, 7421 &io_status_block, 7422 rename_info_buf.ptr, 7423 @intCast(rename_info_buf.len), 7424 .Rename, 7425 ); 7426 } 7427 7428 switch (rc) { 7429 .SUCCESS => {}, 7430 .INVALID_HANDLE => |err| return w.statusBug(err), 7431 .INVALID_PARAMETER => |err| return w.statusBug(err), 7432 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err), 7433 .ACCESS_DENIED => return error.AccessDenied, 7434 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, 7435 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, 7436 .NOT_SAME_DEVICE => return error.CrossDevice, 7437 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, 7438 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, 7439 .FILE_IS_A_DIRECTORY => return error.IsDir, 7440 .NOT_A_DIRECTORY => return error.NotDir, 7441 else => return w.unexpectedStatus(rc), 7442 } 7443 } 7444 7445 fn dirRenameWasi( 7446 userdata: ?*anyopaque, 7447 old_dir: Dir, 7448 old_sub_path: []const u8, 7449 new_dir: Dir, 7450 new_sub_path: []const u8, 7451 ) Dir.RenameError!void { 7452 if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path); 7453 7454 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7455 _ = t; 7456 7457 const syscall: Syscall = try .start(); 7458 while (true) { 7459 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)) { 7460 .SUCCESS => return syscall.finish(), 7461 .INTR => { 7462 try syscall.checkCancel(); 7463 continue; 7464 }, 7465 else => |e| { 7466 syscall.finish(); 7467 switch (e) { 7468 .ACCES => return error.AccessDenied, 7469 .PERM => return error.PermissionDenied, 7470 .BUSY => return error.FileBusy, 7471 .DQUOT => return error.DiskQuota, 7472 .FAULT => |err| return errnoBug(err), 7473 .INVAL => |err| return errnoBug(err), 7474 .ISDIR => return error.IsDir, 7475 .LOOP => return error.SymLinkLoop, 7476 .MLINK => return error.LinkQuotaExceeded, 7477 .NAMETOOLONG => return error.NameTooLong, 7478 .NOENT => return error.FileNotFound, 7479 .NOTDIR => return error.NotDir, 7480 .NOMEM => return error.SystemResources, 7481 .NOSPC => return error.NoSpaceLeft, 7482 .EXIST => return error.DirNotEmpty, 7483 .NOTEMPTY => return error.DirNotEmpty, 7484 .ROFS => return error.ReadOnlyFileSystem, 7485 .XDEV => return error.CrossDevice, 7486 .NOTCAPABLE => return error.AccessDenied, 7487 .ILSEQ => return error.BadPathName, 7488 else => |err| return posix.unexpectedErrno(err), 7489 } 7490 }, 7491 } 7492 } 7493 } 7494 7495 fn dirRenamePosix( 7496 userdata: ?*anyopaque, 7497 old_dir: Dir, 7498 old_sub_path: []const u8, 7499 new_dir: Dir, 7500 new_sub_path: []const u8, 7501 ) Dir.RenameError!void { 7502 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7503 _ = t; 7504 7505 var old_path_buffer: [posix.PATH_MAX]u8 = undefined; 7506 var new_path_buffer: [posix.PATH_MAX]u8 = undefined; 7507 7508 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); 7509 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); 7510 7511 return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix); 7512 } 7513 7514 fn dirRenamePreserveLinux( 7515 old_dir: Dir, 7516 old_sub_path: []const u8, 7517 new_dir: Dir, 7518 new_sub_path: []const u8, 7519 ) Dir.RenamePreserveError!void { 7520 const linux = std.os.linux; 7521 7522 var old_path_buffer: [linux.PATH_MAX]u8 = undefined; 7523 var new_path_buffer: [linux.PATH_MAX]u8 = undefined; 7524 7525 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); 7526 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); 7527 7528 const syscall: Syscall = try .start(); 7529 while (true) switch (linux.errno(linux.renameat2( 7530 old_dir.handle, 7531 old_sub_path_posix, 7532 new_dir.handle, 7533 new_sub_path_posix, 7534 .{ .NOREPLACE = true }, 7535 ))) { 7536 .SUCCESS => return syscall.finish(), 7537 .INTR => { 7538 try syscall.checkCancel(); 7539 continue; 7540 }, 7541 .ACCES => return syscall.fail(error.AccessDenied), 7542 .PERM => return syscall.fail(error.PermissionDenied), 7543 .BUSY => return syscall.fail(error.FileBusy), 7544 .DQUOT => return syscall.fail(error.DiskQuota), 7545 .ISDIR => return syscall.fail(error.IsDir), 7546 .LOOP => return syscall.fail(error.SymLinkLoop), 7547 .MLINK => return syscall.fail(error.LinkQuotaExceeded), 7548 .NAMETOOLONG => return syscall.fail(error.NameTooLong), 7549 .NOENT => return syscall.fail(error.FileNotFound), 7550 .NOTDIR => return syscall.fail(error.NotDir), 7551 .NOMEM => return syscall.fail(error.SystemResources), 7552 .NOSPC => return syscall.fail(error.NoSpaceLeft), 7553 .EXIST => return syscall.fail(error.PathAlreadyExists), 7554 .NOTEMPTY => return syscall.fail(error.DirNotEmpty), 7555 .ROFS => return syscall.fail(error.ReadOnlyFileSystem), 7556 .XDEV => return syscall.fail(error.CrossDevice), 7557 .ILSEQ => return syscall.fail(error.BadPathName), 7558 .FAULT => |err| return syscall.errnoBug(err), 7559 .INVAL => |err| return syscall.errnoBug(err), 7560 else => |err| return syscall.unexpectedErrno(err), 7561 }; 7562 } 7563 7564 fn renameat( 7565 old_dir: posix.fd_t, 7566 old_sub_path: [*:0]const u8, 7567 new_dir: posix.fd_t, 7568 new_sub_path: [*:0]const u8, 7569 ) Dir.RenameError!void { 7570 const syscall: Syscall = try .start(); 7571 while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) { 7572 .SUCCESS => return syscall.finish(), 7573 .INTR => { 7574 try syscall.checkCancel(); 7575 continue; 7576 }, 7577 .ACCES => return syscall.fail(error.AccessDenied), 7578 .PERM => return syscall.fail(error.PermissionDenied), 7579 .BUSY => return syscall.fail(error.FileBusy), 7580 .DQUOT => return syscall.fail(error.DiskQuota), 7581 .ISDIR => return syscall.fail(error.IsDir), 7582 .IO => return syscall.fail(error.HardwareFailure), 7583 .LOOP => return syscall.fail(error.SymLinkLoop), 7584 .MLINK => return syscall.fail(error.LinkQuotaExceeded), 7585 .NAMETOOLONG => return syscall.fail(error.NameTooLong), 7586 .NOENT => return syscall.fail(error.FileNotFound), 7587 .NOTDIR => return syscall.fail(error.NotDir), 7588 .NOMEM => return syscall.fail(error.SystemResources), 7589 .NOSPC => return syscall.fail(error.NoSpaceLeft), 7590 .EXIST => return syscall.fail(error.DirNotEmpty), 7591 .NOTEMPTY => return syscall.fail(error.DirNotEmpty), 7592 .ROFS => return syscall.fail(error.ReadOnlyFileSystem), 7593 .XDEV => return syscall.fail(error.CrossDevice), 7594 .ILSEQ => return syscall.fail(error.BadPathName), 7595 .FAULT => |err| return syscall.errnoBug(err), 7596 .INVAL => |err| return syscall.errnoBug(err), 7597 else => |err| return syscall.unexpectedErrno(err), 7598 }; 7599 } 7600 7601 fn renameatPreserve( 7602 old_dir: posix.fd_t, 7603 old_sub_path: [*:0]const u8, 7604 new_dir: posix.fd_t, 7605 new_sub_path: [*:0]const u8, 7606 ) Dir.RenameError!void { 7607 const syscall: Syscall = try .start(); 7608 while (true) { 7609 switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) { 7610 .SUCCESS => return syscall.finish(), 7611 .INTR => { 7612 try syscall.checkCancel(); 7613 continue; 7614 }, 7615 else => |e| { 7616 syscall.finish(); 7617 switch (e) { 7618 .ACCES => return error.AccessDenied, 7619 .PERM => return error.PermissionDenied, 7620 .BUSY => return error.FileBusy, 7621 .DQUOT => return error.DiskQuota, 7622 .FAULT => |err| return errnoBug(err), 7623 .INVAL => |err| return errnoBug(err), 7624 .ISDIR => return error.IsDir, 7625 .LOOP => return error.SymLinkLoop, 7626 .MLINK => return error.LinkQuotaExceeded, 7627 .NAMETOOLONG => return error.NameTooLong, 7628 .NOENT => return error.FileNotFound, 7629 .NOTDIR => return error.NotDir, 7630 .NOMEM => return error.SystemResources, 7631 .NOSPC => return error.NoSpaceLeft, 7632 .EXIST => return error.PathAlreadyExists, 7633 .NOTEMPTY => return error.PathAlreadyExists, 7634 .ROFS => return error.ReadOnlyFileSystem, 7635 .XDEV => return error.CrossDevice, 7636 .ILSEQ => return error.BadPathName, 7637 else => |err| return posix.unexpectedErrno(err), 7638 } 7639 }, 7640 } 7641 } 7642 } 7643 7644 const dirSymLink = switch (native_os) { 7645 .windows => dirSymLinkWindows, 7646 .wasi => dirSymLinkWasi, 7647 else => dirSymLinkPosix, 7648 }; 7649 7650 fn dirSymLinkWindows( 7651 userdata: ?*anyopaque, 7652 dir: Dir, 7653 target_path: []const u8, 7654 sym_link_path: []const u8, 7655 flags: Dir.SymLinkFlags, 7656 ) Dir.SymLinkError!void { 7657 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7658 _ = t; 7659 const w = windows; 7660 7661 // Target path does not use sliceToPrefixedFileW because certain paths 7662 // are handled differently when creating a symlink than they would be 7663 // when converting to an NT namespaced path. 7664 var target_path_w: WindowsPathSpace = undefined; 7665 target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path); 7666 target_path_w.data[target_path_w.len] = 0; 7667 // However, we need to canonicalize any path separators to `\`, since if 7668 // the target path is relative, then it must use `\` as the path separator. 7669 std.mem.replaceScalar( 7670 u16, 7671 target_path_w.data[0..target_path_w.len], 7672 std.mem.nativeToLittle(u16, '/'), 7673 std.mem.nativeToLittle(u16, '\\'), 7674 ); 7675 7676 const sym_link_path_w = try sliceToPrefixedFileW(dir.handle, sym_link_path); 7677 7678 const SYMLINK_DATA = extern struct { 7679 ReparseTag: w.IO_REPARSE_TAG, 7680 ReparseDataLength: w.USHORT, 7681 Reserved: w.USHORT, 7682 SubstituteNameOffset: w.USHORT, 7683 SubstituteNameLength: w.USHORT, 7684 PrintNameOffset: w.USHORT, 7685 PrintNameLength: w.USHORT, 7686 Flags: w.ULONG, 7687 }; 7688 7689 const symlink_handle = handle: { 7690 if (OpenFile(sym_link_path_w.span(), .{ 7691 .access_mask = .{ 7692 .GENERIC = .{ .READ = true, .WRITE = true }, 7693 .STANDARD = .{ .SYNCHRONIZE = true }, 7694 }, 7695 .dir = dir.handle, 7696 .creation = .CREATE, 7697 .filter = if (flags.is_directory) .dir_only else .non_directory_only, 7698 })) |handle| { 7699 break :handle handle; 7700 } else |err| switch (err) { 7701 error.IsDir => return error.PathAlreadyExists, 7702 error.NotDir => return error.Unexpected, 7703 error.WouldBlock => return error.Unexpected, 7704 error.PipeBusy => return error.Unexpected, 7705 error.FileBusy => return error.Unexpected, 7706 error.NoDevice => return error.Unexpected, 7707 error.AntivirusInterference => return error.Unexpected, 7708 else => |e| return e, 7709 } 7710 }; 7711 defer w.CloseHandle(symlink_handle); 7712 7713 // Relevant portions of the documentation: 7714 // > Relative links are specified using the following conventions: 7715 // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32". 7716 // > - Current working directory–relative—for example, if the current working directory is 7717 // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt". 7718 // > Note: If you specify a current working directory–relative link, it is created as an absolute 7719 // > link, due to the way the current working directory is processed based on the user and the thread. 7720 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw 7721 var is_target_absolute = false; 7722 const final_target_path = target_path: { 7723 if (w.hasCommonNtPrefix(u16, target_path_w.span())) { 7724 // Already an NT path, no need to do anything to it 7725 break :target_path target_path_w.span(); 7726 } else { 7727 switch (Dir.path.getWin32PathType(u16, target_path_w.span())) { 7728 // Rooted paths need to avoid getting put through wToPrefixedFileW 7729 // (and they are treated as relative in this context) 7730 // Note: It seems that rooted paths in symbolic links are relative to 7731 // the drive that the symbolic exists on, not to the CWD's drive. 7732 // So, if the symlink is on C:\ and the CWD is on D:\, 7733 // it will still resolve the path relative to the root of 7734 // the C:\ drive. 7735 .rooted => break :target_path target_path_w.span(), 7736 // Keep relative paths relative, but anything else needs to get NT-prefixed. 7737 else => if (!Dir.path.isAbsoluteWindowsWtf16(target_path_w.span())) 7738 break :target_path target_path_w.span(), 7739 } 7740 } 7741 var prefixed_target_path = try wToPrefixedFileW(dir.handle, target_path_w.span()); 7742 // We do this after prefixing to ensure that drive-relative paths are treated as absolute 7743 is_target_absolute = Dir.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); 7744 break :target_path prefixed_target_path.span(); 7745 }; 7746 7747 // prepare reparse data buffer 7748 var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; 7749 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; 7750 const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2; 7751 const target_is_absolute = Dir.path.isAbsoluteWindowsWtf16(final_target_path); 7752 const symlink_data: SYMLINK_DATA = .{ 7753 .ReparseTag = .SYMLINK, 7754 .ReparseDataLength = @intCast(buf_len - header_len), 7755 .Reserved = 0, 7756 .SubstituteNameOffset = @intCast(final_target_path.len * 2), 7757 .SubstituteNameLength = @intCast(final_target_path.len * 2), 7758 .PrintNameOffset = 0, 7759 .PrintNameLength = @intCast(final_target_path.len * 2), 7760 .Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0, 7761 }; 7762 7763 @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data)); 7764 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); 7765 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; 7766 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); 7767 switch ((try deviceIoControl(&.{ 7768 .file = .{ .handle = symlink_handle, .flags = .{ .nonblocking = false } }, 7769 .code = .SET_REPARSE_POINT, 7770 .in = buffer[0..buf_len], 7771 })).u.Status) { 7772 .SUCCESS => {}, 7773 .PRIVILEGE_NOT_HELD => return error.PermissionDenied, 7774 .ACCESS_DENIED => return error.AccessDenied, 7775 .INVALID_DEVICE_REQUEST => return error.FileSystem, 7776 else => |status| return w.unexpectedStatus(status), 7777 } 7778 } 7779 7780 fn dirSymLinkWasi( 7781 userdata: ?*anyopaque, 7782 dir: Dir, 7783 target_path: []const u8, 7784 sym_link_path: []const u8, 7785 flags: Dir.SymLinkFlags, 7786 ) Dir.SymLinkError!void { 7787 if (builtin.link_libc) return dirSymLinkPosix(userdata, dir, target_path, sym_link_path, flags); 7788 7789 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7790 _ = t; 7791 7792 const syscall: Syscall = try .start(); 7793 while (true) { 7794 switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) { 7795 .SUCCESS => return syscall.finish(), 7796 .INTR => { 7797 try syscall.checkCancel(); 7798 continue; 7799 }, 7800 else => |e| { 7801 syscall.finish(); 7802 switch (e) { 7803 .FAULT => |err| return errnoBug(err), 7804 .INVAL => |err| return errnoBug(err), 7805 .BADF => |err| return errnoBug(err), 7806 .ACCES => return error.AccessDenied, 7807 .PERM => return error.PermissionDenied, 7808 .DQUOT => return error.DiskQuota, 7809 .EXIST => return error.PathAlreadyExists, 7810 .IO => return error.FileSystem, 7811 .LOOP => return error.SymLinkLoop, 7812 .NAMETOOLONG => return error.NameTooLong, 7813 .NOENT => return error.FileNotFound, 7814 .NOTDIR => return error.NotDir, 7815 .NOMEM => return error.SystemResources, 7816 .NOSPC => return error.NoSpaceLeft, 7817 .ROFS => return error.ReadOnlyFileSystem, 7818 .NOTCAPABLE => return error.AccessDenied, 7819 .ILSEQ => return error.BadPathName, 7820 else => |err| return posix.unexpectedErrno(err), 7821 } 7822 }, 7823 } 7824 } 7825 } 7826 7827 fn dirSymLinkPosix( 7828 userdata: ?*anyopaque, 7829 dir: Dir, 7830 target_path: []const u8, 7831 sym_link_path: []const u8, 7832 flags: Dir.SymLinkFlags, 7833 ) Dir.SymLinkError!void { 7834 _ = flags; 7835 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7836 _ = t; 7837 7838 var target_path_buffer: [posix.PATH_MAX]u8 = undefined; 7839 var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined; 7840 7841 const target_path_posix = try pathToPosix(target_path, &target_path_buffer); 7842 const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer); 7843 7844 const syscall: Syscall = try .start(); 7845 while (true) { 7846 switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) { 7847 .SUCCESS => return syscall.finish(), 7848 .INTR => { 7849 try syscall.checkCancel(); 7850 continue; 7851 }, 7852 else => |e| { 7853 syscall.finish(); 7854 switch (e) { 7855 .FAULT => |err| return errnoBug(err), 7856 .INVAL => |err| return errnoBug(err), 7857 .ACCES => return error.AccessDenied, 7858 .PERM => return error.PermissionDenied, 7859 .DQUOT => return error.DiskQuota, 7860 .EXIST => return error.PathAlreadyExists, 7861 .IO => return error.FileSystem, 7862 .LOOP => return error.SymLinkLoop, 7863 .NAMETOOLONG => return error.NameTooLong, 7864 .NOENT => return error.FileNotFound, 7865 .NOTDIR => return error.NotDir, 7866 .NOMEM => return error.SystemResources, 7867 .NOSPC => return error.NoSpaceLeft, 7868 .ROFS => return error.ReadOnlyFileSystem, 7869 .ILSEQ => return error.BadPathName, 7870 else => |err| return posix.unexpectedErrno(err), 7871 } 7872 }, 7873 } 7874 } 7875 } 7876 7877 fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { 7878 const t: *Threaded = @ptrCast(@alignCast(userdata)); 7879 _ = t; 7880 switch (native_os) { 7881 .windows => return dirReadLinkWindows(dir, sub_path, buffer), 7882 .wasi => return dirReadLinkWasi(dir, sub_path, buffer), 7883 else => return dirReadLinkPosix(dir, sub_path, buffer), 7884 } 7885 } 7886 7887 fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { 7888 // This gets used once for `sub_path` and then reused again temporarily 7889 // before converting back to `buffer`. 7890 var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); 7891 const attr: windows.OBJECT.ATTRIBUTES = .{ 7892 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, 7893 .ObjectName = @constCast(&sub_path_w.string()), 7894 }; 7895 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 7896 var result_handle: windows.HANDLE = undefined; 7897 var attempt: u5 = 0; 7898 var syscall: Syscall = try .start(); 7899 while (true) switch (windows.ntdll.NtCreateFile( 7900 &result_handle, 7901 .{ 7902 .SPECIFIC = .{ .FILE = .{ 7903 .READ_ATTRIBUTES = true, 7904 } }, 7905 .STANDARD = .{ .SYNCHRONIZE = true }, 7906 }, 7907 &attr, 7908 &io_status_block, 7909 null, 7910 .{ .NORMAL = true }, 7911 .VALID_FLAGS, 7912 .OPEN, 7913 .{ 7914 .DIRECTORY_FILE = false, 7915 .NON_DIRECTORY_FILE = false, 7916 .IO = .ASYNCHRONOUS, 7917 .OPEN_REPARSE_POINT = true, 7918 }, 7919 null, 7920 0, 7921 )) { 7922 .SUCCESS => { 7923 syscall.finish(); 7924 break; 7925 }, 7926 .CANCELLED => { 7927 try syscall.checkCancel(); 7928 continue; 7929 }, 7930 .SHARING_VIOLATION => { 7931 // This occurs if the file attempting to be opened is a running 7932 // executable. However, there's a kernel bug: the error may be 7933 // incorrectly returned for an indeterminate amount of time 7934 // after an executable file is closed. Here we work around the 7935 // kernel bug with retry attempts. 7936 syscall.finish(); 7937 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 7938 try parking_sleep.sleep(.{ .duration = .{ 7939 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 7940 .clock = .awake, 7941 } }); 7942 attempt += 1; 7943 syscall = try .start(); 7944 continue; 7945 }, 7946 .DELETE_PENDING => { 7947 // This error means that there *was* a file in this location on 7948 // the file system, but it was deleted. However, the OS is not 7949 // finished with the deletion operation, and so this CreateFile 7950 // call has failed. Here, we simulate the kernel bug being 7951 // fixed by sleeping and retrying until the error goes away. 7952 syscall.finish(); 7953 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 7954 try parking_sleep.sleep(.{ .duration = .{ 7955 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 7956 .clock = .awake, 7957 } }); 7958 attempt += 1; 7959 syscall = try .start(); 7960 continue; 7961 }, 7962 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 7963 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 7964 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 7965 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found 7966 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't 7967 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.FileNotFound), 7968 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 7969 .PIPE_BUSY => return syscall.fail(error.AccessDenied), 7970 .PIPE_NOT_AVAILABLE => return syscall.fail(error.FileNotFound), 7971 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 7972 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), 7973 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 7974 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), 7975 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), 7976 else => |status| return syscall.unexpectedNtstatus(status), 7977 }; 7978 defer windows.CloseHandle(result_handle); 7979 7980 var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined; 7981 7982 syscall = try .start(); 7983 while (true) switch (windows.ntdll.NtFsControlFile( 7984 result_handle, 7985 null, // event 7986 null, // APC routine 7987 null, // APC context 7988 &io_status_block, 7989 .GET_REPARSE_POINT, 7990 null, // input buffer 7991 0, // input buffer length 7992 &reparse_buf, 7993 reparse_buf.len, 7994 )) { 7995 .SUCCESS => { 7996 syscall.finish(); 7997 break; 7998 }, 7999 .CANCELLED => { 8000 try syscall.checkCancel(); 8001 continue; 8002 }, 8003 .NOT_A_REPARSE_POINT => return syscall.fail(error.NotLink), 8004 else => |status| return syscall.unexpectedNtstatus(status), 8005 }; 8006 8007 const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf)); 8008 const IoReparseTagInt = @typeInfo(windows.IO_REPARSE_TAG).@"struct".backing_integer.?; 8009 const result_w = switch (@as(IoReparseTagInt, @bitCast(reparse_struct.ReparseTag))) { 8010 @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.SYMLINK)) => r: { 8011 const buf: *const windows.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); 8012 const offset = buf.SubstituteNameOffset >> 1; 8013 const len = buf.SubstituteNameLength >> 1; 8014 const path_buf = @as([*]const u16, &buf.PathBuffer); 8015 const is_relative = buf.Flags & windows.SYMLINK_FLAG_RELATIVE != 0; 8016 break :r try parseReadLinkPath(path_buf[offset..][0..len], is_relative, &sub_path_w.data); 8017 }, 8018 @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.MOUNT_POINT)) => r: { 8019 const buf: *const windows.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); 8020 const offset = buf.SubstituteNameOffset >> 1; 8021 const len = buf.SubstituteNameLength >> 1; 8022 const path_buf = @as([*]const u16, &buf.PathBuffer); 8023 break :r try parseReadLinkPath(path_buf[offset..][0..len], false, &sub_path_w.data); 8024 }, 8025 else => return error.UnsupportedReparsePointType, 8026 }; 8027 const len = std.unicode.calcWtf8Len(result_w); 8028 if (len > buffer.len) return error.NameTooLong; 8029 8030 return std.unicode.wtf16LeToWtf8(buffer, result_w); 8031 } 8032 8033 fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 { 8034 path: { 8035 if (is_relative) break :path; 8036 return windows.ntToWin32Namespace(path, out_buffer) catch |err| switch (err) { 8037 error.NameTooLong => |e| return e, 8038 error.NotNtPath => break :path, 8039 }; 8040 } 8041 if (out_buffer.len < path.len) return error.NameTooLong; 8042 const dest = out_buffer[0..path.len]; 8043 @memcpy(dest, path); 8044 return dest; 8045 } 8046 8047 fn dirReadLinkWasi(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { 8048 if (builtin.link_libc) return dirReadLinkPosix(dir, sub_path, buffer); 8049 8050 var n: usize = undefined; 8051 const syscall: Syscall = try .start(); 8052 while (true) { 8053 switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) { 8054 .SUCCESS => { 8055 syscall.finish(); 8056 return n; 8057 }, 8058 .INTR => { 8059 try syscall.checkCancel(); 8060 continue; 8061 }, 8062 else => |e| { 8063 syscall.finish(); 8064 switch (e) { 8065 .ACCES => return error.AccessDenied, 8066 .FAULT => |err| return errnoBug(err), 8067 .INVAL => return error.NotLink, 8068 .IO => return error.FileSystem, 8069 .LOOP => return error.SymLinkLoop, 8070 .NAMETOOLONG => return error.NameTooLong, 8071 .NOENT => return error.FileNotFound, 8072 .NOMEM => return error.SystemResources, 8073 .NOTDIR => return error.NotDir, 8074 .NOTCAPABLE => return error.AccessDenied, 8075 .ILSEQ => return error.BadPathName, 8076 else => |err| return posix.unexpectedErrno(err), 8077 } 8078 }, 8079 } 8080 } 8081 } 8082 8083 fn dirReadLinkPosix(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { 8084 var sub_path_buffer: [posix.PATH_MAX]u8 = undefined; 8085 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer); 8086 8087 const syscall: Syscall = try .start(); 8088 while (true) { 8089 const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len); 8090 switch (posix.errno(rc)) { 8091 .SUCCESS => { 8092 syscall.finish(); 8093 const len: usize = @bitCast(rc); 8094 return len; 8095 }, 8096 .INTR => { 8097 try syscall.checkCancel(); 8098 continue; 8099 }, 8100 else => |e| { 8101 syscall.finish(); 8102 switch (e) { 8103 .ACCES => return error.AccessDenied, 8104 .FAULT => |err| return errnoBug(err), 8105 .INVAL => return error.NotLink, 8106 .IO => return error.FileSystem, 8107 .LOOP => return error.SymLinkLoop, 8108 .NAMETOOLONG => return error.NameTooLong, 8109 .NOENT => return error.FileNotFound, 8110 .NOMEM => return error.SystemResources, 8111 .NOTDIR => return error.NotDir, 8112 .ILSEQ => return error.BadPathName, 8113 else => |err| return posix.unexpectedErrno(err), 8114 } 8115 }, 8116 } 8117 } 8118 } 8119 8120 const dirSetPermissions = switch (native_os) { 8121 .windows => dirSetPermissionsWindows, 8122 else => dirSetPermissionsPosix, 8123 }; 8124 8125 fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { 8126 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8127 _ = t; 8128 _ = dir; 8129 _ = permissions; 8130 @panic("TODO implement dirSetPermissionsWindows"); 8131 } 8132 8133 fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { 8134 if (@sizeOf(Dir.Permissions) == 0) return; 8135 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8136 _ = t; 8137 return setPermissionsPosix(dir.handle, permissions.toMode()); 8138 } 8139 8140 fn dirSetFilePermissions( 8141 userdata: ?*anyopaque, 8142 dir: Dir, 8143 sub_path: []const u8, 8144 permissions: Dir.Permissions, 8145 options: Dir.SetFilePermissionsOptions, 8146 ) Dir.SetFilePermissionsError!void { 8147 if (@sizeOf(Dir.Permissions) == 0) return; 8148 if (is_windows) @panic("TODO implement dirSetFilePermissions windows"); 8149 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8150 8151 var path_buffer: [posix.PATH_MAX]u8 = undefined; 8152 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 8153 8154 const mode = permissions.toMode(); 8155 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; 8156 8157 return posixFchmodat(t, dir.handle, sub_path_posix, mode, flags); 8158 } 8159 8160 fn posixFchmodat( 8161 t: *Threaded, 8162 dir_fd: posix.fd_t, 8163 path: [*:0]const u8, 8164 mode: posix.mode_t, 8165 flags: u32, 8166 ) Dir.SetFilePermissionsError!void { 8167 // No special handling for linux is needed if we can use the libc fallback 8168 // or `flags` is empty. Glibc only added the fallback in 2.32. 8169 if (have_fchmodat_flags or flags == 0) { 8170 const syscall: Syscall = try .start(); 8171 while (true) { 8172 const rc = if (have_fchmodat_flags or builtin.link_libc) 8173 posix.system.fchmodat(dir_fd, path, mode, flags) 8174 else 8175 posix.system.fchmodat(dir_fd, path, mode); 8176 switch (posix.errno(rc)) { 8177 .SUCCESS => return syscall.finish(), 8178 .INTR => { 8179 try syscall.checkCancel(); 8180 continue; 8181 }, 8182 else => |e| { 8183 syscall.finish(); 8184 switch (e) { 8185 .BADF => |err| return errnoBug(err), 8186 .FAULT => |err| return errnoBug(err), 8187 .INVAL => |err| return errnoBug(err), 8188 .ACCES => return error.AccessDenied, 8189 .IO => return error.InputOutput, 8190 .LOOP => return error.SymLinkLoop, 8191 .MFILE => return error.ProcessFdQuotaExceeded, 8192 .NAMETOOLONG => return error.NameTooLong, 8193 .NFILE => return error.SystemFdQuotaExceeded, 8194 .NOENT => return error.FileNotFound, 8195 .NOTDIR => return error.FileNotFound, 8196 .NOMEM => return error.SystemResources, 8197 .OPNOTSUPP => return error.OperationUnsupported, 8198 .PERM => return error.PermissionDenied, 8199 .ROFS => return error.ReadOnlyFileSystem, 8200 else => |err| return posix.unexpectedErrno(err), 8201 } 8202 }, 8203 } 8204 } 8205 } 8206 8207 if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled) 8208 return fchmodatFallback(dir_fd, path, mode); 8209 8210 comptime assert(native_os == .linux); 8211 8212 const syscall: Syscall = try .start(); 8213 while (true) { 8214 switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) { 8215 .SUCCESS => return syscall.finish(), 8216 .INTR => { 8217 try syscall.checkCancel(); 8218 continue; 8219 }, 8220 else => |e| { 8221 syscall.finish(); 8222 switch (e) { 8223 .BADF => |err| return errnoBug(err), 8224 .FAULT => |err| return errnoBug(err), 8225 .INVAL => |err| return errnoBug(err), 8226 .ACCES => return error.AccessDenied, 8227 .IO => return error.InputOutput, 8228 .LOOP => return error.SymLinkLoop, 8229 .NOENT => return error.FileNotFound, 8230 .NOMEM => return error.SystemResources, 8231 .NOTDIR => return error.FileNotFound, 8232 .OPNOTSUPP => return error.OperationUnsupported, 8233 .PERM => return error.PermissionDenied, 8234 .ROFS => return error.ReadOnlyFileSystem, 8235 .NOSYS => { 8236 @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic); 8237 return fchmodatFallback(dir_fd, path, mode); 8238 }, 8239 else => |err| return posix.unexpectedErrno(err), 8240 } 8241 }, 8242 } 8243 } 8244 } 8245 8246 fn fchmodatFallback( 8247 dir_fd: posix.fd_t, 8248 path: [*:0]const u8, 8249 mode: posix.mode_t, 8250 ) Dir.SetFilePermissionsError!void { 8251 comptime assert(native_os == .linux); 8252 8253 // Fallback to changing permissions using procfs: 8254 // 8255 // 1. Open `path` as a `PATH` descriptor. 8256 // 2. Stat the fd and check if it isn't a symbolic link. 8257 // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. 8258 // 4. Pass the procfs path to `chmod` with the `mode`. 8259 const path_fd: posix.fd_t = fd: { 8260 const syscall: Syscall = try .start(); 8261 while (true) { 8262 const rc = posix.system.openat(dir_fd, path, .{ 8263 .PATH = true, 8264 .NOFOLLOW = true, 8265 .CLOEXEC = true, 8266 }, @as(posix.mode_t, 0)); 8267 switch (posix.errno(rc)) { 8268 .SUCCESS => { 8269 syscall.finish(); 8270 break :fd @intCast(rc); 8271 }, 8272 .INTR => { 8273 try syscall.checkCancel(); 8274 continue; 8275 }, 8276 else => |e| { 8277 syscall.finish(); 8278 switch (e) { 8279 .FAULT => |err| return errnoBug(err), 8280 .INVAL => |err| return errnoBug(err), 8281 .ACCES => return error.AccessDenied, 8282 .PERM => return error.PermissionDenied, 8283 .LOOP => return error.SymLinkLoop, 8284 .MFILE => return error.ProcessFdQuotaExceeded, 8285 .NAMETOOLONG => return error.NameTooLong, 8286 .NFILE => return error.SystemFdQuotaExceeded, 8287 .NOENT => return error.FileNotFound, 8288 .NOMEM => return error.SystemResources, 8289 else => |err| return posix.unexpectedErrno(err), 8290 } 8291 }, 8292 } 8293 } 8294 }; 8295 defer closeFd(path_fd); 8296 8297 const path_mode = mode: { 8298 const sys = if (statx_use_c) std.c else std.os.linux; 8299 const syscall: Syscall = try .start(); 8300 while (true) { 8301 var statx = std.mem.zeroes(std.os.linux.Statx); 8302 switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { 8303 .SUCCESS => { 8304 syscall.finish(); 8305 if (!statx.mask.TYPE) return error.Unexpected; 8306 break :mode statx.mode; 8307 }, 8308 .INTR => { 8309 try syscall.checkCancel(); 8310 continue; 8311 }, 8312 else => |e| { 8313 syscall.finish(); 8314 switch (e) { 8315 .ACCES => return error.AccessDenied, 8316 .LOOP => return error.SymLinkLoop, 8317 .NOMEM => return error.SystemResources, 8318 else => |err| return posix.unexpectedErrno(err), 8319 } 8320 }, 8321 } 8322 } 8323 }; 8324 8325 // Even though we only wanted TYPE, the kernel can still fill in the additional bits. 8326 if ((path_mode & posix.S.IFMT) == posix.S.IFLNK) 8327 return error.OperationUnsupported; 8328 8329 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; 8330 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable; 8331 const syscall: Syscall = try .start(); 8332 while (true) { 8333 switch (posix.errno(posix.system.chmod(proc_path, mode))) { 8334 .SUCCESS => return syscall.finish(), 8335 .INTR => { 8336 try syscall.checkCancel(); 8337 continue; 8338 }, 8339 else => |e| { 8340 syscall.finish(); 8341 switch (e) { 8342 .NOENT => return error.OperationUnsupported, // procfs not mounted. 8343 .BADF => |err| return errnoBug(err), 8344 .FAULT => |err| return errnoBug(err), 8345 .INVAL => |err| return errnoBug(err), 8346 .ACCES => return error.AccessDenied, 8347 .IO => return error.InputOutput, 8348 .LOOP => return error.SymLinkLoop, 8349 .NOMEM => return error.SystemResources, 8350 .NOTDIR => return error.FileNotFound, 8351 .PERM => return error.PermissionDenied, 8352 .ROFS => return error.ReadOnlyFileSystem, 8353 else => |err| return posix.unexpectedErrno(err), 8354 } 8355 }, 8356 } 8357 } 8358 } 8359 8360 const dirSetOwner = switch (native_os) { 8361 .windows => dirSetOwnerUnsupported, 8362 else => dirSetOwnerPosix, 8363 }; 8364 8365 fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { 8366 _ = userdata; 8367 _ = dir; 8368 _ = owner; 8369 _ = group; 8370 return error.Unexpected; 8371 } 8372 8373 fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { 8374 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. 8375 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8376 _ = t; 8377 const uid = owner orelse ~@as(posix.uid_t, 0); 8378 const gid = group orelse ~@as(posix.gid_t, 0); 8379 return posixFchown(dir.handle, uid, gid); 8380 } 8381 8382 fn posixFchown(fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void { 8383 comptime assert(have_fchown); 8384 const syscall: Syscall = try .start(); 8385 while (true) { 8386 switch (posix.errno(posix.system.fchown(fd, uid, gid))) { 8387 .SUCCESS => return syscall.finish(), 8388 .INTR => { 8389 try syscall.checkCancel(); 8390 continue; 8391 }, 8392 else => |e| { 8393 syscall.finish(); 8394 switch (e) { 8395 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate` 8396 .FAULT => |err| return errnoBug(err), 8397 .INVAL => |err| return errnoBug(err), 8398 .ACCES => return error.AccessDenied, 8399 .IO => return error.InputOutput, 8400 .LOOP => return error.SymLinkLoop, 8401 .NOENT => return error.FileNotFound, 8402 .NOMEM => return error.SystemResources, 8403 .NOTDIR => return error.FileNotFound, 8404 .PERM => return error.PermissionDenied, 8405 .ROFS => return error.ReadOnlyFileSystem, 8406 else => |err| return posix.unexpectedErrno(err), 8407 } 8408 }, 8409 } 8410 } 8411 } 8412 8413 fn dirSetFileOwner( 8414 userdata: ?*anyopaque, 8415 dir: Dir, 8416 sub_path: []const u8, 8417 owner: ?File.Uid, 8418 group: ?File.Gid, 8419 options: Dir.SetFileOwnerOptions, 8420 ) Dir.SetFileOwnerError!void { 8421 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. 8422 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8423 _ = t; 8424 8425 var path_buffer: [posix.PATH_MAX]u8 = undefined; 8426 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 8427 8428 _ = dir; 8429 _ = sub_path_posix; 8430 _ = owner; 8431 _ = group; 8432 _ = options; 8433 @panic("TODO implement dirSetFileOwner"); 8434 } 8435 8436 const fileSync = switch (native_os) { 8437 .windows => fileSyncWindows, 8438 .wasi => fileSyncWasi, 8439 else => fileSyncPosix, 8440 }; 8441 8442 fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { 8443 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8444 _ = t; 8445 8446 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 8447 const syscall: Syscall = try .start(); 8448 while (true) { 8449 switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) { 8450 .SUCCESS => break syscall.finish(), 8451 .CANCELLED => { 8452 try syscall.checkCancel(); 8453 continue; 8454 }, 8455 .INVALID_HANDLE => unreachable, 8456 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time 8457 .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput), 8458 else => |status| return syscall.unexpectedNtstatus(status), 8459 } 8460 } 8461 } 8462 8463 fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void { 8464 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8465 _ = t; 8466 const syscall: Syscall = try .start(); 8467 while (true) { 8468 switch (posix.errno(posix.system.fsync(file.handle))) { 8469 .SUCCESS => return syscall.finish(), 8470 .INTR => { 8471 try syscall.checkCancel(); 8472 continue; 8473 }, 8474 else => |e| { 8475 syscall.finish(); 8476 switch (e) { 8477 .BADF => |err| return errnoBug(err), 8478 .INVAL => |err| return errnoBug(err), 8479 .ROFS => |err| return errnoBug(err), 8480 .IO => return error.InputOutput, 8481 .NOSPC => return error.NoSpaceLeft, 8482 .DQUOT => return error.DiskQuota, 8483 else => |err| return posix.unexpectedErrno(err), 8484 } 8485 }, 8486 } 8487 } 8488 } 8489 8490 fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { 8491 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8492 _ = t; 8493 const syscall: Syscall = try .start(); 8494 while (true) { 8495 switch (std.os.wasi.fd_sync(file.handle)) { 8496 .SUCCESS => return syscall.finish(), 8497 .INTR => { 8498 try syscall.checkCancel(); 8499 continue; 8500 }, 8501 else => |e| { 8502 syscall.finish(); 8503 switch (e) { 8504 .BADF => |err| return errnoBug(err), 8505 .INVAL => |err| return errnoBug(err), 8506 .ROFS => |err| return errnoBug(err), 8507 .IO => return error.InputOutput, 8508 .NOSPC => return error.NoSpaceLeft, 8509 .DQUOT => return error.DiskQuota, 8510 else => |err| return posix.unexpectedErrno(err), 8511 } 8512 }, 8513 } 8514 } 8515 } 8516 8517 fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { 8518 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8519 _ = t; 8520 return isTty(file); 8521 } 8522 8523 fn isTty(file: File) Io.Cancelable!bool { 8524 if (is_windows) { 8525 var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; 8526 switch ((try deviceIoControl(&.{ 8527 .file = .{ 8528 .handle = windows.peb().ProcessParameters.ConsoleHandle, 8529 .flags = .{ .nonblocking = false }, 8530 }, 8531 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, 8532 .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), 8533 })).u.Status) { 8534 .SUCCESS => return true, 8535 .INVALID_HANDLE => return isCygwinPty(file), 8536 else => return false, 8537 } 8538 } 8539 8540 if (builtin.link_libc) { 8541 const syscall: Syscall = try .start(); 8542 while (true) { 8543 const rc = posix.system.isatty(file.handle); 8544 switch (posix.errno(rc - 1)) { 8545 .SUCCESS => { 8546 syscall.finish(); 8547 return true; 8548 }, 8549 .INTR => { 8550 try syscall.checkCancel(); 8551 continue; 8552 }, 8553 else => { 8554 syscall.finish(); 8555 return false; 8556 }, 8557 } 8558 } 8559 } 8560 8561 if (native_os == .wasi) { 8562 var statbuf: std.os.wasi.fdstat_t = undefined; 8563 const err = std.os.wasi.fd_fdstat_get(file.handle, &statbuf); 8564 if (err != .SUCCESS) 8565 return false; 8566 8567 // A tty is a character device that we can't seek or tell on. 8568 if (statbuf.fs_filetype != .CHARACTER_DEVICE) 8569 return false; 8570 if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) 8571 return false; 8572 8573 return true; 8574 } 8575 8576 if (native_os == .linux) { 8577 const linux = std.os.linux; 8578 const syscall: Syscall = try .start(); 8579 while (true) { 8580 var wsz: posix.winsize = undefined; 8581 const fd: usize = @bitCast(@as(isize, file.handle)); 8582 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); 8583 switch (linux.errno(rc)) { 8584 .SUCCESS => { 8585 syscall.finish(); 8586 return true; 8587 }, 8588 .INTR => { 8589 try syscall.checkCancel(); 8590 continue; 8591 }, 8592 else => { 8593 syscall.finish(); 8594 return false; 8595 }, 8596 } 8597 } 8598 } 8599 8600 @compileError("unimplemented"); 8601 } 8602 8603 fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { 8604 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8605 _ = t; 8606 8607 if (!is_windows) return if (!try supportsAnsiEscapeCodes(file)) error.NotTerminalDevice; 8608 8609 // For Windows Terminal, VT Sequences processing is enabled by default. 8610 const console: File = .{ 8611 .handle = windows.peb().ProcessParameters.ConsoleHandle, 8612 .flags = .{ .nonblocking = false }, 8613 }; 8614 var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; 8615 switch ((try deviceIoControl(&.{ 8616 .file = console, 8617 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, 8618 .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), 8619 })).u.Status) { 8620 .SUCCESS => {}, 8621 .INVALID_HANDLE => return if (!try isCygwinPty(file)) error.NotTerminalDevice, 8622 else => return error.NotTerminalDevice, 8623 } 8624 8625 if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; 8626 8627 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. 8628 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ 8629 // 8630 // Note: In Microsoft's example for enabling virtual terminal processing, it 8631 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: 8632 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing 8633 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) 8634 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). 8635 // Additionally, the default console mode in Windows Terminal does not have 8636 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` 8637 // we end up matching the mode of Windows Terminal. 8638 var set_console_mode = windows.CONSOLE.USER_IO.SET_MODE( 8639 get_console_mode.Data | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING, 8640 ); 8641 switch ((try deviceIoControl(&.{ 8642 .file = console, 8643 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, 8644 .in = @ptrCast(&set_console_mode.request(file, 0, .{}, 0, .{})), 8645 })).u.Status) { 8646 .SUCCESS => {}, 8647 else => |status| return windows.unexpectedStatus(status), 8648 } 8649 } 8650 8651 fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { 8652 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8653 _ = t; 8654 return supportsAnsiEscapeCodes(file); 8655 } 8656 8657 fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { 8658 if (is_windows) { 8659 var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; 8660 switch ((try deviceIoControl(&.{ 8661 .file = .{ 8662 .handle = windows.peb().ProcessParameters.ConsoleHandle, 8663 .flags = .{ .nonblocking = false }, 8664 }, 8665 .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, 8666 .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), 8667 })).u.Status) { 8668 .SUCCESS => if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) 8669 return true, 8670 .INVALID_HANDLE => return isCygwinPty(file), 8671 else => return false, 8672 } 8673 } 8674 8675 if (native_os == .wasi) { 8676 // WASI sanitizes stdout when fd is a tty so ANSI escape codes will not 8677 // be interpreted as actual cursor commands, and stderr is always 8678 // sanitized. 8679 return false; 8680 } 8681 8682 if (try isTty(file)) return true; 8683 8684 return false; 8685 } 8686 8687 fn isCygwinPty(file: File) Io.Cancelable!bool { 8688 if (!is_windows) return false; 8689 8690 const handle = file.handle; 8691 8692 // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: 8693 // msys-[...]-ptyN-[...] 8694 // cygwin-[...]-ptyN-[...] 8695 // 8696 // Example: msys-1888ae32e00d56aa-pty0-to-master 8697 8698 // First, just check that the handle is a named pipe. 8699 // This allows us to avoid the more costly NtQueryInformationFile call 8700 // for handles that aren't named pipes. 8701 { 8702 var io_status: windows.IO_STATUS_BLOCK = undefined; 8703 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; 8704 const syscall: Syscall = try .start(); 8705 while (true) switch (windows.ntdll.NtQueryVolumeInformationFile( 8706 handle, 8707 &io_status, 8708 &device_info, 8709 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), 8710 .Device, 8711 )) { 8712 .SUCCESS => break syscall.finish(), 8713 .CANCELLED => { 8714 try syscall.checkCancel(); 8715 continue; 8716 }, 8717 else => { 8718 syscall.finish(); 8719 return false; 8720 }, 8721 }; 8722 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; 8723 } 8724 8725 const name_bytes_offset = @offsetOf(windows.FILE.NAME_INFORMATION, "FileName"); 8726 // `NAME_MAX` UTF-16 code units (2 bytes each) 8727 // This buffer may not be long enough to handle *all* possible paths 8728 // (PATH_MAX_WIDE would be necessary for that), but because we only care 8729 // about certain paths and we know they must be within a reasonable length, 8730 // we can use this smaller buffer and just return false on any error from 8731 // NtQueryInformationFile. 8732 const num_name_bytes = windows.MAX_PATH * 2; 8733 var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes); 8734 8735 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 8736 const syscall: Syscall = try .start(); 8737 while (true) switch (windows.ntdll.NtQueryInformationFile( 8738 handle, 8739 &io_status_block, 8740 &name_info_bytes, 8741 @intCast(name_info_bytes.len), 8742 .Name, 8743 )) { 8744 .SUCCESS => break syscall.finish(), 8745 .CANCELLED => { 8746 try syscall.checkCancel(); 8747 continue; 8748 }, 8749 .INVALID_PARAMETER => unreachable, 8750 else => { 8751 syscall.finish(); 8752 return false; 8753 }, 8754 }; 8755 8756 const name_info: *const windows.FILE.NAME_INFORMATION = @ptrCast(&name_info_bytes); 8757 const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; 8758 const name_wide = std.mem.bytesAsSlice(u16, name_bytes); 8759 // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master 8760 return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or 8761 std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and 8762 std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; 8763 } 8764 8765 fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void { 8766 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8767 _ = t; 8768 8769 const signed_len: i64 = @bitCast(length); 8770 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. 8771 8772 if (is_windows) { 8773 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 8774 var eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ 8775 .EndOfFile = signed_len, 8776 }; 8777 8778 const syscall: Syscall = try .start(); 8779 while (true) switch (windows.ntdll.NtSetInformationFile( 8780 file.handle, 8781 &io_status_block, 8782 &eof_info, 8783 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), 8784 .EndOfFile, 8785 )) { 8786 .SUCCESS => return syscall.finish(), 8787 .CANCELLED => { 8788 try syscall.checkCancel(); 8789 continue; 8790 }, 8791 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing. 8792 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 8793 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 8794 .INVALID_PARAMETER => return syscall.fail(error.FileTooBig), 8795 else => |status| return syscall.unexpectedNtstatus(status), 8796 }; 8797 } 8798 8799 if (native_os == .wasi and !builtin.link_libc) { 8800 const syscall: Syscall = try .start(); 8801 while (true) { 8802 switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) { 8803 .SUCCESS => return syscall.finish(), 8804 .INTR => { 8805 try syscall.checkCancel(); 8806 continue; 8807 }, 8808 else => |e| { 8809 syscall.finish(); 8810 switch (e) { 8811 .FBIG => return error.FileTooBig, 8812 .IO => return error.InputOutput, 8813 .PERM => return error.PermissionDenied, 8814 .TXTBSY => return error.FileBusy, 8815 .BADF => |err| return errnoBug(err), // Handle not open for writing 8816 .INVAL => return error.NonResizable, 8817 .NOTCAPABLE => return error.AccessDenied, 8818 else => |err| return posix.unexpectedErrno(err), 8819 } 8820 }, 8821 } 8822 } 8823 } 8824 8825 const syscall: Syscall = try .start(); 8826 while (true) { 8827 switch (posix.errno(ftruncate_sym(file.handle, signed_len))) { 8828 .SUCCESS => return syscall.finish(), 8829 .INTR => { 8830 try syscall.checkCancel(); 8831 continue; 8832 }, 8833 else => |e| { 8834 syscall.finish(); 8835 switch (e) { 8836 .FBIG => return error.FileTooBig, 8837 .IO => return error.InputOutput, 8838 .PERM => return error.PermissionDenied, 8839 .TXTBSY => return error.FileBusy, 8840 .BADF => |err| return errnoBug(err), // Handle not open for writing. 8841 .INVAL => return error.NonResizable, // This is returned for /dev/null for example. 8842 else => |err| return posix.unexpectedErrno(err), 8843 } 8844 }, 8845 } 8846 } 8847 } 8848 8849 fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void { 8850 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. 8851 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8852 _ = t; 8853 const uid = owner orelse ~@as(posix.uid_t, 0); 8854 const gid = group orelse ~@as(posix.gid_t, 0); 8855 return posixFchown(file.handle, uid, gid); 8856 } 8857 8858 fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void { 8859 if (@sizeOf(File.Permissions) == 0) return; 8860 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8861 _ = t; 8862 switch (native_os) { 8863 .windows => { 8864 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 8865 var info: windows.FILE.BASIC_INFORMATION = .{ 8866 .CreationTime = 0, 8867 .LastAccessTime = 0, 8868 .LastWriteTime = 0, 8869 .ChangeTime = 0, 8870 .FileAttributes = permissions.toAttributes(), 8871 }; 8872 const syscall: Syscall = try .start(); 8873 while (true) switch (windows.ntdll.NtSetInformationFile( 8874 file.handle, 8875 &io_status_block, 8876 &info, 8877 @sizeOf(windows.FILE.BASIC_INFORMATION), 8878 .Basic, 8879 )) { 8880 .SUCCESS => return syscall.finish(), 8881 .CANCELLED => { 8882 try syscall.checkCancel(); 8883 continue; 8884 }, 8885 .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), 8886 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 8887 else => |status| return syscall.unexpectedNtstatus(status), 8888 }; 8889 }, 8890 .wasi => return error.Unexpected, // Unsupported OS. 8891 else => return setPermissionsPosix(file.handle, permissions.toMode()), 8892 } 8893 } 8894 8895 fn setPermissionsPosix(fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void { 8896 comptime assert(have_fchmod); 8897 const syscall: Syscall = try .start(); 8898 while (true) { 8899 switch (posix.errno(posix.system.fchmod(fd, mode))) { 8900 .SUCCESS => return syscall.finish(), 8901 .INTR => { 8902 try syscall.checkCancel(); 8903 continue; 8904 }, 8905 else => |e| { 8906 syscall.finish(); 8907 switch (e) { 8908 .BADF => |err| return errnoBug(err), 8909 .FAULT => |err| return errnoBug(err), 8910 .INVAL => |err| return errnoBug(err), 8911 .ACCES => return error.AccessDenied, 8912 .IO => return error.InputOutput, 8913 .LOOP => return error.SymLinkLoop, 8914 .NOENT => return error.FileNotFound, 8915 .NOMEM => return error.SystemResources, 8916 .NOTDIR => return error.FileNotFound, 8917 .PERM => return error.PermissionDenied, 8918 .ROFS => return error.ReadOnlyFileSystem, 8919 else => |err| return posix.unexpectedErrno(err), 8920 } 8921 }, 8922 } 8923 } 8924 } 8925 8926 fn dirSetTimestamps( 8927 userdata: ?*anyopaque, 8928 dir: Dir, 8929 sub_path: []const u8, 8930 options: Dir.SetTimestampsOptions, 8931 ) Dir.SetTimestampsError!void { 8932 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8933 _ = t; 8934 8935 if (is_windows) { 8936 @panic("TODO implement dirSetTimestamps windows"); 8937 } 8938 8939 if (native_os == .wasi and !builtin.link_libc) { 8940 @panic("TODO implement dirSetTimestamps wasi"); 8941 } 8942 8943 var times_buffer: [2]posix.timespec = undefined; 8944 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { 8945 times_buffer = .{ 8946 setTimestampToPosix(options.access_timestamp), 8947 setTimestampToPosix(options.modify_timestamp), 8948 }; 8949 break :p ×_buffer; 8950 }; 8951 8952 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; 8953 8954 var path_buffer: [posix.PATH_MAX]u8 = undefined; 8955 const sub_path_posix = try pathToPosix(sub_path, &path_buffer); 8956 8957 const syscall: Syscall = try .start(); 8958 while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) { 8959 .SUCCESS => return syscall.finish(), 8960 .INTR => { 8961 try syscall.checkCancel(); 8962 continue; 8963 }, 8964 .BADF => |err| return syscall.errnoBug(err), // always a race condition 8965 .FAULT => |err| return syscall.errnoBug(err), 8966 .INVAL => |err| return syscall.errnoBug(err), 8967 .ACCES => return syscall.fail(error.AccessDenied), 8968 .PERM => return syscall.fail(error.PermissionDenied), 8969 .ROFS => return syscall.fail(error.ReadOnlyFileSystem), 8970 else => |err| return syscall.unexpectedErrno(err), 8971 }; 8972 } 8973 8974 fn fileSetTimestamps( 8975 userdata: ?*anyopaque, 8976 file: File, 8977 options: File.SetTimestampsOptions, 8978 ) File.SetTimestampsError!void { 8979 const t: *Threaded = @ptrCast(@alignCast(userdata)); 8980 _ = t; 8981 8982 if (is_windows) { 8983 const now_sys = if (options.access_timestamp == .now or options.modify_timestamp == .now) 8984 windows.ntdll.RtlGetSystemTimePrecise() 8985 else 8986 undefined; 8987 var iosb: windows.IO_STATUS_BLOCK = undefined; 8988 var info: windows.FILE.BASIC_INFORMATION = .{ 8989 .CreationTime = 0, 8990 .LastAccessTime = switch (options.access_timestamp) { 8991 .unchanged => 0, 8992 .now => now_sys, 8993 .new => |ts| windows.toSysTime(ts), 8994 }, 8995 .LastWriteTime = switch (options.modify_timestamp) { 8996 .unchanged => 0, 8997 .now => now_sys, 8998 .new => |ts| windows.toSysTime(ts), 8999 }, 9000 .ChangeTime = 0, 9001 .FileAttributes = .{}, 9002 }; 9003 var syscall: Syscall = try .start(); 9004 while (true) switch (windows.ntdll.NtSetInformationFile( 9005 file.handle, 9006 &iosb, 9007 &info, 9008 @sizeOf(windows.FILE.BASIC_INFORMATION), 9009 .Basic, 9010 )) { 9011 .SUCCESS => return syscall.finish(), 9012 .CANCELLED => try syscall.checkCancel(), 9013 else => |status| return syscall.unexpectedNtstatus(status), 9014 }; 9015 } 9016 9017 if (native_os == .wasi and !builtin.link_libc) { 9018 var atime: std.os.wasi.timestamp_t = 0; 9019 var mtime: std.os.wasi.timestamp_t = 0; 9020 var flags: std.os.wasi.fstflags_t = .{}; 9021 9022 switch (options.access_timestamp) { 9023 .unchanged => {}, 9024 .now => flags.ATIM_NOW = true, 9025 .new => |ts| { 9026 atime = timestampToPosix(ts.nanoseconds).toTimestamp(); 9027 flags.ATIM = true; 9028 }, 9029 } 9030 9031 switch (options.modify_timestamp) { 9032 .unchanged => {}, 9033 .now => flags.MTIM_NOW = true, 9034 .new => |ts| { 9035 mtime = timestampToPosix(ts.nanoseconds).toTimestamp(); 9036 flags.MTIM = true; 9037 }, 9038 } 9039 9040 const syscall: Syscall = try .start(); 9041 while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) { 9042 .SUCCESS => return syscall.finish(), 9043 .INTR => { 9044 try syscall.checkCancel(); 9045 continue; 9046 }, 9047 .BADF => |err| return syscall.errnoBug(err), // File descriptor use-after-free. 9048 .FAULT => |err| return syscall.errnoBug(err), 9049 .INVAL => |err| return syscall.errnoBug(err), 9050 .ACCES => return syscall.fail(error.AccessDenied), 9051 .PERM => return syscall.fail(error.PermissionDenied), 9052 .ROFS => return syscall.fail(error.ReadOnlyFileSystem), 9053 else => |err| return syscall.unexpectedErrno(err), 9054 }; 9055 } 9056 9057 var times_buffer: [2]posix.timespec = undefined; 9058 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { 9059 times_buffer = .{ 9060 setTimestampToPosix(options.access_timestamp), 9061 setTimestampToPosix(options.modify_timestamp), 9062 }; 9063 break :p ×_buffer; 9064 }; 9065 9066 const syscall: Syscall = try .start(); 9067 while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) { 9068 .SUCCESS => return syscall.finish(), 9069 .INTR => { 9070 try syscall.checkCancel(); 9071 continue; 9072 }, 9073 .BADF => |err| return syscall.errnoBug(err), // always a race condition 9074 .FAULT => |err| return syscall.errnoBug(err), 9075 .INVAL => |err| return syscall.errnoBug(err), 9076 .ACCES => return syscall.fail(error.AccessDenied), 9077 .PERM => return syscall.fail(error.PermissionDenied), 9078 .ROFS => return syscall.fail(error.ReadOnlyFileSystem), 9079 else => |err| return syscall.unexpectedErrno(err), 9080 }; 9081 } 9082 9083 const windows_lock_range_off: windows.LARGE_INTEGER = 0; 9084 const windows_lock_range_len: windows.LARGE_INTEGER = 1; 9085 9086 fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void { 9087 if (native_os == .wasi) return error.FileLocksUnsupported; 9088 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9089 _ = t; 9090 9091 if (is_windows) { 9092 const exclusive = switch (lock) { 9093 .none => { 9094 // To match the non-Windows behavior, unlock 9095 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 9096 while (true) switch (windows.ntdll.NtUnlockFile( 9097 file.handle, 9098 &io_status_block, 9099 &windows_lock_range_off, 9100 &windows_lock_range_len, 9101 0, 9102 )) { 9103 .SUCCESS => return, 9104 .CANCELLED => continue, 9105 .RANGE_NOT_LOCKED => return, 9106 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer 9107 else => |status| return windows.unexpectedStatus(status), 9108 }; 9109 }, 9110 .shared => false, 9111 .exclusive => true, 9112 }; 9113 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 9114 const syscall: Syscall = try .start(); 9115 while (true) switch (windows.ntdll.NtLockFile( 9116 file.handle, 9117 null, 9118 null, 9119 null, 9120 &io_status_block, 9121 &windows_lock_range_off, 9122 &windows_lock_range_len, 9123 null, 9124 windows.FALSE, 9125 @intFromBool(exclusive), 9126 )) { 9127 .SUCCESS => return syscall.finish(), 9128 .CANCELLED => { 9129 try syscall.checkCancel(); 9130 continue; 9131 }, 9132 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), 9133 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // passed FailImmediately=false 9134 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer 9135 else => |status| return syscall.unexpectedNtstatus(status), 9136 }; 9137 } 9138 9139 const operation: i32 = switch (lock) { 9140 .none => posix.LOCK.UN, 9141 .shared => posix.LOCK.SH, 9142 .exclusive => posix.LOCK.EX, 9143 }; 9144 const syscall: Syscall = try .start(); 9145 while (true) { 9146 switch (posix.errno(posix.system.flock(file.handle, operation))) { 9147 .SUCCESS => return syscall.finish(), 9148 .INTR => { 9149 try syscall.checkCancel(); 9150 continue; 9151 }, 9152 else => |e| { 9153 syscall.finish(); 9154 switch (e) { 9155 .BADF => |err| return errnoBug(err), 9156 .INVAL => |err| return errnoBug(err), // invalid parameters 9157 .NOLCK => return error.SystemResources, 9158 .AGAIN => |err| return errnoBug(err), 9159 .OPNOTSUPP => return error.FileLocksUnsupported, 9160 else => |err| return posix.unexpectedErrno(err), 9161 } 9162 }, 9163 } 9164 } 9165 } 9166 9167 fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool { 9168 if (native_os == .wasi) return error.FileLocksUnsupported; 9169 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9170 _ = t; 9171 9172 if (is_windows) { 9173 const exclusive = switch (lock) { 9174 .none => { 9175 // To match the non-Windows behavior, unlock 9176 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 9177 while (true) switch (windows.ntdll.NtUnlockFile( 9178 file.handle, 9179 &io_status_block, 9180 &windows_lock_range_off, 9181 &windows_lock_range_len, 9182 0, 9183 )) { 9184 .SUCCESS => return true, 9185 .CANCELLED => continue, 9186 .RANGE_NOT_LOCKED => return false, 9187 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer 9188 else => |status| return windows.unexpectedStatus(status), 9189 }; 9190 }, 9191 .shared => false, 9192 .exclusive => true, 9193 }; 9194 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 9195 const syscall: Syscall = try .start(); 9196 while (true) switch (windows.ntdll.NtLockFile( 9197 file.handle, 9198 null, 9199 null, 9200 null, 9201 &io_status_block, 9202 &windows_lock_range_off, 9203 &windows_lock_range_len, 9204 null, 9205 windows.TRUE, 9206 @intFromBool(exclusive), 9207 )) { 9208 .SUCCESS => { 9209 syscall.finish(); 9210 return true; 9211 }, 9212 .LOCK_NOT_GRANTED => { 9213 syscall.finish(); 9214 return false; 9215 }, 9216 .CANCELLED => { 9217 try syscall.checkCancel(); 9218 continue; 9219 }, 9220 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), 9221 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer 9222 else => |status| return syscall.unexpectedNtstatus(status), 9223 }; 9224 } 9225 9226 const operation: i32 = switch (lock) { 9227 .none => posix.LOCK.UN, 9228 .shared => posix.LOCK.SH | posix.LOCK.NB, 9229 .exclusive => posix.LOCK.EX | posix.LOCK.NB, 9230 }; 9231 const syscall: Syscall = try .start(); 9232 while (true) { 9233 switch (posix.errno(posix.system.flock(file.handle, operation))) { 9234 .SUCCESS => { 9235 syscall.finish(); 9236 return true; 9237 }, 9238 .INTR => { 9239 try syscall.checkCancel(); 9240 continue; 9241 }, 9242 .AGAIN => { 9243 syscall.finish(); 9244 return false; 9245 }, 9246 else => |e| { 9247 syscall.finish(); 9248 switch (e) { 9249 .BADF => |err| return errnoBug(err), 9250 .INVAL => |err| return errnoBug(err), // invalid parameters 9251 .NOLCK => return error.SystemResources, 9252 .OPNOTSUPP => return error.FileLocksUnsupported, 9253 else => |err| return posix.unexpectedErrno(err), 9254 } 9255 }, 9256 } 9257 } 9258 } 9259 9260 fn fileUnlock(userdata: ?*anyopaque, file: File) void { 9261 if (native_os == .wasi) return; 9262 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9263 _ = t; 9264 9265 if (is_windows) { 9266 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 9267 while (true) switch (windows.ntdll.NtUnlockFile( 9268 file.handle, 9269 &io_status_block, 9270 &windows_lock_range_off, 9271 &windows_lock_range_len, 9272 0, 9273 )) { 9274 .SUCCESS => return, 9275 .CANCELLED => continue, 9276 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked. 9277 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer 9278 else => if (is_debug) unreachable else return, // Resource deallocation must succeed. 9279 }; 9280 } 9281 9282 while (true) { 9283 switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) { 9284 .SUCCESS => return, 9285 .CANCELED, .INTR => continue, 9286 .AGAIN => return assert(!is_debug), // unlocking can't block 9287 .BADF => return assert(!is_debug), // File descriptor used after closed. 9288 .INVAL => return assert(!is_debug), // invalid parameters 9289 .NOLCK => return assert(!is_debug), // Resource deallocation. 9290 .OPNOTSUPP => return assert(!is_debug), // We already got the lock. 9291 else => return assert(!is_debug), // Resource deallocation must succeed. 9292 } 9293 } 9294 } 9295 9296 fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void { 9297 if (native_os == .wasi) return; 9298 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9299 _ = t; 9300 9301 if (is_windows) { 9302 // On Windows it works like a semaphore + exclusivity flag. To 9303 // implement this function, we first obtain another lock in shared 9304 // mode. This changes the exclusivity flag, but increments the 9305 // semaphore to 2. So we follow up with an NtUnlockFile which 9306 // decrements the semaphore but does not modify the exclusivity flag. 9307 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 9308 const syscall: Syscall = try .start(); 9309 while (true) switch (windows.ntdll.NtLockFile( 9310 file.handle, 9311 null, 9312 null, 9313 null, 9314 &io_status_block, 9315 &windows_lock_range_off, 9316 &windows_lock_range_len, 9317 null, 9318 windows.TRUE, 9319 windows.FALSE, 9320 )) { 9321 .SUCCESS => break syscall.finish(), 9322 .CANCELLED => { 9323 try syscall.checkCancel(); 9324 continue; 9325 }, 9326 .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err), 9327 .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // File was not locked in exclusive mode. 9328 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer 9329 else => |status| return syscall.unexpectedNtstatus(status), 9330 }; 9331 while (true) switch (windows.ntdll.NtUnlockFile( 9332 file.handle, 9333 &io_status_block, 9334 &windows_lock_range_off, 9335 &windows_lock_range_len, 9336 0, 9337 )) { 9338 .SUCCESS => return, 9339 .CANCELLED => continue, 9340 .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked. 9341 .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer 9342 else => if (is_debug) unreachable else return, // Resource deallocation must succeed. 9343 }; 9344 } 9345 9346 const operation = posix.LOCK.SH | posix.LOCK.NB; 9347 9348 const syscall: Syscall = try .start(); 9349 while (true) { 9350 switch (posix.errno(posix.system.flock(file.handle, operation))) { 9351 .SUCCESS => { 9352 syscall.finish(); 9353 return; 9354 }, 9355 .INTR => { 9356 try syscall.checkCancel(); 9357 continue; 9358 }, 9359 else => |e| { 9360 syscall.finish(); 9361 switch (e) { 9362 .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode. 9363 .BADF => |err| return errnoBug(err), 9364 .INVAL => |err| return errnoBug(err), // invalid parameters 9365 .NOLCK => |err| return errnoBug(err), // Lock already obtained. 9366 .OPNOTSUPP => |err| return errnoBug(err), // Lock already obtained. 9367 else => |err| return posix.unexpectedErrno(err), 9368 } 9369 }, 9370 } 9371 } 9372 } 9373 9374 fn dirOpenDirWasi( 9375 userdata: ?*anyopaque, 9376 dir: Dir, 9377 sub_path: []const u8, 9378 options: Dir.OpenOptions, 9379 ) Dir.OpenError!Dir { 9380 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); 9381 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9382 _ = t; 9383 const wasi = std.os.wasi; 9384 9385 var base: std.os.wasi.rights_t = .{ 9386 .FD_FILESTAT_GET = true, 9387 .FD_FDSTAT_SET_FLAGS = true, 9388 .FD_FILESTAT_SET_TIMES = true, 9389 }; 9390 if (options.access_sub_paths) { 9391 base.FD_READDIR = true; 9392 base.PATH_CREATE_DIRECTORY = true; 9393 base.PATH_CREATE_FILE = true; 9394 base.PATH_LINK_SOURCE = true; 9395 base.PATH_LINK_TARGET = true; 9396 base.PATH_OPEN = true; 9397 base.PATH_READLINK = true; 9398 base.PATH_RENAME_SOURCE = true; 9399 base.PATH_RENAME_TARGET = true; 9400 base.PATH_FILESTAT_GET = true; 9401 base.PATH_FILESTAT_SET_SIZE = true; 9402 base.PATH_FILESTAT_SET_TIMES = true; 9403 base.PATH_SYMLINK = true; 9404 base.PATH_REMOVE_DIRECTORY = true; 9405 base.PATH_UNLINK_FILE = true; 9406 } 9407 9408 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks }; 9409 const oflags: wasi.oflags_t = .{ .DIRECTORY = true }; 9410 const fdflags: wasi.fdflags_t = .{}; 9411 var fd: posix.fd_t = undefined; 9412 const syscall: Syscall = try .start(); 9413 while (true) { 9414 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) { 9415 .SUCCESS => { 9416 syscall.finish(); 9417 return .{ .handle = fd }; 9418 }, 9419 .INTR => { 9420 try syscall.checkCancel(); 9421 continue; 9422 }, 9423 else => |e| { 9424 syscall.finish(); 9425 switch (e) { 9426 .FAULT => |err| return errnoBug(err), 9427 .INVAL => return error.BadPathName, 9428 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 9429 .ACCES => return error.AccessDenied, 9430 .LOOP => return error.SymLinkLoop, 9431 .MFILE => return error.ProcessFdQuotaExceeded, 9432 .NAMETOOLONG => return error.NameTooLong, 9433 .NFILE => return error.SystemFdQuotaExceeded, 9434 .NODEV => return error.NoDevice, 9435 .NOENT => return error.FileNotFound, 9436 .NOMEM => return error.SystemResources, 9437 .NOTDIR => return error.NotDir, 9438 .PERM => return error.PermissionDenied, 9439 .NOTCAPABLE => return error.AccessDenied, 9440 .ILSEQ => return error.BadPathName, 9441 else => |err| return posix.unexpectedErrno(err), 9442 } 9443 }, 9444 } 9445 } 9446 } 9447 9448 fn dirHardLink( 9449 userdata: ?*anyopaque, 9450 old_dir: Dir, 9451 old_sub_path: []const u8, 9452 new_dir: Dir, 9453 new_sub_path: []const u8, 9454 options: Dir.HardLinkOptions, 9455 ) Dir.HardLinkError!void { 9456 if (is_windows) return error.OperationUnsupported; 9457 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9458 _ = t; 9459 9460 if (native_os == .wasi and !builtin.link_libc) { 9461 const flags: std.os.wasi.lookupflags_t = .{ 9462 .SYMLINK_FOLLOW = options.follow_symlinks, 9463 }; 9464 const syscall: Syscall = try .start(); 9465 while (true) { 9466 switch (std.os.wasi.path_link( 9467 old_dir.handle, 9468 flags, 9469 old_sub_path.ptr, 9470 old_sub_path.len, 9471 new_dir.handle, 9472 new_sub_path.ptr, 9473 new_sub_path.len, 9474 )) { 9475 .SUCCESS => return syscall.finish(), 9476 .INTR => { 9477 try syscall.checkCancel(); 9478 continue; 9479 }, 9480 else => |e| { 9481 syscall.finish(); 9482 switch (e) { 9483 .ACCES => return error.AccessDenied, 9484 .DQUOT => return error.DiskQuota, 9485 .EXIST => return error.PathAlreadyExists, 9486 .FAULT => |err| return errnoBug(err), 9487 .IO => return error.HardwareFailure, 9488 .LOOP => return error.SymLinkLoop, 9489 .MLINK => return error.LinkQuotaExceeded, 9490 .NAMETOOLONG => return error.NameTooLong, 9491 .NOENT => return error.FileNotFound, 9492 .NOMEM => return error.SystemResources, 9493 .NOSPC => return error.NoSpaceLeft, 9494 .NOTDIR => return error.NotDir, 9495 .PERM => return error.PermissionDenied, 9496 .ROFS => return error.ReadOnlyFileSystem, 9497 .XDEV => return error.CrossDevice, 9498 .INVAL => |err| return errnoBug(err), 9499 .ILSEQ => return error.BadPathName, 9500 else => |err| return posix.unexpectedErrno(err), 9501 } 9502 }, 9503 } 9504 } 9505 } 9506 9507 var old_path_buffer: [posix.PATH_MAX]u8 = undefined; 9508 var new_path_buffer: [posix.PATH_MAX]u8 = undefined; 9509 9510 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); 9511 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); 9512 9513 const flags: u32 = if (options.follow_symlinks) posix.AT.SYMLINK_FOLLOW else 0; 9514 return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags); 9515 } 9516 9517 fn fileClose(userdata: ?*anyopaque, files: []const File) void { 9518 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9519 _ = t; 9520 for (files) |file| { 9521 if (is_windows) { 9522 windows.CloseHandle(file.handle); 9523 } else { 9524 closeFd(file.handle); 9525 } 9526 } 9527 } 9528 9529 fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize { 9530 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9531 _ = t; 9532 if (is_windows) return fileReadStreamingWindows(file, data); 9533 return fileReadStreamingPosix(file, data); 9534 } 9535 9536 fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize { 9537 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; 9538 var i: usize = 0; 9539 for (data) |buf| { 9540 if (iovecs_buffer.len - i == 0) break; 9541 if (buf.len != 0) { 9542 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; 9543 i += 1; 9544 } 9545 } 9546 if (i == 0) return 0; 9547 const dest = iovecs_buffer[0..i]; 9548 assert(dest[0].len > 0); 9549 9550 if (native_os == .wasi and !builtin.link_libc) { 9551 const syscall: Syscall = try .start(); 9552 while (true) { 9553 var nread: usize = undefined; 9554 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { 9555 .SUCCESS => { 9556 syscall.finish(); 9557 if (nread == 0) return error.EndOfStream; 9558 return nread; 9559 }, 9560 .INTR, .TIMEDOUT => { 9561 try syscall.checkCancel(); 9562 continue; 9563 }, 9564 .BADF => return syscall.fail(error.IsDir), // File operation on directory. 9565 .IO => return syscall.fail(error.InputOutput), 9566 .ISDIR => return syscall.fail(error.IsDir), 9567 .NOBUFS => return syscall.fail(error.SystemResources), 9568 .NOMEM => return syscall.fail(error.SystemResources), 9569 .NOTCONN => return syscall.fail(error.SocketUnconnected), 9570 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), 9571 .NOTCAPABLE => return syscall.fail(error.AccessDenied), 9572 .INVAL => |err| return syscall.errnoBug(err), 9573 .FAULT => |err| return syscall.errnoBug(err), 9574 else => |err| return syscall.unexpectedErrno(err), 9575 } 9576 } 9577 } 9578 9579 const syscall: Syscall = try .start(); 9580 while (true) { 9581 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len)); 9582 switch (posix.errno(rc)) { 9583 .SUCCESS => { 9584 syscall.finish(); 9585 if (rc == 0) return error.EndOfStream; 9586 return @intCast(rc); 9587 }, 9588 .INTR, .TIMEDOUT => { 9589 try syscall.checkCancel(); 9590 continue; 9591 }, 9592 .BADF => { 9593 syscall.finish(); 9594 if (native_os == .wasi) return error.IsDir; // File operation on directory. 9595 return error.NotOpenForReading; 9596 }, 9597 .AGAIN => return syscall.fail(error.WouldBlock), 9598 .IO => return syscall.fail(error.InputOutput), 9599 .ISDIR => return syscall.fail(error.IsDir), 9600 .NOBUFS => return syscall.fail(error.SystemResources), 9601 .NOMEM => return syscall.fail(error.SystemResources), 9602 .NOTCONN => return syscall.fail(error.SocketUnconnected), 9603 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), 9604 .INVAL => |err| return syscall.errnoBug(err), 9605 .FAULT => |err| return syscall.errnoBug(err), 9606 else => |err| return syscall.unexpectedErrno(err), 9607 } 9608 } 9609 } 9610 9611 fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { 9612 var iosb: windows.IO_STATUS_BLOCK = undefined; 9613 var index: usize = 0; 9614 while (data.len - index != 0 and data[index].len == 0) index += 1; 9615 if (data.len - index == 0) return 0; 9616 const buffer = data[index]; 9617 const short_buffer_len = std.math.lossyCast(u32, buffer.len); 9618 if (file.flags.nonblocking) { 9619 var done: bool = false; 9620 switch (windows.ntdll.NtReadFile( 9621 file.handle, 9622 null, // event 9623 flagApc, 9624 &done, // APC context 9625 &iosb, 9626 buffer.ptr, 9627 short_buffer_len, 9628 null, // byte offset 9629 null, // key 9630 )) { 9631 // We must wait for the APC routine. 9632 .PENDING, .SUCCESS => while (!done) { 9633 // Once we get here we must not return from the function until the 9634 // operation completes, thereby releasing reference to the iosb. 9635 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { 9636 error.Canceled => |e| { 9637 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; 9638 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); 9639 while (!done) waitForApcOrAlert(); 9640 return e; 9641 }, 9642 }; 9643 waitForApcOrAlert(); 9644 alertable_syscall.finish(); 9645 }, 9646 else => |status| iosb.u.Status = status, 9647 } 9648 } else { 9649 const syscall: Syscall = try .start(); 9650 while (true) switch (windows.ntdll.NtReadFile( 9651 file.handle, 9652 null, // event 9653 null, // APC routine 9654 null, // APC context 9655 &iosb, 9656 buffer.ptr, 9657 short_buffer_len, 9658 null, // byte offset 9659 null, // key 9660 )) { 9661 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag 9662 .CANCELLED => { 9663 try syscall.checkCancel(); 9664 continue; 9665 }, 9666 else => |status| { 9667 syscall.finish(); 9668 iosb.u.Status = status; 9669 break; 9670 }, 9671 }; 9672 } 9673 return ntReadFileResult(&iosb); 9674 } 9675 9676 fn flagApc(userdata: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void { 9677 const flag: *bool = @ptrCast(userdata); 9678 flag.* = true; 9679 } 9680 9681 fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { 9682 switch (io_status_block.u.Status) { 9683 .PENDING => unreachable, 9684 .CANCELLED => unreachable, 9685 .SUCCESS => return io_status_block.Information, 9686 .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream, 9687 .INVALID_HANDLE => return error.NotOpenForReading, 9688 .INVALID_DEVICE_REQUEST => return error.IsDir, 9689 .FILE_LOCK_CONFLICT => return error.LockViolation, 9690 .ACCESS_DENIED => return error.AccessDenied, 9691 else => |status| return windows.unexpectedStatus(status), 9692 } 9693 } 9694 9695 fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { 9696 switch (io_status_block.u.Status) { 9697 .PENDING => unreachable, 9698 .CANCELLED => unreachable, 9699 .SUCCESS => return io_status_block.Information, 9700 .INVALID_USER_BUFFER => return error.SystemResources, 9701 .NO_MEMORY => return error.SystemResources, 9702 .QUOTA_EXCEEDED => return error.SystemResources, 9703 .PIPE_BROKEN => return error.BrokenPipe, 9704 .INVALID_HANDLE => return error.NotOpenForWriting, 9705 .FILE_LOCK_CONFLICT => return error.LockViolation, 9706 .ACCESS_DENIED => return error.AccessDenied, 9707 .WORKING_SET_QUOTA => return error.SystemResources, 9708 .DISK_FULL => return error.NoSpaceLeft, 9709 else => |status| return windows.unexpectedStatus(status), 9710 } 9711 } 9712 9713 fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { 9714 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; 9715 var i: usize = 0; 9716 for (data) |buf| { 9717 if (iovecs_buffer.len - i == 0) break; 9718 if (buf.len != 0) { 9719 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; 9720 i += 1; 9721 } 9722 } 9723 if (i == 0) return 0; 9724 const dest = iovecs_buffer[0..i]; 9725 assert(dest[0].len > 0); 9726 9727 if (native_os == .wasi and !builtin.link_libc) { 9728 const syscall: Syscall = try .start(); 9729 while (true) { 9730 var nread: usize = undefined; 9731 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { 9732 .SUCCESS => { 9733 syscall.finish(); 9734 return nread; 9735 }, 9736 .INTR, .TIMEDOUT => { 9737 try syscall.checkCancel(); 9738 continue; 9739 }, 9740 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket 9741 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket 9742 .INVAL => |err| return syscall.errnoBug(err), 9743 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault 9744 .AGAIN => |err| return syscall.errnoBug(err), 9745 .IO => return syscall.fail(error.InputOutput), 9746 .ISDIR => return syscall.fail(error.IsDir), 9747 .BADF => return syscall.fail(error.IsDir), 9748 .NOBUFS => return syscall.fail(error.SystemResources), 9749 .NOMEM => return syscall.fail(error.SystemResources), 9750 .NXIO => return syscall.fail(error.Unseekable), 9751 .SPIPE => return syscall.fail(error.Unseekable), 9752 .OVERFLOW => return syscall.fail(error.Unseekable), 9753 .NOTCAPABLE => return syscall.fail(error.AccessDenied), 9754 else => |err| return syscall.unexpectedErrno(err), 9755 } 9756 } 9757 } 9758 9759 if (have_preadv) { 9760 const syscall: Syscall = try .start(); 9761 while (true) { 9762 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); 9763 switch (posix.errno(rc)) { 9764 .SUCCESS => { 9765 syscall.finish(); 9766 return @bitCast(rc); 9767 }, 9768 .INTR, .TIMEDOUT => { 9769 try syscall.checkCancel(); 9770 continue; 9771 }, 9772 .NXIO => return syscall.fail(error.Unseekable), 9773 .SPIPE => return syscall.fail(error.Unseekable), 9774 .OVERFLOW => return syscall.fail(error.Unseekable), 9775 .NOBUFS => return syscall.fail(error.SystemResources), 9776 .NOMEM => return syscall.fail(error.SystemResources), 9777 .AGAIN => return syscall.fail(error.WouldBlock), 9778 .IO => return syscall.fail(error.InputOutput), 9779 .ISDIR => return syscall.fail(error.IsDir), 9780 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket 9781 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket 9782 .INVAL => |err| return syscall.errnoBug(err), 9783 .FAULT => |err| return syscall.errnoBug(err), 9784 .BADF => { 9785 syscall.finish(); 9786 if (native_os == .wasi) return error.IsDir; // File operation on directory. 9787 return error.NotOpenForReading; 9788 }, 9789 else => |err| return syscall.unexpectedErrno(err), 9790 } 9791 } 9792 } 9793 9794 const syscall: Syscall = try .start(); 9795 while (true) { 9796 const rc = posix.pread(file.handle, dest[0].ptr, @intCast(dest[0].len), @bitCast(offset)); 9797 switch (posix.errno(rc)) { 9798 .SUCCESS => { 9799 syscall.finish(); 9800 return @bitCast(rc); 9801 }, 9802 .INTR, .TIMEDOUT => { 9803 try syscall.checkCancel(); 9804 continue; 9805 }, 9806 .NXIO => return syscall.fail(error.Unseekable), 9807 .SPIPE => return syscall.fail(error.Unseekable), 9808 .OVERFLOW => return syscall.fail(error.Unseekable), 9809 .NOBUFS => return syscall.fail(error.SystemResources), 9810 .NOMEM => return syscall.fail(error.SystemResources), 9811 .AGAIN => return syscall.fail(error.WouldBlock), 9812 .IO => return syscall.fail(error.InputOutput), 9813 .ISDIR => return syscall.fail(error.IsDir), 9814 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket 9815 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket 9816 .INVAL => |err| return syscall.errnoBug(err), 9817 .FAULT => |err| return syscall.errnoBug(err), 9818 .BADF => { 9819 syscall.finish(); 9820 if (native_os == .wasi) return error.IsDir; // File operation on directory. 9821 return error.NotOpenForReading; 9822 }, 9823 else => |err| return syscall.unexpectedErrno(err), 9824 } 9825 } 9826 } 9827 9828 fn fileReadPositional(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { 9829 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9830 _ = t; 9831 if (is_windows) return fileReadPositionalWindows(file, data, offset); 9832 return fileReadPositionalPosix(file, data, offset); 9833 } 9834 9835 fn fileReadPositionalWindows(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { 9836 var index: usize = 0; 9837 while (index < data.len and data[index].len == 0) index += 1; 9838 if (index == data.len) return 0; 9839 const buffer = data[index]; 9840 9841 return readFilePositionalWindows(file, buffer, offset); 9842 } 9843 9844 fn readFilePositionalWindows(file: File, buffer: []u8, offset: u64) File.ReadPositionalError!usize { 9845 var iosb: windows.IO_STATUS_BLOCK = undefined; 9846 const short_buffer_len = std.math.lossyCast(u32, buffer.len); 9847 const signed_offset: windows.LARGE_INTEGER = @intCast(offset); 9848 if (file.flags.nonblocking) { 9849 var done: bool = false; 9850 switch (windows.ntdll.NtReadFile( 9851 file.handle, 9852 null, // event 9853 flagApc, 9854 &done, // APC context 9855 &iosb, 9856 buffer.ptr, 9857 short_buffer_len, 9858 &signed_offset, 9859 null, // key 9860 )) { 9861 // We must wait for the APC routine. 9862 .PENDING, .SUCCESS => while (!done) { 9863 // Once we get here we must not return from the function until the 9864 // operation completes, thereby releasing reference to the iosb. 9865 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { 9866 error.Canceled => |e| { 9867 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; 9868 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); 9869 while (!done) waitForApcOrAlert(); 9870 return e; 9871 }, 9872 }; 9873 waitForApcOrAlert(); 9874 alertable_syscall.finish(); 9875 }, 9876 else => |status| iosb.u.Status = status, 9877 } 9878 } else { 9879 const syscall: Syscall = try .start(); 9880 while (true) switch (windows.ntdll.NtReadFile( 9881 file.handle, 9882 null, // event 9883 null, // APC routine 9884 null, // APC context 9885 &iosb, 9886 buffer.ptr, 9887 short_buffer_len, 9888 &signed_offset, 9889 null, // key 9890 )) { 9891 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag 9892 .CANCELLED => try syscall.checkCancel(), 9893 else => |status| { 9894 syscall.finish(); 9895 iosb.u.Status = status; 9896 break; 9897 }, 9898 }; 9899 } 9900 return ntReadFileResult(&iosb) catch |err| switch (err) { 9901 error.EndOfStream => 0, 9902 else => |e| e, 9903 }; 9904 } 9905 9906 fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void { 9907 const t: *Threaded = @ptrCast(@alignCast(userdata)); 9908 _ = t; 9909 9910 if (is_windows) { 9911 var iosb: windows.IO_STATUS_BLOCK = undefined; 9912 var info: windows.FILE.POSITION_INFORMATION = undefined; 9913 const syscall: Syscall = try .start(); 9914 while (true) switch (windows.ntdll.NtQueryInformationFile( 9915 file.handle, 9916 &iosb, 9917 &info, 9918 @sizeOf(windows.FILE.POSITION_INFORMATION), 9919 .Position, 9920 )) { 9921 .SUCCESS => break, 9922 .CANCELLED => try syscall.checkCancel(), 9923 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 9924 .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), 9925 else => |status| return syscall.unexpectedNtstatus(status), 9926 }; 9927 info.CurrentByteOffset = @bitCast((if (offset >= 0) std.math.add( 9928 u64, 9929 @bitCast(info.CurrentByteOffset), 9930 @intCast(offset), 9931 ) else std.math.sub( 9932 u64, 9933 @bitCast(info.CurrentByteOffset), 9934 @intCast(-offset), 9935 )) catch |err| switch (err) { 9936 error.Overflow => return syscall.fail(error.Unseekable), 9937 }); 9938 while (true) switch (windows.ntdll.NtSetInformationFile( 9939 file.handle, 9940 &iosb, 9941 &info, 9942 @sizeOf(windows.FILE.POSITION_INFORMATION), 9943 .Position, 9944 )) { 9945 .SUCCESS => return syscall.finish(), 9946 .CANCELLED => try syscall.checkCancel(), 9947 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 9948 .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), 9949 else => |status| return syscall.unexpectedNtstatus(status), 9950 }; 9951 } 9952 9953 if (native_os == .wasi and !builtin.link_libc) { 9954 var new_offset: std.os.wasi.filesize_t = undefined; 9955 const syscall: Syscall = try .start(); 9956 while (true) { 9957 switch (std.os.wasi.fd_seek(file.handle, offset, .CUR, &new_offset)) { 9958 .SUCCESS => { 9959 syscall.finish(); 9960 return; 9961 }, 9962 .INTR => { 9963 try syscall.checkCancel(); 9964 continue; 9965 }, 9966 else => |e| { 9967 syscall.finish(); 9968 switch (e) { 9969 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 9970 .INVAL => return error.Unseekable, 9971 .OVERFLOW => return error.Unseekable, 9972 .SPIPE => return error.Unseekable, 9973 .NXIO => return error.Unseekable, 9974 .NOTCAPABLE => return error.AccessDenied, 9975 else => |err| return posix.unexpectedErrno(err), 9976 } 9977 }, 9978 } 9979 } 9980 } 9981 9982 if (posix.SEEK == void) return error.Unseekable; 9983 9984 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { 9985 var result: u64 = undefined; 9986 const syscall: Syscall = try .start(); 9987 while (true) { 9988 switch (posix.errno(posix.system.llseek(file.handle, @bitCast(offset), &result, posix.SEEK.CUR))) { 9989 .SUCCESS => { 9990 syscall.finish(); 9991 return; 9992 }, 9993 .INTR => { 9994 try syscall.checkCancel(); 9995 continue; 9996 }, 9997 else => |e| { 9998 syscall.finish(); 9999 switch (e) { 10000 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 10001 .INVAL => return error.Unseekable, 10002 .OVERFLOW => return error.Unseekable, 10003 .SPIPE => return error.Unseekable, 10004 .NXIO => return error.Unseekable, 10005 else => |err| return posix.unexpectedErrno(err), 10006 } 10007 }, 10008 } 10009 } 10010 } 10011 10012 const syscall: Syscall = try .start(); 10013 while (true) { 10014 switch (posix.errno(lseek_sym(file.handle, offset, posix.SEEK.CUR))) { 10015 .SUCCESS => { 10016 syscall.finish(); 10017 return; 10018 }, 10019 .INTR => { 10020 try syscall.checkCancel(); 10021 continue; 10022 }, 10023 else => |e| { 10024 syscall.finish(); 10025 switch (e) { 10026 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 10027 .INVAL => return error.Unseekable, 10028 .OVERFLOW => return error.Unseekable, 10029 .SPIPE => return error.Unseekable, 10030 .NXIO => return error.Unseekable, 10031 else => |err| return posix.unexpectedErrno(err), 10032 } 10033 }, 10034 } 10035 } 10036 } 10037 10038 fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void { 10039 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10040 _ = t; 10041 10042 if (is_windows) { 10043 var iosb: windows.IO_STATUS_BLOCK = undefined; 10044 var info: windows.FILE.POSITION_INFORMATION = .{ .CurrentByteOffset = @bitCast(offset) }; 10045 const syscall: Syscall = try .start(); 10046 while (true) switch (windows.ntdll.NtSetInformationFile( 10047 file.handle, 10048 &iosb, 10049 &info, 10050 @sizeOf(windows.FILE.POSITION_INFORMATION), 10051 .Position, 10052 )) { 10053 .SUCCESS => return syscall.finish(), 10054 .CANCELLED => try syscall.checkCancel(), 10055 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 10056 .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), 10057 else => |status| return syscall.unexpectedNtstatus(status), 10058 }; 10059 } 10060 10061 if (native_os == .wasi and !builtin.link_libc) { 10062 const syscall: Syscall = try .start(); 10063 while (true) { 10064 var new_offset: std.os.wasi.filesize_t = undefined; 10065 switch (std.os.wasi.fd_seek(file.handle, @bitCast(offset), .SET, &new_offset)) { 10066 .SUCCESS => { 10067 syscall.finish(); 10068 return; 10069 }, 10070 .INTR => { 10071 try syscall.checkCancel(); 10072 continue; 10073 }, 10074 else => |e| { 10075 syscall.finish(); 10076 switch (e) { 10077 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 10078 .INVAL => return error.Unseekable, 10079 .OVERFLOW => return error.Unseekable, 10080 .SPIPE => return error.Unseekable, 10081 .NXIO => return error.Unseekable, 10082 .NOTCAPABLE => return error.AccessDenied, 10083 else => |err| return posix.unexpectedErrno(err), 10084 } 10085 }, 10086 } 10087 } 10088 } 10089 10090 if (posix.SEEK == void) return error.Unseekable; 10091 10092 return posixSeekTo(file.handle, offset); 10093 } 10094 10095 fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void { 10096 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { 10097 const syscall: Syscall = try .start(); 10098 while (true) { 10099 var result: u64 = undefined; 10100 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { 10101 .SUCCESS => { 10102 syscall.finish(); 10103 return; 10104 }, 10105 .INTR => { 10106 try syscall.checkCancel(); 10107 continue; 10108 }, 10109 else => |e| { 10110 syscall.finish(); 10111 switch (e) { 10112 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 10113 .INVAL => return error.Unseekable, 10114 .OVERFLOW => return error.Unseekable, 10115 .SPIPE => return error.Unseekable, 10116 .NXIO => return error.Unseekable, 10117 else => |err| return posix.unexpectedErrno(err), 10118 } 10119 }, 10120 } 10121 } 10122 } 10123 10124 const syscall: Syscall = try .start(); 10125 while (true) { 10126 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) { 10127 .SUCCESS => { 10128 syscall.finish(); 10129 return; 10130 }, 10131 .INTR => { 10132 try syscall.checkCancel(); 10133 continue; 10134 }, 10135 else => |e| { 10136 syscall.finish(); 10137 switch (e) { 10138 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 10139 .INVAL => return error.Unseekable, 10140 .OVERFLOW => return error.Unseekable, 10141 .SPIPE => return error.Unseekable, 10142 .NXIO => return error.Unseekable, 10143 else => |err| return posix.unexpectedErrno(err), 10144 } 10145 }, 10146 } 10147 } 10148 } 10149 10150 fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) process.OpenExecutableError!File { 10151 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10152 switch (native_os) { 10153 .wasi => return error.OperationUnsupported, 10154 .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), 10155 .windows => { 10156 // If ImagePathName is a symlink, then it will contain the path of the symlink, 10157 // not the path that the symlink points to. However, because we are opening 10158 // the file, we can let the openFileW call follow the symlink for us. 10159 const image_path_name = windows.peb().ProcessParameters.ImagePathName.sliceZ(); 10160 const prefixed_path_w = try wToPrefixedFileW(null, image_path_name); 10161 return dirOpenFileWtf16(null, prefixed_path_w.span(), flags); 10162 }, 10163 .driverkit, 10164 .ios, 10165 .maccatalyst, 10166 .macos, 10167 .tvos, 10168 .visionos, 10169 .watchos, 10170 => { 10171 // _NSGetExecutablePath() returns a path that might be a symlink to 10172 // the executable. Here it does not matter since we open it. 10173 var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined; 10174 var n: u32 = symlink_path_buf.len; 10175 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n); 10176 if (rc != 0) return error.NameTooLong; 10177 const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); 10178 return dirOpenFilePosix(t, .cwd(), symlink_path, flags); 10179 }, 10180 else => { 10181 var buffer: [Dir.max_path_bytes]u8 = undefined; 10182 const n = try processExecutablePath(t, &buffer); 10183 buffer[n] = 0; 10184 const executable_path = buffer[0..n :0]; 10185 return dirOpenFilePosix(t, .cwd(), executable_path, flags); 10186 }, 10187 } 10188 } 10189 10190 fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.ExecutablePathError!usize { 10191 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10192 10193 switch (native_os) { 10194 .driverkit, 10195 .ios, 10196 .maccatalyst, 10197 .macos, 10198 .tvos, 10199 .visionos, 10200 .watchos, 10201 => { 10202 // _NSGetExecutablePath() returns a path that might be a symlink to 10203 // the executable. 10204 var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined; 10205 var n: u32 = symlink_path_buf.len; 10206 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n); 10207 if (rc != 0) return error.NameTooLong; 10208 const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); 10209 return Io.Dir.realPathFileAbsolute(io(t), symlink_path, out_buffer) catch |err| switch (err) { 10210 error.NetworkNotFound => unreachable, // Windows-only 10211 error.FileBusy => unreachable, // Windows-only 10212 else => |e| return e, 10213 }; 10214 }, 10215 .linux, .serenity => return Io.Dir.readLinkAbsolute(io(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { 10216 error.UnsupportedReparsePointType => unreachable, // Windows-only 10217 error.NetworkNotFound => unreachable, // Windows-only 10218 error.FileBusy => unreachable, // Windows-only 10219 else => |e| return e, 10220 }, 10221 .illumos => return Io.Dir.readLinkAbsolute(io(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) { 10222 error.UnsupportedReparsePointType => unreachable, // Windows-only 10223 error.NetworkNotFound => unreachable, // Windows-only 10224 error.FileBusy => unreachable, // Windows-only 10225 else => |e| return e, 10226 }, 10227 .freebsd, .dragonfly => { 10228 var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; 10229 var out_len: usize = out_buffer.len; 10230 const syscall: Syscall = try .start(); 10231 while (true) switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { 10232 .SUCCESS => { 10233 syscall.finish(); 10234 return out_len - 1; // discard terminating NUL 10235 }, 10236 .INTR => { 10237 try syscall.checkCancel(); 10238 continue; 10239 }, 10240 .PERM => return syscall.fail(error.PermissionDenied), 10241 .NOMEM => return syscall.fail(error.SystemResources), 10242 .FAULT => |err| return syscall.errnoBug(err), 10243 .NOENT => |err| return syscall.errnoBug(err), 10244 else => |err| return syscall.unexpectedErrno(err), 10245 }; 10246 }, 10247 .netbsd => { 10248 var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; 10249 var out_len: usize = out_buffer.len; 10250 const syscall: Syscall = try .start(); 10251 while (true) { 10252 switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { 10253 .SUCCESS => { 10254 syscall.finish(); 10255 return out_len - 1; // discard terminating NUL 10256 }, 10257 .INTR => { 10258 try syscall.checkCancel(); 10259 continue; 10260 }, 10261 .PERM => return syscall.fail(error.PermissionDenied), 10262 .NOMEM => return syscall.fail(error.SystemResources), 10263 .FAULT => |err| return syscall.errnoBug(err), 10264 .NOENT => |err| return syscall.errnoBug(err), 10265 else => |err| return syscall.unexpectedErrno(err), 10266 } 10267 } 10268 }, 10269 .openbsd, .haiku => { 10270 // The best we can do on these operating systems is check based on 10271 // the first process argument. 10272 const argv0 = std.mem.span(t.argv0.value orelse return error.OperationUnsupported); 10273 if (std.mem.findScalar(u8, argv0, '/') != null) { 10274 // argv[0] is a path (relative or absolute): use realpath(3) directly 10275 var resolved_buf: [std.c.PATH_MAX]u8 = undefined; 10276 const syscall: Syscall = try .start(); 10277 while (true) { 10278 if (std.c.realpath(argv0, &resolved_buf)) |p| { 10279 assert(p == &resolved_buf); 10280 break syscall.finish(); 10281 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { 10282 .INTR => { 10283 try syscall.checkCancel(); 10284 continue; 10285 }, 10286 else => |e| { 10287 syscall.finish(); 10288 switch (e) { 10289 .ACCES => return error.AccessDenied, 10290 .INVAL => |err| return errnoBug(err), // the pathname argument is a null pointer 10291 .IO => return error.InputOutput, 10292 .LOOP => return error.SymLinkLoop, 10293 .NAMETOOLONG => return error.NameTooLong, 10294 .NOENT => return error.FileNotFound, 10295 .NOTDIR => return error.NotDir, 10296 .NOMEM => |err| return errnoBug(err), // sufficient storage space is unavailable for allocation 10297 else => |err| return posix.unexpectedErrno(err), 10298 } 10299 }, 10300 } 10301 } 10302 const resolved = std.mem.sliceTo(&resolved_buf, 0); 10303 if (resolved.len > out_buffer.len) 10304 return error.NameTooLong; 10305 @memcpy(out_buffer[0..resolved.len], resolved); 10306 return resolved.len; 10307 } else if (argv0.len != 0) { 10308 // argv[0] is not empty (and not a path): search PATH 10309 t.scanEnviron(); 10310 const PATH = t.environ.string.PATH orelse return error.FileNotFound; 10311 var it = std.mem.tokenizeScalar(u8, PATH, ':'); 10312 it: while (it.next()) |dir| { 10313 var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined; 10314 const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ 10315 dir, argv0, 10316 }, 0) catch continue; 10317 10318 var resolved_buf: [std.c.PATH_MAX]u8 = undefined; 10319 const syscall: Syscall = try .start(); 10320 while (true) { 10321 if (std.c.realpath(resolved_path, &resolved_buf)) |p| { 10322 assert(p == &resolved_buf); 10323 break syscall.finish(); 10324 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) { 10325 .INTR => { 10326 try syscall.checkCancel(); 10327 continue; 10328 }, 10329 .NAMETOOLONG => { 10330 syscall.finish(); 10331 return error.NameTooLong; 10332 }, 10333 .NOMEM => { 10334 syscall.finish(); 10335 return error.SystemResources; 10336 }, 10337 .IO => { 10338 syscall.finish(); 10339 return error.InputOutput; 10340 }, 10341 .ACCES, .LOOP, .NOENT, .NOTDIR => { 10342 syscall.finish(); 10343 continue :it; 10344 }, 10345 else => |err| { 10346 syscall.finish(); 10347 return posix.unexpectedErrno(err); 10348 }, 10349 } 10350 } 10351 const resolved = std.mem.sliceTo(&resolved_buf, 0); 10352 if (resolved.len > out_buffer.len) 10353 return error.NameTooLong; 10354 @memcpy(out_buffer[0..resolved.len], resolved); 10355 return resolved.len; 10356 } 10357 } 10358 return error.FileNotFound; 10359 }, 10360 .windows => { 10361 // If ImagePathName is a symlink, then it will contain the path of the 10362 // symlink, not the path that the symlink points to. We want the path 10363 // that the symlink points to, though, so we need to get the realpath. 10364 var path_name_w_buf = try wToPrefixedFileW( 10365 null, 10366 windows.peb().ProcessParameters.ImagePathName.sliceZ(), 10367 ); 10368 10369 const h_file = handle: { 10370 if (OpenFile(path_name_w_buf.span(), .{ 10371 .dir = null, 10372 .access_mask = .{ 10373 .GENERIC = .{ .READ = true }, 10374 .STANDARD = .{ .SYNCHRONIZE = true }, 10375 }, 10376 .creation = .OPEN, 10377 .filter = .any, 10378 })) |handle| { 10379 break :handle handle; 10380 } else |err| switch (err) { 10381 error.WouldBlock => unreachable, 10382 error.FileBusy => unreachable, 10383 else => |e| return e, 10384 } 10385 }; 10386 defer windows.CloseHandle(h_file); 10387 10388 const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); 10389 10390 const len = std.unicode.calcWtf8Len(wide_slice); 10391 if (len > out_buffer.len) 10392 return error.NameTooLong; 10393 10394 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); 10395 return end_index; 10396 }, 10397 else => return error.OperationUnsupported, 10398 } 10399 } 10400 10401 fn fileWritePositional( 10402 userdata: ?*anyopaque, 10403 file: File, 10404 header: []const u8, 10405 data: []const []const u8, 10406 splat: usize, 10407 offset: u64, 10408 ) File.WritePositionalError!usize { 10409 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10410 _ = t; 10411 10412 if (is_windows) { 10413 if (header.len != 0) { 10414 return writeFilePositionalWindows(file, header, offset); 10415 } 10416 for (data[0 .. data.len - 1]) |buf| { 10417 if (buf.len == 0) continue; 10418 return writeFilePositionalWindows(file, buf, offset); 10419 } 10420 const pattern = data[data.len - 1]; 10421 if (pattern.len == 0 or splat == 0) return 0; 10422 return writeFilePositionalWindows(file, pattern, offset); 10423 } 10424 10425 var iovecs: [max_iovecs_len]posix.iovec_const = undefined; 10426 var iovlen: iovlen_t = 0; 10427 addBuf(&iovecs, &iovlen, header); 10428 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); 10429 const pattern = data[data.len - 1]; 10430 10431 var splat_backup_buffer: [splat_buffer_size]u8 = undefined; 10432 if (iovecs.len - iovlen != 0) switch (splat) { 10433 0 => {}, 10434 1 => addBuf(&iovecs, &iovlen, pattern), 10435 else => switch (pattern.len) { 10436 0 => {}, 10437 1 => { 10438 const splat_buffer = &splat_backup_buffer; 10439 const memset_len = @min(splat_buffer.len, splat); 10440 const buf = splat_buffer[0..memset_len]; 10441 @memset(buf, pattern[0]); 10442 addBuf(&iovecs, &iovlen, buf); 10443 var remaining_splat = splat - buf.len; 10444 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { 10445 assert(buf.len == splat_buffer.len); 10446 addBuf(&iovecs, &iovlen, splat_buffer); 10447 remaining_splat -= splat_buffer.len; 10448 } 10449 addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); 10450 }, 10451 else => for (0..@min(splat, iovecs.len - iovlen)) |_| { 10452 addBuf(&iovecs, &iovlen, pattern); 10453 }, 10454 }, 10455 }; 10456 10457 if (iovlen == 0) return 0; 10458 10459 if (native_os == .wasi and !builtin.link_libc) { 10460 var n_written: usize = undefined; 10461 const syscall: Syscall = try .start(); 10462 while (true) { 10463 switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) { 10464 .SUCCESS => { 10465 syscall.finish(); 10466 return n_written; 10467 }, 10468 .INTR => { 10469 try syscall.checkCancel(); 10470 continue; 10471 }, 10472 else => |e| { 10473 syscall.finish(); 10474 switch (e) { 10475 .INVAL => |err| return errnoBug(err), 10476 .FAULT => |err| return errnoBug(err), 10477 .AGAIN => |err| return errnoBug(err), 10478 .BADF => return error.NotOpenForWriting, 10479 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. 10480 .DQUOT => return error.DiskQuota, 10481 .FBIG => return error.FileTooBig, 10482 .IO => return error.InputOutput, 10483 .NOSPC => return error.NoSpaceLeft, 10484 .PERM => return error.PermissionDenied, 10485 .PIPE => return error.BrokenPipe, 10486 .NOTCAPABLE => return error.AccessDenied, 10487 .NXIO => return error.Unseekable, 10488 .SPIPE => return error.Unseekable, 10489 .OVERFLOW => return error.Unseekable, 10490 else => |err| return posix.unexpectedErrno(err), 10491 } 10492 }, 10493 } 10494 } 10495 } 10496 10497 const syscall: Syscall = try .start(); 10498 while (true) { 10499 const rc = pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset)); 10500 switch (posix.errno(rc)) { 10501 .SUCCESS => { 10502 syscall.finish(); 10503 return @intCast(rc); 10504 }, 10505 .INTR => { 10506 try syscall.checkCancel(); 10507 continue; 10508 }, 10509 .INVAL => |err| return syscall.errnoBug(err), 10510 .FAULT => |err| return syscall.errnoBug(err), 10511 .DESTADDRREQ => |err| return syscall.errnoBug(err), // `connect` was never called. 10512 .CONNRESET => |err| return syscall.errnoBug(err), // Not a socket handle. 10513 .BADF => return syscall.fail(error.NotOpenForWriting), 10514 .AGAIN => return syscall.fail(error.WouldBlock), 10515 .DQUOT => return syscall.fail(error.DiskQuota), 10516 .FBIG => return syscall.fail(error.FileTooBig), 10517 .IO => return syscall.fail(error.InputOutput), 10518 .NOSPC => return syscall.fail(error.NoSpaceLeft), 10519 .PERM => return syscall.fail(error.PermissionDenied), 10520 .PIPE => return syscall.fail(error.BrokenPipe), 10521 .BUSY => return syscall.fail(error.DeviceBusy), 10522 .TXTBSY => return syscall.fail(error.FileBusy), 10523 .NXIO => return syscall.fail(error.Unseekable), 10524 .SPIPE => return syscall.fail(error.Unseekable), 10525 .OVERFLOW => return syscall.fail(error.Unseekable), 10526 else => |err| return syscall.unexpectedErrno(err), 10527 } 10528 } 10529 } 10530 10531 fn writeFilePositionalWindows(file: File, buffer: []const u8, offset: u64) File.WritePositionalError!usize { 10532 assert(buffer.len != 0); 10533 var iosb: windows.IO_STATUS_BLOCK = undefined; 10534 const short_buffer_len = std.math.lossyCast(u32, buffer.len); 10535 const signed_offset: windows.LARGE_INTEGER = @intCast(offset); 10536 if (file.flags.nonblocking) { 10537 var done: bool = false; 10538 switch (windows.ntdll.NtWriteFile( 10539 file.handle, 10540 null, // event 10541 flagApc, 10542 &done, // APC context 10543 &iosb, 10544 buffer.ptr, 10545 short_buffer_len, 10546 &signed_offset, 10547 null, // key 10548 )) { 10549 // We must wait for the APC routine. 10550 .PENDING, .SUCCESS => while (!done) { 10551 // Once we get here we must not return from the function until the 10552 // operation completes, thereby releasing reference to the iosb. 10553 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { 10554 error.Canceled => |e| { 10555 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; 10556 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); 10557 while (!done) waitForApcOrAlert(); 10558 return e; 10559 }, 10560 }; 10561 waitForApcOrAlert(); 10562 alertable_syscall.finish(); 10563 }, 10564 else => |status| iosb.u.Status = status, 10565 } 10566 } else { 10567 const syscall: Syscall = try .start(); 10568 while (true) switch (windows.ntdll.NtWriteFile( 10569 file.handle, 10570 null, // event 10571 null, // APC routine 10572 null, // APC context 10573 &iosb, 10574 buffer.ptr, 10575 short_buffer_len, 10576 &signed_offset, 10577 null, // key 10578 )) { 10579 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag 10580 .CANCELLED => try syscall.checkCancel(), 10581 else => |status| { 10582 syscall.finish(); 10583 iosb.u.Status = status; 10584 return ntWriteFileResult(&iosb); 10585 }, 10586 }; 10587 } 10588 return ntWriteFileResult(&iosb); 10589 } 10590 10591 fn fileWriteStreaming( 10592 userdata: ?*anyopaque, 10593 file: File, 10594 header: []const u8, 10595 data: []const []const u8, 10596 splat: usize, 10597 ) File.Writer.Error!usize { 10598 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10599 _ = t; 10600 10601 if (is_windows) { 10602 const buffer = windowsWriteBuffer(header, data, splat); 10603 if (buffer.len == 0) return 0; 10604 return fileWriteStreamingWindows(file, buffer); 10605 } 10606 10607 var iovecs: [max_iovecs_len]posix.iovec_const = undefined; 10608 var iovlen: iovlen_t = 0; 10609 addBuf(&iovecs, &iovlen, header); 10610 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); 10611 const pattern = data[data.len - 1]; 10612 10613 var splat_backup_buffer: [splat_buffer_size]u8 = undefined; 10614 if (iovecs.len - iovlen != 0) switch (splat) { 10615 0 => {}, 10616 1 => addBuf(&iovecs, &iovlen, pattern), 10617 else => switch (pattern.len) { 10618 0 => {}, 10619 1 => { 10620 const splat_buffer = &splat_backup_buffer; 10621 const memset_len = @min(splat_buffer.len, splat); 10622 const buf = splat_buffer[0..memset_len]; 10623 @memset(buf, pattern[0]); 10624 addBuf(&iovecs, &iovlen, buf); 10625 var remaining_splat = splat - buf.len; 10626 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { 10627 assert(buf.len == splat_buffer.len); 10628 addBuf(&iovecs, &iovlen, splat_buffer); 10629 remaining_splat -= splat_buffer.len; 10630 } 10631 addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); 10632 }, 10633 else => for (0..@min(splat, iovecs.len - iovlen)) |_| { 10634 addBuf(&iovecs, &iovlen, pattern); 10635 }, 10636 }, 10637 }; 10638 10639 if (iovlen == 0) return 0; 10640 10641 if (native_os == .wasi and !builtin.link_libc) { 10642 var n_written: usize = undefined; 10643 const syscall: Syscall = try .start(); 10644 while (true) { 10645 switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) { 10646 .SUCCESS => { 10647 syscall.finish(); 10648 return n_written; 10649 }, 10650 .INTR => { 10651 try syscall.checkCancel(); 10652 continue; 10653 }, 10654 else => |e| { 10655 syscall.finish(); 10656 switch (e) { 10657 .INVAL => |err| return errnoBug(err), 10658 .FAULT => |err| return errnoBug(err), 10659 .AGAIN => |err| return errnoBug(err), 10660 .BADF => return error.NotOpenForWriting, // can be a race condition. 10661 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. 10662 .DQUOT => return error.DiskQuota, 10663 .FBIG => return error.FileTooBig, 10664 .IO => return error.InputOutput, 10665 .NOSPC => return error.NoSpaceLeft, 10666 .PERM => return error.PermissionDenied, 10667 .PIPE => return error.BrokenPipe, 10668 .NOTCAPABLE => return error.AccessDenied, 10669 else => |err| return posix.unexpectedErrno(err), 10670 } 10671 }, 10672 } 10673 } 10674 } 10675 10676 const syscall: Syscall = try .start(); 10677 while (true) { 10678 const rc = posix.system.writev(file.handle, &iovecs, @intCast(iovlen)); 10679 switch (posix.errno(rc)) { 10680 .SUCCESS => { 10681 syscall.finish(); 10682 return @intCast(rc); 10683 }, 10684 .INTR => { 10685 try syscall.checkCancel(); 10686 continue; 10687 }, 10688 else => |e| { 10689 syscall.finish(); 10690 switch (e) { 10691 .INVAL => |err| return errnoBug(err), 10692 .FAULT => |err| return errnoBug(err), 10693 .AGAIN => return error.WouldBlock, 10694 .BADF => return error.NotOpenForWriting, // Can be a race condition. 10695 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. 10696 .DQUOT => return error.DiskQuota, 10697 .FBIG => return error.FileTooBig, 10698 .IO => return error.InputOutput, 10699 .NOSPC => return error.NoSpaceLeft, 10700 .PERM => return error.PermissionDenied, 10701 .PIPE => return error.BrokenPipe, 10702 .CONNRESET => |err| return errnoBug(err), // Not a socket handle. 10703 .BUSY => return error.DeviceBusy, 10704 else => |err| return posix.unexpectedErrno(err), 10705 } 10706 }, 10707 } 10708 } 10709 } 10710 10711 fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!usize { 10712 assert(buffer.len != 0); 10713 var iosb: windows.IO_STATUS_BLOCK = undefined; 10714 if (file.flags.nonblocking) { 10715 var done: bool = false; 10716 switch (windows.ntdll.NtWriteFile( 10717 file.handle, 10718 null, // event 10719 flagApc, 10720 &done, // APC context 10721 &iosb, 10722 buffer.ptr, 10723 @intCast(buffer.len), 10724 null, // byte offset 10725 null, // key 10726 )) { 10727 // We must wait for the APC routine. 10728 .PENDING, .SUCCESS => while (!done) { 10729 // Once we get here we must not return from the function until the 10730 // operation completes, thereby releasing reference to io_status_block. 10731 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { 10732 error.Canceled => |e| { 10733 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; 10734 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); 10735 while (!done) waitForApcOrAlert(); 10736 return e; 10737 }, 10738 }; 10739 waitForApcOrAlert(); 10740 alertable_syscall.finish(); 10741 }, 10742 else => |status| iosb.u.Status = status, 10743 } 10744 } else { 10745 const syscall: Syscall = try .start(); 10746 while (true) switch (windows.ntdll.NtWriteFile( 10747 file.handle, 10748 null, // event 10749 null, // APC routine 10750 null, // APC context 10751 &iosb, 10752 buffer.ptr, 10753 @intCast(buffer.len), 10754 null, // byte offset 10755 null, // key 10756 )) { 10757 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag 10758 .CANCELLED => try syscall.checkCancel(), 10759 else => |status| { 10760 syscall.finish(); 10761 iosb.u.Status = status; 10762 break; 10763 }, 10764 }; 10765 } 10766 return ntWriteFileResult(&iosb); 10767 } 10768 10769 fn fileWriteFileStreaming( 10770 userdata: ?*anyopaque, 10771 file: File, 10772 header: []const u8, 10773 file_reader: *File.Reader, 10774 limit: Io.Limit, 10775 ) File.Writer.WriteFileError!usize { 10776 const t: *Threaded = @ptrCast(@alignCast(userdata)); 10777 const reader_buffered = file_reader.interface.buffered(); 10778 if (reader_buffered.len >= @intFromEnum(limit)) { 10779 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 10780 file_reader.interface.toss(n -| header.len); 10781 return n; 10782 } 10783 const file_limit = @intFromEnum(limit) - reader_buffered.len; 10784 const out_fd = file.handle; 10785 const in_fd = file_reader.file.handle; 10786 10787 if (file_reader.size) |size| { 10788 if (size - file_reader.pos == 0) { 10789 if (reader_buffered.len != 0) { 10790 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 10791 file_reader.interface.toss(n -| header.len); 10792 return n; 10793 } else { 10794 return error.EndOfStream; 10795 } 10796 } 10797 } 10798 10799 if (native_os == .freebsd) sf: { 10800 // Try using sendfile on FreeBSD. 10801 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; 10802 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; 10803 var hdtr_data: std.c.sf_hdtr = undefined; 10804 var headers: [2]posix.iovec_const = undefined; 10805 var headers_i: u8 = 0; 10806 if (header.len != 0) { 10807 headers[headers_i] = .{ .base = header.ptr, .len = header.len }; 10808 headers_i += 1; 10809 } 10810 if (reader_buffered.len != 0) { 10811 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; 10812 headers_i += 1; 10813 } 10814 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { 10815 hdtr_data = .{ 10816 .headers = &headers, 10817 .hdr_cnt = headers_i, 10818 .trailers = null, 10819 .trl_cnt = 0, 10820 }; 10821 break :b &hdtr_data; 10822 }; 10823 var sbytes: std.c.off_t = 0; 10824 const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); 10825 const flags = 0; 10826 10827 const syscall: Syscall = try .start(); 10828 while (true) { 10829 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { 10830 .SUCCESS => { 10831 syscall.finish(); 10832 break; 10833 }, 10834 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { 10835 // Give calling code chance to observe before trying 10836 // something else. 10837 syscall.finish(); 10838 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 10839 return 0; 10840 }, 10841 .INTR, .BUSY => { 10842 if (sbytes == 0) { 10843 try syscall.checkCancel(); 10844 continue; 10845 } else { 10846 // Even if we are being canceled, there have been side 10847 // effects, so it is better to report those side 10848 // effects to the caller. 10849 syscall.finish(); 10850 break; 10851 } 10852 }, 10853 .AGAIN => { 10854 syscall.finish(); 10855 if (sbytes == 0) return error.WouldBlock; 10856 break; 10857 }, 10858 else => |e| { 10859 syscall.finish(); 10860 assert(error.Unexpected == switch (e) { 10861 .NOTCONN => return error.BrokenPipe, 10862 .IO => return error.InputOutput, 10863 .PIPE => return error.BrokenPipe, 10864 .NOBUFS => return error.SystemResources, 10865 .BADF => |err| errnoBug(err), 10866 .FAULT => |err| errnoBug(err), 10867 else => |err| posix.unexpectedErrno(err), 10868 }); 10869 // Give calling code chance to observe the error before trying 10870 // something else. 10871 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 10872 return 0; 10873 }, 10874 } 10875 } 10876 if (sbytes == 0) { 10877 file_reader.size = file_reader.pos; 10878 return error.EndOfStream; 10879 } 10880 const ubytes: usize = @intCast(sbytes); 10881 file_reader.interface.toss(ubytes -| header.len); 10882 return ubytes; 10883 } 10884 10885 if (is_darwin) sf: { 10886 // Try using sendfile on macOS. 10887 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; 10888 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; 10889 var hdtr_data: std.c.sf_hdtr = undefined; 10890 var headers: [2]posix.iovec_const = undefined; 10891 var headers_i: u8 = 0; 10892 if (header.len != 0) { 10893 headers[headers_i] = .{ .base = header.ptr, .len = header.len }; 10894 headers_i += 1; 10895 } 10896 if (reader_buffered.len != 0) { 10897 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; 10898 headers_i += 1; 10899 } 10900 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { 10901 hdtr_data = .{ 10902 .headers = &headers, 10903 .hdr_cnt = headers_i, 10904 .trailers = null, 10905 .trl_cnt = 0, 10906 }; 10907 break :b &hdtr_data; 10908 }; 10909 const max_count = std.math.maxInt(i32); // Avoid EINVAL. 10910 var len: std.c.off_t = @min(file_limit, max_count); 10911 const flags = 0; 10912 const syscall: Syscall = try .start(); 10913 while (true) { 10914 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { 10915 .SUCCESS => { 10916 syscall.finish(); 10917 break; 10918 }, 10919 .OPNOTSUPP, .NOTSOCK, .NOSYS => { 10920 // Give calling code chance to observe before trying 10921 // something else. 10922 syscall.finish(); 10923 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 10924 return 0; 10925 }, 10926 .INTR => { 10927 if (len == 0) { 10928 try syscall.checkCancel(); 10929 continue; 10930 } else { 10931 // Even if we are being canceled, there have been side 10932 // effects, so it is better to report those side 10933 // effects to the caller. 10934 syscall.finish(); 10935 break; 10936 } 10937 }, 10938 .AGAIN => { 10939 syscall.finish(); 10940 if (len == 0) return error.WouldBlock; 10941 break; 10942 }, 10943 else => |e| { 10944 syscall.finish(); 10945 assert(error.Unexpected == switch (e) { 10946 .NOTCONN => return error.BrokenPipe, 10947 .IO => return error.InputOutput, 10948 .PIPE => return error.BrokenPipe, 10949 .BADF => |err| errnoBug(err), 10950 .FAULT => |err| errnoBug(err), 10951 .INVAL => |err| errnoBug(err), 10952 else => |err| posix.unexpectedErrno(err), 10953 }); 10954 // Give calling code chance to observe the error before trying 10955 // something else. 10956 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 10957 return 0; 10958 }, 10959 } 10960 } 10961 if (len == 0) { 10962 file_reader.size = file_reader.pos; 10963 return error.EndOfStream; 10964 } 10965 const u_len: usize = @bitCast(len); 10966 file_reader.interface.toss(u_len -| header.len); 10967 return u_len; 10968 } 10969 10970 if (native_os == .linux) sf: { 10971 // Try using sendfile on Linux. 10972 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; 10973 // Linux sendfile does not support headers. 10974 if (header.len != 0 or reader_buffered.len != 0) { 10975 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 10976 file_reader.interface.toss(n -| header.len); 10977 return n; 10978 } 10979 const max_count = 0x7ffff000; // Avoid EINVAL. 10980 var off: std.os.linux.off_t = undefined; 10981 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { 10982 .positional => o: { 10983 const size = file_reader.getSize() catch return 0; 10984 off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; 10985 break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) }; 10986 }, 10987 .streaming => .{ null, limit.minInt(max_count) }, 10988 .streaming_simple, .positional_simple => break :sf, 10989 .failure => return error.ReadFailed, 10990 }; 10991 const syscall: Syscall = try .start(); 10992 const n: usize = while (true) { 10993 const rc = sendfile_sym(out_fd, in_fd, off_ptr, count); 10994 switch (posix.errno(rc)) { 10995 .SUCCESS => { 10996 syscall.finish(); 10997 break @intCast(rc); 10998 }, 10999 .NOSYS, .INVAL => { 11000 // Give calling code chance to observe before trying 11001 // something else. 11002 syscall.finish(); 11003 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 11004 return 0; 11005 }, 11006 .INTR => { 11007 try syscall.checkCancel(); 11008 continue; 11009 }, 11010 else => |e| { 11011 syscall.finish(); 11012 assert(error.Unexpected == switch (e) { 11013 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket 11014 .AGAIN => return error.WouldBlock, 11015 .IO => return error.InputOutput, 11016 .PIPE => return error.BrokenPipe, 11017 .NOMEM => return error.SystemResources, 11018 .NXIO, .SPIPE => { 11019 file_reader.mode = file_reader.mode.toStreaming(); 11020 const pos = file_reader.pos; 11021 if (pos != 0) { 11022 file_reader.pos = 0; 11023 file_reader.seekBy(@intCast(pos)) catch { 11024 file_reader.mode = .failure; 11025 return error.ReadFailed; 11026 }; 11027 } 11028 return 0; 11029 }, 11030 .BADF => |err| errnoBug(err), // Always a race condition. 11031 .FAULT => |err| errnoBug(err), // Segmentation fault. 11032 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`. 11033 else => |err| posix.unexpectedErrno(err), 11034 }); 11035 // Give calling code chance to observe the error before trying 11036 // something else. 11037 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); 11038 return 0; 11039 }, 11040 } 11041 }; 11042 if (n == 0) { 11043 file_reader.size = file_reader.pos; 11044 return error.EndOfStream; 11045 } 11046 file_reader.pos += n; 11047 return n; 11048 } 11049 11050 if (have_copy_file_range) cfr: { 11051 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; 11052 if (header.len != 0 or reader_buffered.len != 0) { 11053 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); 11054 file_reader.interface.toss(n -| header.len); 11055 return n; 11056 } 11057 var len: usize = @intFromEnum(limit); 11058 var off_in: i64 = undefined; 11059 const off_in_ptr: ?*i64 = switch (file_reader.mode) { 11060 .positional_simple, .streaming_simple => return error.Unimplemented, 11061 .positional => p: { 11062 len = @min(len, std.math.maxInt(usize) - file_reader.pos); 11063 off_in = @intCast(file_reader.pos); 11064 break :p &off_in; 11065 }, 11066 .streaming => null, 11067 .failure => return error.ReadFailed, 11068 }; 11069 const n: usize = switch (native_os) { 11070 .linux => n: { 11071 const syscall: Syscall = try .start(); 11072 while (true) { 11073 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, len, 0); 11074 switch (linux_copy_file_range_sys.errno(rc)) { 11075 .SUCCESS => { 11076 syscall.finish(); 11077 break :n @intCast(rc); 11078 }, 11079 .INTR => { 11080 try syscall.checkCancel(); 11081 continue; 11082 }, 11083 .OPNOTSUPP, .INVAL, .NOSYS => { 11084 // Give calling code chance to observe before trying 11085 // something else. 11086 syscall.finish(); 11087 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11088 return 0; 11089 }, 11090 else => |e| { 11091 syscall.finish(); 11092 assert(error.Unexpected == switch (e) { 11093 .FBIG => return error.FileTooBig, 11094 .IO => return error.InputOutput, 11095 .NOMEM => return error.SystemResources, 11096 .NOSPC => return error.NoSpaceLeft, 11097 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. 11098 .PERM => return error.PermissionDenied, 11099 .BUSY => return error.DeviceBusy, 11100 .TXTBSY => return error.FileBusy, 11101 // copy_file_range can still work but not on 11102 // this pair of file descriptors. 11103 .XDEV => return error.Unimplemented, 11104 .ISDIR => |err| errnoBug(err), 11105 .BADF => |err| errnoBug(err), 11106 else => |err| posix.unexpectedErrno(err), 11107 }); 11108 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11109 return 0; 11110 }, 11111 } 11112 } 11113 }, 11114 .freebsd => n: { 11115 const syscall: Syscall = try .start(); 11116 while (true) { 11117 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); 11118 switch (std.c.errno(rc)) { 11119 .SUCCESS => { 11120 syscall.finish(); 11121 break :n @intCast(rc); 11122 }, 11123 .INTR => { 11124 try syscall.checkCancel(); 11125 continue; 11126 }, 11127 .OPNOTSUPP, .INVAL, .NOSYS => { 11128 // Give calling code chance to observe before trying 11129 // something else. 11130 syscall.finish(); 11131 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11132 return 0; 11133 }, 11134 else => |e| { 11135 syscall.finish(); 11136 assert(error.Unexpected == switch (e) { 11137 .FBIG => return error.FileTooBig, 11138 .IO => return error.InputOutput, 11139 .INTEGRITY => return error.CorruptedData, 11140 .NOSPC => return error.NoSpaceLeft, 11141 .ISDIR => |err| errnoBug(err), 11142 .BADF => |err| errnoBug(err), 11143 else => |err| posix.unexpectedErrno(err), 11144 }); 11145 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11146 return 0; 11147 }, 11148 } 11149 } 11150 }, 11151 else => comptime unreachable, 11152 }; 11153 if (n == 0) { 11154 file_reader.size = file_reader.pos; 11155 return error.EndOfStream; 11156 } 11157 file_reader.pos += n; 11158 return n; 11159 } 11160 11161 return error.Unimplemented; 11162 } 11163 11164 fn netWriteFile( 11165 userdata: ?*anyopaque, 11166 socket_handle: net.Socket.Handle, 11167 header: []const u8, 11168 file_reader: *File.Reader, 11169 limit: Io.Limit, 11170 ) net.Stream.Writer.WriteFileError!usize { 11171 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11172 _ = t; 11173 _ = socket_handle; 11174 _ = header; 11175 _ = file_reader; 11176 _ = limit; 11177 @panic("TODO implement netWriteFile"); 11178 } 11179 11180 fn netWriteFileUnavailable( 11181 userdata: ?*anyopaque, 11182 socket_handle: net.Socket.Handle, 11183 header: []const u8, 11184 file_reader: *File.Reader, 11185 limit: Io.Limit, 11186 ) net.Stream.Writer.WriteFileError!usize { 11187 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11188 _ = t; 11189 _ = socket_handle; 11190 _ = header; 11191 _ = file_reader; 11192 _ = limit; 11193 return error.NetworkDown; 11194 } 11195 11196 fn fileWriteFilePositional( 11197 userdata: ?*anyopaque, 11198 file: File, 11199 header: []const u8, 11200 file_reader: *File.Reader, 11201 limit: Io.Limit, 11202 offset: u64, 11203 ) File.WriteFilePositionalError!usize { 11204 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11205 const reader_buffered = file_reader.interface.buffered(); 11206 if (reader_buffered.len >= @intFromEnum(limit)) { 11207 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 11208 file_reader.interface.toss(n -| header.len); 11209 return n; 11210 } 11211 const out_fd = file.handle; 11212 const in_fd = file_reader.file.handle; 11213 11214 if (file_reader.size) |size| { 11215 if (size - file_reader.pos == 0) { 11216 if (reader_buffered.len != 0) { 11217 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 11218 file_reader.interface.toss(n -| header.len); 11219 return n; 11220 } else { 11221 return error.EndOfStream; 11222 } 11223 } 11224 } 11225 11226 if (have_copy_file_range) cfr: { 11227 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; 11228 if (header.len != 0 or reader_buffered.len != 0) { 11229 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 11230 file_reader.interface.toss(n -| header.len); 11231 return n; 11232 } 11233 var len: usize = @min(@intFromEnum(limit), std.math.maxInt(usize) - offset); 11234 var off_in: i64 = undefined; 11235 const off_in_ptr: ?*i64 = switch (file_reader.mode) { 11236 .positional_simple, .streaming_simple => return error.Unimplemented, 11237 .positional => p: { 11238 len = @min(len, std.math.maxInt(usize) - file_reader.pos); 11239 off_in = @intCast(file_reader.pos); 11240 break :p &off_in; 11241 }, 11242 .streaming => null, 11243 .failure => return error.ReadFailed, 11244 }; 11245 var off_out: i64 = @intCast(offset); 11246 const n: usize = switch (native_os) { 11247 .linux => n: { 11248 const syscall: Syscall = try .start(); 11249 while (true) { 11250 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, len, 0); 11251 switch (linux_copy_file_range_sys.errno(rc)) { 11252 .SUCCESS => { 11253 syscall.finish(); 11254 break :n @intCast(rc); 11255 }, 11256 .INTR => { 11257 try syscall.checkCancel(); 11258 continue; 11259 }, 11260 .OPNOTSUPP, .INVAL, .NOSYS => { 11261 // Give calling code chance to observe before trying 11262 // something else. 11263 syscall.finish(); 11264 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11265 return 0; 11266 }, 11267 else => |e| { 11268 syscall.finish(); 11269 assert(error.Unexpected == switch (e) { 11270 .FBIG => return error.FileTooBig, 11271 .IO => return error.InputOutput, 11272 .NOMEM => return error.SystemResources, 11273 .NOSPC => return error.NoSpaceLeft, 11274 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. 11275 .NXIO => return error.Unseekable, 11276 .SPIPE => return error.Unseekable, 11277 .PERM => return error.PermissionDenied, 11278 .TXTBSY => return error.FileBusy, 11279 // copy_file_range can still work but not on 11280 // this pair of file descriptors. 11281 .XDEV => return error.Unimplemented, 11282 .ISDIR => |err| errnoBug(err), 11283 .BADF => |err| errnoBug(err), 11284 else => |err| posix.unexpectedErrno(err), 11285 }); 11286 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11287 return 0; 11288 }, 11289 } 11290 } 11291 }, 11292 .freebsd => n: { 11293 const syscall: Syscall = try .start(); 11294 while (true) { 11295 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); 11296 switch (std.c.errno(rc)) { 11297 .SUCCESS => { 11298 syscall.finish(); 11299 break :n @intCast(rc); 11300 }, 11301 .INTR => { 11302 try syscall.checkCancel(); 11303 continue; 11304 }, 11305 .OPNOTSUPP, .INVAL, .NOSYS => { 11306 // Give calling code chance to observe before trying 11307 // something else. 11308 syscall.finish(); 11309 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11310 return 0; 11311 }, 11312 else => |e| { 11313 syscall.finish(); 11314 assert(error.Unexpected == switch (e) { 11315 .FBIG => return error.FileTooBig, 11316 .IO => return error.InputOutput, 11317 .INTEGRITY => return error.CorruptedData, 11318 .NOSPC => return error.NoSpaceLeft, 11319 .OVERFLOW => return error.Unseekable, 11320 .NXIO => return error.Unseekable, 11321 .SPIPE => return error.Unseekable, 11322 .ISDIR => |err| errnoBug(err), 11323 .BADF => |err| errnoBug(err), 11324 else => |err| posix.unexpectedErrno(err), 11325 }); 11326 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); 11327 return 0; 11328 }, 11329 } 11330 } 11331 }, 11332 else => comptime unreachable, 11333 }; 11334 if (n == 0) { 11335 file_reader.size = file_reader.pos; 11336 return error.EndOfStream; 11337 } 11338 file_reader.pos += n; 11339 return n; 11340 } 11341 11342 if (is_darwin) fcf: { 11343 if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf; 11344 if (file_reader.pos != 0) break :fcf; 11345 if (offset != 0) break :fcf; 11346 if (limit != .unlimited) break :fcf; 11347 const size = file_reader.getSize() catch break :fcf; 11348 if (header.len != 0 or reader_buffered.len != 0) { 11349 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); 11350 file_reader.interface.toss(n -| header.len); 11351 return n; 11352 } 11353 const syscall: Syscall = try .start(); 11354 while (true) { 11355 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); 11356 switch (posix.errno(rc)) { 11357 .SUCCESS => { 11358 syscall.finish(); 11359 break; 11360 }, 11361 .INTR => { 11362 try syscall.checkCancel(); 11363 continue; 11364 }, 11365 .OPNOTSUPP => { 11366 // Give calling code chance to observe before trying 11367 // something else. 11368 syscall.finish(); 11369 @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic); 11370 return 0; 11371 }, 11372 else => |e| { 11373 syscall.finish(); 11374 assert(error.Unexpected == switch (e) { 11375 .NOMEM => return error.SystemResources, 11376 .INVAL => |err| errnoBug(err), 11377 else => |err| posix.unexpectedErrno(err), 11378 }); 11379 return 0; 11380 }, 11381 } 11382 } 11383 file_reader.pos = size; 11384 return size; 11385 } 11386 11387 return error.Unimplemented; 11388 } 11389 11390 fn nowPosix(clock: Io.Clock) Io.Timestamp { 11391 const clock_id: posix.clockid_t = clockToPosix(clock); 11392 var timespec: posix.timespec = undefined; 11393 switch (posix.errno(posix.system.clock_gettime(clock_id, ×pec))) { 11394 .SUCCESS => return timestampFromPosix(×pec), 11395 else => return .zero, 11396 } 11397 } 11398 11399 fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp { 11400 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11401 _ = t; 11402 return switch (native_os) { 11403 .windows => nowWindows(clock), 11404 .wasi => nowWasi(clock), 11405 else => nowPosix(clock), 11406 }; 11407 } 11408 11409 fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { 11410 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11411 _ = t; 11412 return switch (native_os) { 11413 .windows => switch (clock) { 11414 .awake, .boot, .real => { 11415 // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA 11416 // (a read-only page of info updated and mapped by the kernel to all processes): 11417 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data 11418 // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm 11419 var qpf: windows.LARGE_INTEGER = undefined; 11420 if (windows.ntdll.RtlQueryPerformanceFrequency(&qpf) != 0) { 11421 recoverableOsBugDetected(); 11422 return .zero; 11423 } 11424 // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. 11425 // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 11426 const common_qpf = 10_000_000; 11427 if (qpf == common_qpf) return .fromNanoseconds(std.time.ns_per_s / common_qpf); 11428 11429 // Convert to ns using fixed point. 11430 const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); 11431 const result = scale >> 32; 11432 return .fromNanoseconds(result); 11433 }, 11434 .cpu_process, .cpu_thread => return error.ClockUnavailable, 11435 }, 11436 .wasi => { 11437 if (builtin.link_libc) return clockResolutionPosix(clock); 11438 var ns: std.os.wasi.timestamp_t = undefined; 11439 return switch (std.os.wasi.clock_res_get(clockToWasi(clock), &ns)) { 11440 .SUCCESS => .fromNanoseconds(ns), 11441 .INVAL => return error.ClockUnavailable, 11442 else => |err| return posix.unexpectedErrno(err), 11443 }; 11444 }, 11445 else => return clockResolutionPosix(clock), 11446 }; 11447 } 11448 11449 fn clockResolutionPosix(clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { 11450 const clock_id: posix.clockid_t = clockToPosix(clock); 11451 var timespec: posix.timespec = undefined; 11452 return switch (posix.errno(posix.system.clock_getres(clock_id, ×pec))) { 11453 .SUCCESS => .fromNanoseconds(nanosecondsFromPosix(×pec)), 11454 .INVAL => return error.ClockUnavailable, 11455 else => |err| return posix.unexpectedErrno(err), 11456 }; 11457 } 11458 11459 fn nowWindows(clock: Io.Clock) Io.Timestamp { 11460 switch (clock) { 11461 .real => { 11462 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds 11463 // and uses the NTFS/Windows epoch, which is 1601-01-01. 11464 const epoch_ns = std.time.epoch.windows * std.time.ns_per_s; 11465 return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 + epoch_ns }; 11466 }, 11467 .awake, .boot => { 11468 // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA 11469 // (a read-only page of info updated and mapped by the kernel to all processes): 11470 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data 11471 // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm 11472 const qpf: u64 = qpf: { 11473 var qpf: windows.LARGE_INTEGER = undefined; 11474 assert(windows.ntdll.RtlQueryPerformanceFrequency(&qpf) != windows.FALSE); 11475 break :qpf @bitCast(qpf); 11476 }; 11477 11478 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. 11479 const qpc: u64 = qpc: { 11480 var qpc: windows.LARGE_INTEGER = undefined; 11481 assert(windows.ntdll.RtlQueryPerformanceCounter(&qpc) != windows.FALSE); 11482 break :qpc @bitCast(qpc); 11483 }; 11484 11485 // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. 11486 // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 11487 const common_qpf = 10_000_000; 11488 if (qpf == common_qpf) return .{ .nanoseconds = qpc * (std.time.ns_per_s / common_qpf) }; 11489 11490 // Convert to ns using fixed point. 11491 const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); 11492 const result = (@as(u96, qpc) * scale) >> 32; 11493 return .{ .nanoseconds = @intCast(result) }; 11494 }, 11495 .cpu_process => { 11496 const handle = windows.GetCurrentProcess(); 11497 var times: windows.KERNEL_USER_TIMES = undefined; 11498 11499 // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L442-L485 11500 if (windows.ntdll.NtQueryInformationProcess( 11501 handle, 11502 .Times, 11503 ×, 11504 @sizeOf(windows.KERNEL_USER_TIMES), 11505 null, 11506 ) != .SUCCESS) return .zero; 11507 11508 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); 11509 return .{ .nanoseconds = sum * 100 }; 11510 }, 11511 .cpu_thread => { 11512 const handle = windows.GetCurrentThread(); 11513 var times: windows.KERNEL_USER_TIMES = undefined; 11514 11515 // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L2971-L3019 11516 if (windows.ntdll.NtQueryInformationThread( 11517 handle, 11518 .Times, 11519 ×, 11520 @sizeOf(windows.KERNEL_USER_TIMES), 11521 null, 11522 ) != .SUCCESS) return .zero; 11523 11524 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); 11525 return .{ .nanoseconds = sum * 100 }; 11526 }, 11527 } 11528 } 11529 11530 fn nowWasi(clock: Io.Clock) Io.Timestamp { 11531 var ns: std.os.wasi.timestamp_t = undefined; 11532 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); 11533 if (err != .SUCCESS) return .zero; 11534 return .fromNanoseconds(ns); 11535 } 11536 11537 fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void { 11538 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11539 if (timeout == .none) return; 11540 if (use_parking_sleep) return parking_sleep.sleep(timeout); 11541 if (native_os == .wasi) return sleepWasi(t, timeout); 11542 if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); 11543 return sleepNanosleep(t, timeout); 11544 } 11545 11546 fn sleepPosix(timeout: Io.Timeout) Io.Cancelable!void { 11547 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { 11548 .none => .awake, 11549 .duration => |d| d.clock, 11550 .deadline => |d| d.clock, 11551 }); 11552 const deadline_nanoseconds: i96 = switch (timeout) { 11553 .none => std.math.maxInt(i96), 11554 .duration => |duration| duration.raw.nanoseconds, 11555 .deadline => |deadline| deadline.raw.nanoseconds, 11556 }; 11557 var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds); 11558 const syscall: Syscall = try .start(); 11559 while (true) { 11560 const rc = posix.system.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { 11561 .none, .duration => false, 11562 .deadline => true, 11563 } }, ×pec, ×pec); 11564 // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly 11565 switch (if (builtin.link_libc) @as(posix.E, @enumFromInt(rc)) else posix.errno(rc)) { 11566 .INTR => { 11567 try syscall.checkCancel(); 11568 continue; 11569 }, 11570 // Handles SUCCESS as well as clock not available and unexpected 11571 // errors. The user had a chance to check clock resolution before 11572 // getting here, which would have reported 0, making this a legal 11573 // amount of time to sleep. 11574 else => { 11575 syscall.finish(); 11576 return; 11577 }, 11578 } 11579 } 11580 } 11581 11582 fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { 11583 const t_io = io(t); 11584 const w = std.os.wasi; 11585 11586 const clock: w.subscription_clock_t = if (timeout.toDurationFromNow(t_io)) |d| .{ 11587 .id = clockToWasi(d.clock), 11588 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds), 11589 .precision = 0, 11590 .flags = 0, 11591 } else .{ 11592 .id = .MONOTONIC, 11593 .timeout = std.math.maxInt(u64), 11594 .precision = 0, 11595 .flags = 0, 11596 }; 11597 const in: w.subscription_t = .{ 11598 .userdata = 0, 11599 .u = .{ 11600 .tag = .CLOCK, 11601 .u = .{ .clock = clock }, 11602 }, 11603 }; 11604 var event: w.event_t = undefined; 11605 var nevents: usize = undefined; 11606 const syscall: Syscall = try .start(); 11607 _ = w.poll_oneoff(&in, &event, 1, &nevents); 11608 syscall.finish(); 11609 } 11610 11611 fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { 11612 const t_io = io(t); 11613 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; 11614 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; 11615 11616 var timespec: posix.timespec = t: { 11617 const d = timeout.toDurationFromNow(t_io) orelse break :t .{ 11618 .sec = std.math.maxInt(sec_type), 11619 .nsec = std.math.maxInt(nsec_type), 11620 }; 11621 break :t timestampToPosix(d.raw.toNanoseconds()); 11622 }; 11623 const syscall: Syscall = try .start(); 11624 while (true) { 11625 switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { 11626 .INTR => { 11627 try syscall.checkCancel(); 11628 continue; 11629 }, 11630 // This prong handles success as well as unexpected errors. 11631 else => return syscall.finish(), 11632 } 11633 } 11634 } 11635 11636 fn netListenIpPosix( 11637 userdata: ?*anyopaque, 11638 address: IpAddress, 11639 options: IpAddress.ListenOptions, 11640 ) IpAddress.ListenError!net.Server { 11641 if (!have_networking) return error.NetworkDown; 11642 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11643 _ = t; 11644 const family = posixAddressFamily(&address); 11645 const socket_fd = try openSocketPosix(family, .{ 11646 .mode = options.mode, 11647 .protocol = options.protocol, 11648 }); 11649 errdefer closeFd(socket_fd); 11650 11651 if (options.reuse_address) { 11652 try setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); 11653 if (@hasDecl(posix.SO, "REUSEPORT")) 11654 try setSocketOption(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); 11655 } 11656 11657 var storage: PosixAddress = undefined; 11658 var addr_len = addressToPosix(&address, &storage); 11659 try posixBind(socket_fd, &storage.any, addr_len); 11660 11661 const syscall: Syscall = try .start(); 11662 while (true) { 11663 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { 11664 .SUCCESS => { 11665 syscall.finish(); 11666 break; 11667 }, 11668 .INTR => { 11669 try syscall.checkCancel(); 11670 continue; 11671 }, 11672 .ADDRINUSE => return syscall.fail(error.AddressInUse), 11673 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. 11674 else => |err| return syscall.unexpectedErrno(err), 11675 } 11676 } 11677 11678 try posixGetSockName(socket_fd, &storage.any, &addr_len); 11679 return .{ 11680 .socket = .{ 11681 .handle = socket_fd, 11682 .address = addressFromPosix(&storage), 11683 }, 11684 }; 11685 } 11686 11687 fn netListenIpWindows( 11688 userdata: ?*anyopaque, 11689 address: IpAddress, 11690 options: IpAddress.ListenOptions, 11691 ) IpAddress.ListenError!net.Server { 11692 if (!have_networking) return error.NetworkDown; 11693 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11694 const family = posixAddressFamily(&address); 11695 const socket_handle = try openSocketWsa(t, family, .{ 11696 .mode = options.mode, 11697 .protocol = options.protocol, 11698 }); 11699 errdefer closeSocketWindows(socket_handle); 11700 11701 if (options.reuse_address) 11702 try setSocketOptionWsa(t, socket_handle, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); 11703 11704 var storage: WsaAddress = undefined; 11705 var addr_len = addressToWsa(&address, &storage); 11706 11707 var syscall: AlertableSyscall = try .start(); 11708 while (true) { 11709 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); 11710 if (rc != ws2_32.SOCKET_ERROR) { 11711 syscall.finish(); 11712 break; 11713 } 11714 switch (ws2_32.WSAGetLastError()) { 11715 .NOTINITIALISED => { 11716 syscall.finish(); 11717 try initializeWsa(t); 11718 syscall = try .start(); 11719 continue; 11720 }, 11721 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 11722 try syscall.checkCancel(); 11723 continue; 11724 }, 11725 .EADDRINUSE => return syscall.fail(error.AddressInUse), 11726 .EADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), 11727 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 11728 .EFAULT => |err| return syscall.wsaErrorBug(err), 11729 .EINVAL => |err| return syscall.wsaErrorBug(err), 11730 .ENOBUFS => return syscall.fail(error.SystemResources), 11731 .ENETDOWN => return syscall.fail(error.NetworkDown), 11732 else => |err| return syscall.unexpectedWsaError(err), 11733 } 11734 } 11735 11736 syscall = try .start(); 11737 while (true) { 11738 const rc = ws2_32.listen(socket_handle, options.kernel_backlog); 11739 if (rc != ws2_32.SOCKET_ERROR) { 11740 syscall.finish(); 11741 break; 11742 } 11743 switch (ws2_32.WSAGetLastError()) { 11744 .NOTINITIALISED => { 11745 syscall.finish(); 11746 try initializeWsa(t); 11747 syscall = try .start(); 11748 continue; 11749 }, 11750 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 11751 try syscall.checkCancel(); 11752 continue; 11753 }, 11754 .ENETDOWN => return syscall.fail(error.NetworkDown), 11755 .EADDRINUSE => return syscall.fail(error.AddressInUse), 11756 .EMFILE, .ENOBUFS => return syscall.fail(error.SystemResources), 11757 .EISCONN => |err| return syscall.wsaErrorBug(err), 11758 .EINVAL => |err| return syscall.wsaErrorBug(err), 11759 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 11760 .EOPNOTSUPP => |err| return syscall.wsaErrorBug(err), 11761 .EINPROGRESS => |err| return syscall.wsaErrorBug(err), 11762 else => |err| return syscall.unexpectedWsaError(err), 11763 } 11764 } 11765 11766 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); 11767 11768 return .{ 11769 .socket = .{ 11770 .handle = socket_handle, 11771 .address = addressFromWsa(&storage), 11772 }, 11773 }; 11774 } 11775 11776 fn netListenIpUnavailable( 11777 userdata: ?*anyopaque, 11778 address: IpAddress, 11779 options: IpAddress.ListenOptions, 11780 ) IpAddress.ListenError!net.Server { 11781 _ = userdata; 11782 _ = address; 11783 _ = options; 11784 return error.NetworkDown; 11785 } 11786 11787 fn netListenUnixPosix( 11788 userdata: ?*anyopaque, 11789 address: *const net.UnixAddress, 11790 options: net.UnixAddress.ListenOptions, 11791 ) net.UnixAddress.ListenError!net.Socket.Handle { 11792 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 11793 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11794 _ = t; 11795 const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { 11796 error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported, 11797 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, 11798 error.SocketModeUnsupported => return error.AddressFamilyUnsupported, 11799 error.OptionUnsupported => return error.Unexpected, 11800 else => |e| return e, 11801 }; 11802 errdefer closeFd(socket_fd); 11803 11804 var storage: UnixAddress = undefined; 11805 const addr_len = addressUnixToPosix(address, &storage); 11806 try posixBindUnix(socket_fd, &storage.any, addr_len); 11807 11808 const syscall: Syscall = try .start(); 11809 while (true) { 11810 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { 11811 .SUCCESS => { 11812 syscall.finish(); 11813 break; 11814 }, 11815 .INTR => { 11816 try syscall.checkCancel(); 11817 continue; 11818 }, 11819 .ADDRINUSE => return syscall.fail(error.AddressInUse), 11820 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. 11821 else => |err| return syscall.unexpectedErrno(err), 11822 } 11823 } 11824 11825 return socket_fd; 11826 } 11827 11828 fn netListenUnixWindows( 11829 userdata: ?*anyopaque, 11830 address: *const net.UnixAddress, 11831 options: net.UnixAddress.ListenOptions, 11832 ) net.UnixAddress.ListenError!net.Socket.Handle { 11833 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 11834 if (!have_networking) return error.NetworkDown; 11835 const t: *Threaded = @ptrCast(@alignCast(userdata)); 11836 11837 const socket_handle = openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { 11838 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, 11839 else => |e| return e, 11840 }; 11841 errdefer closeSocketWindows(socket_handle); 11842 11843 var storage: WsaAddress = undefined; 11844 const addr_len = addressUnixToWsa(address, &storage); 11845 11846 var syscall: AlertableSyscall = try .start(); 11847 while (true) { 11848 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); 11849 if (rc != ws2_32.SOCKET_ERROR) { 11850 syscall.finish(); 11851 break; 11852 } 11853 switch (ws2_32.WSAGetLastError()) { 11854 .NOTINITIALISED => { 11855 syscall.finish(); 11856 try initializeWsa(t); 11857 syscall = try .start(); 11858 continue; 11859 }, 11860 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 11861 try syscall.checkCancel(); 11862 continue; 11863 }, 11864 .EADDRINUSE => return syscall.fail(error.AddressInUse), 11865 .EADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), 11866 .ENOBUFS => return syscall.fail(error.SystemResources), 11867 .ENETDOWN => return syscall.fail(error.NetworkDown), 11868 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 11869 .EFAULT => |err| return syscall.wsaErrorBug(err), 11870 .EINVAL => |err| return syscall.wsaErrorBug(err), 11871 else => |err| return syscall.unexpectedWsaError(err), 11872 } 11873 } 11874 11875 syscall = try .start(); 11876 while (true) { 11877 const rc = ws2_32.listen(socket_handle, options.kernel_backlog); 11878 if (rc != ws2_32.SOCKET_ERROR) { 11879 syscall.finish(); 11880 return socket_handle; 11881 } 11882 switch (ws2_32.WSAGetLastError()) { 11883 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 11884 try syscall.checkCancel(); 11885 continue; 11886 }, 11887 .NOTINITIALISED => { 11888 syscall.finish(); 11889 try initializeWsa(t); 11890 syscall = try .start(); 11891 continue; 11892 }, 11893 .ENETDOWN => return syscall.fail(error.NetworkDown), 11894 .EADDRINUSE => return syscall.fail(error.AddressInUse), 11895 .EMFILE, .ENOBUFS => return syscall.fail(error.SystemResources), 11896 .EISCONN => |err| return syscall.wsaErrorBug(err), 11897 .EINVAL => |err| return syscall.wsaErrorBug(err), 11898 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 11899 .EOPNOTSUPP => |err| return syscall.wsaErrorBug(err), 11900 .EINPROGRESS => |err| return syscall.wsaErrorBug(err), 11901 else => |err| return syscall.unexpectedWsaError(err), 11902 } 11903 } 11904 } 11905 11906 fn netListenUnixUnavailable( 11907 userdata: ?*anyopaque, 11908 address: *const net.UnixAddress, 11909 options: net.UnixAddress.ListenOptions, 11910 ) net.UnixAddress.ListenError!net.Socket.Handle { 11911 _ = userdata; 11912 _ = address; 11913 _ = options; 11914 return error.AddressFamilyUnsupported; 11915 } 11916 11917 fn posixBindUnix( 11918 fd: posix.socket_t, 11919 addr: *const posix.sockaddr, 11920 addr_len: posix.socklen_t, 11921 ) !void { 11922 const syscall: Syscall = try .start(); 11923 while (true) { 11924 switch (posix.errno(posix.system.bind(fd, addr, addr_len))) { 11925 .SUCCESS => { 11926 syscall.finish(); 11927 break; 11928 }, 11929 .INTR => { 11930 try syscall.checkCancel(); 11931 continue; 11932 }, 11933 else => |e| { 11934 syscall.finish(); 11935 switch (e) { 11936 .ACCES => return error.AccessDenied, 11937 .ADDRINUSE => return error.AddressInUse, 11938 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 11939 .ADDRNOTAVAIL => return error.AddressUnavailable, 11940 .NOMEM => return error.SystemResources, 11941 11942 .LOOP => return error.SymLinkLoop, 11943 .NOENT => return error.FileNotFound, 11944 .NOTDIR => return error.NotDir, 11945 .ROFS => return error.ReadOnlyFileSystem, 11946 .PERM => return error.PermissionDenied, 11947 11948 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 11949 .INVAL => |err| return errnoBug(err), // invalid parameters 11950 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` 11951 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer 11952 .NAMETOOLONG => |err| return errnoBug(err), 11953 else => |err| return posix.unexpectedErrno(err), 11954 } 11955 }, 11956 } 11957 } 11958 } 11959 11960 fn posixBind( 11961 socket_fd: posix.socket_t, 11962 addr: *const posix.sockaddr, 11963 addr_len: posix.socklen_t, 11964 ) !void { 11965 const syscall: Syscall = try .start(); 11966 while (true) { 11967 switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) { 11968 .SUCCESS => { 11969 syscall.finish(); 11970 break; 11971 }, 11972 .INTR => { 11973 try syscall.checkCancel(); 11974 continue; 11975 }, 11976 else => |e| { 11977 syscall.finish(); 11978 switch (e) { 11979 .ADDRINUSE => return error.AddressInUse, 11980 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 11981 .INVAL => |err| return errnoBug(err), // invalid parameters 11982 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` 11983 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 11984 .ADDRNOTAVAIL => return error.AddressUnavailable, 11985 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer 11986 .NOMEM => return error.SystemResources, 11987 else => |err| return posix.unexpectedErrno(err), 11988 } 11989 }, 11990 } 11991 } 11992 } 11993 11994 fn posixConnect( 11995 socket_fd: posix.socket_t, 11996 addr: *const posix.sockaddr, 11997 addr_len: posix.socklen_t, 11998 ) !void { 11999 const syscall: Syscall = try .start(); 12000 while (true) switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { 12001 .SUCCESS => { 12002 syscall.finish(); 12003 return; 12004 }, 12005 .INTR => { 12006 try syscall.checkCancel(); 12007 continue; 12008 }, 12009 .ADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), 12010 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 12011 .AGAIN, .INPROGRESS => return syscall.fail(error.WouldBlock), 12012 .ALREADY => return syscall.fail(error.ConnectionPending), 12013 .CONNREFUSED => return syscall.fail(error.ConnectionRefused), 12014 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), 12015 .HOSTUNREACH => return syscall.fail(error.HostUnreachable), 12016 .NETUNREACH => return syscall.fail(error.NetworkUnreachable), 12017 .TIMEDOUT => return syscall.fail(error.Timeout), 12018 .ACCES => return syscall.fail(error.AccessDenied), 12019 .NETDOWN => return syscall.fail(error.NetworkDown), 12020 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. 12021 .CONNABORTED => |err| return syscall.errnoBug(err), 12022 .FAULT => |err| return syscall.errnoBug(err), 12023 .ISCONN => |err| return syscall.errnoBug(err), 12024 .NOENT => |err| return syscall.errnoBug(err), 12025 .NOTSOCK => |err| return syscall.errnoBug(err), 12026 .PERM => |err| return syscall.errnoBug(err), 12027 .PROTOTYPE => |err| return syscall.errnoBug(err), 12028 else => |err| return syscall.unexpectedErrno(err), 12029 }; 12030 } 12031 12032 fn posixConnectUnix( 12033 fd: posix.socket_t, 12034 addr: *const posix.sockaddr, 12035 addr_len: posix.socklen_t, 12036 ) !void { 12037 const syscall: Syscall = try .start(); 12038 while (true) { 12039 switch (posix.errno(posix.system.connect(fd, addr, addr_len))) { 12040 .SUCCESS => { 12041 syscall.finish(); 12042 return; 12043 }, 12044 .INTR => { 12045 try syscall.checkCancel(); 12046 continue; 12047 }, 12048 else => |e| { 12049 syscall.finish(); 12050 switch (e) { 12051 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 12052 .AGAIN => return error.WouldBlock, 12053 .INPROGRESS => return error.WouldBlock, 12054 .ACCES => return error.AccessDenied, 12055 12056 .LOOP => return error.SymLinkLoop, 12057 .NOENT => return error.FileNotFound, 12058 .NOTDIR => return error.NotDir, 12059 .ROFS => return error.ReadOnlyFileSystem, 12060 .PERM => return error.PermissionDenied, 12061 12062 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 12063 .CONNABORTED => |err| return errnoBug(err), 12064 .FAULT => |err| return errnoBug(err), 12065 .ISCONN => |err| return errnoBug(err), 12066 .NOTSOCK => |err| return errnoBug(err), 12067 .PROTOTYPE => |err| return errnoBug(err), 12068 else => |err| return posix.unexpectedErrno(err), 12069 } 12070 }, 12071 } 12072 } 12073 } 12074 12075 fn posixGetSockName( 12076 socket_fd: posix.fd_t, 12077 addr: *posix.sockaddr, 12078 addr_len: *posix.socklen_t, 12079 ) !void { 12080 const syscall: Syscall = try .start(); 12081 while (true) { 12082 switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) { 12083 .SUCCESS => { 12084 syscall.finish(); 12085 break; 12086 }, 12087 .INTR => { 12088 try syscall.checkCancel(); 12089 continue; 12090 }, 12091 else => |e| { 12092 syscall.finish(); 12093 switch (e) { 12094 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 12095 .FAULT => |err| return errnoBug(err), 12096 .INVAL => |err| return errnoBug(err), // invalid parameters 12097 .NOTSOCK => |err| return errnoBug(err), // always a race condition 12098 .NOBUFS => return error.SystemResources, 12099 else => |err| return posix.unexpectedErrno(err), 12100 } 12101 }, 12102 } 12103 } 12104 } 12105 12106 fn wsaGetSockName( 12107 t: *Threaded, 12108 handle: ws2_32.SOCKET, 12109 addr: *ws2_32.sockaddr, 12110 addr_len: *i32, 12111 ) !void { 12112 var syscall: AlertableSyscall = try .start(); 12113 while (true) { 12114 const rc = ws2_32.getsockname(handle, addr, addr_len); 12115 if (rc != ws2_32.SOCKET_ERROR) { 12116 syscall.finish(); 12117 return; 12118 } 12119 switch (ws2_32.WSAGetLastError()) { 12120 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12121 try syscall.checkCancel(); 12122 continue; 12123 }, 12124 .NOTINITIALISED => { 12125 syscall.finish(); 12126 try initializeWsa(t); 12127 syscall = try .start(); 12128 continue; 12129 }, 12130 .ENETDOWN => return syscall.fail(error.NetworkDown), 12131 .EFAULT => |err| return syscall.wsaErrorBug(err), 12132 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 12133 .EINVAL => |err| return syscall.wsaErrorBug(err), 12134 else => |err| return syscall.unexpectedWsaError(err), 12135 } 12136 } 12137 } 12138 12139 fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { 12140 const o: []const u8 = @ptrCast(&option); 12141 const syscall: Syscall = try .start(); 12142 while (true) { 12143 switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) { 12144 .SUCCESS => { 12145 syscall.finish(); 12146 return; 12147 }, 12148 .INTR => { 12149 try syscall.checkCancel(); 12150 continue; 12151 }, 12152 else => |e| { 12153 syscall.finish(); 12154 switch (e) { 12155 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 12156 .NOTSOCK => |err| return errnoBug(err), 12157 .INVAL => |err| return errnoBug(err), 12158 .FAULT => |err| return errnoBug(err), 12159 else => |err| return posix.unexpectedErrno(err), 12160 } 12161 }, 12162 } 12163 } 12164 } 12165 12166 fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void { 12167 const o: []const u8 = @ptrCast(&option); 12168 var syscall: AlertableSyscall = try .start(); 12169 while (true) { 12170 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len)); 12171 if (rc != ws2_32.SOCKET_ERROR) { 12172 syscall.finish(); 12173 return; 12174 } 12175 switch (ws2_32.WSAGetLastError()) { 12176 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12177 try syscall.checkCancel(); 12178 continue; 12179 }, 12180 .NOTINITIALISED => { 12181 syscall.finish(); 12182 try initializeWsa(t); 12183 syscall = try .start(); 12184 continue; 12185 }, 12186 .ENETDOWN => return syscall.fail(error.NetworkDown), 12187 .EFAULT => |err| return syscall.wsaErrorBug(err), 12188 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 12189 .EINVAL => |err| return syscall.wsaErrorBug(err), 12190 else => |err| return syscall.unexpectedWsaError(err), 12191 } 12192 } 12193 } 12194 12195 fn netConnectIpPosix( 12196 userdata: ?*anyopaque, 12197 address: *const IpAddress, 12198 options: IpAddress.ConnectOptions, 12199 ) IpAddress.ConnectError!net.Stream { 12200 if (!have_networking) return error.NetworkDown; 12201 if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout"); 12202 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12203 _ = t; 12204 const family = posixAddressFamily(address); 12205 const socket_fd = try openSocketPosix(family, .{ 12206 .mode = options.mode, 12207 .protocol = options.protocol, 12208 }); 12209 errdefer closeFd(socket_fd); 12210 var storage: PosixAddress = undefined; 12211 var addr_len = addressToPosix(address, &storage); 12212 try posixConnect(socket_fd, &storage.any, addr_len); 12213 try posixGetSockName(socket_fd, &storage.any, &addr_len); 12214 return .{ .socket = .{ 12215 .handle = socket_fd, 12216 .address = addressFromPosix(&storage), 12217 } }; 12218 } 12219 12220 fn netConnectIpWindows( 12221 userdata: ?*anyopaque, 12222 address: *const IpAddress, 12223 options: IpAddress.ConnectOptions, 12224 ) IpAddress.ConnectError!net.Stream { 12225 if (!have_networking) return error.NetworkDown; 12226 if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout"); 12227 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12228 const family = posixAddressFamily(address); 12229 const socket_handle = try openSocketWsa(t, family, .{ 12230 .mode = options.mode, 12231 .protocol = options.protocol, 12232 }); 12233 errdefer closeSocketWindows(socket_handle); 12234 12235 var storage: WsaAddress = undefined; 12236 var addr_len = addressToWsa(address, &storage); 12237 12238 var syscall: AlertableSyscall = try .start(); 12239 while (true) { 12240 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); 12241 if (rc != ws2_32.SOCKET_ERROR) { 12242 syscall.finish(); 12243 break; 12244 } 12245 switch (ws2_32.WSAGetLastError()) { 12246 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12247 try syscall.checkCancel(); 12248 continue; 12249 }, 12250 .NOTINITIALISED => { 12251 syscall.finish(); 12252 try initializeWsa(t); 12253 syscall = try .start(); 12254 continue; 12255 }, 12256 .EADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), 12257 .ECONNREFUSED => return syscall.fail(error.ConnectionRefused), 12258 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), 12259 .ETIMEDOUT => return syscall.fail(error.Timeout), 12260 .EHOSTUNREACH => return syscall.fail(error.HostUnreachable), 12261 .ENETUNREACH => return syscall.fail(error.NetworkUnreachable), 12262 .EFAULT => |err| return syscall.wsaErrorBug(err), 12263 .EINVAL => |err| return syscall.wsaErrorBug(err), 12264 .EISCONN => |err| return syscall.wsaErrorBug(err), 12265 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 12266 .EWOULDBLOCK => return syscall.fail(error.WouldBlock), 12267 .EACCES => return syscall.fail(error.AccessDenied), 12268 .ENOBUFS => return syscall.fail(error.SystemResources), 12269 .EAFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 12270 else => |err| return syscall.unexpectedWsaError(err), 12271 } 12272 } 12273 12274 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); 12275 12276 return .{ .socket = .{ 12277 .handle = socket_handle, 12278 .address = addressFromWsa(&storage), 12279 } }; 12280 } 12281 12282 fn netConnectIpUnavailable( 12283 userdata: ?*anyopaque, 12284 address: *const IpAddress, 12285 options: IpAddress.ConnectOptions, 12286 ) IpAddress.ConnectError!net.Stream { 12287 _ = userdata; 12288 _ = address; 12289 _ = options; 12290 return error.NetworkDown; 12291 } 12292 12293 fn netConnectUnixPosix( 12294 userdata: ?*anyopaque, 12295 address: *const net.UnixAddress, 12296 ) net.UnixAddress.ConnectError!net.Socket.Handle { 12297 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 12298 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12299 _ = t; 12300 const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { 12301 error.OptionUnsupported => return error.Unexpected, 12302 else => |e| return e, 12303 }; 12304 errdefer closeFd(socket_fd); 12305 var storage: UnixAddress = undefined; 12306 const addr_len = addressUnixToPosix(address, &storage); 12307 try posixConnectUnix(socket_fd, &storage.any, addr_len); 12308 return socket_fd; 12309 } 12310 12311 fn netConnectUnixWindows( 12312 userdata: ?*anyopaque, 12313 address: *const net.UnixAddress, 12314 ) net.UnixAddress.ConnectError!net.Socket.Handle { 12315 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; 12316 if (!have_networking) return error.NetworkDown; 12317 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12318 12319 const socket_handle = try openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }); 12320 errdefer closeSocketWindows(socket_handle); 12321 var storage: WsaAddress = undefined; 12322 const addr_len = addressUnixToWsa(address, &storage); 12323 12324 var syscall: AlertableSyscall = try .start(); 12325 while (true) { 12326 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); 12327 if (rc != ws2_32.SOCKET_ERROR) { 12328 syscall.finish(); 12329 break; 12330 } 12331 switch (ws2_32.WSAGetLastError()) { 12332 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12333 try syscall.checkCancel(); 12334 continue; 12335 }, 12336 .NOTINITIALISED => { 12337 syscall.finish(); 12338 try initializeWsa(t); 12339 syscall = try .start(); 12340 continue; 12341 }, 12342 .ECONNREFUSED => return syscall.fail(error.FileNotFound), 12343 .EWOULDBLOCK => return syscall.fail(error.WouldBlock), 12344 .EACCES => return syscall.fail(error.AccessDenied), 12345 .ENOBUFS => return syscall.fail(error.SystemResources), 12346 .EAFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 12347 .EFAULT => |err| return syscall.wsaErrorBug(err), 12348 .EINVAL => |err| return syscall.wsaErrorBug(err), 12349 .EISCONN => |err| return syscall.wsaErrorBug(err), 12350 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 12351 else => |err| return syscall.unexpectedWsaError(err), 12352 } 12353 } 12354 12355 return socket_handle; 12356 } 12357 12358 fn netConnectUnixUnavailable( 12359 userdata: ?*anyopaque, 12360 address: *const net.UnixAddress, 12361 ) net.UnixAddress.ConnectError!net.Socket.Handle { 12362 _ = userdata; 12363 _ = address; 12364 return error.AddressFamilyUnsupported; 12365 } 12366 12367 fn netBindIpPosix( 12368 userdata: ?*anyopaque, 12369 address: *const IpAddress, 12370 options: IpAddress.BindOptions, 12371 ) IpAddress.BindError!net.Socket { 12372 if (!have_networking) return error.NetworkDown; 12373 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12374 _ = t; 12375 const family = posixAddressFamily(address); 12376 const socket_fd = try openSocketPosix(family, options); 12377 errdefer closeFd(socket_fd); 12378 var storage: PosixAddress = undefined; 12379 var addr_len = addressToPosix(address, &storage); 12380 try posixBind(socket_fd, &storage.any, addr_len); 12381 try posixGetSockName(socket_fd, &storage.any, &addr_len); 12382 return .{ 12383 .handle = socket_fd, 12384 .address = addressFromPosix(&storage), 12385 }; 12386 } 12387 12388 fn netBindIpWindows( 12389 userdata: ?*anyopaque, 12390 address: *const IpAddress, 12391 options: IpAddress.BindOptions, 12392 ) IpAddress.BindError!net.Socket { 12393 if (!have_networking) return error.NetworkDown; 12394 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12395 const family = posixAddressFamily(address); 12396 const socket_handle = try openSocketWsa(t, family, .{ 12397 .mode = options.mode, 12398 .protocol = options.protocol, 12399 }); 12400 errdefer closeSocketWindows(socket_handle); 12401 12402 var storage: WsaAddress = undefined; 12403 var addr_len = addressToWsa(address, &storage); 12404 12405 var syscall: AlertableSyscall = try .start(); 12406 while (true) { 12407 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); 12408 if (rc != ws2_32.SOCKET_ERROR) { 12409 syscall.finish(); 12410 break; 12411 } 12412 switch (ws2_32.WSAGetLastError()) { 12413 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12414 try syscall.checkCancel(); 12415 continue; 12416 }, 12417 .NOTINITIALISED => { 12418 syscall.finish(); 12419 try initializeWsa(t); 12420 syscall = try .start(); 12421 continue; 12422 }, 12423 .EADDRINUSE => return syscall.fail(error.AddressInUse), 12424 .EADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), 12425 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 12426 .EFAULT => |err| return syscall.wsaErrorBug(err), 12427 .EINVAL => |err| return syscall.wsaErrorBug(err), 12428 .ENOBUFS => return syscall.fail(error.SystemResources), 12429 .ENETDOWN => return syscall.fail(error.NetworkDown), 12430 else => |err| return syscall.unexpectedWsaError(err), 12431 } 12432 } 12433 12434 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); 12435 12436 return .{ 12437 .handle = socket_handle, 12438 .address = addressFromWsa(&storage), 12439 }; 12440 } 12441 12442 fn netBindIpUnavailable( 12443 userdata: ?*anyopaque, 12444 address: *const IpAddress, 12445 options: IpAddress.BindOptions, 12446 ) IpAddress.BindError!net.Socket { 12447 _ = userdata; 12448 _ = address; 12449 _ = options; 12450 return error.NetworkDown; 12451 } 12452 12453 fn openSocketPosix( 12454 family: posix.sa_family_t, 12455 options: IpAddress.BindOptions, 12456 ) error{ 12457 AddressFamilyUnsupported, 12458 ProtocolUnsupportedBySystem, 12459 ProcessFdQuotaExceeded, 12460 SystemFdQuotaExceeded, 12461 SystemResources, 12462 ProtocolUnsupportedByAddressFamily, 12463 SocketModeUnsupported, 12464 OptionUnsupported, 12465 Unexpected, 12466 Canceled, 12467 }!posix.socket_t { 12468 const mode = posixSocketMode(options.mode); 12469 const protocol = posixProtocol(options.protocol); 12470 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; 12471 const syscall: Syscall = try .start(); 12472 const socket_fd = while (true) { 12473 const rc = posix.system.socket(family, flags, protocol); 12474 switch (posix.errno(rc)) { 12475 .SUCCESS => { 12476 syscall.finish(); 12477 const fd: posix.fd_t = @intCast(rc); 12478 errdefer closeFd(fd); 12479 if (socket_flags_unsupported) try setCloexec(fd); 12480 break fd; 12481 }, 12482 .INTR => { 12483 try syscall.checkCancel(); 12484 continue; 12485 }, 12486 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 12487 .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), 12488 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 12489 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), 12490 .NOBUFS => return syscall.fail(error.SystemResources), 12491 .NOMEM => return syscall.fail(error.SystemResources), 12492 .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), 12493 .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), 12494 else => |err| return syscall.unexpectedErrno(err), 12495 } 12496 }; 12497 errdefer closeFd(socket_fd); 12498 12499 if (options.ip6_only) { 12500 if (posix.IPV6 == void) return error.OptionUnsupported; 12501 try setSocketOption(socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0); 12502 } 12503 12504 return socket_fd; 12505 } 12506 12507 fn setCloexec(fd: posix.fd_t) error{ Canceled, Unexpected }!void { 12508 const syscall: Syscall = try .start(); 12509 while (true) switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { 12510 .SUCCESS => return syscall.finish(), 12511 .INTR => { 12512 try syscall.checkCancel(); 12513 continue; 12514 }, 12515 else => |err| return syscall.unexpectedErrno(err), 12516 }; 12517 } 12518 12519 fn netSocketCreatePair( 12520 userdata: ?*anyopaque, 12521 options: net.Socket.CreatePairOptions, 12522 ) net.Socket.CreatePairError![2]net.Socket { 12523 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12524 _ = t; 12525 if (!have_networking) return error.OperationUnsupported; 12526 if (@TypeOf(posix.system.socketpair) == void) return error.OperationUnsupported; 12527 if (native_os == .haiku) @panic("TODO"); 12528 12529 const family: posix.sa_family_t = switch (options.family) { 12530 .ip4 => posix.AF.INET, 12531 .ip6 => posix.AF.INET6, 12532 }; 12533 const mode = posixSocketMode(options.mode); 12534 const protocol = posixProtocol(options.protocol); 12535 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; 12536 12537 var sockets: [2]posix.socket_t = undefined; 12538 const syscall: Syscall = try .start(); 12539 while (true) switch (posix.errno(posix.system.socketpair(family, flags, protocol, &sockets))) { 12540 .SUCCESS => { 12541 syscall.finish(); 12542 errdefer { 12543 closeFd(sockets[0]); 12544 closeFd(sockets[1]); 12545 } 12546 if (socket_flags_unsupported) { 12547 try setCloexec(sockets[0]); 12548 try setCloexec(sockets[1]); 12549 } 12550 var storages: [2]PosixAddress = undefined; 12551 var addr_lens: [2]posix.socklen_t = .{ @sizeOf(PosixAddress), @sizeOf(PosixAddress) }; 12552 try posixGetSockName(sockets[0], &storages[0].any, &addr_lens[0]); 12553 try posixGetSockName(sockets[1], &storages[1].any, &addr_lens[1]); 12554 return .{ 12555 .{ .handle = sockets[0], .address = addressFromPosix(&storages[0]) }, 12556 .{ .handle = sockets[1], .address = addressFromPosix(&storages[1]) }, 12557 }; 12558 }, 12559 .INTR => { 12560 try syscall.checkCancel(); 12561 continue; 12562 }, 12563 .ACCES => return syscall.fail(error.AccessDenied), 12564 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 12565 .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), 12566 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 12567 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), 12568 .NOBUFS => return syscall.fail(error.SystemResources), 12569 .NOMEM => return syscall.fail(error.SystemResources), 12570 .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), 12571 .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), 12572 else => |err| return syscall.unexpectedErrno(err), 12573 }; 12574 } 12575 12576 fn netSocketCreatePairUnavailable( 12577 userdata: ?*anyopaque, 12578 options: net.Socket.CreatePairOptions, 12579 ) net.Socket.CreatePairError![2]net.Socket { 12580 _ = userdata; 12581 _ = options; 12582 return error.OperationUnsupported; 12583 } 12584 12585 fn openSocketWsa( 12586 t: *Threaded, 12587 family: posix.sa_family_t, 12588 options: IpAddress.BindOptions, 12589 ) !ws2_32.SOCKET { 12590 const mode = posixSocketMode(options.mode); 12591 const protocol = posixProtocol(options.protocol); 12592 // WSA_FLAG_OVERLAPPED is chosen here because without this different 12593 // threads cannot use the same open socket handle. 12594 const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; 12595 var syscall: AlertableSyscall = try .start(); 12596 while (true) { 12597 const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); 12598 if (rc != ws2_32.INVALID_SOCKET) { 12599 syscall.finish(); 12600 return rc; 12601 } 12602 switch (ws2_32.WSAGetLastError()) { 12603 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12604 try syscall.checkCancel(); 12605 continue; 12606 }, 12607 .NOTINITIALISED => { 12608 syscall.finish(); 12609 try initializeWsa(t); 12610 syscall = try .start(); 12611 continue; 12612 }, 12613 .EAFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 12614 .EMFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 12615 .ENOBUFS => return syscall.fail(error.SystemResources), 12616 .EPROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), 12617 else => |err| return syscall.unexpectedWsaError(err), 12618 } 12619 } 12620 } 12621 12622 fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream { 12623 if (!have_networking) return error.NetworkDown; 12624 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12625 _ = t; 12626 var storage: PosixAddress = undefined; 12627 var addr_len: posix.socklen_t = @sizeOf(PosixAddress); 12628 const syscall: Syscall = try .start(); 12629 const fd = while (true) { 12630 const rc = if (have_accept4) 12631 posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC) 12632 else 12633 posix.system.accept(listen_fd, &storage.any, &addr_len); 12634 switch (posix.errno(rc)) { 12635 .SUCCESS => { 12636 syscall.finish(); 12637 const fd: posix.fd_t = @intCast(rc); 12638 errdefer closeFd(fd); 12639 if (!have_accept4) try setCloexec(fd); 12640 break fd; 12641 }, 12642 .INTR => { 12643 try syscall.checkCancel(); 12644 continue; 12645 }, 12646 else => |e| { 12647 syscall.finish(); 12648 switch (e) { 12649 .AGAIN => |err| return errnoBug(err), 12650 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 12651 .CONNABORTED => return error.ConnectionAborted, 12652 .FAULT => |err| return errnoBug(err), 12653 .INVAL => return error.SocketNotListening, 12654 .NOTSOCK => |err| return errnoBug(err), 12655 .MFILE => return error.ProcessFdQuotaExceeded, 12656 .NFILE => return error.SystemFdQuotaExceeded, 12657 .NOBUFS => return error.SystemResources, 12658 .NOMEM => return error.SystemResources, 12659 .OPNOTSUPP => |err| return errnoBug(err), 12660 .PROTO => return error.ProtocolFailure, 12661 .PERM => return error.BlockedByFirewall, 12662 else => |err| return posix.unexpectedErrno(err), 12663 } 12664 }, 12665 } 12666 }; 12667 return .{ .socket = .{ 12668 .handle = fd, 12669 .address = addressFromPosix(&storage), 12670 } }; 12671 } 12672 12673 fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream { 12674 if (!have_networking) return error.NetworkDown; 12675 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12676 var storage: WsaAddress = undefined; 12677 var addr_len: i32 = @sizeOf(WsaAddress); 12678 var syscall: AlertableSyscall = try .start(); 12679 while (true) { 12680 const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len); 12681 if (rc != ws2_32.INVALID_SOCKET) { 12682 syscall.finish(); 12683 return .{ .socket = .{ 12684 .handle = rc, 12685 .address = addressFromWsa(&storage), 12686 } }; 12687 } 12688 switch (ws2_32.WSAGetLastError()) { 12689 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12690 try syscall.checkCancel(); 12691 continue; 12692 }, 12693 .NOTINITIALISED => { 12694 syscall.finish(); 12695 try initializeWsa(t); 12696 syscall = try .start(); 12697 continue; 12698 }, 12699 .ECONNRESET => return syscall.fail(error.ConnectionAborted), 12700 .EMFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 12701 .ENETDOWN => return syscall.fail(error.NetworkDown), 12702 .ENOBUFS => return syscall.fail(error.SystemResources), 12703 .EFAULT => |err| return syscall.wsaErrorBug(err), 12704 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 12705 .EINVAL => |err| return syscall.wsaErrorBug(err), 12706 .EOPNOTSUPP => |err| return syscall.wsaErrorBug(err), 12707 else => |err| return syscall.unexpectedWsaError(err), 12708 } 12709 } 12710 } 12711 12712 fn netAcceptUnavailable(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream { 12713 _ = userdata; 12714 _ = listen_handle; 12715 return error.NetworkDown; 12716 } 12717 12718 fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { 12719 if (!have_networking) return error.NetworkDown; 12720 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12721 _ = t; 12722 12723 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; 12724 var i: usize = 0; 12725 for (data) |buf| { 12726 if (iovecs_buffer.len - i == 0) break; 12727 if (buf.len != 0) { 12728 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; 12729 i += 1; 12730 } 12731 } 12732 const dest = iovecs_buffer[0..i]; 12733 assert(dest[0].len > 0); 12734 12735 if (native_os == .wasi and !builtin.link_libc) { 12736 const syscall: Syscall = try .start(); 12737 while (true) { 12738 var n: usize = undefined; 12739 switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) { 12740 .SUCCESS => { 12741 syscall.finish(); 12742 return n; 12743 }, 12744 .INTR => { 12745 try syscall.checkCancel(); 12746 continue; 12747 }, 12748 else => |e| { 12749 syscall.finish(); 12750 switch (e) { 12751 .INVAL => |err| return errnoBug(err), 12752 .FAULT => |err| return errnoBug(err), 12753 .AGAIN => |err| return errnoBug(err), 12754 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 12755 .NOBUFS => return error.SystemResources, 12756 .NOMEM => return error.SystemResources, 12757 .NOTCONN => return error.SocketUnconnected, 12758 .CONNRESET => return error.ConnectionResetByPeer, 12759 .TIMEDOUT => return error.Timeout, 12760 .NOTCAPABLE => return error.AccessDenied, 12761 else => |err| return posix.unexpectedErrno(err), 12762 } 12763 }, 12764 } 12765 } 12766 } 12767 12768 const syscall: Syscall = try .start(); 12769 while (true) { 12770 const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len)); 12771 switch (posix.errno(rc)) { 12772 .SUCCESS => { 12773 syscall.finish(); 12774 return @intCast(rc); 12775 }, 12776 .INTR => { 12777 try syscall.checkCancel(); 12778 continue; 12779 }, 12780 else => |e| { 12781 syscall.finish(); 12782 switch (e) { 12783 .INVAL => |err| return errnoBug(err), 12784 .FAULT => |err| return errnoBug(err), 12785 .AGAIN => |err| return errnoBug(err), 12786 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 12787 .NOBUFS => return error.SystemResources, 12788 .NOMEM => return error.SystemResources, 12789 .NOTCONN => return error.SocketUnconnected, 12790 .CONNRESET => return error.ConnectionResetByPeer, 12791 .TIMEDOUT => return error.Timeout, 12792 .PIPE => return error.SocketUnconnected, 12793 .NETDOWN => return error.NetworkDown, 12794 else => |err| return posix.unexpectedErrno(err), 12795 } 12796 }, 12797 } 12798 } 12799 } 12800 12801 fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { 12802 if (!have_networking) return error.NetworkDown; 12803 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12804 12805 var iovec_buffer: [max_iovecs_len]ws2_32.WSABUF = undefined; 12806 const bufs = b: { 12807 var i: usize = 0; 12808 var n: usize = 0; 12809 for (data) |buf| { 12810 if (iovec_buffer.len - i == 0) break; 12811 if (buf.len == 0) continue; 12812 if (std.math.cast(u32, buf.len)) |len| { 12813 iovec_buffer[i] = .{ .buf = buf.ptr, .len = len }; 12814 i += 1; 12815 n += len; 12816 continue; 12817 } 12818 iovec_buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) }; 12819 i += 1; 12820 n += std.math.maxInt(u32); 12821 break; 12822 } 12823 12824 const bufs = iovec_buffer[0..i]; 12825 assert(bufs[0].len != 0); 12826 12827 break :b bufs; 12828 }; 12829 12830 var syscall: AlertableSyscall = try .start(); 12831 while (true) { 12832 var flags: u32 = 0; 12833 var n: u32 = undefined; 12834 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, null, null); 12835 if (rc != ws2_32.SOCKET_ERROR) { 12836 syscall.finish(); 12837 return n; 12838 } 12839 switch (ws2_32.WSAGetLastError()) { 12840 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12841 try syscall.checkCancel(); 12842 continue; 12843 }, 12844 .NOTINITIALISED => { 12845 syscall.finish(); 12846 try initializeWsa(t); 12847 syscall = try .start(); 12848 continue; 12849 }, 12850 12851 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), 12852 .ENETDOWN => return syscall.fail(error.NetworkDown), 12853 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer), 12854 .ENOTCONN => return syscall.fail(error.SocketUnconnected), 12855 .EFAULT => unreachable, // a pointer is not completely contained in user address space. 12856 .EINVAL => |err| return syscall.wsaErrorBug(err), 12857 .EMSGSIZE => |err| return syscall.wsaErrorBug(err), 12858 else => |err| return syscall.unexpectedWsaError(err), 12859 } 12860 } 12861 } 12862 12863 fn netReadUnavailable(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { 12864 _ = userdata; 12865 _ = fd; 12866 _ = data; 12867 return error.NetworkDown; 12868 } 12869 12870 fn netSendPosix( 12871 userdata: ?*anyopaque, 12872 handle: net.Socket.Handle, 12873 messages: []net.OutgoingMessage, 12874 flags: net.SendFlags, 12875 ) struct { ?net.Socket.SendError, usize } { 12876 if (!have_networking) return .{ error.NetworkDown, 0 }; 12877 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12878 12879 const posix_flags: u32 = 12880 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) | 12881 @as(u32, if (@hasDecl(posix.MSG, "DONTROUTE") and flags.dont_route) posix.MSG.DONTROUTE else 0) | 12882 @as(u32, if (@hasDecl(posix.MSG, "EOR") and flags.eor) posix.MSG.EOR else 0) | 12883 @as(u32, if (@hasDecl(posix.MSG, "OOB") and flags.oob) posix.MSG.OOB else 0) | 12884 @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) | 12885 posix.MSG.NOSIGNAL; 12886 12887 var i: usize = 0; 12888 while (messages.len - i != 0) { 12889 if (have_sendmmsg) { 12890 i += netSendMany(handle, messages[i..], posix_flags) catch |err| return .{ err, i }; 12891 continue; 12892 } 12893 netSendOne(t, handle, &messages[i], posix_flags) catch |err| return .{ err, i }; 12894 i += 1; 12895 } 12896 return .{ null, i }; 12897 } 12898 12899 fn netSendWindows( 12900 userdata: ?*anyopaque, 12901 handle: net.Socket.Handle, 12902 messages: []net.OutgoingMessage, 12903 flags: net.SendFlags, 12904 ) struct { ?net.Socket.SendError, usize } { 12905 if (!have_networking) return .{ error.NetworkDown, 0 }; 12906 const t: *Threaded = @ptrCast(@alignCast(userdata)); 12907 12908 // Ignored flags: confirm, eor, fastopen 12909 const windows_flags: u32 = 12910 @as(u32, if (flags.oob) ws2_32.MSG.OOB else 0) | 12911 @as(u32, if (flags.dont_route) ws2_32.MSG.DONTROUTE else 0); 12912 12913 for (messages, 0..) |*m, i| { 12914 netSendWindowsOne(t, handle, m, windows_flags) catch |err| return .{ err, i }; 12915 } 12916 return .{ null, messages.len }; 12917 } 12918 12919 fn netSendWindowsOne( 12920 t: *Threaded, 12921 handle: net.Socket.Handle, 12922 message: *net.OutgoingMessage, 12923 flags: u32, 12924 ) net.Socket.SendError!void { 12925 var buf: ws2_32.WSABUF = .{ 12926 .buf = @constCast(message.data_ptr), 12927 .len = std.math.cast(u32, message.data_len) orelse return error.MessageOversize, 12928 }; 12929 var n: u32 = undefined; 12930 var address: WsaAddress = undefined; 12931 const address_size = addressToWsa(message.address, &address); 12932 var syscall: AlertableSyscall = try .start(); 12933 while (true) { 12934 const rc = ws2_32.WSASendTo( 12935 handle, 12936 (&buf)[0..1], 12937 1, 12938 &n, 12939 flags, 12940 &address.any, 12941 address_size, 12942 null, 12943 null, 12944 ); 12945 if (rc != ws2_32.SOCKET_ERROR) { 12946 syscall.finish(); 12947 return; 12948 } 12949 switch (ws2_32.WSAGetLastError()) { 12950 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 12951 try syscall.checkCancel(); 12952 continue; 12953 }, 12954 .NOTINITIALISED => { 12955 syscall.finish(); 12956 try initializeWsa(t); 12957 syscall = try .start(); 12958 continue; 12959 }, 12960 12961 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), 12962 .ENETDOWN => return syscall.fail(error.NetworkDown), 12963 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer), 12964 .ENOTCONN => return syscall.fail(error.SocketUnconnected), 12965 .EFAULT => unreachable, // a pointer is not completely contained in user address space. 12966 .EINVAL => |err| return syscall.wsaErrorBug(err), 12967 .EMSGSIZE => |err| return syscall.wsaErrorBug(err), 12968 else => |err| return syscall.unexpectedWsaError(err), 12969 } 12970 } 12971 } 12972 12973 fn netSendUnavailable( 12974 userdata: ?*anyopaque, 12975 handle: net.Socket.Handle, 12976 messages: []net.OutgoingMessage, 12977 flags: net.SendFlags, 12978 ) struct { ?net.Socket.SendError, usize } { 12979 _ = userdata; 12980 _ = handle; 12981 _ = messages; 12982 _ = flags; 12983 return .{ error.NetworkDown, 0 }; 12984 } 12985 12986 fn netSendOne( 12987 t: *Threaded, 12988 handle: net.Socket.Handle, 12989 message: *net.OutgoingMessage, 12990 flags: u32, 12991 ) net.Socket.SendError!void { 12992 var addr: PosixAddress = undefined; 12993 var iovec: posix.iovec_const = .{ .base = @constCast(message.data_ptr), .len = message.data_len }; 12994 const msg: posix.msghdr_const = .{ 12995 .name = &addr.any, 12996 .namelen = addressToPosix(message.address, &addr), 12997 .iov = (&iovec)[0..1], 12998 .iovlen = 1, 12999 // OS returns EINVAL if this pointer is invalid even if controllen is zero. 13000 .control = if (message.control.len == 0) null else @constCast(message.control.ptr), 13001 .controllen = @intCast(message.control.len), 13002 .flags = 0, 13003 }; 13004 var syscall: if (is_windows) AlertableSyscall else Syscall = try .start(); 13005 while (true) { 13006 const rc = posix.system.sendmsg(handle, &msg, flags); 13007 if (is_windows) { 13008 if (rc != ws2_32.SOCKET_ERROR) { 13009 syscall.finish(); 13010 message.data_len = @intCast(rc); 13011 return; 13012 } 13013 switch (ws2_32.WSAGetLastError()) { 13014 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 13015 try syscall.checkCancel(); 13016 continue; 13017 }, 13018 .NOTINITIALISED => { 13019 syscall.finish(); 13020 try initializeWsa(t); 13021 syscall = try .start(); 13022 continue; 13023 }, 13024 .EACCES => return syscall.fail(error.AccessDenied), 13025 .EADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), 13026 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), 13027 .EMSGSIZE => return syscall.fail(error.MessageOversize), 13028 .ENOBUFS => return syscall.fail(error.SystemResources), 13029 .ENOTSOCK => return syscall.fail(error.FileDescriptorNotASocket), 13030 .EAFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 13031 .EHOSTUNREACH => return syscall.fail(error.NetworkUnreachable), 13032 .ENETDOWN => return syscall.fail(error.NetworkDown), 13033 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer), 13034 .ENETUNREACH => return syscall.fail(error.NetworkUnreachable), 13035 .ENOTCONN => return syscall.fail(error.SocketUnconnected), 13036 .EDESTADDRREQ => unreachable, // A destination address is required. 13037 .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. 13038 .EINVAL => unreachable, 13039 .ESHUTDOWN => |err| return syscall.wsaErrorBug(err), 13040 else => |err| return syscall.unexpectedWsaError(err), 13041 } 13042 } 13043 switch (posix.errno(rc)) { 13044 .SUCCESS => { 13045 syscall.finish(); 13046 message.data_len = @intCast(rc); 13047 return; 13048 }, 13049 .INTR => { 13050 try syscall.checkCancel(); 13051 continue; 13052 }, 13053 .ACCES => return syscall.fail(error.AccessDenied), 13054 .ALREADY => return syscall.fail(error.FastOpenAlreadyInProgress), 13055 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), 13056 .MSGSIZE => return syscall.fail(error.MessageOversize), 13057 .NOBUFS => return syscall.fail(error.SystemResources), 13058 .NOMEM => return syscall.fail(error.SystemResources), 13059 .PIPE => return syscall.fail(error.SocketUnconnected), 13060 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 13061 .HOSTUNREACH => return syscall.fail(error.HostUnreachable), 13062 .NETUNREACH => return syscall.fail(error.NetworkUnreachable), 13063 .NOTCONN => return syscall.fail(error.SocketUnconnected), 13064 .NETDOWN => return syscall.fail(error.NetworkDown), 13065 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. 13066 .DESTADDRREQ => |err| return syscall.errnoBug(err), 13067 .FAULT => |err| return syscall.errnoBug(err), 13068 .INVAL => |err| return syscall.errnoBug(err), 13069 .ISCONN => |err| return syscall.errnoBug(err), 13070 .NOTSOCK => |err| return syscall.errnoBug(err), 13071 .OPNOTSUPP => |err| return syscall.errnoBug(err), 13072 else => |err| return syscall.unexpectedErrno(err), 13073 } 13074 } 13075 } 13076 13077 fn netSendMany( 13078 handle: net.Socket.Handle, 13079 messages: []net.OutgoingMessage, 13080 flags: u32, 13081 ) net.Socket.SendError!usize { 13082 var msg_buffer: [64]posix.system.mmsghdr = undefined; 13083 var addr_buffer: [msg_buffer.len]PosixAddress = undefined; 13084 var iovecs_buffer: [msg_buffer.len]posix.iovec = undefined; 13085 const min_len: usize = @min(messages.len, msg_buffer.len); 13086 const clamped_messages = messages[0..min_len]; 13087 const clamped_msgs = (&msg_buffer)[0..min_len]; 13088 const clamped_addrs = (&addr_buffer)[0..min_len]; 13089 const clamped_iovecs = (&iovecs_buffer)[0..min_len]; 13090 13091 for (clamped_messages, clamped_msgs, clamped_addrs, clamped_iovecs) |*message, *msg, *addr, *iovec| { 13092 iovec.* = .{ .base = @constCast(message.data_ptr), .len = message.data_len }; 13093 msg.* = .{ 13094 .hdr = .{ 13095 .name = &addr.any, 13096 .namelen = addressToPosix(message.address, addr), 13097 .iov = iovec[0..1], 13098 .iovlen = 1, 13099 .control = @constCast(message.control.ptr), 13100 .controllen = message.control.len, 13101 .flags = 0, 13102 }, 13103 .len = undefined, // Populated by calling sendmmsg below. 13104 }; 13105 } 13106 13107 const syscall: Syscall = try .start(); 13108 while (true) { 13109 const rc = posix.system.sendmmsg(handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags); 13110 switch (posix.errno(rc)) { 13111 .SUCCESS => { 13112 syscall.finish(); 13113 const n: usize = @intCast(rc); 13114 for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| { 13115 message.data_len = msg.len; 13116 } 13117 return n; 13118 }, 13119 .INTR => { 13120 try syscall.checkCancel(); 13121 continue; 13122 }, 13123 .ACCES => return syscall.fail(error.AccessDenied), 13124 .ALREADY => return syscall.fail(error.FastOpenAlreadyInProgress), 13125 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), 13126 .MSGSIZE => return syscall.fail(error.MessageOversize), 13127 .NOBUFS => return syscall.fail(error.SystemResources), 13128 .NOMEM => return syscall.fail(error.SystemResources), 13129 .PIPE => return syscall.fail(error.SocketUnconnected), 13130 .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), 13131 .HOSTUNREACH => return syscall.fail(error.HostUnreachable), 13132 .NETUNREACH => return syscall.fail(error.NetworkUnreachable), 13133 .NOTCONN => return syscall.fail(error.SocketUnconnected), 13134 .NETDOWN => return syscall.fail(error.NetworkDown), 13135 13136 .AGAIN => |err| return syscall.errnoBug(err), 13137 .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. 13138 .DESTADDRREQ => |err| return syscall.errnoBug(err), // The socket is not connection-mode, and no peer address is set. 13139 .FAULT => |err| return syscall.errnoBug(err), // An invalid user space address was specified for an argument. 13140 .INVAL => |err| return syscall.errnoBug(err), // Invalid argument passed. 13141 .ISCONN => |err| return syscall.errnoBug(err), // connection-mode socket was connected already but a recipient was specified 13142 .NOTSOCK => |err| return syscall.errnoBug(err), // The file descriptor sockfd does not refer to a socket. 13143 .OPNOTSUPP => |err| return syscall.errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. 13144 13145 else => |err| return syscall.unexpectedErrno(err), 13146 } 13147 } 13148 } 13149 13150 fn netReceivePosix( 13151 socket_handle: net.Socket.Handle, 13152 message: *net.IncomingMessage, 13153 data_buffer: []u8, 13154 flags: net.ReceiveFlags, 13155 nonblocking: bool, 13156 ) (net.Socket.ReceiveError || error{WouldBlock})!void { 13157 // recvmmsg is useless, here's why: 13158 // * [timeout bug](https://bugzilla.kernel.org/show_bug.cgi?id=75371) 13159 // * it wants iovecs for each message but we have a better API: one data 13160 // buffer to handle all the messages. The better API cannot be lowered to 13161 // the split vectors though because reducing the buffer size might make 13162 // some messages unreceivable. 13163 const posix_flags: u32 = 13164 @as(u32, if (flags.oob) posix.MSG.OOB else 0) | 13165 @as(u32, if (flags.peek) posix.MSG.PEEK else 0) | 13166 @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) | 13167 posix.MSG.NOSIGNAL | 13168 @as(u32, if (nonblocking) posix.MSG.DONTWAIT else 0); 13169 13170 var storage: PosixAddress = undefined; 13171 var iov: posix.iovec = .{ .base = data_buffer.ptr, .len = data_buffer.len }; 13172 var msg: posix.msghdr = .{ 13173 .name = &storage.any, 13174 .namelen = @sizeOf(PosixAddress), 13175 .iov = (&iov)[0..1], 13176 .iovlen = 1, 13177 .control = message.control.ptr, 13178 .controllen = @intCast(message.control.len), 13179 .flags = undefined, 13180 }; 13181 13182 const syscall = try Syscall.start(); 13183 while (true) { 13184 const rc = posix.system.recvmsg(socket_handle, &msg, posix_flags); 13185 switch (posix.errno(rc)) { 13186 .SUCCESS => { 13187 syscall.finish(); 13188 const data = data_buffer[0..@intCast(rc)]; 13189 message.* = .{ 13190 .from = addressFromPosix(&storage), 13191 .data = data, 13192 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control, 13193 .flags = .{ 13194 .eor = (msg.flags & posix.MSG.EOR) != 0, 13195 .trunc = (msg.flags & posix.MSG.TRUNC) != 0, 13196 .ctrunc = (msg.flags & posix.MSG.CTRUNC) != 0, 13197 .oob = (msg.flags & posix.MSG.OOB) != 0, 13198 .errqueue = if (@hasDecl(posix.MSG, "ERRQUEUE")) (msg.flags & posix.MSG.ERRQUEUE) != 0 else false, 13199 }, 13200 }; 13201 return; 13202 }, 13203 .INTR => { 13204 try syscall.checkCancel(); 13205 continue; 13206 }, 13207 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), 13208 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 13209 .NOBUFS => return syscall.fail(error.SystemResources), 13210 .NOMEM => return syscall.fail(error.SystemResources), 13211 .NOTCONN => return syscall.fail(error.SocketUnconnected), 13212 .MSGSIZE => return syscall.fail(error.MessageOversize), 13213 .PIPE => return syscall.fail(error.SocketUnconnected), 13214 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), 13215 .NETDOWN => return syscall.fail(error.NetworkDown), 13216 .AGAIN => return syscall.fail(error.WouldBlock), 13217 .BADF => |err| return syscall.errnoBug(err), 13218 .FAULT => |err| return syscall.errnoBug(err), 13219 .INVAL => |err| return syscall.errnoBug(err), 13220 .NOTSOCK => |err| return syscall.errnoBug(err), 13221 .OPNOTSUPP => |err| return syscall.errnoBug(err), 13222 else => |err| return syscall.unexpectedErrno(err), 13223 } 13224 } 13225 } 13226 13227 fn netReceiveWindows( 13228 t: *Threaded, 13229 socket_handle: net.Socket.Handle, 13230 message_buffer: []net.IncomingMessage, 13231 data_buffer: []u8, 13232 flags: net.ReceiveFlags, 13233 ) struct { ?net.Socket.ReceiveError, usize } { 13234 netReceiveWindowsOne(t, socket_handle, &message_buffer[0], data_buffer, flags) catch |err| return .{ err, 0 }; 13235 return .{ null, 1 }; 13236 } 13237 13238 fn netReceiveWindowsOne( 13239 t: *Threaded, 13240 socket_handle: net.Socket.Handle, 13241 message: *net.IncomingMessage, 13242 data_buffer: []u8, 13243 flags: net.ReceiveFlags, 13244 ) net.Socket.ReceiveError!void { 13245 if (!have_networking) return error.NetworkDown; 13246 13247 var windows_flags: u32 = 13248 @as(u32, if (flags.oob) ws2_32.MSG.OOB else 0) | 13249 @as(u32, if (flags.peek) ws2_32.MSG.PEEK else 0) | 13250 @as(u32, if (flags.trunc) ws2_32.MSG.TRUNC else 0); 13251 13252 var buf: ws2_32.WSABUF = .{ 13253 .buf = data_buffer.ptr, 13254 .len = std.math.cast(u32, data_buffer.len) orelse return error.MessageOversize, 13255 }; 13256 var n: u32 = undefined; 13257 var from_storage: WsaAddress = undefined; 13258 var from_storage_len: i32 = @sizeOf(WsaAddress); 13259 var syscall: AlertableSyscall = try .start(); 13260 13261 while (true) { 13262 const rc = ws2_32.WSARecvFrom( 13263 socket_handle, 13264 (&buf)[0..1], 13265 1, 13266 &n, 13267 &windows_flags, 13268 &from_storage.any, 13269 &from_storage_len, 13270 null, 13271 null, 13272 ); 13273 if (rc != ws2_32.SOCKET_ERROR) { 13274 syscall.finish(); 13275 message.* = .{ 13276 .from = addressFromWsa(&from_storage), 13277 .data = data_buffer[0..n], 13278 .control = &.{}, 13279 .flags = .{ 13280 .eor = false, 13281 .trunc = (windows_flags & ws2_32.MSG.TRUNC) != 0, 13282 .ctrunc = (windows_flags & ws2_32.MSG.CTRUNC) != 0, 13283 .oob = false, 13284 .errqueue = false, 13285 }, 13286 }; 13287 return; 13288 } 13289 switch (ws2_32.WSAGetLastError()) { 13290 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 13291 try syscall.checkCancel(); 13292 continue; 13293 }, 13294 .NOTINITIALISED => { 13295 syscall.finish(); 13296 try initializeWsa(t); 13297 syscall = try .start(); 13298 continue; 13299 }, 13300 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), 13301 .ENETDOWN => return syscall.fail(error.NetworkDown), 13302 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer), 13303 .ENOTCONN => return syscall.fail(error.SocketUnconnected), 13304 .EFAULT => unreachable, // a pointer is not completely contained in user address space. 13305 .EINVAL => |err| return syscall.wsaErrorBug(err), 13306 .EMSGSIZE => |err| return syscall.wsaErrorBug(err), 13307 else => |err| return syscall.unexpectedWsaError(err), 13308 } 13309 } 13310 } 13311 13312 fn netWritePosix( 13313 userdata: ?*anyopaque, 13314 fd: net.Socket.Handle, 13315 header: []const u8, 13316 data: []const []const u8, 13317 splat: usize, 13318 ) net.Stream.Writer.Error!usize { 13319 if (!have_networking) return error.NetworkDown; 13320 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13321 _ = t; 13322 13323 var iovecs: [max_iovecs_len]posix.iovec_const = undefined; 13324 var msg: posix.msghdr_const = .{ 13325 .name = null, 13326 .namelen = 0, 13327 .iov = &iovecs, 13328 .iovlen = 0, 13329 .control = null, 13330 .controllen = 0, 13331 .flags = 0, 13332 }; 13333 addBuf(&iovecs, &msg.iovlen, header); 13334 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes); 13335 const pattern = data[data.len - 1]; 13336 13337 var splat_backup_buffer: [splat_buffer_size]u8 = undefined; 13338 if (iovecs.len - msg.iovlen != 0) switch (splat) { 13339 0 => {}, 13340 1 => addBuf(&iovecs, &msg.iovlen, pattern), 13341 else => switch (pattern.len) { 13342 0 => {}, 13343 1 => { 13344 const splat_buffer = &splat_backup_buffer; 13345 const memset_len = @min(splat_buffer.len, splat); 13346 const buf = splat_buffer[0..memset_len]; 13347 @memset(buf, pattern[0]); 13348 addBuf(&iovecs, &msg.iovlen, buf); 13349 var remaining_splat = splat - buf.len; 13350 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) { 13351 assert(buf.len == splat_buffer.len); 13352 addBuf(&iovecs, &msg.iovlen, splat_buffer); 13353 remaining_splat -= splat_buffer.len; 13354 } 13355 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); 13356 }, 13357 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| { 13358 addBuf(&iovecs, &msg.iovlen, pattern); 13359 }, 13360 }, 13361 }; 13362 const flags = posix.MSG.NOSIGNAL; 13363 13364 const syscall: Syscall = try .start(); 13365 while (true) { 13366 const rc = posix.system.sendmsg(fd, &msg, flags); 13367 switch (posix.errno(rc)) { 13368 .SUCCESS => { 13369 syscall.finish(); 13370 return @intCast(rc); 13371 }, 13372 .INTR => { 13373 try syscall.checkCancel(); 13374 continue; 13375 }, 13376 else => |e| { 13377 syscall.finish(); 13378 switch (e) { 13379 .ACCES => |err| return errnoBug(err), 13380 .AGAIN => |err| return errnoBug(err), 13381 .ALREADY => return error.FastOpenAlreadyInProgress, 13382 .BADF => |err| return errnoBug(err), // File descriptor used after closed. 13383 .CONNRESET => return error.ConnectionResetByPeer, 13384 .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. 13385 .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. 13386 .INVAL => |err| return errnoBug(err), // Invalid argument passed. 13387 .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified 13388 .MSGSIZE => |err| return errnoBug(err), 13389 .NOBUFS => return error.SystemResources, 13390 .NOMEM => return error.SystemResources, 13391 .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. 13392 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. 13393 .PIPE => return error.SocketUnconnected, 13394 .AFNOSUPPORT => return error.AddressFamilyUnsupported, 13395 .HOSTUNREACH => return error.HostUnreachable, 13396 .NETUNREACH => return error.NetworkUnreachable, 13397 .NOTCONN => return error.SocketUnconnected, 13398 .NETDOWN => return error.NetworkDown, 13399 else => |err| return posix.unexpectedErrno(err), 13400 } 13401 }, 13402 } 13403 } 13404 } 13405 13406 fn netWriteWindows( 13407 userdata: ?*anyopaque, 13408 handle: net.Socket.Handle, 13409 header: []const u8, 13410 data: []const []const u8, 13411 splat: usize, 13412 ) net.Stream.Writer.Error!usize { 13413 if (!have_networking) return error.NetworkDown; 13414 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13415 comptime assert(is_windows); 13416 13417 var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined; 13418 var len: u32 = 0; 13419 addWsaBuf(&iovecs, &len, header); 13420 for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes); 13421 const pattern = data[data.len - 1]; 13422 var backup_buffer: [64]u8 = undefined; 13423 if (iovecs.len - len != 0) switch (splat) { 13424 0 => {}, 13425 1 => addWsaBuf(&iovecs, &len, pattern), 13426 else => switch (pattern.len) { 13427 0 => {}, 13428 1 => { 13429 const splat_buffer = &backup_buffer; 13430 const memset_len = @min(splat_buffer.len, splat); 13431 const buf = splat_buffer[0..memset_len]; 13432 @memset(buf, pattern[0]); 13433 addWsaBuf(&iovecs, &len, buf); 13434 var remaining_splat = splat - buf.len; 13435 while (remaining_splat > splat_buffer.len and len < iovecs.len) { 13436 addWsaBuf(&iovecs, &len, splat_buffer); 13437 remaining_splat -= splat_buffer.len; 13438 } 13439 addWsaBuf(&iovecs, &len, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); 13440 }, 13441 else => for (0..@min(splat, iovecs.len - len)) |_| { 13442 addWsaBuf(&iovecs, &len, pattern); 13443 }, 13444 }, 13445 }; 13446 13447 var syscall: AlertableSyscall = try .start(); 13448 while (true) { 13449 var n: u32 = undefined; 13450 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, null, null); 13451 if (rc != ws2_32.SOCKET_ERROR) { 13452 syscall.finish(); 13453 return n; 13454 } 13455 switch (ws2_32.WSAGetLastError()) { 13456 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 13457 try syscall.checkCancel(); 13458 continue; 13459 }, 13460 .NOTINITIALISED => { 13461 syscall.finish(); 13462 try initializeWsa(t); 13463 syscall = try .start(); 13464 continue; 13465 }, 13466 13467 .ECONNABORTED => return syscall.fail(error.ConnectionResetByPeer), 13468 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), 13469 .EINVAL => return syscall.fail(error.SocketUnconnected), 13470 .ENETDOWN => return syscall.fail(error.NetworkDown), 13471 .ENETRESET => return syscall.fail(error.ConnectionResetByPeer), 13472 .ENOBUFS => return syscall.fail(error.SystemResources), 13473 .ENOTCONN => return syscall.fail(error.SocketUnconnected), 13474 .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 13475 .EOPNOTSUPP => |err| return syscall.wsaErrorBug(err), 13476 .ESHUTDOWN => |err| return syscall.wsaErrorBug(err), 13477 .IO_PENDING => |err| return syscall.wsaErrorBug(err), 13478 else => |err| return syscall.unexpectedWsaError(err), 13479 } 13480 } 13481 } 13482 13483 fn addWsaBuf(v: []ws2_32.WSABUF, i: *u32, bytes: []const u8) void { 13484 const cap = std.math.maxInt(u32); 13485 var remaining = bytes; 13486 while (remaining.len > cap) { 13487 if (v.len - i.* == 0) return; 13488 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap }; 13489 i.* += 1; 13490 remaining = remaining[cap..]; 13491 } else { 13492 @branchHint(.likely); 13493 if (v.len - i.* == 0) return; 13494 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) }; 13495 i.* += 1; 13496 } 13497 } 13498 13499 fn netWriteUnavailable( 13500 userdata: ?*anyopaque, 13501 handle: net.Socket.Handle, 13502 header: []const u8, 13503 data: []const []const u8, 13504 splat: usize, 13505 ) net.Stream.Writer.Error!usize { 13506 _ = userdata; 13507 _ = handle; 13508 _ = header; 13509 _ = data; 13510 _ = splat; 13511 return error.NetworkDown; 13512 } 13513 13514 /// This is either usize or u32. Since, either is fine, let's use the same 13515 /// `addBuf` function for both writing to a file and sending network messages. 13516 const iovlen_t = switch (native_os) { 13517 .wasi => u32, 13518 else => @FieldType(posix.msghdr_const, "iovlen"), 13519 }; 13520 13521 fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { 13522 // OS checks ptr addr before length so zero length vectors must be omitted. 13523 if (bytes.len == 0) return; 13524 if (v.len - i.* == 0) return; 13525 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len }; 13526 i.* += 1; 13527 } 13528 13529 fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { 13530 if (!have_networking) unreachable; 13531 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13532 _ = t; 13533 switch (native_os) { 13534 .windows => for (handles) |handle| closeSocketWindows(handle), 13535 else => for (handles) |handle| closeFd(handle), 13536 } 13537 } 13538 13539 fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void { 13540 _ = userdata; 13541 _ = handles; 13542 unreachable; // How you gonna close something that was impossible to open? 13543 } 13544 13545 fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { 13546 if (!have_networking) return error.NetworkDown; 13547 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13548 _ = t; 13549 13550 const posix_how: i32 = switch (how) { 13551 .recv => posix.SHUT.RD, 13552 .send => posix.SHUT.WR, 13553 .both => posix.SHUT.RDWR, 13554 }; 13555 13556 const syscall: Syscall = try .start(); 13557 while (true) { 13558 switch (posix.errno(posix.system.shutdown(handle, posix_how))) { 13559 .SUCCESS => return syscall.finish(), 13560 .INTR => { 13561 try syscall.checkCancel(); 13562 continue; 13563 }, 13564 else => |e| { 13565 syscall.finish(); 13566 switch (e) { 13567 .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err), 13568 .NOTCONN => return error.SocketUnconnected, 13569 .NOBUFS => return error.SystemResources, 13570 else => |err| return posix.unexpectedErrno(err), 13571 } 13572 }, 13573 } 13574 } 13575 } 13576 13577 fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { 13578 if (!have_networking) return error.NetworkDown; 13579 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13580 13581 const wsa_how: i32 = switch (how) { 13582 .recv => ws2_32.SD_RECEIVE, 13583 .send => ws2_32.SD_SEND, 13584 .both => ws2_32.SD_BOTH, 13585 }; 13586 13587 var syscall: AlertableSyscall = try .start(); 13588 while (true) { 13589 const rc = ws2_32.shutdown(handle, wsa_how); 13590 if (rc != ws2_32.SOCKET_ERROR) { 13591 syscall.finish(); 13592 return; 13593 } 13594 switch (ws2_32.WSAGetLastError()) { 13595 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => { 13596 try syscall.checkCancel(); 13597 continue; 13598 }, 13599 .NOTINITIALISED => { 13600 syscall.finish(); 13601 try initializeWsa(t); 13602 syscall = try .start(); 13603 continue; 13604 }, 13605 .ECONNABORTED => return syscall.fail(error.ConnectionAborted), 13606 .ECONNRESET => return syscall.fail(error.ConnectionResetByPeer), 13607 .ENETDOWN => return syscall.fail(error.NetworkDown), 13608 .ENOTCONN => return syscall.fail(error.SocketUnconnected), 13609 .EINVAL, .ENOTSOCK => |err| return syscall.wsaErrorBug(err), 13610 else => |err| return syscall.unexpectedWsaError(err), 13611 } 13612 } 13613 } 13614 13615 fn netShutdownUnavailable(_: ?*anyopaque, _: net.Socket.Handle, _: net.ShutdownHow) net.ShutdownError!void { 13616 unreachable; // How you gonna shutdown something that was impossible to open? 13617 } 13618 13619 fn netInterfaceNameResolve( 13620 userdata: ?*anyopaque, 13621 name: *const net.Interface.Name, 13622 ) net.Interface.Name.ResolveError!net.Interface { 13623 if (!have_networking) return error.InterfaceNotFound; 13624 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13625 _ = t; 13626 13627 if (native_os == .linux) { 13628 const sock_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) { 13629 error.ProcessFdQuotaExceeded => return error.SystemResources, 13630 error.SystemFdQuotaExceeded => return error.SystemResources, 13631 error.AddressFamilyUnsupported => return error.Unexpected, 13632 error.ProtocolUnsupportedBySystem => return error.Unexpected, 13633 error.ProtocolUnsupportedByAddressFamily => return error.Unexpected, 13634 error.SocketModeUnsupported => return error.Unexpected, 13635 error.OptionUnsupported => return error.Unexpected, 13636 else => |e| return e, 13637 }; 13638 defer closeFd(sock_fd); 13639 13640 var ifr: posix.ifreq = .{ 13641 .ifrn = .{ .name = @bitCast(name.bytes) }, 13642 .ifru = undefined, 13643 }; 13644 13645 const syscall: Syscall = try .start(); 13646 while (true) switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { 13647 .SUCCESS => { 13648 syscall.finish(); 13649 return .{ .index = @bitCast(ifr.ifru.ivalue) }; 13650 }, 13651 .INTR => { 13652 try syscall.checkCancel(); 13653 continue; 13654 }, 13655 .NODEV => return syscall.fail(error.InterfaceNotFound), 13656 else => |err| return syscall.unexpectedErrno(err), 13657 }; 13658 } 13659 13660 if (is_windows) { 13661 try Thread.checkCancel(); 13662 @panic("TODO implement netInterfaceNameResolve for Windows"); 13663 } 13664 13665 if (builtin.link_libc) { 13666 try Thread.checkCancel(); 13667 const index = std.c.if_nametoindex(&name.bytes); 13668 if (index == 0) return error.InterfaceNotFound; 13669 return .{ .index = @bitCast(index) }; 13670 } 13671 13672 @panic("unimplemented"); 13673 } 13674 13675 fn netInterfaceNameResolveUnavailable( 13676 userdata: ?*anyopaque, 13677 name: *const net.Interface.Name, 13678 ) net.Interface.Name.ResolveError!net.Interface { 13679 _ = userdata; 13680 _ = name; 13681 return error.InterfaceNotFound; 13682 } 13683 13684 fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { 13685 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13686 _ = t; 13687 try Thread.checkCancel(); 13688 13689 if (native_os == .linux) { 13690 _ = interface; 13691 @panic("TODO implement netInterfaceName for linux"); 13692 } 13693 13694 if (is_windows) { 13695 @panic("TODO implement netInterfaceName for windows"); 13696 } 13697 13698 if (builtin.link_libc) { 13699 @panic("TODO implement netInterfaceName for libc"); 13700 } 13701 13702 @panic("unimplemented"); 13703 } 13704 13705 fn netInterfaceNameUnavailable(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { 13706 _ = userdata; 13707 _ = interface; 13708 return error.Unexpected; 13709 } 13710 13711 fn netLookup( 13712 userdata: ?*anyopaque, 13713 host_name: HostName, 13714 resolved: *Io.Queue(HostName.LookupResult), 13715 options: HostName.LookupOptions, 13716 ) net.HostName.LookupError!void { 13717 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13718 defer resolved.close(io(t)); 13719 netLookupFallible(t, host_name, resolved, options) catch |err| switch (err) { 13720 error.Closed => unreachable, // `resolved` must not be closed until `netLookup` returns 13721 else => |e| return e, 13722 }; 13723 } 13724 13725 fn netLookupUnavailable( 13726 userdata: ?*anyopaque, 13727 host_name: HostName, 13728 resolved: *Io.Queue(HostName.LookupResult), 13729 options: HostName.LookupOptions, 13730 ) net.HostName.LookupError!void { 13731 _ = host_name; 13732 _ = options; 13733 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13734 resolved.close(io(t)); 13735 return error.NetworkDown; 13736 } 13737 13738 fn netLookupFallible( 13739 t: *Threaded, 13740 host_name: HostName, 13741 resolved: *Io.Queue(HostName.LookupResult), 13742 options: HostName.LookupOptions, 13743 ) (net.HostName.LookupError || Io.QueueClosedError)!void { 13744 if (!have_networking) return error.NetworkDown; 13745 13746 const t_io = io(t); 13747 const name = host_name.bytes; 13748 assert(name.len <= HostName.max_len); 13749 13750 if (is_windows) { 13751 var name_buffer: [HostName.max_len + 1]u16 = undefined; 13752 const name_len = std.unicode.wtf8ToWtf16Le(&name_buffer, host_name.bytes) catch 13753 unreachable; // HostName is prevalidated. 13754 name_buffer[name_len] = 0; 13755 const name_w = name_buffer[0..name_len :0]; 13756 13757 var port_buffer: [8]u8 = undefined; 13758 var port_buffer_wide: [8]u16 = undefined; 13759 const port = std.fmt.bufPrint(&port_buffer, "{d}", .{options.port}) catch 13760 unreachable; // `port_buffer` is big enough for decimal u16. 13761 for (port, port_buffer_wide[0..port.len]) |byte, *wide| 13762 wide.* = std.mem.nativeToLittle(u16, byte); 13763 port_buffer_wide[port.len] = 0; 13764 const port_w = port_buffer_wide[0..port.len :0]; 13765 13766 const hints: ws2_32.ADDRINFOEXW = .{ 13767 .flags = .{ .NUMERICSERV = true }, 13768 .family = if (options.family) |f| switch (f) { 13769 .ip4 => posix.AF.INET, 13770 .ip6 => posix.AF.INET6, 13771 } else posix.AF.UNSPEC, 13772 .socktype = posix.SOCK.STREAM, 13773 .protocol = posix.IPPROTO.TCP, 13774 .canonname = null, 13775 .addr = null, 13776 .addrlen = 0, 13777 .blob = null, 13778 .bloblen = 0, 13779 .provider = null, 13780 .next = null, 13781 }; 13782 var res: *ws2_32.ADDRINFOEXW = undefined; 13783 const timeout: ?*ws2_32.timeval = null; 13784 while (true) { 13785 // TODO: hook this up to cancelation with `NtDelayExecution` and APC callbacks. 13786 try Thread.checkCancel(); 13787 // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes 13788 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, null)); 13789 switch (rc) { 13790 @as(ws2_32.WinsockError, @enumFromInt(0)) => break, 13791 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, 13792 .NOTINITIALISED => { 13793 try initializeWsa(t); 13794 continue; 13795 }, 13796 .TRY_AGAIN => return error.NameServerFailure, 13797 .NO_RECOVERY => return error.NameServerFailure, 13798 .EAFNOSUPPORT => return error.AddressFamilyUnsupported, 13799 .NOT_ENOUGH_MEMORY => return error.SystemResources, 13800 .HOST_NOT_FOUND => return error.UnknownHostName, 13801 .TYPE_NOT_FOUND => return error.ProtocolUnsupportedByAddressFamily, 13802 .ESOCKTNOSUPPORT => return error.ProtocolUnsupportedBySystem, 13803 .EINVAL => |err| return windows.wsaErrorBug(err), 13804 else => |err| return windows.unexpectedWsaError(err), 13805 } 13806 } 13807 defer ws2_32.FreeAddrInfoExW(res); 13808 13809 var it: ?*ws2_32.ADDRINFOEXW = res; 13810 var canon_name: ?[*:0]const u16 = null; 13811 while (it) |info| : (it = info.next) { 13812 const addr = info.addr orelse continue; 13813 try resolved.putOne(t_io, .{ .address = addressFromWsa(@alignCast(@fieldParentPtr("any", addr))) }); 13814 13815 if (info.canonname) |n| { 13816 if (canon_name == null) { 13817 canon_name = n; 13818 } 13819 } 13820 } 13821 if (canon_name) |n| { 13822 const len = std.unicode.wtf16LeToWtf8(options.canonical_name_buffer, std.mem.sliceTo(n, 0)); 13823 try resolved.putOne(t_io, .{ .canonical_name = .{ 13824 .bytes = options.canonical_name_buffer[0..len], 13825 } }); 13826 } 13827 return; 13828 } 13829 13830 // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics. 13831 // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it. 13832 13833 if (builtin.target.isGnuLibC()) { 13834 // TODO use getaddrinfo_a / gai_cancel 13835 } 13836 13837 if (native_os == .linux) { 13838 if (options.family != .ip4) { 13839 if (IpAddress.parseIp6(name, options.port)) |addr| { 13840 try resolved.putAll(t_io, &.{ 13841 .{ .address = addr }, 13842 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, 13843 }); 13844 return; 13845 } else |_| {} 13846 } 13847 13848 if (options.family != .ip6) { 13849 if (IpAddress.parseIp4(name, options.port)) |addr| { 13850 try resolved.putAll(t_io, &.{ 13851 .{ .address = addr }, 13852 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, 13853 }); 13854 return; 13855 } else |_| {} 13856 } 13857 13858 lookupHosts(t, host_name, resolved, options) catch |err| switch (err) { 13859 error.UnknownHostName => {}, 13860 else => |e| return e, 13861 }; 13862 13863 // RFC 6761 Section 6.3.3 13864 // Name resolution APIs and libraries SHOULD recognize 13865 // localhost names as special and SHOULD always return the IP 13866 // loopback address for address queries and negative responses 13867 // for all other query types. 13868 13869 // Check for equal to "localhost(.)" or ends in ".localhost(.)" 13870 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; 13871 if (std.mem.endsWith(u8, name, localhost) and 13872 (name.len == localhost.len or name[name.len - localhost.len] == '.')) 13873 { 13874 var results_buffer: [3]HostName.LookupResult = undefined; 13875 var results_index: usize = 0; 13876 if (options.family != .ip4) { 13877 results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } }; 13878 results_index += 1; 13879 } 13880 if (options.family != .ip6) { 13881 results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } }; 13882 results_index += 1; 13883 } 13884 const canon_name = "localhost"; 13885 const canon_name_dest = options.canonical_name_buffer[0..canon_name.len]; 13886 canon_name_dest.* = canon_name.*; 13887 results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } }; 13888 results_index += 1; 13889 try resolved.putAll(t_io, results_buffer[0..results_index]); 13890 return; 13891 } 13892 13893 return lookupDnsSearch(t, host_name, resolved, options); 13894 } 13895 13896 if (native_os == .openbsd) { 13897 // TODO use getaddrinfo_async / asr_abort 13898 } 13899 13900 if (native_os == .freebsd) { 13901 // TODO use dnsres_getaddrinfo 13902 } 13903 13904 if (is_darwin) { 13905 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution 13906 } 13907 13908 if (builtin.link_libc) { 13909 // This operating system lacks a way to resolve asynchronously. We are 13910 // stuck with getaddrinfo. 13911 var name_buffer: [HostName.max_len + 1]u8 = undefined; 13912 @memcpy(name_buffer[0..host_name.bytes.len], host_name.bytes); 13913 name_buffer[host_name.bytes.len] = 0; 13914 const name_c = name_buffer[0..host_name.bytes.len :0]; 13915 13916 var port_buffer: [8]u8 = undefined; 13917 const port_c = std.fmt.bufPrintZ(&port_buffer, "{d}", .{options.port}) catch unreachable; 13918 13919 const hints: posix.addrinfo = .{ 13920 .flags = .{ .NUMERICSERV = true }, 13921 .family = posix.AF.UNSPEC, 13922 .socktype = posix.SOCK.STREAM, 13923 .protocol = posix.IPPROTO.TCP, 13924 .canonname = null, 13925 .addr = null, 13926 .addrlen = 0, 13927 .next = null, 13928 }; 13929 var res: ?*posix.addrinfo = null; 13930 const syscall: Syscall = try .start(); 13931 while (true) { 13932 switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) { 13933 @as(posix.system.EAI, @enumFromInt(0)) => { 13934 syscall.finish(); 13935 break; 13936 }, 13937 .SYSTEM => switch (posix.errno(-1)) { 13938 .INTR => { 13939 try syscall.checkCancel(); 13940 continue; 13941 }, 13942 else => |e| { 13943 syscall.finish(); 13944 return posix.unexpectedErrno(e); 13945 }, 13946 }, 13947 else => |e| { 13948 syscall.finish(); 13949 switch (e) { 13950 .ADDRFAMILY => return error.AddressFamilyUnsupported, 13951 .AGAIN => return error.NameServerFailure, 13952 .FAIL => return error.NameServerFailure, 13953 .FAMILY => return error.AddressFamilyUnsupported, 13954 .MEMORY => return error.SystemResources, 13955 .NODATA => return error.UnknownHostName, 13956 .NONAME => return error.UnknownHostName, 13957 else => return error.Unexpected, 13958 } 13959 }, 13960 } 13961 } 13962 defer if (res) |some| posix.system.freeaddrinfo(some); 13963 13964 var it = res; 13965 var canon_name: ?[*:0]const u8 = null; 13966 while (it) |info| : (it = info.next) { 13967 const addr = info.addr orelse continue; 13968 try resolved.putOne(t_io, .{ .address = addressFromPosix(@alignCast(@fieldParentPtr("any", addr))) }); 13969 13970 if (info.canonname) |n| { 13971 if (canon_name == null) { 13972 canon_name = n; 13973 } 13974 } 13975 } 13976 if (canon_name) |n| { 13977 try resolved.putOne(t_io, .{ 13978 .canonical_name = copyCanon(options.canonical_name_buffer, std.mem.sliceTo(n, 0)), 13979 }); 13980 } 13981 return; 13982 } 13983 13984 return error.OptionUnsupported; 13985 } 13986 13987 fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr { 13988 const t: *Threaded = @ptrCast(@alignCast(userdata)); 13989 const current_thread_id = Thread.currentId(); 13990 13991 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) { 13992 mutexLock(&t.stderr_mutex); 13993 assert(t.stderr_mutex_lock_count == 0); 13994 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered); 13995 } 13996 t.stderr_mutex_lock_count += 1; 13997 13998 return initLockedStderr(t, terminal_mode); 13999 } 14000 14001 fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr { 14002 const t: *Threaded = @ptrCast(@alignCast(userdata)); 14003 const current_thread_id = Thread.currentId(); 14004 14005 if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) { 14006 if (!t.stderr_mutex.tryLock()) return null; 14007 assert(t.stderr_mutex_lock_count == 0); 14008 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered); 14009 } 14010 t.stderr_mutex_lock_count += 1; 14011 14012 return try initLockedStderr(t, terminal_mode); 14013 } 14014 14015 fn initLockedStderr(t: *Threaded, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr { 14016 if (!t.stderr_writer_initialized) { 14017 const io_t = io(t); 14018 if (is_windows) t.stderr_writer.file = .stderr(); 14019 t.stderr_writer.io = io_t; 14020 t.stderr_writer_initialized = true; 14021 t.scanEnviron(); 14022 const NO_COLOR = t.environ.exist.NO_COLOR; 14023 const CLICOLOR_FORCE = t.environ.exist.CLICOLOR_FORCE; 14024 t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file, NO_COLOR, CLICOLOR_FORCE); 14025 } 14026 return .{ 14027 .file_writer = &t.stderr_writer, 14028 .terminal_mode = terminal_mode orelse t.stderr_mode, 14029 }; 14030 } 14031 14032 fn unlockStderr(userdata: ?*anyopaque) void { 14033 const t: *Threaded = @ptrCast(@alignCast(userdata)); 14034 if (t.stderr_writer.err == null) t.stderr_writer.interface.flush() catch {}; 14035 if (t.stderr_writer.err) |err| { 14036 switch (err) { 14037 error.Canceled => recancelInner(), 14038 else => {}, 14039 } 14040 t.stderr_writer.err = null; 14041 } 14042 t.stderr_writer.interface.end = 0; 14043 t.stderr_writer.interface.buffer = &.{}; 14044 14045 t.stderr_mutex_lock_count -= 1; 14046 if (t.stderr_mutex_lock_count == 0) { 14047 @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, Thread.invalid_id, .unordered); 14048 mutexUnlock(&t.stderr_mutex); 14049 } 14050 } 14051 14052 fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize { 14053 const t: *Threaded = @ptrCast(@alignCast(userdata)); 14054 _ = t; 14055 if (is_windows) { 14056 var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; 14057 const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len * 2 + 2, &wtf16le_buf) / 2; 14058 if (n == 0) return error.Unexpected; 14059 assert(n <= wtf16le_buf.len); 14060 const wtf16le_slice = wtf16le_buf[0..n]; 14061 var end_index: usize = 0; 14062 var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice); 14063 while (it.nextCodepoint()) |codepoint| { 14064 const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable; 14065 if (end_index + seq_len >= buffer.len) 14066 return error.NameTooLong; 14067 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable; 14068 } 14069 return end_index; 14070 } else if (native_os == .wasi and !builtin.link_libc) { 14071 if (buffer.len == 0) return error.NameTooLong; 14072 buffer[0] = '.'; 14073 return 1; 14074 } 14075 14076 const err: posix.E = if (builtin.link_libc) err: { 14077 const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*; 14078 break :err @enumFromInt(c_err); 14079 } else err: { 14080 break :err posix.errno(posix.system.getcwd(buffer.ptr, buffer.len)); 14081 }; 14082 switch (err) { 14083 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?, 14084 .NOENT => return error.CurrentDirUnlinked, 14085 .RANGE => return error.NameTooLong, 14086 .FAULT => |e| return errnoBug(e), 14087 .INVAL => |e| return errnoBug(e), 14088 else => return posix.unexpectedErrno(err), 14089 } 14090 } 14091 14092 fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void { 14093 const t: *Threaded = @ptrCast(@alignCast(userdata)); 14094 _ = t; 14095 14096 if (native_os == .wasi) return error.OperationUnsupported; 14097 14098 if (is_windows) { 14099 var dir_path_buf: [windows.PATH_MAX_WIDE]u16 = undefined; 14100 const dir_path = try GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buf); 14101 const syscall: Syscall = try .start(); 14102 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(dir_path))) { 14103 .SUCCESS => return syscall.finish(), 14104 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 14105 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 14106 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 14107 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), 14108 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), 14109 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 14110 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), 14111 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 14112 .CANCELLED => { 14113 try syscall.checkCancel(); 14114 continue; 14115 }, 14116 else => |status| return syscall.unexpectedNtstatus(status), 14117 }; 14118 } 14119 14120 return fchdir(dir.handle); 14121 } 14122 14123 fn processSetCurrentPath(userdata: ?*anyopaque, path: []const u8) process.SetCurrentPathError!void { 14124 const t: *Threaded = @ptrCast(@alignCast(userdata)); 14125 _ = t; 14126 14127 if (native_os == .wasi) return error.OperationUnsupported; 14128 14129 if (is_windows) { 14130 var path_w_buf: [windows.PATH_MAX_WIDE]u16 = undefined; 14131 const len = std.unicode.calcWtf16LeLen(path) catch return error.InvalidWtf8; 14132 if (len > path_w_buf.len) return error.NameTooLong; 14133 const path_w_len = std.unicode.wtf8ToWtf16Le(&path_w_buf, path) catch |err| switch (err) { 14134 error.InvalidWtf8 => unreachable, // already validated 14135 }; 14136 const path_w = path_w_buf[0..path_w_len]; 14137 14138 const syscall: Syscall = try .start(); 14139 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(path_w))) { 14140 .SUCCESS => return syscall.finish(), 14141 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 14142 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 14143 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 14144 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), 14145 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), 14146 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 14147 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), 14148 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 14149 .CANCELLED => { 14150 try syscall.checkCancel(); 14151 continue; 14152 }, 14153 else => |status| return syscall.unexpectedNtstatus(status), 14154 }; 14155 } 14156 14157 return chdir(path); 14158 } 14159 14160 pub const PosixAddress = extern union { 14161 any: posix.sockaddr, 14162 in: posix.sockaddr.in, 14163 in6: posix.sockaddr.in6, 14164 }; 14165 14166 const UnixAddress = extern union { 14167 any: posix.sockaddr, 14168 un: posix.sockaddr.un, 14169 }; 14170 14171 const WsaAddress = extern union { 14172 any: ws2_32.sockaddr, 14173 in: ws2_32.sockaddr.in, 14174 in6: ws2_32.sockaddr.in6, 14175 un: ws2_32.sockaddr.un, 14176 }; 14177 14178 pub fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t { 14179 return switch (a.*) { 14180 .ip4 => posix.AF.INET, 14181 .ip6 => posix.AF.INET6, 14182 }; 14183 } 14184 14185 pub fn addressFromPosix(posix_address: *const PosixAddress) IpAddress { 14186 return switch (posix_address.any.family) { 14187 posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) }, 14188 posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) }, 14189 else => .{ .ip4 = .loopback(0) }, 14190 }; 14191 } 14192 14193 fn addressFromWsa(wsa_address: *const WsaAddress) IpAddress { 14194 return switch (wsa_address.any.family) { 14195 posix.AF.INET => .{ .ip4 = address4FromWsa(&wsa_address.in) }, 14196 posix.AF.INET6 => .{ .ip6 = address6FromWsa(&wsa_address.in6) }, 14197 else => .{ .ip4 = .loopback(0) }, 14198 }; 14199 } 14200 14201 pub fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t { 14202 return switch (a.*) { 14203 .ip4 => |ip4| { 14204 storage.in = address4ToPosix(ip4); 14205 return @sizeOf(posix.sockaddr.in); 14206 }, 14207 .ip6 => |*ip6| { 14208 storage.in6 = address6ToPosix(ip6); 14209 return @sizeOf(posix.sockaddr.in6); 14210 }, 14211 }; 14212 } 14213 14214 fn addressToWsa(a: *const IpAddress, storage: *WsaAddress) i32 { 14215 return switch (a.*) { 14216 .ip4 => |ip4| { 14217 storage.in = address4ToPosix(ip4); 14218 return @sizeOf(posix.sockaddr.in); 14219 }, 14220 .ip6 => |*ip6| { 14221 storage.in6 = address6ToPosix(ip6); 14222 return @sizeOf(posix.sockaddr.in6); 14223 }, 14224 }; 14225 } 14226 14227 fn addressUnixToPosix(a: *const net.UnixAddress, storage: *UnixAddress) posix.socklen_t { 14228 @memcpy(storage.un.path[0..a.path.len], a.path); 14229 storage.un.family = posix.AF.UNIX; 14230 storage.un.path[a.path.len] = 0; 14231 return @sizeOf(posix.sockaddr.un); 14232 } 14233 14234 fn addressUnixToWsa(a: *const net.UnixAddress, storage: *WsaAddress) i32 { 14235 @memcpy(storage.un.path[0..a.path.len], a.path); 14236 storage.un.family = posix.AF.UNIX; 14237 storage.un.path[a.path.len] = 0; 14238 return @sizeOf(posix.sockaddr.un); 14239 } 14240 14241 fn address4FromPosix(in: *const posix.sockaddr.in) net.Ip4Address { 14242 return .{ 14243 .port = std.mem.bigToNative(u16, in.port), 14244 .bytes = @bitCast(in.addr), 14245 }; 14246 } 14247 14248 fn address6FromPosix(in6: *const posix.sockaddr.in6) net.Ip6Address { 14249 return .{ 14250 .port = std.mem.bigToNative(u16, in6.port), 14251 .bytes = in6.addr, 14252 .flow = in6.flowinfo, 14253 .interface = .{ .index = in6.scope_id }, 14254 }; 14255 } 14256 14257 fn address4FromWsa(in: *const ws2_32.sockaddr.in) net.Ip4Address { 14258 return .{ 14259 .port = std.mem.bigToNative(u16, in.port), 14260 .bytes = @bitCast(in.addr), 14261 }; 14262 } 14263 14264 fn address6FromWsa(in6: *const ws2_32.sockaddr.in6) net.Ip6Address { 14265 return .{ 14266 .port = std.mem.bigToNative(u16, in6.port), 14267 .bytes = in6.addr, 14268 .flow = in6.flowinfo, 14269 .interface = .{ .index = in6.scope_id }, 14270 }; 14271 } 14272 14273 fn address4ToPosix(a: net.Ip4Address) posix.sockaddr.in { 14274 return .{ 14275 .port = std.mem.nativeToBig(u16, a.port), 14276 .addr = @bitCast(a.bytes), 14277 }; 14278 } 14279 14280 fn address6ToPosix(a: *const net.Ip6Address) posix.sockaddr.in6 { 14281 return .{ 14282 .port = std.mem.nativeToBig(u16, a.port), 14283 .flowinfo = a.flow, 14284 .addr = a.bytes, 14285 .scope_id = a.interface.index, 14286 }; 14287 } 14288 14289 pub fn errnoBug(err: posix.E) Io.UnexpectedError { 14290 if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err}); 14291 return error.Unexpected; 14292 } 14293 14294 pub fn posixSocketMode(mode: net.Socket.Mode) u32 { 14295 return switch (mode) { 14296 .stream => posix.SOCK.STREAM, 14297 .dgram => posix.SOCK.DGRAM, 14298 .seqpacket => posix.SOCK.SEQPACKET, 14299 .raw => posix.SOCK.RAW, 14300 .rdm => posix.SOCK.RDM, 14301 }; 14302 } 14303 14304 pub fn posixProtocol(protocol: ?net.Protocol) u32 { 14305 return @intFromEnum(protocol orelse return 0); 14306 } 14307 14308 pub fn recoverableOsBugDetected() void { 14309 if (is_debug) unreachable; 14310 } 14311 14312 pub fn clockToPosix(clock: Io.Clock) posix.clockid_t { 14313 return switch (clock) { 14314 .real => posix.CLOCK.REALTIME, 14315 .awake => switch (native_os) { 14316 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW, 14317 else => posix.CLOCK.MONOTONIC, 14318 }, 14319 .boot => switch (native_os) { 14320 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.MONOTONIC_RAW, 14321 // On freebsd derivatives, use MONOTONIC_FAST as currently there's 14322 // no precision tradeoff. 14323 .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST, 14324 // On linux, use BOOTTIME instead of MONOTONIC as it ticks while 14325 // suspended. 14326 .linux => posix.CLOCK.BOOTTIME, 14327 // On other posix systems, MONOTONIC is generally the fastest and 14328 // ticks while suspended. 14329 else => posix.CLOCK.MONOTONIC, 14330 }, 14331 .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID, 14332 .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID, 14333 }; 14334 } 14335 14336 fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { 14337 return switch (clock) { 14338 .real => .REALTIME, 14339 .awake => .MONOTONIC, 14340 .boot => .MONOTONIC, 14341 .cpu_process => .PROCESS_CPUTIME_ID, 14342 .cpu_thread => .THREAD_CPUTIME_ID, 14343 }; 14344 } 14345 14346 pub const linux_statx_request: std.os.linux.STATX = .{ 14347 .TYPE = true, 14348 .MODE = true, 14349 .ATIME = true, 14350 .MTIME = true, 14351 .CTIME = true, 14352 .INO = true, 14353 .SIZE = true, 14354 .NLINK = true, 14355 .BLOCKS = true, 14356 }; 14357 14358 pub const linux_statx_check: std.os.linux.STATX = .{ 14359 .TYPE = true, 14360 .MODE = true, 14361 .ATIME = false, 14362 .MTIME = true, 14363 .CTIME = true, 14364 .INO = true, 14365 .SIZE = true, 14366 .NLINK = true, 14367 .BLOCKS = false, 14368 }; 14369 14370 pub fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { 14371 const actual_mask_int: u32 = @bitCast(stx.mask); 14372 const wanted_mask_int: u32 = @bitCast(linux_statx_check); 14373 if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected; 14374 14375 return .{ 14376 .inode = stx.ino, 14377 .nlink = stx.nlink, 14378 .size = stx.size, 14379 .permissions = .fromMode(stx.mode), 14380 .kind = statxKind(stx.mode), 14381 .atime = if (!stx.mask.ATIME) null else .{ 14382 .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec), 14383 }, 14384 .mtime = .{ .nanoseconds = @intCast(@as(i128, stx.mtime.sec) * std.time.ns_per_s + stx.mtime.nsec) }, 14385 .ctime = .{ .nanoseconds = @intCast(@as(i128, stx.ctime.sec) * std.time.ns_per_s + stx.ctime.nsec) }, 14386 .block_size = if (stx.mask.BLOCKS) stx.blksize else 1, 14387 }; 14388 } 14389 14390 pub fn statxKind(stx_mode: u16) File.Kind { 14391 return switch (stx_mode & std.os.linux.S.IFMT) { 14392 std.os.linux.S.IFDIR => .directory, 14393 std.os.linux.S.IFCHR => .character_device, 14394 std.os.linux.S.IFBLK => .block_device, 14395 std.os.linux.S.IFREG => .file, 14396 std.os.linux.S.IFIFO => .named_pipe, 14397 std.os.linux.S.IFLNK => .sym_link, 14398 std.os.linux.S.IFSOCK => .unix_domain_socket, 14399 else => .unknown, 14400 }; 14401 } 14402 14403 pub fn statFromPosix(st: *const posix.Stat) File.Stat { 14404 const atime = st.atime(); 14405 const mtime = st.mtime(); 14406 const ctime = st.ctime(); 14407 return .{ 14408 .inode = st.ino, 14409 .nlink = st.nlink, 14410 .size = @bitCast(st.size), 14411 .permissions = .fromMode(st.mode), 14412 .kind = k: { 14413 const m = st.mode & posix.S.IFMT; 14414 switch (m) { 14415 posix.S.IFBLK => break :k .block_device, 14416 posix.S.IFCHR => break :k .character_device, 14417 posix.S.IFDIR => break :k .directory, 14418 posix.S.IFIFO => break :k .named_pipe, 14419 posix.S.IFLNK => break :k .sym_link, 14420 posix.S.IFREG => break :k .file, 14421 posix.S.IFSOCK => break :k .unix_domain_socket, 14422 else => {}, 14423 } 14424 if (native_os == .illumos) switch (m) { 14425 posix.S.IFDOOR => break :k .door, 14426 posix.S.IFPORT => break :k .event_port, 14427 else => {}, 14428 }; 14429 14430 break :k .unknown; 14431 }, 14432 .atime = timestampFromPosix(&atime), 14433 .mtime = timestampFromPosix(&mtime), 14434 .ctime = timestampFromPosix(&ctime), 14435 .block_size = @intCast(st.blksize), 14436 }; 14437 } 14438 14439 fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { 14440 return .{ 14441 .inode = st.ino, 14442 .nlink = st.nlink, 14443 .size = @bitCast(st.size), 14444 .permissions = .default_file, 14445 .kind = switch (st.filetype) { 14446 .BLOCK_DEVICE => .block_device, 14447 .CHARACTER_DEVICE => .character_device, 14448 .DIRECTORY => .directory, 14449 .SYMBOLIC_LINK => .sym_link, 14450 .REGULAR_FILE => .file, 14451 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, 14452 else => .unknown, 14453 }, 14454 .atime = .fromNanoseconds(st.atim), 14455 .mtime = .fromNanoseconds(st.mtim), 14456 .ctime = .fromNanoseconds(st.ctim), 14457 .block_size = 1, 14458 }; 14459 } 14460 14461 pub fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp { 14462 return .{ .nanoseconds = nanosecondsFromPosix(timespec) }; 14463 } 14464 14465 pub fn nanosecondsFromPosix(timespec: *const posix.timespec) i96 { 14466 return @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec); 14467 } 14468 14469 fn timestampToPosix(nanoseconds: i96) posix.timespec { 14470 if (builtin.zig_backend == .stage2_wasm) { 14471 // Workaround for https://codeberg.org/ziglang/zig/issues/30575 14472 return .{ 14473 .sec = @intCast(@divTrunc(nanoseconds, std.time.ns_per_s)), 14474 .nsec = @intCast(@rem(nanoseconds, std.time.ns_per_s)), 14475 }; 14476 } 14477 return .{ 14478 .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)), 14479 .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)), 14480 }; 14481 } 14482 14483 pub fn setTimestampToPosix(set_ts: File.SetTimestamp) posix.timespec { 14484 return switch (set_ts) { 14485 .unchanged => .OMIT, 14486 .now => .NOW, 14487 .new => |t| timestampToPosix(t.nanoseconds), 14488 }; 14489 } 14490 14491 pub fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { 14492 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName; 14493 // >= rather than > to make room for the null byte 14494 if (file_path.len >= buffer.len) return error.NameTooLong; 14495 @memcpy(buffer[0..file_path.len], file_path); 14496 buffer[file_path.len] = 0; 14497 return buffer[0..file_path.len :0]; 14498 } 14499 14500 fn lookupDnsSearch( 14501 t: *Threaded, 14502 host_name: HostName, 14503 resolved: *Io.Queue(HostName.LookupResult), 14504 options: HostName.LookupOptions, 14505 ) (HostName.LookupError || Io.QueueClosedError)!void { 14506 const t_io = io(t); 14507 const rc = HostName.ResolvConf.init(t_io) catch return error.ResolvConfParseFailed; 14508 14509 // Count dots, suppress search when >=ndots or name ends in 14510 // a dot, which is an explicit request for global scope. 14511 const dots = std.mem.countScalar(u8, host_name.bytes, '.'); 14512 const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len; 14513 const search = rc.search_buffer[0..search_len]; 14514 14515 var canon_name = host_name.bytes; 14516 14517 // Strip final dot for canon, fail if multiple trailing dots. 14518 if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; 14519 if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName; 14520 14521 // Name with search domain appended is set up in `canon_name`. This 14522 // both provides the desired default canonical name (if the requested 14523 // name is not a CNAME record) and serves as a buffer for passing the 14524 // full requested name to `lookupDns`. 14525 @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name); 14526 options.canonical_name_buffer[canon_name.len] = '.'; 14527 var it = std.mem.tokenizeAny(u8, search, " \t"); 14528 while (it.next()) |token| { 14529 @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token); 14530 const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len]; 14531 if (lookupDns(t, lookup_canon_name, &rc, resolved, options)) |result| { 14532 return result; 14533 } else |err| switch (err) { 14534 error.UnknownHostName, error.NoAddressReturned => continue, 14535 else => |e| return e, 14536 } 14537 } 14538 14539 const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len]; 14540 return lookupDns(t, lookup_canon_name, &rc, resolved, options); 14541 } 14542 14543 fn lookupDns( 14544 t: *Threaded, 14545 lookup_canon_name: []const u8, 14546 rc: *const HostName.ResolvConf, 14547 resolved: *Io.Queue(HostName.LookupResult), 14548 options: HostName.LookupOptions, 14549 ) (HostName.LookupError || Io.QueueClosedError)!void { 14550 const t_io = io(t); 14551 const family_records: [2]struct { af: IpAddress.Family, rr: HostName.DnsRecord } = .{ 14552 .{ .af = .ip6, .rr = .A }, 14553 .{ .af = .ip4, .rr = .AAAA }, 14554 }; 14555 var query_buffers: [2][280]u8 = undefined; 14556 var answer_buffer: [2 * 512]u8 = undefined; 14557 var queries_buffer: [2][]const u8 = undefined; 14558 var answers_buffer: [2][]const u8 = undefined; 14559 var nq: usize = 0; 14560 var answer_buffer_i: usize = 0; 14561 14562 for (family_records) |fr| { 14563 if (options.family != fr.af) { 14564 var entropy: [2]u8 = undefined; 14565 random(t, &entropy); 14566 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy); 14567 queries_buffer[nq] = query_buffers[nq][0..len]; 14568 nq += 1; 14569 } 14570 } 14571 14572 var ip4_mapped_buffer: [HostName.ResolvConf.max_nameservers]IpAddress = undefined; 14573 const ip4_mapped = ip4_mapped_buffer[0..rc.nameservers_len]; 14574 var any_ip6 = false; 14575 for (rc.nameservers(), ip4_mapped) |*ns, *m| { 14576 m.* = .{ .ip6 = .fromAny(ns.*) }; 14577 any_ip6 = any_ip6 or ns.* == .ip6; 14578 } 14579 var socket = s: { 14580 if (any_ip6) ip6: { 14581 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; 14582 const socket = ip6_addr.bind(t_io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) { 14583 error.AddressFamilyUnsupported => break :ip6, 14584 else => |e| return e, 14585 }; 14586 break :s socket; 14587 } 14588 any_ip6 = false; 14589 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; 14590 const socket = try ip4_addr.bind(t_io, .{ .mode = .dgram }); 14591 break :s socket; 14592 }; 14593 defer socket.close(t_io); 14594 14595 const mapped_nameservers = if (any_ip6) ip4_mapped else rc.nameservers(); 14596 const queries = queries_buffer[0..nq]; 14597 const answers = answers_buffer[0..queries.len]; 14598 var answers_remaining = answers.len; 14599 for (answers) |*answer| answer.len = 0; 14600 14601 // boot clock is chosen because time the computer is suspended should count 14602 // against time spent waiting for external messages to arrive. 14603 const clock: Io.Clock = .boot; 14604 var now_ts = clock.now(t_io); 14605 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); 14606 const attempt_duration: Io.Duration = .{ 14607 .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds), 14608 }; 14609 14610 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = clock.now(t_io)) { 14611 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers; 14612 { 14613 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; 14614 var message_i: usize = 0; 14615 for (queries, answers) |query, *answer| { 14616 if (answer.len != 0) continue; 14617 for (mapped_nameservers) |*ns| { 14618 message_buffer[message_i] = .{ 14619 .address = ns, 14620 .data_ptr = query.ptr, 14621 .data_len = query.len, 14622 }; 14623 message_i += 1; 14624 } 14625 } 14626 _ = netSendPosix(t, socket.handle, message_buffer[0..message_i], .{}); 14627 } 14628 14629 const timeout: Io.Timeout = .{ .deadline = .{ 14630 .raw = now_ts.addDuration(attempt_duration), 14631 .clock = clock, 14632 } }; 14633 14634 while (true) { 14635 var message_buffer: [max_messages]Io.net.IncomingMessage = @splat(.init); 14636 const buf = answer_buffer[answer_buffer_i..]; 14637 const recv_err, const recv_n = socket.receiveManyTimeout(t_io, &message_buffer, buf, .{}, timeout); 14638 for (message_buffer[0..recv_n]) |*received_message| { 14639 const reply = received_message.data; 14640 // Ignore non-identifiable packets. 14641 if (reply.len < 4) continue; 14642 14643 // Ignore replies from addresses we didn't send to. 14644 const ns = for (mapped_nameservers) |*ns| { 14645 if (received_message.from.eql(ns)) break ns; 14646 } else { 14647 continue; 14648 }; 14649 14650 // Find which query this answer goes with, if any. 14651 const query, const answer = for (queries, answers) |query, *answer| { 14652 if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer }; 14653 } else { 14654 continue; 14655 }; 14656 if (answer.len != 0) continue; 14657 14658 // Only accept positive or negative responses; retry immediately on 14659 // server failure, and ignore all other codes such as refusal. 14660 switch (reply[3] & 15) { 14661 0, 3 => { 14662 answer.* = reply; 14663 answer_buffer_i += reply.len; 14664 answers_remaining -= 1; 14665 if (answer_buffer.len - answer_buffer_i == 0) break :send; 14666 if (answers_remaining == 0) break :send; 14667 }, 14668 2 => { 14669 var retry_message: Io.net.OutgoingMessage = .{ 14670 .address = ns, 14671 .data_ptr = query.ptr, 14672 .data_len = query.len, 14673 }; 14674 _ = netSendPosix(t, socket.handle, (&retry_message)[0..1], .{}); 14675 continue; 14676 }, 14677 else => continue, 14678 } 14679 } 14680 if (recv_err) |err| switch (err) { 14681 error.Canceled => return error.Canceled, 14682 error.Timeout => continue :send, 14683 else => continue, 14684 }; 14685 } 14686 } else { 14687 return error.NameServerFailure; 14688 } 14689 14690 var addresses_len: usize = 0; 14691 var canonical_name: ?HostName = null; 14692 14693 for (answers) |answer| { 14694 var it = HostName.DnsResponse.init(answer) catch { 14695 // Here we could potentially add diagnostics to the results queue. 14696 continue; 14697 }; 14698 while (it.next() catch { 14699 // Here we could potentially add diagnostics to the results queue. 14700 continue; 14701 }) |record| switch (record.rr) { 14702 .A => { 14703 const data = record.packet[record.data_off..][0..record.data_len]; 14704 if (data.len != 4) return error.InvalidDnsARecord; 14705 try resolved.putOne(t_io, .{ .address = .{ .ip4 = .{ 14706 .bytes = data[0..4].*, 14707 .port = options.port, 14708 } } }); 14709 addresses_len += 1; 14710 }, 14711 .AAAA => { 14712 const data = record.packet[record.data_off..][0..record.data_len]; 14713 if (data.len != 16) return error.InvalidDnsAAAARecord; 14714 try resolved.putOne(t_io, .{ .address = .{ .ip6 = .{ 14715 .bytes = data[0..16].*, 14716 .port = options.port, 14717 } } }); 14718 addresses_len += 1; 14719 }, 14720 .CNAME => { 14721 _, canonical_name = HostName.expand(record.packet, record.data_off, options.canonical_name_buffer) catch 14722 return error.InvalidDnsCnameRecord; 14723 }, 14724 _ => continue, 14725 }; 14726 } 14727 14728 try resolved.putOne(t_io, .{ .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name } }); 14729 if (addresses_len == 0) return error.NoAddressReturned; 14730 } 14731 14732 fn lookupHosts( 14733 t: *Threaded, 14734 host_name: HostName, 14735 resolved: *Io.Queue(HostName.LookupResult), 14736 options: HostName.LookupOptions, 14737 ) !void { 14738 const t_io = io(t); 14739 const file = Dir.openFileAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) { 14740 error.FileNotFound, 14741 error.NotDir, 14742 error.AccessDenied, 14743 => return error.UnknownHostName, 14744 14745 error.Canceled => |e| return e, 14746 14747 else => { 14748 // Here we could add more detailed diagnostics to the results queue. 14749 return error.DetectingNetworkConfigurationFailed; 14750 }, 14751 }; 14752 defer file.close(t_io); 14753 14754 var line_buf: [512]u8 = undefined; 14755 var file_reader = file.reader(t_io, &line_buf); 14756 return lookupHostsReader(t, host_name, resolved, options, &file_reader.interface) catch |err| switch (err) { 14757 error.ReadFailed => switch (file_reader.err.?) { 14758 error.Canceled => |e| return e, 14759 else => { 14760 // Here we could add more detailed diagnostics to the results queue. 14761 return error.DetectingNetworkConfigurationFailed; 14762 }, 14763 }, 14764 error.Canceled, 14765 error.Closed, 14766 error.UnknownHostName, 14767 => |e| return e, 14768 }; 14769 } 14770 14771 fn lookupHostsReader( 14772 t: *Threaded, 14773 host_name: HostName, 14774 resolved: *Io.Queue(HostName.LookupResult), 14775 options: HostName.LookupOptions, 14776 reader: *Io.Reader, 14777 ) error{ ReadFailed, Canceled, UnknownHostName, Closed }!void { 14778 const t_io = io(t); 14779 var addresses_len: usize = 0; 14780 var canonical_name: ?HostName = null; 14781 while (true) { 14782 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { 14783 error.StreamTooLong => { 14784 // Skip lines that are too long. 14785 _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) { 14786 error.EndOfStream => break, 14787 error.ReadFailed => return error.ReadFailed, 14788 }; 14789 continue; 14790 }, 14791 error.ReadFailed => return error.ReadFailed, 14792 error.EndOfStream => break, 14793 }; 14794 reader.toss(@min(1, reader.bufferedLen())); 14795 var split_it = std.mem.splitScalar(u8, line, '#'); 14796 const no_comment_line = split_it.first(); 14797 14798 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); 14799 const ip_text = line_it.next() orelse continue; 14800 var first_name_text: ?[]const u8 = null; 14801 while (line_it.next()) |name_text| { 14802 if (std.mem.eql(u8, name_text, host_name.bytes)) { 14803 if (first_name_text == null) first_name_text = name_text; 14804 break; 14805 } 14806 } else continue; 14807 14808 if (canonical_name == null) { 14809 if (HostName.init(first_name_text.?)) |name_text| { 14810 if (name_text.bytes.len <= options.canonical_name_buffer.len) { 14811 const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len]; 14812 @memcpy(canonical_name_dest, name_text.bytes); 14813 canonical_name = .{ .bytes = canonical_name_dest }; 14814 } 14815 } else |_| {} 14816 } 14817 14818 if (options.family != .ip6) { 14819 if (IpAddress.parseIp4(ip_text, options.port)) |addr| { 14820 try resolved.putOne(t_io, .{ .address = addr }); 14821 addresses_len += 1; 14822 } else |_| {} 14823 } 14824 if (options.family != .ip4) { 14825 if (IpAddress.parseIp6(ip_text, options.port)) |addr| { 14826 try resolved.putOne(t_io, .{ .address = addr }); 14827 addresses_len += 1; 14828 } else |_| {} 14829 } 14830 } 14831 14832 if (canonical_name) |canon_name| try resolved.putOne(t_io, .{ .canonical_name = canon_name }); 14833 if (addresses_len == 0) return error.UnknownHostName; 14834 } 14835 14836 /// Writes DNS resolution query packet data to `w`; at most 280 bytes. 14837 fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: HostName.DnsRecord, entropy: [2]u8) usize { 14838 // This implementation is ported from musl libc. 14839 // A more idiomatic "ziggy" implementation would be welcome. 14840 var name = dname; 14841 if (std.mem.endsWith(u8, name, ".")) name.len -= 1; 14842 assert(name.len <= 253); 14843 const n = 17 + name.len + @intFromBool(name.len != 0); 14844 14845 // Construct query template - ID will be filled later 14846 q[0..2].* = entropy; 14847 @memset(q[2..n], 0); 14848 q[2] = @as(u8, op) * 8 + 1; 14849 q[5] = 1; 14850 @memcpy(q[13..][0..name.len], name); 14851 var i: usize = 13; 14852 var j: usize = undefined; 14853 while (q[i] != 0) : (i = j + 1) { 14854 j = i; 14855 while (q[j] != 0 and q[j] != '.') : (j += 1) {} 14856 // TODO determine the circumstances for this and whether or 14857 // not this should be an error. 14858 if (j - i - 1 > 62) unreachable; 14859 q[i - 1] = @intCast(j - i); 14860 } 14861 q[i + 1] = @intFromEnum(ty); 14862 q[i + 3] = class; 14863 return n; 14864 } 14865 14866 fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) HostName { 14867 const dest = canonical_name_buffer[0..name.len]; 14868 @memcpy(dest, name); 14869 return .{ .bytes = dest }; 14870 } 14871 14872 /// Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it: 14873 /// https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6 14874 /// 14875 /// This XNU version appears to correspond to 11.0.1: 14876 /// https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html 14877 /// 14878 /// ulock_wait() uses 32-bit micro-second timeouts where 0 = INFINITE or no-timeout 14879 /// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) 14880 const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11; 14881 14882 fn closeSocketWindows(s: ws2_32.SOCKET) void { 14883 const rc = ws2_32.closesocket(s); 14884 if (is_debug) switch (rc) { 14885 0 => {}, 14886 ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) { 14887 else => recoverableOsBugDetected(), 14888 }, 14889 else => recoverableOsBugDetected(), 14890 }; 14891 } 14892 14893 const Wsa = struct { 14894 status: Status = .uninitialized, 14895 mutex: Io.Mutex = .init, 14896 init_error: ?Wsa.InitError = null, 14897 14898 const Status = enum { uninitialized, initialized, failure }; 14899 14900 const InitError = error{ 14901 ProcessFdQuotaExceeded, 14902 NetworkDown, 14903 VersionUnsupported, 14904 BlockingOperationInProgress, 14905 } || Io.UnexpectedError; 14906 }; 14907 14908 fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { 14909 const wsa = &t.wsa; 14910 mutexLock(&wsa.mutex); 14911 defer mutexUnlock(&wsa.mutex); 14912 switch (wsa.status) { 14913 .uninitialized => { 14914 var wsa_data: ws2_32.WSADATA = undefined; 14915 const minor_version = 2; 14916 const major_version = 2; 14917 switch (ws2_32.WSAStartup((@as(windows.WORD, minor_version) << 8) | major_version, &wsa_data)) { 14918 0 => { 14919 wsa.status = .initialized; 14920 return; 14921 }, 14922 else => |err_int| { 14923 wsa.status = .failure; 14924 wsa.init_error = switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) { 14925 .SYSNOTREADY => error.NetworkDown, 14926 .VERNOTSUPPORTED => error.VersionUnsupported, 14927 .EINPROGRESS => error.BlockingOperationInProgress, 14928 .EPROCLIM => error.ProcessFdQuotaExceeded, 14929 else => |err| windows.unexpectedWsaError(err), 14930 }; 14931 }, 14932 } 14933 }, 14934 .initialized => return, 14935 .failure => {}, 14936 } 14937 return error.NetworkDown; 14938 } 14939 14940 fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} 14941 14942 const WindowsEnvironStrings = struct { 14943 PATH: ?[:0]const u16 = null, 14944 PATHEXT: ?[:0]const u16 = null, 14945 14946 fn scan() WindowsEnvironStrings { 14947 const peb = windows.peb(); 14948 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); 14949 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); 14950 const ptr = peb.ProcessParameters.Environment; 14951 14952 var result: WindowsEnvironStrings = .{}; 14953 var i: usize = 0; 14954 while (ptr[i] != 0) { 14955 const key_start = i; 14956 14957 // There are some special environment variables that start with =, 14958 // so we need a special case to not treat = as a key/value separator 14959 // if it's the first character. 14960 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 14961 if (ptr[key_start] == '=') i += 1; 14962 14963 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} 14964 const key_w = ptr[key_start..i]; 14965 14966 if (ptr[i] == '=') i += 1; 14967 14968 const value_start = i; 14969 while (ptr[i] != 0) : (i += 1) {} 14970 const value_w = ptr[value_start..i :0]; 14971 14972 i += 1; // skip over null byte 14973 14974 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| { 14975 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name); 14976 if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w; 14977 } 14978 } 14979 14980 return result; 14981 } 14982 }; 14983 14984 fn scanEnviron(t: *Threaded) void { 14985 mutexLock(&t.mutex); 14986 defer mutexUnlock(&t.mutex); 14987 if (t.environ_initialized) return; 14988 t.environ.scan(t.allocator); 14989 t.environ_initialized = true; 14990 } 14991 14992 fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError { 14993 const t: *Threaded = @ptrCast(@alignCast(userdata)); 14994 14995 if (!process.can_replace) return error.OperationUnsupported; 14996 14997 t.scanEnviron(); // for PATH 14998 const PATH = t.environ.string.PATH orelse default_PATH; 14999 15000 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); 15001 defer arena_allocator.deinit(); 15002 const arena = arena_allocator.allocator(); 15003 15004 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); 15005 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; 15006 15007 const env_block = env_block: { 15008 const prog_fd: i32 = -1; 15009 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ 15010 .zig_progress_fd = prog_fd, 15011 }); 15012 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ 15013 .zig_progress_fd = prog_fd, 15014 }); 15015 }; 15016 15017 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); 15018 } 15019 15020 fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { 15021 if (!process.can_replace) return error.OperationUnsupported; 15022 _ = userdata; 15023 _ = dir; 15024 _ = options; 15025 @panic("TODO processReplacePath"); 15026 } 15027 15028 fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { 15029 if (!process.can_spawn) return error.OperationUnsupported; 15030 _ = userdata; 15031 _ = dir; 15032 _ = options; 15033 @panic("TODO processSpawnPath"); 15034 } 15035 15036 const processSpawn = switch (native_os) { 15037 .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported, 15038 .windows => processSpawnWindows, 15039 else => processSpawnPosix, 15040 }; 15041 15042 fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { 15043 _ = userdata; 15044 _ = options; 15045 return error.OperationUnsupported; 15046 } 15047 15048 const Spawned = struct { 15049 pid: posix.pid_t, 15050 err_fd: posix.fd_t, 15051 stdin: ?File, 15052 stdout: ?File, 15053 stderr: ?File, 15054 }; 15055 15056 fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Spawned { 15057 // The child process does need to access (one end of) these pipes. However, 15058 // we must initially set CLOEXEC to avoid a race condition. If another thread 15059 // is racing to spawn a different child process, we don't want it to inherit 15060 // these FDs in any scenario; that would mean that, for instance, calls to 15061 // `poll` from the parent would not report the child's stdout as closing when 15062 // expected, since the other child may retain a reference to the write end of 15063 // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we 15064 // need to do something in the new child to make sure we preserve the reference 15065 // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it 15066 // turns out, we `dup2` everything anyway, so there's no need! 15067 const pipe_flags: posix.O = .{ .CLOEXEC = true }; 15068 15069 const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined; 15070 errdefer if (options.stdin == .pipe) { 15071 destroyPipe(stdin_pipe); 15072 }; 15073 15074 const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined; 15075 errdefer if (options.stdout == .pipe) { 15076 destroyPipe(stdout_pipe); 15077 }; 15078 15079 const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined; 15080 errdefer if (options.stderr == .pipe) { 15081 destroyPipe(stderr_pipe); 15082 }; 15083 15084 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); 15085 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; 15086 15087 const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none) pipe: { 15088 // We use CLOEXEC for the same reason as in `pipe_flags`. 15089 const pipe = try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); 15090 switch (native_os) { 15091 .linux => _ = posix.system.fcntl(pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2)), 15092 else => {}, 15093 } 15094 break :pipe pipe; 15095 } else .{ -1, -1 }; 15096 errdefer destroyPipe(prog_pipe); 15097 15098 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); 15099 defer arena_allocator.deinit(); 15100 const arena = arena_allocator.allocator(); 15101 15102 // The POSIX standard does not allow malloc() between fork() and execve(), 15103 // and this allocator may be a libc allocator. 15104 // I have personally observed the child process deadlocking when it tries 15105 // to call malloc() due to a heap allocation between fork() and execve(), 15106 // in musl v1.1.24. 15107 // Additionally, we want to reduce the number of possible ways things 15108 // can fail between fork() and execve(). 15109 // Therefore, we do all the allocation for the execve() before the fork(). 15110 // This means we must do the null-termination of argv and env vars here. 15111 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); 15112 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; 15113 15114 const prog_fileno = 3; 15115 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); 15116 15117 const env_block = env_block: { 15118 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; 15119 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ 15120 .zig_progress_fd = prog_fd, 15121 }); 15122 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ 15123 .zig_progress_fd = prog_fd, 15124 }); 15125 }; 15126 15127 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. 15128 // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. 15129 const err_pipe = try pipe2(.{ .CLOEXEC = true }); 15130 errdefer destroyPipe(err_pipe); 15131 15132 t.scanEnviron(); // for PATH 15133 const PATH = t.environ.string.PATH orelse default_PATH; 15134 15135 const pid_result: posix.pid_t = fork: { 15136 const rc = posix.system.fork(); 15137 switch (posix.errno(rc)) { 15138 .SUCCESS => break :fork @intCast(rc), 15139 .AGAIN => return error.SystemResources, 15140 .NOMEM => return error.SystemResources, 15141 .NOSYS => return error.OperationUnsupported, 15142 else => |err| return posix.unexpectedErrno(err), 15143 } 15144 }; 15145 15146 if (pid_result == 0) { 15147 defer comptime unreachable; // We are the child. 15148 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; 15149 const ep1 = err_pipe[1]; 15150 15151 setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); 15152 setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); 15153 setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); 15154 15155 switch (options.cwd) { 15156 .inherit => {}, 15157 .dir => |cwd| { 15158 fchdir(cwd.handle) catch |err| forkBail(ep1, err); 15159 }, 15160 .path => |cwd| { 15161 chdir(cwd) catch |err| forkBail(ep1, err); 15162 }, 15163 } 15164 15165 // Must happen after fchdir above, the cwd file descriptor might be 15166 // equal to prog_fileno and be clobbered by this dup2 call. 15167 if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err); 15168 15169 if (options.gid) |gid| { 15170 switch (posix.errno(posix.system.setregid(gid, gid))) { 15171 .SUCCESS => {}, 15172 .AGAIN => forkBail(ep1, error.ResourceLimitReached), 15173 .INVAL => forkBail(ep1, error.InvalidUserId), 15174 .PERM => forkBail(ep1, error.PermissionDenied), 15175 else => forkBail(ep1, error.Unexpected), 15176 } 15177 } 15178 15179 if (options.uid) |uid| { 15180 switch (posix.errno(posix.system.setreuid(uid, uid))) { 15181 .SUCCESS => {}, 15182 .AGAIN => forkBail(ep1, error.ResourceLimitReached), 15183 .INVAL => forkBail(ep1, error.InvalidUserId), 15184 .PERM => forkBail(ep1, error.PermissionDenied), 15185 else => forkBail(ep1, error.Unexpected), 15186 } 15187 } 15188 15189 if (options.pgid) |pid| { 15190 switch (posix.errno(posix.system.setpgid(0, pid))) { 15191 .SUCCESS => {}, 15192 .ACCES => forkBail(ep1, error.ProcessAlreadyExec), 15193 .INVAL => forkBail(ep1, error.InvalidProcessGroupId), 15194 .PERM => forkBail(ep1, error.PermissionDenied), 15195 else => forkBail(ep1, error.Unexpected), 15196 } 15197 } 15198 15199 if (options.start_suspended) { 15200 switch (posix.errno(posix.system.kill(0, .STOP))) { 15201 .SUCCESS => {}, 15202 .PERM => forkBail(ep1, error.PermissionDenied), 15203 else => forkBail(ep1, error.Unexpected), 15204 } 15205 } 15206 15207 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); 15208 forkBail(ep1, err); 15209 } 15210 15211 const pid: posix.pid_t = @intCast(pid_result); // We are the parent. 15212 errdefer comptime unreachable; // The child is forked; we must not error from now on 15213 15214 closeFd(err_pipe[1]); // make sure only the child holds the write end open 15215 15216 if (options.stdin == .pipe) closeFd(stdin_pipe[0]); 15217 if (options.stdout == .pipe) closeFd(stdout_pipe[1]); 15218 if (options.stderr == .pipe) closeFd(stderr_pipe[1]); 15219 15220 if (prog_pipe[1] != -1) closeFd(prog_pipe[1]); 15221 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); 15222 15223 return .{ 15224 .pid = pid, 15225 .err_fd = err_pipe[0], 15226 .stdin = switch (options.stdin) { 15227 .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } }, 15228 else => null, 15229 }, 15230 .stdout = switch (options.stdout) { 15231 .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } }, 15232 else => null, 15233 }, 15234 .stderr = switch (options.stderr) { 15235 .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } }, 15236 else => null, 15237 }, 15238 }; 15239 } 15240 15241 fn getDevNullFd(t: *Threaded) !posix.fd_t { 15242 { 15243 mutexLock(&t.mutex); 15244 defer mutexUnlock(&t.mutex); 15245 if (t.null_file.fd != -1) return t.null_file.fd; 15246 } 15247 const mode: u32 = 0; 15248 const syscall: Syscall = try .start(); 15249 while (true) { 15250 const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, mode); 15251 switch (posix.errno(rc)) { 15252 .SUCCESS => { 15253 syscall.finish(); 15254 const fresh_fd: posix.fd_t = @intCast(rc); 15255 mutexLock(&t.mutex); // Another thread might have won the race. 15256 defer mutexUnlock(&t.mutex); 15257 if (t.null_file.fd != -1) { 15258 closeFd(fresh_fd); 15259 return t.null_file.fd; 15260 } else { 15261 t.null_file.fd = fresh_fd; 15262 return fresh_fd; 15263 } 15264 }, 15265 .INTR => { 15266 try syscall.checkCancel(); 15267 continue; 15268 }, 15269 .ACCES => return syscall.fail(error.AccessDenied), 15270 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 15271 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), 15272 .NODEV => return syscall.fail(error.NoDevice), 15273 .NOENT => return syscall.fail(error.FileNotFound), 15274 .NOMEM => return syscall.fail(error.SystemResources), 15275 .PERM => return syscall.fail(error.PermissionDenied), 15276 else => |err| return syscall.unexpectedErrno(err), 15277 } 15278 } 15279 } 15280 15281 fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { 15282 const t: *Threaded = @ptrCast(@alignCast(userdata)); 15283 const spawned = try spawnPosix(t, options); 15284 defer closeFd(spawned.err_fd); 15285 15286 // Wait for the child to report any errors in or before `execvpe`. 15287 if (readIntFd(spawned.err_fd)) |child_err_int| { 15288 const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int)); 15289 return child_err; 15290 } else |read_err| switch (read_err) { 15291 error.EndOfStream => { 15292 // Write end closed by CLOEXEC at the time of the `execvpe` call, 15293 // indicating success. 15294 }, 15295 else => { 15296 // Problem reading the error from the error reporting pipe. We 15297 // don't know if the child is alive or dead. Better to assume it is 15298 // alive so the resource does not risk being leaked. 15299 }, 15300 } 15301 15302 return .{ 15303 .id = spawned.pid, 15304 .thread_handle = {}, 15305 .stdin = spawned.stdin, 15306 .stdout = spawned.stdout, 15307 .stderr = spawned.stderr, 15308 .request_resource_usage_statistics = options.request_resource_usage_statistics, 15309 }; 15310 } 15311 15312 fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term { 15313 if (native_os == .wasi) unreachable; 15314 const t: *Threaded = @ptrCast(@alignCast(userdata)); 15315 _ = t; 15316 switch (native_os) { 15317 .windows => return childWaitWindows(child), 15318 else => return childWaitPosix(child), 15319 } 15320 } 15321 15322 fn childKill(userdata: ?*anyopaque, child: *process.Child) void { 15323 if (native_os == .wasi) unreachable; 15324 const t: *Threaded = @ptrCast(@alignCast(userdata)); 15325 if (is_windows) { 15326 childKillWindows(t, child, 1) catch childCleanupWindows(child); 15327 } else { 15328 childKillPosix(child) catch {}; 15329 childCleanupPosix(child); 15330 } 15331 } 15332 15333 fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { 15334 _ = t; // TODO cancelation 15335 const handle = child.id.?; 15336 _ = windows.ntdll.RtlReportSilentProcessExit(handle, @enumFromInt(exit_code)); 15337 switch (windows.ntdll.NtTerminateProcess(handle, @enumFromInt(exit_code))) { 15338 .SUCCESS, .PROCESS_IS_TERMINATING => { 15339 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); 15340 _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout); 15341 childCleanupWindows(child); 15342 }, 15343 .ACCESS_DENIED => { 15344 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it 15345 // indicates that the process has already exited, but there may be 15346 // some rare edge cases where our process handle no longer has the 15347 // PROCESS_TERMINATE access right, so let's do another check to make 15348 // sure the process is really no longer running: 15349 const minimal_timeout: windows.LARGE_INTEGER = -1; 15350 return switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) { 15351 windows.NTSTATUS.WAIT_0 => error.AlreadyTerminated, 15352 else => error.AccessDenied, 15353 }; 15354 }, 15355 else => |status| return windows.unexpectedStatus(status), 15356 } 15357 } 15358 15359 fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { 15360 const handle = child.id.?; 15361 15362 const alertable_syscall: AlertableSyscall = try .start(); 15363 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); 15364 while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) { 15365 windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(), 15366 .USER_APC, .ALERTED, .TIMEOUT => { 15367 try alertable_syscall.checkCancel(); 15368 continue; 15369 }, 15370 else => |status| return alertable_syscall.unexpectedNtstatus(status), 15371 }; 15372 15373 var info: windows.PROCESS.BASIC_INFORMATION = undefined; 15374 const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess( 15375 handle, 15376 .BasicInformation, 15377 &info, 15378 @sizeOf(windows.PROCESS.BASIC_INFORMATION), 15379 null, 15380 )) { 15381 .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) }, 15382 else => .{ .unknown = 0 }, 15383 }; 15384 15385 childCleanupWindows(child); 15386 return term; 15387 } 15388 15389 fn childCleanupWindows(child: *process.Child) void { 15390 const handle = child.id orelse return; 15391 15392 if (child.request_resource_usage_statistics) { 15393 var vmc: windows.PROCESS.VM_COUNTERS = undefined; 15394 switch (windows.ntdll.NtQueryInformationProcess( 15395 handle, 15396 .VmCounters, 15397 &vmc, 15398 @sizeOf(windows.PROCESS.VM_COUNTERS), 15399 null, 15400 )) { 15401 .SUCCESS => child.resource_usage_statistics.rusage = vmc, 15402 else => child.resource_usage_statistics.rusage = null, 15403 } 15404 } 15405 15406 windows.CloseHandle(handle); 15407 child.id = null; 15408 15409 windows.CloseHandle(child.thread_handle); 15410 child.thread_handle = undefined; 15411 15412 if (child.stdin) |stdin| { 15413 windows.CloseHandle(stdin.handle); 15414 child.stdin = null; 15415 } 15416 if (child.stdout) |stdout| { 15417 windows.CloseHandle(stdout.handle); 15418 child.stdout = null; 15419 } 15420 if (child.stderr) |stderr| { 15421 windows.CloseHandle(stderr.handle); 15422 child.stderr = null; 15423 } 15424 } 15425 15426 fn childWaitPosix(child: *process.Child) process.Child.WaitError!process.Child.Term { 15427 defer childCleanupPosix(child); 15428 15429 const pid = child.id.?; 15430 15431 var ru: posix.rusage = undefined; 15432 const ru_ptr = if (child.request_resource_usage_statistics) &ru else null; 15433 15434 if (have_wait4) { 15435 var status: if (builtin.link_libc) c_int else u32 = undefined; 15436 const syscall: Syscall = try .start(); 15437 while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) { 15438 .SUCCESS => { 15439 syscall.finish(); 15440 if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; 15441 return statusToTerm(@bitCast(status)); 15442 }, 15443 .INTR => { 15444 try syscall.checkCancel(); 15445 continue; 15446 }, 15447 .CHILD => |err| return syscall.errnoBug(err), // Double-free. 15448 else => |err| return syscall.unexpectedErrno(err), 15449 }; 15450 } 15451 15452 if (have_waitid) { 15453 const linux = std.os.linux; // Bypass libc which has the wrong signature. 15454 var info: linux.siginfo_t = undefined; 15455 const syscall: Syscall = try .start(); 15456 while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) { 15457 .SUCCESS => { 15458 syscall.finish(); 15459 if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; 15460 const status: u32 = @bitCast(info.fields.common.second.sigchld.status); 15461 const code: linux.CLD = @enumFromInt(info.code); 15462 return switch (code) { 15463 .EXITED => .{ .exited = @truncate(status) }, 15464 .KILLED, .DUMPED => .{ .signal = @enumFromInt(status) }, 15465 .TRAPPED, .STOPPED => .{ .stopped = status }, 15466 _, .CONTINUED => .{ .unknown = status }, 15467 }; 15468 }, 15469 .INTR => { 15470 try syscall.checkCancel(); 15471 continue; 15472 }, 15473 .CHILD => |err| return syscall.errnoBug(err), // Double-free. 15474 else => |err| return syscall.unexpectedErrno(err), 15475 }; 15476 } 15477 15478 var status: if (builtin.link_libc) c_int else u32 = undefined; 15479 const syscall: Syscall = try .start(); 15480 while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { 15481 .SUCCESS => { 15482 syscall.finish(); 15483 return statusToTerm(@bitCast(status)); 15484 }, 15485 .INTR => { 15486 try syscall.checkCancel(); 15487 continue; 15488 }, 15489 .CHILD => |err| return syscall.errnoBug(err), // Double-free. 15490 else => |err| return syscall.unexpectedErrno(err), 15491 }; 15492 } 15493 15494 pub fn statusToTerm(status: u32) process.Child.Term { 15495 return if (posix.W.IFEXITED(status)) 15496 .{ .exited = posix.W.EXITSTATUS(status) } 15497 else if (posix.W.IFSIGNALED(status)) 15498 .{ .signal = posix.W.TERMSIG(status) } 15499 else if (posix.W.IFSTOPPED(status)) 15500 .{ .stopped = posix.W.STOPSIG(status) } 15501 else 15502 .{ .unknown = status }; 15503 } 15504 15505 fn childKillPosix(child: *process.Child) !void { 15506 // Entire function body is intentionally uncancelable. 15507 15508 const pid = child.id.?; 15509 15510 while (true) switch (posix.errno(posix.system.kill(pid, .TERM))) { 15511 .SUCCESS => break, 15512 .INTR => continue, 15513 .PERM => return error.PermissionDenied, 15514 .INVAL => |err| return errnoBug(err), 15515 .SRCH => |err| return errnoBug(err), 15516 else => |err| return posix.unexpectedErrno(err), 15517 }; 15518 15519 if (have_wait4) { 15520 var status: if (builtin.link_libc) c_int else u32 = undefined; 15521 while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, null))) { 15522 .SUCCESS => return, 15523 .INTR => continue, 15524 .CHILD => |err| return errnoBug(err), // Double-free. 15525 else => |err| return posix.unexpectedErrno(err), 15526 }; 15527 } 15528 15529 if (have_waitid) { 15530 const linux = std.os.linux; // Bypass libc which has the wrong signature. 15531 var info: linux.siginfo_t = undefined; 15532 while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, null))) { 15533 .SUCCESS => return, 15534 .INTR => continue, 15535 .CHILD => |err| return errnoBug(err), // Double-free. 15536 else => |err| return posix.unexpectedErrno(err), 15537 }; 15538 } 15539 15540 var status: if (builtin.link_libc) c_int else u32 = undefined; 15541 while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { 15542 .SUCCESS => return, 15543 .INTR => continue, 15544 .CHILD => |err| return errnoBug(err), // Double-free. 15545 else => |err| return posix.unexpectedErrno(err), 15546 }; 15547 } 15548 15549 fn childCleanupPosix(child: *process.Child) void { 15550 if (child.stdin) |stdin| { 15551 closeFd(stdin.handle); 15552 child.stdin = null; 15553 } 15554 if (child.stdout) |stdout| { 15555 closeFd(stdout.handle); 15556 child.stdout = null; 15557 } 15558 if (child.stderr) |stderr| { 15559 closeFd(stderr.handle); 15560 child.stderr = null; 15561 } 15562 child.id = null; 15563 } 15564 15565 /// Errors that can occur between fork() and execv() 15566 const ForkBailError = process.SpawnError || process.ReplaceError; 15567 15568 /// Child of fork calls this to report an error to the fork parent. Then the 15569 /// child exits. 15570 fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn { 15571 writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {}; 15572 // If we're linking libc, some naughty applications may have registered atexit handlers 15573 // which we really do not want to run in the fork child. I caught LLVM doing this and 15574 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne, 15575 // "Why'd you have to go and make things so complicated?" 15576 if (builtin.link_libc) { 15577 // The `_exit` function does nothing but make the exit syscall, unlike `exit`. 15578 std.c._exit(1); 15579 } else if (native_os == .linux and !builtin.single_threaded) { 15580 std.os.linux.exit_group(1); 15581 } else { 15582 posix.system.exit(1); 15583 } 15584 } 15585 15586 fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void { 15587 var buffer: [8]u8 = undefined; 15588 std.mem.writeInt(u64, &buffer, value, .little); 15589 // Skip the cancel mechanism. 15590 var i: usize = 0; 15591 while (true) { 15592 const rc = posix.system.write(fd, buffer[i..].ptr, buffer.len - i); 15593 switch (posix.errno(rc)) { 15594 .SUCCESS => { 15595 const n: usize = @intCast(rc); 15596 i += n; 15597 if (buffer.len - i == 0) return; 15598 }, 15599 .INTR => continue, 15600 else => return error.SystemResources, 15601 } 15602 } 15603 } 15604 15605 fn readIntFd(fd: posix.fd_t) !ErrInt { 15606 var buffer: [8]u8 = undefined; 15607 var i: usize = 0; 15608 while (true) { 15609 const rc = posix.system.read(fd, buffer[i..].ptr, buffer.len - i); 15610 switch (posix.errno(rc)) { 15611 .SUCCESS => { 15612 const n: usize = @intCast(rc); 15613 if (n == 0) break; 15614 i += n; 15615 continue; 15616 }, 15617 .INTR => continue, 15618 else => |err| return posix.unexpectedErrno(err), 15619 } 15620 } 15621 if (buffer.len - i != 0) return error.EndOfStream; 15622 return @intCast(std.mem.readInt(u64, &buffer, .little)); 15623 } 15624 15625 const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); 15626 15627 fn destroyPipe(pipe: [2]posix.fd_t) void { 15628 if (pipe[0] != -1) closeFd(pipe[0]); 15629 if (pipe[0] != pipe[1]) closeFd(pipe[1]); 15630 } 15631 15632 fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { 15633 switch (stdio) { 15634 .pipe => try dup2(pipe_fd, std_fileno), 15635 .close => closeFd(std_fileno), 15636 .inherit => {}, 15637 .ignore => try dup2(dev_null_fd, std_fileno), 15638 .file => |file| try dup2(file.handle, std_fileno), 15639 } 15640 } 15641 15642 fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { 15643 const t: *Threaded = @ptrCast(@alignCast(userdata)); 15644 15645 const any_ignore = 15646 options.stdin == .ignore or 15647 options.stdout == .ignore or 15648 options.stderr == .ignore; 15649 const nul_handle = if (any_ignore) try getNulDevice(t) else undefined; 15650 15651 const any_inherit = 15652 options.stdin == .inherit or 15653 options.stdout == .inherit or 15654 options.stderr == .inherit; 15655 const peb = if (any_inherit) windows.peb() else undefined; 15656 15657 const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{ 15658 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, 15659 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, 15660 .outbound = true, 15661 }) else undefined; 15662 errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle); 15663 15664 const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{ 15665 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, 15666 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, 15667 .inbound = true, 15668 }) else undefined; 15669 errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle); 15670 15671 const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{ 15672 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, 15673 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, 15674 .inbound = true, 15675 }) else undefined; 15676 errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle); 15677 15678 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{ 15679 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, 15680 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } }, 15681 .inbound = true, 15682 .quota = std.Progress.max_packet_len * 2, 15683 }) else undefined; 15684 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle); 15685 15686 var siStartInfo: windows.STARTUPINFOW = .{ 15687 .cb = @sizeOf(windows.STARTUPINFOW), 15688 .dwFlags = windows.STARTF_USESTDHANDLES, 15689 .hStdInput = switch (options.stdin) { 15690 .inherit => peb.ProcessParameters.hStdInput, 15691 .file => |file| try OpenFile(&.{}, .{ 15692 .access_mask = .{ 15693 .STANDARD = .{ .SYNCHRONIZE = true }, 15694 .GENERIC = .{ .READ = true }, 15695 }, 15696 .dir = file.handle, 15697 .sa = &.{ 15698 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), 15699 .lpSecurityDescriptor = null, 15700 .bInheritHandle = windows.TRUE, 15701 }, 15702 .creation = .OPEN, 15703 }), 15704 .ignore => nul_handle, 15705 .pipe => stdin_pipe[1], 15706 .close => null, 15707 }, 15708 .hStdOutput = switch (options.stdout) { 15709 .inherit => peb.ProcessParameters.hStdOutput, 15710 .file => |file| try OpenFile(&.{}, .{ 15711 .access_mask = .{ 15712 .STANDARD = .{ .SYNCHRONIZE = true }, 15713 .GENERIC = .{ .WRITE = true }, 15714 }, 15715 .dir = file.handle, 15716 .sa = &.{ 15717 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), 15718 .lpSecurityDescriptor = null, 15719 .bInheritHandle = windows.TRUE, 15720 }, 15721 .creation = .OPEN, 15722 }), 15723 .ignore => nul_handle, 15724 .pipe => stdout_pipe[1], 15725 .close => null, 15726 }, 15727 .hStdError = switch (options.stderr) { 15728 .inherit => peb.ProcessParameters.hStdError, 15729 .file => |file| try OpenFile(&.{}, .{ 15730 .access_mask = .{ 15731 .STANDARD = .{ .SYNCHRONIZE = true }, 15732 .GENERIC = .{ .WRITE = true }, 15733 }, 15734 .dir = file.handle, 15735 .sa = &.{ 15736 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), 15737 .lpSecurityDescriptor = null, 15738 .bInheritHandle = windows.TRUE, 15739 }, 15740 .creation = .OPEN, 15741 }), 15742 .ignore => nul_handle, 15743 .pipe => stderr_pipe[1], 15744 .close => null, 15745 }, 15746 15747 .lpReserved = null, 15748 .lpDesktop = null, 15749 .lpTitle = null, 15750 .dwX = 0, 15751 .dwY = 0, 15752 .dwXSize = 0, 15753 .dwYSize = 0, 15754 .dwXCountChars = 0, 15755 .dwYCountChars = 0, 15756 .dwFillAttribute = 0, 15757 .wShowWindow = 0, 15758 .cbReserved2 = 0, 15759 .lpReserved2 = null, 15760 }; 15761 var piProcInfo: windows.PROCESS.INFORMATION = undefined; 15762 15763 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); 15764 defer arena_allocator.deinit(); 15765 const arena = arena_allocator.allocator(); 15766 15767 const cwd_w = cwd_w: { 15768 switch (options.cwd) { 15769 .inherit => break :cwd_w null, 15770 .dir => |cwd_dir| { 15771 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); 15772 const dir_path = try GetFinalPathNameByHandle( 15773 cwd_dir.handle, 15774 .{}, 15775 dir_path_buffer[0..windows.PATH_MAX_WIDE], 15776 ); 15777 dir_path_buffer[dir_path.len] = 0; 15778 // Shrink the allocation down to just the path buffer + sentinel 15779 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1); 15780 break :cwd_w dir_path_buffer[0..dir_path.len :0]; 15781 }, 15782 .path => |cwd| { 15783 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); 15784 }, 15785 } 15786 }; 15787 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; 15788 15789 const env_block = env_block: { 15790 const prog_handle = if (options.progress_node.index != .none) 15791 prog_pipe[1] 15792 else 15793 windows.INVALID_HANDLE_VALUE; 15794 if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{ 15795 .zig_progress_handle = prog_handle, 15796 }); 15797 break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{ 15798 .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE, 15799 }); 15800 }; 15801 15802 const app_name_wtf8 = options.argv[0]; 15803 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); 15804 15805 // The cwd provided by options is in effect when choosing the executable 15806 // path to match POSIX semantics. 15807 const cwd_path_w = x: { 15808 // If the app name is absolute, then we need to use its dirname as the cwd 15809 if (app_name_is_absolute) { 15810 const dir = Dir.path.dirname(app_name_wtf8).?; 15811 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir); 15812 } else if (cwd_w) |cwd| { 15813 break :x cwd; 15814 } else { 15815 break :x &[_:0]u16{}; // empty for cwd 15816 } 15817 }; 15818 15819 // If the app name has more than just a filename, then we need to separate 15820 // that into the basename and dirname and use the dirname as an addition to 15821 // the cwd path. This is because NtQueryDirectoryFile cannot accept 15822 // FileName params with path separators. 15823 const app_basename_wtf8 = Dir.path.basename(app_name_wtf8); 15824 // If the app name is absolute, then the cwd will already have the app's dirname in it, 15825 // so only populate app_dirname if app name is a relative path with > 0 path separators. 15826 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null; 15827 const app_dirname_w: ?[:0]u16 = x: { 15828 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| { 15829 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8); 15830 } 15831 break :x null; 15832 }; 15833 const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8); 15834 15835 const flags: windows.CreateProcessFlags = .{ 15836 .create_suspended = options.start_suspended, 15837 .create_unicode_environment = true, 15838 .create_no_window = options.create_no_window, 15839 }; 15840 15841 run: { 15842 // We have to scan each time because the PEB environment pointer is not stable. 15843 const env_strings: WindowsEnvironStrings = .scan(); 15844 const PATH = env_strings.PATH orelse &[_:0]u16{}; 15845 const PATHEXT = env_strings.PATHEXT orelse &[_:0]u16{}; 15846 15847 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules 15848 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously 15849 // constructed arguments. 15850 // 15851 // We'll need to wait until we're actually trying to run the command to know for sure 15852 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually 15853 // serializing the command line until we determine how it should be serialized. 15854 var cmd_line_cache = WindowsCommandLineCache.init(arena, options.argv); 15855 15856 var app_buf: std.ArrayList(u16) = .empty; 15857 try app_buf.appendSlice(arena, app_name_w); 15858 15859 var dir_buf: std.ArrayList(u16) = .empty; 15860 15861 if (cwd_path_w.len > 0) { 15862 try dir_buf.appendSlice(arena, cwd_path_w); 15863 } 15864 if (app_dirname_w) |app_dir| { 15865 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep); 15866 try dir_buf.appendSlice(arena, app_dir); 15867 } 15868 15869 windowsCreateProcessPathExt( 15870 arena, 15871 &dir_buf, 15872 &app_buf, 15873 PATHEXT, 15874 &cmd_line_cache, 15875 env_block, 15876 cwd_w_ptr, 15877 flags, 15878 &siStartInfo, 15879 &piProcInfo, 15880 ) catch |no_path_err| { 15881 const original_err = switch (no_path_err) { 15882 // argv[0] contains unsupported characters that will never resolve to a valid exe. 15883 error.InvalidArg0 => return error.FileNotFound, 15884 error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e, 15885 error.UnrecoverableInvalidExe => return error.InvalidExe, 15886 else => |e| return e, 15887 }; 15888 15889 // If the app name had path separators, that disallows PATH searching, 15890 // and there's no need to search the PATH if the app name is absolute. 15891 // We still search the path if the cwd is absolute because of the 15892 // "cwd provided by options is in effect when choosing the executable path 15893 // to match posix semantics" behavior--we don't want to skip searching 15894 // the PATH just because we were trying to set the cwd of the child process. 15895 if (app_dirname_w != null or app_name_is_absolute) { 15896 return original_err; 15897 } 15898 15899 var it = std.mem.tokenizeScalar(u16, PATH, ';'); 15900 while (it.next()) |search_path| { 15901 dir_buf.clearRetainingCapacity(); 15902 try dir_buf.appendSlice(arena, search_path); 15903 15904 if (windowsCreateProcessPathExt( 15905 arena, 15906 &dir_buf, 15907 &app_buf, 15908 PATHEXT, 15909 &cmd_line_cache, 15910 env_block, 15911 cwd_w_ptr, 15912 flags, 15913 &siStartInfo, 15914 &piProcInfo, 15915 )) { 15916 break :run; 15917 } else |err| switch (err) { 15918 // argv[0] contains unsupported characters that will never resolve to a valid exe. 15919 error.InvalidArg0 => return error.FileNotFound, 15920 error.FileNotFound, error.AccessDenied, error.InvalidExe => continue, 15921 error.UnrecoverableInvalidExe => return error.InvalidExe, 15922 else => |e| return e, 15923 } 15924 } else { 15925 return original_err; 15926 } 15927 }; 15928 } 15929 15930 if (options.progress_node.index != .none) { 15931 windows.CloseHandle(prog_pipe[1]); 15932 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); 15933 } 15934 15935 return .{ 15936 .id = piProcInfo.hProcess, 15937 .thread_handle = piProcInfo.hThread, 15938 .stdin = stdin: switch (options.stdin) { 15939 .file => { 15940 windows.CloseHandle(siStartInfo.hStdInput.?); 15941 break :stdin null; 15942 }, 15943 .pipe => { 15944 windows.CloseHandle(stdin_pipe[1]); 15945 break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } }; 15946 }, 15947 else => null, 15948 }, 15949 .stdout = stdout: switch (options.stdout) { 15950 .file => { 15951 windows.CloseHandle(siStartInfo.hStdOutput.?); 15952 break :stdout null; 15953 }, 15954 .pipe => { 15955 windows.CloseHandle(stdout_pipe[1]); 15956 break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } }; 15957 }, 15958 else => null, 15959 }, 15960 .stderr = stderr: switch (options.stderr) { 15961 .file => { 15962 windows.CloseHandle(siStartInfo.hStdError.?); 15963 break :stderr null; 15964 }, 15965 .pipe => { 15966 windows.CloseHandle(stderr_pipe[1]); 15967 break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } }; 15968 }, 15969 else => null, 15970 }, 15971 .request_resource_usage_statistics = options.request_resource_usage_statistics, 15972 }; 15973 } 15974 15975 fn inheritFile() windows.HANDLE {} 15976 15977 fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { 15978 { 15979 mutexLock(&t.mutex); 15980 defer mutexUnlock(&t.mutex); 15981 if (t.random_file.handle) |handle| return handle; 15982 } 15983 15984 var fresh_handle: windows.HANDLE = undefined; 15985 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 15986 var syscall: Syscall = try .start(); 15987 while (true) switch (windows.ntdll.NtOpenFile( 15988 &fresh_handle, 15989 .{ 15990 .STANDARD = .{ .SYNCHRONIZE = true }, 15991 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, 15992 }, 15993 &.{ .ObjectName = @constCast(&windows.UNICODE_STRING.init( 15994 &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }, 15995 )) }, 15996 &io_status_block, 15997 .VALID_FLAGS, 15998 .{ .IO = .SYNCHRONOUS_NONALERT }, 15999 )) { 16000 .SUCCESS => { 16001 syscall.finish(); 16002 mutexLock(&t.mutex); // Another thread might have won the race. 16003 defer mutexUnlock(&t.mutex); 16004 if (t.random_file.handle) |prev_handle| { 16005 windows.CloseHandle(fresh_handle); 16006 return prev_handle; 16007 } else { 16008 t.random_file.handle = fresh_handle; 16009 return fresh_handle; 16010 } 16011 }, 16012 .CANCELLED => { 16013 try syscall.checkCancel(); 16014 continue; 16015 }, 16016 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.EntropyUnavailable), // Observed on wine 10.0 16017 else => return syscall.fail(error.EntropyUnavailable), 16018 }; 16019 } 16020 16021 fn getNulDevice(t: *Threaded) !windows.HANDLE { 16022 { 16023 mutexLock(&t.mutex); 16024 defer mutexUnlock(&t.mutex); 16025 if (t.null_file.handle) |handle| return handle; 16026 } 16027 16028 var fresh_handle: windows.HANDLE = undefined; 16029 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 16030 var syscall: Syscall = try .start(); 16031 while (true) switch (windows.ntdll.NtOpenFile( 16032 &fresh_handle, 16033 .{ 16034 .STANDARD = .{ .SYNCHRONIZE = true }, 16035 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } }, 16036 }, 16037 &.{ 16038 .Attributes = .{ .INHERIT = true }, 16039 .ObjectName = @constCast(&windows.UNICODE_STRING.init( 16040 &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, 16041 )), 16042 }, 16043 &io_status_block, 16044 .VALID_FLAGS, 16045 .{ .IO = .SYNCHRONOUS_NONALERT }, 16046 )) { 16047 .SUCCESS => { 16048 syscall.finish(); 16049 mutexLock(&t.mutex); // Another thread might have won the race. 16050 defer mutexUnlock(&t.mutex); 16051 if (t.null_file.handle) |prev_handle| { 16052 windows.CloseHandle(fresh_handle); 16053 return prev_handle; 16054 } else { 16055 t.null_file.handle = fresh_handle; 16056 return fresh_handle; 16057 } 16058 }, 16059 .CANCELLED => { 16060 try syscall.checkCancel(); 16061 continue; 16062 }, 16063 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 16064 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), 16065 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), 16066 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 16067 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 16068 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 16069 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), 16070 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied), 16071 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 16072 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), 16073 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), 16074 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 16075 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 16076 else => |status| return syscall.unexpectedNtstatus(status), 16077 }; 16078 } 16079 16080 fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { 16081 { 16082 mutexLock(&t.mutex); 16083 defer mutexUnlock(&t.mutex); 16084 if (t.pipe_file.handle) |handle| return handle; 16085 } 16086 16087 var fresh_handle: windows.HANDLE = undefined; 16088 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 16089 var syscall: Syscall = try .start(); 16090 while (true) switch (windows.ntdll.NtOpenFile( 16091 &fresh_handle, 16092 .{ .STANDARD = .{ .SYNCHRONIZE = true } }, 16093 &.{ 16094 .ObjectName = @constCast(&windows.UNICODE_STRING.init( 16095 &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' }, 16096 )), 16097 }, 16098 &io_status_block, 16099 .VALID_FLAGS, 16100 .{ .IO = .SYNCHRONOUS_NONALERT }, 16101 )) { 16102 .SUCCESS => { 16103 syscall.finish(); 16104 mutexLock(&t.mutex); // Another thread might have won the race. 16105 defer mutexUnlock(&t.mutex); 16106 if (t.pipe_file.handle) |prev_handle| { 16107 windows.CloseHandle(fresh_handle); 16108 return prev_handle; 16109 } else { 16110 t.pipe_file.handle = fresh_handle; 16111 return fresh_handle; 16112 } 16113 }, 16114 .DELETE_PENDING => { 16115 // This error means that there *was* a file in this location on 16116 // the file system, but it was deleted. However, the OS is not 16117 // finished with the deletion operation, and so this CreateFile 16118 // call has failed. There is not really a sane way to handle 16119 // this other than retrying the creation after the OS finishes 16120 // the deletion. 16121 syscall.finish(); 16122 try parking_sleep.sleep(.{ .duration = .{ 16123 .raw = .fromMilliseconds(1), 16124 .clock = .awake, 16125 } }); 16126 syscall = try .start(); 16127 continue; 16128 }, 16129 .CANCELLED => { 16130 try syscall.checkCancel(); 16131 continue; 16132 }, 16133 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 16134 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), 16135 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), 16136 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 16137 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 16138 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 16139 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), 16140 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied), 16141 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 16142 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), 16143 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), 16144 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 16145 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 16146 else => |status| return syscall.unexpectedNtstatus(status), 16147 }; 16148 } 16149 16150 /// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path. 16151 /// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path. 16152 /// Note: `app_buf` should not contain any leading path separators. 16153 /// Note: If the dir is the cwd, dir_buf should be empty (len = 0). 16154 fn windowsCreateProcessPathExt( 16155 arena: Allocator, 16156 dir_buf: *std.ArrayList(u16), 16157 app_buf: *std.ArrayList(u16), 16158 pathext: [:0]const u16, 16159 cmd_line_cache: *WindowsCommandLineCache, 16160 env_block: ?process.Environ.WindowsBlock, 16161 cwd_ptr: ?[*:0]u16, 16162 flags: windows.CreateProcessFlags, 16163 lpStartupInfo: *windows.STARTUPINFOW, 16164 lpProcessInformation: *windows.PROCESS.INFORMATION, 16165 ) !void { 16166 const app_name_len = app_buf.items.len; 16167 const dir_path_len = dir_buf.items.len; 16168 16169 if (app_name_len == 0) return error.FileNotFound; 16170 16171 defer app_buf.shrinkRetainingCapacity(app_name_len); 16172 defer dir_buf.shrinkRetainingCapacity(dir_path_len); 16173 16174 // The name of the game here is to avoid CreateProcessW calls at all costs, 16175 // and only ever try calling it when we have a real candidate for execution. 16176 // Secondarily, we want to minimize the number of syscalls used when checking 16177 // for each PATHEXT-appended version of the app name. 16178 // 16179 // An overview of the technique used: 16180 // - Open the search directory for iteration (either cwd or a path from PATH) 16181 // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to 16182 // check if anything that could possibly match either the unappended version 16183 // of the app name or any of the versions with a PATHEXT value appended exists. 16184 // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early 16185 // without needing to use PATHEXT at all. 16186 // 16187 // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence 16188 // for any directory that doesn't contain any possible matches, instead of having 16189 // to use a separate look up for each individual filename combination (unappended + 16190 // each PATHEXT appended). For directories where the wildcard *does* match something, 16191 // we iterate the matches and take note of any that are either the unappended version, 16192 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW 16193 // with the found versions in the appropriate order. 16194 var dir = dir: { 16195 // needs to be null-terminated 16196 try dir_buf.append(arena, 0); 16197 defer dir_buf.shrinkRetainingCapacity(dir_path_len); 16198 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; 16199 const prefixed_path = try wToPrefixedFileW(null, dir_path_z); 16200 break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ 16201 .iterate = true, 16202 }) catch |err| switch (err) { 16203 // These errors must not be ignored because they should not be able 16204 // to affect which file is chosen to execute. Also `error.Canceled` 16205 // must never be swallowed. 16206 error.Canceled, 16207 error.SystemResources, 16208 error.Unexpected, 16209 error.ProcessFdQuotaExceeded, 16210 error.SystemFdQuotaExceeded, 16211 => |e| return e, 16212 16213 error.AccessDenied, 16214 error.PermissionDenied, 16215 error.SymLinkLoop, 16216 error.FileNotFound, 16217 error.NotDir, 16218 error.NoDevice, 16219 error.NetworkNotFound, 16220 error.NameTooLong, 16221 error.BadPathName, 16222 => return error.FileNotFound, 16223 }; 16224 }; 16225 defer windows.CloseHandle(dir.handle); 16226 16227 // Add wildcard and null-terminator 16228 try app_buf.append(arena, '*'); 16229 try app_buf.append(arena, 0); 16230 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0]; 16231 16232 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries 16233 // returned per NtQueryDirectoryFile call. 16234 var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined; 16235 const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2); 16236 if (file_information_buf.len < file_info_maximum_single_entry_size) { 16237 @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry"); 16238 } 16239 var io_status: windows.IO_STATUS_BLOCK = undefined; 16240 16241 const num_supported_pathext = @typeInfo(process.WindowsExtension).@"enum".fields.len; 16242 var pathext_seen = [_]bool{false} ** num_supported_pathext; 16243 var any_pathext_seen = false; 16244 var unappended_exists = false; 16245 16246 // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions 16247 // of the app_name we should try to spawn. 16248 // Note: This is necessary because the order of the files returned is filesystem-dependent: 16249 // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists. 16250 // On FAT32, it's possible for something like `blah.exe.obj` to be returned first. 16251 while (true) { 16252 // If we get nothing with the wildcard, then we can just bail out 16253 // as we know appending PATHEXT will not yield anything. 16254 switch (windows.ntdll.NtQueryDirectoryFile( 16255 dir.handle, 16256 null, 16257 null, 16258 null, 16259 &io_status, 16260 &file_information_buf, 16261 file_information_buf.len, 16262 .Directory, 16263 windows.FALSE, // single result 16264 &.init(app_name_wildcard), 16265 windows.FALSE, // restart iteration 16266 )) { 16267 .SUCCESS => {}, 16268 .NO_SUCH_FILE => return error.FileNotFound, 16269 .NO_MORE_FILES => break, 16270 .ACCESS_DENIED => return error.AccessDenied, 16271 else => |status| return windows.unexpectedStatus(status), 16272 } 16273 16274 // According to the docs, this can only happen if there is not enough room in the 16275 // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry. 16276 // Therefore, this condition should not be possible to hit with the buffer size we use. 16277 std.debug.assert(io_status.Information != 0); 16278 16279 var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf }; 16280 while (it.next()) |info| { 16281 // Skip directories 16282 if (info.FileAttributes.DIRECTORY) continue; 16283 const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2]; 16284 // Because all results start with the app_name since we're using the wildcard `app_name*`, 16285 // if the length is equal to app_name then this is an exact match 16286 if (filename.len == app_name_len) { 16287 // Note: We can't break early here because it's possible that the unappended version 16288 // fails to spawn, in which case we still want to try the PATHEXT appended versions. 16289 unappended_exists = true; 16290 } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| { 16291 pathext_seen[@intFromEnum(pathext_ext)] = true; 16292 any_pathext_seen = true; 16293 } 16294 } 16295 } 16296 16297 const unappended_err = unappended: { 16298 if (unappended_exists) { 16299 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { 16300 '/', '\\' => {}, 16301 else => try dir_buf.append(arena, Dir.path.sep), 16302 }; 16303 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); 16304 try dir_buf.append(arena, 0); 16305 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; 16306 16307 const is_bat_or_cmd = bat_or_cmd: { 16308 const app_name = app_buf.items[0..app_name_len]; 16309 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :bat_or_cmd false; 16310 const ext = app_name[ext_start..]; 16311 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false; 16312 switch (ext_enum) { 16313 .cmd, .bat => break :bat_or_cmd true, 16314 else => break :bat_or_cmd false, 16315 } 16316 }; 16317 const cmd_line_w = if (is_bat_or_cmd) 16318 try cmd_line_cache.scriptCommandLine(full_app_name) 16319 else 16320 try cmd_line_cache.commandLine(); 16321 const app_name_w = if (is_bat_or_cmd) 16322 try cmd_line_cache.cmdExePath() 16323 else 16324 full_app_name; 16325 16326 if (windowsCreateProcess( 16327 app_name_w.ptr, 16328 cmd_line_w.ptr, 16329 env_block, 16330 cwd_ptr, 16331 flags, 16332 lpStartupInfo, 16333 lpProcessInformation, 16334 )) |_| { 16335 return; 16336 } else |err| switch (err) { 16337 error.FileNotFound, 16338 error.AccessDenied, 16339 => break :unappended err, 16340 error.InvalidExe => { 16341 // On InvalidExe, if the extension of the app name is .exe then 16342 // it's treated as an unrecoverable error. Otherwise, it'll be 16343 // skipped as normal. 16344 const app_name = app_buf.items[0..app_name_len]; 16345 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err; 16346 const ext = app_name[ext_start..]; 16347 if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { 16348 return error.UnrecoverableInvalidExe; 16349 } 16350 break :unappended err; 16351 }, 16352 else => return err, 16353 } 16354 } 16355 break :unappended error.FileNotFound; 16356 }; 16357 16358 if (!any_pathext_seen) return unappended_err; 16359 16360 // Now try any PATHEXT appended versions that we've seen 16361 var ext_it = std.mem.tokenizeScalar(u16, pathext, ';'); 16362 while (ext_it.next()) |ext| { 16363 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue; 16364 if (!pathext_seen[@intFromEnum(ext_enum)]) continue; 16365 16366 dir_buf.shrinkRetainingCapacity(dir_path_len); 16367 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { 16368 '/', '\\' => {}, 16369 else => try dir_buf.append(arena, Dir.path.sep), 16370 }; 16371 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); 16372 try dir_buf.appendSlice(arena, ext); 16373 try dir_buf.append(arena, 0); 16374 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; 16375 16376 const is_bat_or_cmd = switch (ext_enum) { 16377 .cmd, .bat => true, 16378 else => false, 16379 }; 16380 const cmd_line_w = if (is_bat_or_cmd) 16381 try cmd_line_cache.scriptCommandLine(full_app_name) 16382 else 16383 try cmd_line_cache.commandLine(); 16384 const app_name_w = if (is_bat_or_cmd) 16385 try cmd_line_cache.cmdExePath() 16386 else 16387 full_app_name; 16388 16389 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { 16390 return; 16391 } else |err| switch (err) { 16392 error.FileNotFound => continue, 16393 error.AccessDenied => continue, 16394 error.InvalidExe => { 16395 // On InvalidExe, if the extension of the app name is .exe then 16396 // it's treated as an unrecoverable error. Otherwise, it'll be 16397 // skipped as normal. 16398 if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { 16399 return error.UnrecoverableInvalidExe; 16400 } 16401 continue; 16402 }, 16403 else => return err, 16404 } 16405 } 16406 16407 return unappended_err; 16408 } 16409 16410 fn windowsCreateProcess( 16411 app_name: [*:0]u16, 16412 cmd_line: [*:0]u16, 16413 env_block: ?process.Environ.WindowsBlock, 16414 cwd_ptr: ?[*:0]u16, 16415 flags: windows.CreateProcessFlags, 16416 lpStartupInfo: *windows.STARTUPINFOW, 16417 lpProcessInformation: *windows.PROCESS.INFORMATION, 16418 ) !void { 16419 const syscall: Syscall = try .start(); 16420 while (true) { 16421 if (windows.kernel32.CreateProcessW( 16422 app_name, 16423 cmd_line, 16424 null, 16425 null, 16426 windows.TRUE, 16427 flags, 16428 if (env_block) |block| block.slice.ptr else null, 16429 cwd_ptr, 16430 lpStartupInfo, 16431 lpProcessInformation, 16432 ) != 0) { 16433 return syscall.finish(); 16434 } else switch (windows.GetLastError()) { 16435 .INVALID_PARAMETER => unreachable, 16436 .OPERATION_ABORTED => { 16437 try syscall.checkCancel(); 16438 continue; 16439 }, 16440 .FILE_NOT_FOUND => return syscall.fail(error.FileNotFound), 16441 .PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 16442 .DIRECTORY => return syscall.fail(error.FileNotFound), 16443 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 16444 .INVALID_NAME => return syscall.fail(error.InvalidName), 16445 .FILENAME_EXCED_RANGE => return syscall.fail(error.NameTooLong), 16446 .SHARING_VIOLATION => return syscall.fail(error.FileBusy), 16447 .COMMITMENT_LIMIT => return syscall.fail(error.SystemResources), 16448 16449 // These are all the system errors that are mapped to ENOEXEC by 16450 // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error 16451 // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) 16452 // or urt/misc/errno.cpp (newer SDK) in the Windows SDK. 16453 .BAD_FORMAT, 16454 .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp 16455 .INVALID_STACKSEG, 16456 .INVALID_MODULETYPE, 16457 .INVALID_EXE_SIGNATURE, 16458 .EXE_MARKED_INVALID, 16459 .BAD_EXE_FORMAT, 16460 .ITERATED_DATA_EXCEEDS_64k, 16461 .INVALID_MINALLOCSIZE, 16462 .DYNLINK_FROM_INVALID_RING, 16463 .IOPL_NOT_ENABLED, 16464 .INVALID_SEGDPL, 16465 .AUTODATASEG_EXCEEDS_64k, 16466 .RING2SEG_MUST_BE_MOVABLE, 16467 .RELOC_CHAIN_XEEDS_SEGLIM, 16468 .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp 16469 // This one is not mapped to ENOEXEC but it is possible, for example 16470 // when calling CreateProcessW on a plain text file with a .exe extension 16471 .EXE_MACHINE_TYPE_MISMATCH, 16472 => return syscall.fail(error.InvalidExe), 16473 16474 else => |err| { 16475 syscall.finish(); 16476 return windows.unexpectedError(err); 16477 }, 16478 } 16479 } 16480 } 16481 16482 /// Case-insensitive WTF-16 lookup 16483 fn windowsCreateProcessSupportsExtension(ext: []const u16) ?process.WindowsExtension { 16484 comptime { 16485 // Ensures keeping this function in sync with the enum. 16486 const fields = @typeInfo(process.WindowsExtension).@"enum".fields; 16487 assert(fields.len == 4); 16488 assert(@intFromEnum(process.WindowsExtension.bat) == 0); 16489 assert(@intFromEnum(process.WindowsExtension.cmd) == 1); 16490 assert(@intFromEnum(process.WindowsExtension.com) == 2); 16491 assert(@intFromEnum(process.WindowsExtension.exe) == 3); 16492 } 16493 16494 if (ext.len != 4) return null; 16495 const State = enum { 16496 start, 16497 dot, 16498 b, 16499 ba, 16500 c, 16501 cm, 16502 co, 16503 e, 16504 ex, 16505 }; 16506 var state: State = .start; 16507 for (ext) |c| switch (state) { 16508 .start => switch (c) { 16509 '.' => state = .dot, 16510 else => return null, 16511 }, 16512 .dot => switch (c) { 16513 'b', 'B' => state = .b, 16514 'c', 'C' => state = .c, 16515 'e', 'E' => state = .e, 16516 else => return null, 16517 }, 16518 .b => switch (c) { 16519 'a', 'A' => state = .ba, 16520 else => return null, 16521 }, 16522 .c => switch (c) { 16523 'm', 'M' => state = .cm, 16524 'o', 'O' => state = .co, 16525 else => return null, 16526 }, 16527 .e => switch (c) { 16528 'x', 'X' => state = .ex, 16529 else => return null, 16530 }, 16531 .ba => switch (c) { 16532 't', 'T' => return .bat, 16533 else => return null, 16534 }, 16535 .cm => switch (c) { 16536 'd', 'D' => return .cmd, 16537 else => return null, 16538 }, 16539 .co => switch (c) { 16540 'm', 'M' => return .com, 16541 else => return null, 16542 }, 16543 .ex => switch (c) { 16544 'e', 'E' => return .exe, 16545 else => return null, 16546 }, 16547 }; 16548 return null; 16549 } 16550 16551 test windowsCreateProcessSupportsExtension { 16552 try std.testing.expectEqual(process.WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?); 16553 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null); 16554 } 16555 16556 /// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW. 16557 /// 16558 /// Serialization is done on-demand and the result is cached in order to allow for: 16559 /// - Only serializing the particular type of command line needed (`.bat`/`.cmd` 16560 /// command line serialization is different from `.exe`/etc) 16561 /// - Reusing the serialized command lines if necessary (i.e. if the execution 16562 /// of a command fails and the PATH is going to be continued to be searched 16563 /// for more candidates) 16564 const WindowsCommandLineCache = struct { 16565 cmd_line: ?[:0]u16 = null, 16566 script_cmd_line: ?[:0]u16 = null, 16567 cmd_exe_path: ?[:0]u16 = null, 16568 argv: []const []const u8, 16569 allocator: Allocator, 16570 16571 fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache { 16572 return .{ 16573 .allocator = allocator, 16574 .argv = argv, 16575 }; 16576 } 16577 16578 fn deinit(self: *WindowsCommandLineCache) void { 16579 if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line); 16580 if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line); 16581 if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path); 16582 } 16583 16584 fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 { 16585 if (self.cmd_line == null) { 16586 self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv); 16587 } 16588 return self.cmd_line.?; 16589 } 16590 16591 /// Not cached, since the path to the batch script will change during PATH searching. 16592 /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched, 16593 /// then script_path should include both the search path and the script filename 16594 /// (this allows avoiding cmd.exe having to search the PATH again). 16595 fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 { 16596 if (self.script_cmd_line) |v| self.allocator.free(v); 16597 self.script_cmd_line = try argvToScriptCommandLineWindows( 16598 self.allocator, 16599 script_path, 16600 self.argv[1..], 16601 ); 16602 return self.script_cmd_line.?; 16603 } 16604 16605 fn cmdExePath(self: *WindowsCommandLineCache) Allocator.Error![:0]u16 { 16606 if (self.cmd_exe_path == null) { 16607 // Remove trailing slash from system directory path; we'll re-add it below 16608 const system_dir = std.mem.trimEnd(u16, windows.getSystemDirectoryWtf16Le(), &.{ '/', '\\' }); 16609 const suffix = std.unicode.utf8ToUtf16LeStringLiteral("\\cmd.exe"); 16610 const buf = try self.allocator.allocSentinel(u16, system_dir.len + suffix.len, 0); 16611 errdefer comptime unreachable; 16612 @memcpy(buf[0..system_dir.len], system_dir); 16613 @memcpy(buf[system_dir.len..], suffix); 16614 self.cmd_exe_path = buf; 16615 } 16616 return self.cmd_exe_path.?; 16617 } 16618 }; 16619 16620 const ArgvToScriptCommandLineError = error{ 16621 OutOfMemory, 16622 InvalidWtf8, 16623 /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed 16624 /// within arguments when executing a `.bat`/`.cmd` script. 16625 /// - NUL/LF signifiies end of arguments, so anything afterwards 16626 /// would be lost after execution. 16627 /// - CR is stripped by `cmd.exe`, so any CR codepoints 16628 /// would be lost after execution. 16629 InvalidBatchScriptArg, 16630 }; 16631 16632 /// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific 16633 /// escaping rules. The caller owns the returned slice. 16634 /// 16635 /// Escapes `argv` using the suggested mitigation against arbitrary command execution from: 16636 /// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ 16637 /// 16638 /// The return of this function will look like 16639 /// `cmd.exe /d /e:ON /v:OFF /c "<escaped command line>"` 16640 /// and should be used as the `lpCommandLine` of `CreateProcessW`, while the return of 16641 /// `WindowsCommandLineCache.cmdExePath` should be used as `lpApplicationName`. 16642 /// 16643 /// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise. 16644 /// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem. 16645 fn argvToScriptCommandLineWindows( 16646 allocator: Allocator, 16647 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD. 16648 /// The script must have been verified to exist at this path before calling this function. 16649 script_path: []const u16, 16650 /// Arguments, not including the script name itself. Expected to be encoded as WTF-8. 16651 script_args: []const []const u8, 16652 ) ArgvToScriptCommandLineError![:0]u16 { 16653 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 64); 16654 defer buf.deinit(); 16655 16656 // `/d` disables execution of AutoRun commands. 16657 // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation: 16658 // > If delayed expansion is enabled via the registry value DelayedExpansion, 16659 // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option. 16660 // > Escaping for % requires the command extension to be enabled. 16661 // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option. 16662 // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ 16663 buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \""); 16664 16665 // Always quote the path to the script arg 16666 buf.appendAssumeCapacity('"'); 16667 // We always want the path to the batch script to include a path separator in order to 16668 // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary 16669 // command execution mitigation, we just know exactly what script we want to execute 16670 // at this point, and potentially making cmd.exe re-find it is unnecessary. 16671 // 16672 // If the script path does not have a path separator, then we know its relative to CWD and 16673 // we can just put `.\` in the front. 16674 if (std.mem.findAny(u16, script_path, &[_]u16{ 16675 std.mem.nativeToLittle(u16, '\\'), std.mem.nativeToLittle(u16, '/'), 16676 }) == null) { 16677 try buf.appendSlice(".\\"); 16678 } 16679 // Note that we don't do any escaping/mitigations for this argument, since the relevant 16680 // characters (", %, etc) are illegal in file paths and this function should only be called 16681 // with script paths that have been verified to exist. 16682 try std.unicode.wtf16LeToWtf8ArrayList(&buf, script_path); 16683 buf.appendAssumeCapacity('"'); 16684 16685 for (script_args) |arg| { 16686 // Literal carriage returns get stripped when run through cmd.exe 16687 // and NUL/newlines act as 'end of command.' Because of this, it's basically 16688 // always a mistake to include these characters in argv, so it's 16689 // an error condition in order to ensure that the return of this 16690 // function can always roundtrip through cmd.exe. 16691 if (std.mem.findAny(u8, arg, "\x00\r\n") != null) { 16692 return error.InvalidBatchScriptArg; 16693 } 16694 16695 // Separate args with a space. 16696 try buf.append(' '); 16697 16698 // Need to quote if the argument is empty (otherwise the arg would just be lost) 16699 // or if the last character is a `\`, since then something like "%~2" in a .bat 16700 // script would cause the closing " to be escaped which we don't want. 16701 var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\'; 16702 if (!needs_quotes) { 16703 for (arg) |c| { 16704 switch (c) { 16705 // Known good characters that don't need to be quoted 16706 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {}, 16707 // When in doubt, quote 16708 else => { 16709 needs_quotes = true; 16710 break; 16711 }, 16712 } 16713 } 16714 } 16715 if (needs_quotes) { 16716 try buf.append('"'); 16717 } 16718 var backslashes: usize = 0; 16719 for (arg) |c| { 16720 switch (c) { 16721 '\\' => { 16722 backslashes += 1; 16723 }, 16724 '"' => { 16725 try buf.appendNTimes('\\', backslashes); 16726 try buf.append('"'); 16727 backslashes = 0; 16728 }, 16729 // Replace `%` with `%%cd:~,%`. 16730 // 16731 // cmd.exe allows extracting a substring from an environment 16732 // variable with the syntax: `%foo:~<start_index>,<end_index>%`. 16733 // Therefore, `%cd:~,%` will always expand to an empty string 16734 // since both the start and end index are blank, and it is assumed 16735 // that `%cd%` is always available since it is a built-in variable 16736 // that corresponds to the current directory. 16737 // 16738 // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%` 16739 // will stop `%foo%` from being expanded and *after* expansion 16740 // we'll still be left with `%foo%` (the literal string). 16741 '%' => { 16742 // the trailing `%` is appended outside the switch 16743 try buf.appendSlice("%%cd:~,"); 16744 backslashes = 0; 16745 }, 16746 else => { 16747 backslashes = 0; 16748 }, 16749 } 16750 try buf.append(c); 16751 } 16752 if (needs_quotes) { 16753 try buf.appendNTimes('\\', backslashes); 16754 try buf.append('"'); 16755 } 16756 } 16757 16758 try buf.append('"'); 16759 16760 return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); 16761 } 16762 16763 const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 }; 16764 16765 /// Serializes `argv` to a Windows command-line string suitable for passing to a child process and 16766 /// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice. 16767 /// 16768 /// To avoid arbitrary command execution, this function should not be used when spawning `.bat`/`.cmd` scripts. 16769 /// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ 16770 /// 16771 /// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead. 16772 fn argvToCommandLineWindows( 16773 allocator: Allocator, 16774 argv: []const []const u8, 16775 ) ArgvToCommandLineError![:0]u16 { 16776 var buf = std.array_list.Managed(u8).init(allocator); 16777 defer buf.deinit(); 16778 16779 if (argv.len != 0) { 16780 const arg0 = argv[0]; 16781 16782 // The first argument must be quoted if it contains spaces or ASCII control characters 16783 // (excluding DEL). It also follows special quoting rules where backslashes have no special 16784 // interpretation, which makes it impossible to pass certain first arguments containing 16785 // double quotes to a child process without characters from the first argument leaking into 16786 // subsequent ones (which could have security implications). 16787 // 16788 // Empty arguments technically don't need quotes, but we quote them anyway for maximum 16789 // compatibility with different implementations of the 'CommandLineToArgvW' algorithm. 16790 // 16791 // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject 16792 // all first arguments containing double quotes, even ones that we could theoretically 16793 // serialize in unquoted form. 16794 var needs_quotes = arg0.len == 0; 16795 for (arg0) |c| { 16796 if (c <= ' ') { 16797 needs_quotes = true; 16798 } else if (c == '"') { 16799 return error.InvalidArg0; 16800 } 16801 } 16802 if (needs_quotes) { 16803 try buf.append('"'); 16804 try buf.appendSlice(arg0); 16805 try buf.append('"'); 16806 } else { 16807 try buf.appendSlice(arg0); 16808 } 16809 16810 for (argv[1..]) |arg| { 16811 try buf.append(' '); 16812 16813 // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes, 16814 // or if they are empty. For simplicity and for maximum compatibility with different 16815 // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII 16816 // control characters (again, excluding DEL). 16817 needs_quotes = for (arg) |c| { 16818 if (c <= ' ' or c == '"') { 16819 break true; 16820 } 16821 } else arg.len == 0; 16822 if (!needs_quotes) { 16823 try buf.appendSlice(arg); 16824 continue; 16825 } 16826 16827 try buf.append('"'); 16828 var backslash_count: usize = 0; 16829 for (arg) |byte| { 16830 switch (byte) { 16831 '\\' => { 16832 backslash_count += 1; 16833 }, 16834 '"' => { 16835 try buf.appendNTimes('\\', backslash_count * 2 + 1); 16836 try buf.append('"'); 16837 backslash_count = 0; 16838 }, 16839 else => { 16840 try buf.appendNTimes('\\', backslash_count); 16841 try buf.append(byte); 16842 backslash_count = 0; 16843 }, 16844 } 16845 } 16846 try buf.appendNTimes('\\', backslash_count * 2); 16847 try buf.append('"'); 16848 } 16849 } 16850 16851 return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); 16852 } 16853 16854 test argvToCommandLineWindows { 16855 const t = testArgvToCommandLineWindows; 16856 16857 try t(&.{ 16858 \\C:\Program Files\zig\zig.exe 16859 , 16860 \\run 16861 , 16862 \\.\src\main.zig 16863 , 16864 \\-target 16865 , 16866 \\x86_64-windows-gnu 16867 , 16868 \\-O 16869 , 16870 \\ReleaseSafe 16871 , 16872 \\-- 16873 , 16874 \\--emoji=🗿 16875 , 16876 \\--eval=new Regex("Dwayne \"The Rock\" Johnson") 16877 , 16878 }, 16879 \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 "--eval=new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" 16880 ); 16881 16882 try t(&.{}, ""); 16883 try t(&.{""}, "\"\""); 16884 try t(&.{" "}, "\" \""); 16885 try t(&.{"\t"}, "\"\t\""); 16886 try t(&.{"\x07"}, "\"\x07\""); 16887 try t(&.{"🦎"}, "🦎"); 16888 16889 try t( 16890 &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" }, 16891 "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee", 16892 ); 16893 16894 try t( 16895 &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" }, 16896 "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"", 16897 ); 16898 16899 try std.testing.expectError( 16900 error.InvalidArg0, 16901 argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}), 16902 ); 16903 try std.testing.expectError( 16904 error.InvalidArg0, 16905 argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}), 16906 ); 16907 try std.testing.expectError( 16908 error.InvalidArg0, 16909 argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}), 16910 ); 16911 } 16912 16913 fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void { 16914 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv); 16915 defer std.testing.allocator.free(cmd_line_w); 16916 16917 const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w); 16918 defer std.testing.allocator.free(cmd_line); 16919 16920 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); 16921 } 16922 16923 fn posixExecv( 16924 arg0_expand: process.ArgExpansion, 16925 file: [*:0]const u8, 16926 child_argv: [*:null]?[*:0]const u8, 16927 env_block: process.Environ.PosixBlock, 16928 PATH: []const u8, 16929 ) process.ReplaceError { 16930 const file_slice = std.mem.sliceTo(file, 0); 16931 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block); 16932 16933 // Use of PATH_MAX here is valid as the path_buf will be passed 16934 // directly to the operating system in posixExecvPath. 16935 var path_buf: [posix.PATH_MAX]u8 = undefined; 16936 var it = std.mem.tokenizeScalar(u8, PATH, ':'); 16937 var seen_eacces = false; 16938 var err: process.ReplaceError = error.FileNotFound; 16939 16940 // In case of expanding arg0 we must put it back if we return with an error. 16941 const prev_arg0 = child_argv[0]; 16942 defer switch (arg0_expand) { 16943 .expand => child_argv[0] = prev_arg0, 16944 .no_expand => {}, 16945 }; 16946 16947 while (it.next()) |search_path| { 16948 const path_len = search_path.len + file_slice.len + 1; 16949 if (path_buf.len < path_len + 1) return error.NameTooLong; 16950 @memcpy(path_buf[0..search_path.len], search_path); 16951 path_buf[search_path.len] = '/'; 16952 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); 16953 path_buf[path_len] = 0; 16954 const full_path = path_buf[0..path_len :0].ptr; 16955 switch (arg0_expand) { 16956 .expand => child_argv[0] = full_path, 16957 .no_expand => {}, 16958 } 16959 err = posixExecvPath(full_path, child_argv, env_block); 16960 switch (err) { 16961 error.AccessDenied => seen_eacces = true, 16962 error.FileNotFound, error.NotDir => {}, 16963 else => |e| return e, 16964 } 16965 } 16966 if (seen_eacces) return error.AccessDenied; 16967 return err; 16968 } 16969 16970 /// This function ignores PATH environment variable. 16971 pub fn posixExecvPath( 16972 path: [*:0]const u8, 16973 child_argv: [*:null]const ?[*:0]const u8, 16974 env_block: process.Environ.PosixBlock, 16975 ) process.ReplaceError { 16976 try Thread.checkCancel(); 16977 switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) { 16978 .FAULT => |err| return errnoBug(err), // Bad pointer parameter. 16979 .@"2BIG" => return error.SystemResources, 16980 .MFILE => return error.ProcessFdQuotaExceeded, 16981 .NAMETOOLONG => return error.NameTooLong, 16982 .NFILE => return error.SystemFdQuotaExceeded, 16983 .NOMEM => return error.SystemResources, 16984 .ACCES => return error.AccessDenied, 16985 .PERM => return error.PermissionDenied, 16986 .INVAL => return error.InvalidExe, 16987 .NOEXEC => return error.InvalidExe, 16988 .IO => return error.FileSystem, 16989 .LOOP => return error.FileSystem, 16990 .ISDIR => return error.IsDir, 16991 .NOENT => return error.FileNotFound, 16992 .NOTDIR => return error.NotDir, 16993 .TXTBSY => return error.FileBusy, 16994 else => |err| switch (native_os) { 16995 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) { 16996 .BADEXEC => return error.InvalidExe, 16997 .BADARCH => return error.InvalidExe, 16998 else => return posix.unexpectedErrno(err), 16999 }, 17000 .linux => switch (err) { 17001 .LIBBAD => return error.InvalidExe, 17002 else => return posix.unexpectedErrno(err), 17003 }, 17004 else => return posix.unexpectedErrno(err), 17005 }, 17006 } 17007 } 17008 17009 pub const CreatePipeOptions = struct { 17010 server: End, 17011 client: End, 17012 inbound: bool = false, 17013 outbound: bool = false, 17014 maximum_instances: u32 = 1, 17015 quota: u32 = 4096, 17016 default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100, 17017 17018 pub const End = struct { 17019 attributes: windows.OBJECT.ATTRIBUTES.Flags = .{}, 17020 mode: windows.FILE.MODE, 17021 }; 17022 }; 17023 pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE { 17024 const named_pipe_device = try t.getNamedPipeDevice(); 17025 const server_handle = server_handle: { 17026 var handle: windows.HANDLE = undefined; 17027 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 17028 const syscall: Syscall = try .start(); 17029 while (true) switch (windows.ntdll.NtCreateNamedPipeFile( 17030 &handle, 17031 .{ 17032 .SPECIFIC = .{ .FILE_PIPE = .{ 17033 .READ_DATA = options.inbound, 17034 .WRITE_DATA = options.outbound, 17035 .WRITE_ATTRIBUTES = true, 17036 } }, 17037 .STANDARD = .{ .SYNCHRONIZE = true }, 17038 }, 17039 &.{ 17040 .RootDirectory = named_pipe_device, 17041 .Attributes = options.server.attributes, 17042 }, 17043 &io_status_block, 17044 .{ .READ = true, .WRITE = true }, 17045 .CREATE, 17046 options.server.mode, 17047 .{ .TYPE = .BYTE_STREAM }, 17048 .{ .MODE = .BYTE_STREAM }, 17049 .{ .OPERATION = .QUEUE }, 17050 options.maximum_instances, 17051 if (options.inbound) options.quota else 0, 17052 if (options.outbound) options.quota else 0, 17053 &options.default_timeout, 17054 )) { 17055 .SUCCESS => break syscall.finish(), 17056 .CANCELLED => { 17057 try syscall.checkCancel(); 17058 continue; 17059 }, 17060 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 17061 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), 17062 else => |status| return syscall.unexpectedNtstatus(status), 17063 }; 17064 break :server_handle handle; 17065 }; 17066 errdefer windows.CloseHandle(server_handle); 17067 const client_handle = client_handle: { 17068 var handle: windows.HANDLE = undefined; 17069 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 17070 const syscall: Syscall = try .start(); 17071 while (true) switch (windows.ntdll.NtOpenFile( 17072 &handle, 17073 .{ 17074 .SPECIFIC = .{ .FILE_PIPE = .{ 17075 .READ_DATA = options.outbound, 17076 .WRITE_DATA = options.inbound, 17077 .WRITE_ATTRIBUTES = true, 17078 } }, 17079 .STANDARD = .{ .SYNCHRONIZE = true }, 17080 }, 17081 &.{ 17082 .RootDirectory = server_handle, 17083 .Attributes = options.client.attributes, 17084 }, 17085 &io_status_block, 17086 .{ .READ = true, .WRITE = true }, 17087 options.client.mode, 17088 )) { 17089 .SUCCESS => break syscall.finish(), 17090 .CANCELLED => { 17091 try syscall.checkCancel(); 17092 continue; 17093 }, 17094 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 17095 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), 17096 else => |status| return syscall.unexpectedNtstatus(status), 17097 }; 17098 break :client_handle handle; 17099 }; 17100 errdefer windows.CloseHandle(client_handle); 17101 return .{ server_handle, client_handle }; 17102 } 17103 17104 fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { 17105 const t: *Threaded = @ptrCast(@alignCast(userdata)); 17106 t.scanEnviron(); 17107 return t.environ.zig_progress_file; 17108 } 17109 17110 pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { 17111 t.scanEnviron(); 17112 return @field(t.environ.string, name); 17113 } 17114 17115 fn random(userdata: ?*anyopaque, buffer: []u8) void { 17116 const t: *Threaded = @ptrCast(@alignCast(userdata)); 17117 const thread = Thread.current orelse return randomMainThread(t, buffer); 17118 if (!thread.csprng.isInitialized()) { 17119 @branchHint(.unlikely); 17120 var seed: [Csprng.seed_len]u8 = undefined; 17121 randomMainThread(t, &seed); 17122 thread.csprng.rng = .init(seed); 17123 } 17124 thread.csprng.rng.fill(buffer); 17125 } 17126 17127 fn randomMainThread(t: *Threaded, buffer: []u8) void { 17128 mutexLock(&t.mutex); 17129 defer mutexUnlock(&t.mutex); 17130 17131 if (!t.csprng.isInitialized()) { 17132 @branchHint(.unlikely); 17133 var seed: [Csprng.seed_len]u8 = undefined; 17134 { 17135 mutexUnlock(&t.mutex); 17136 defer mutexLock(&t.mutex); 17137 17138 const prev = swapCancelProtection(t, .blocked); 17139 defer _ = swapCancelProtection(t, prev); 17140 17141 randomSecure(t, &seed) catch |err| switch (err) { 17142 error.Canceled => unreachable, 17143 error.EntropyUnavailable => fallbackSeed(t, &seed), 17144 }; 17145 } 17146 t.csprng.rng = .init(seed); 17147 } 17148 17149 t.csprng.rng.fill(buffer); 17150 } 17151 17152 pub fn fallbackSeed(aslr_addr: ?*anyopaque, seed: *[Csprng.seed_len]u8) void { 17153 @memset(seed, 0); 17154 std.mem.writeInt(usize, seed[seed.len - @sizeOf(usize) ..][0..@sizeOf(usize)], @intFromPtr(aslr_addr), .native); 17155 const fallbackSeedImpl = switch (native_os) { 17156 .windows => fallbackSeedWindows, 17157 .wasi => if (builtin.link_libc) fallbackSeedPosix else fallbackSeedWasi, 17158 else => fallbackSeedPosix, 17159 }; 17160 fallbackSeedImpl(seed); 17161 } 17162 17163 fn fallbackSeedPosix(seed: *[Csprng.seed_len]u8) void { 17164 std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native); 17165 const i_1 = @sizeOf(posix.pid_t); 17166 17167 var ts: posix.timespec = undefined; 17168 const Sec = @TypeOf(ts.sec); 17169 const Nsec = @TypeOf(ts.nsec); 17170 const i_2 = i_1 + @sizeOf(Sec); 17171 switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) { 17172 .SUCCESS => { 17173 std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native); 17174 std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native); 17175 }, 17176 else => {}, 17177 } 17178 } 17179 17180 fn fallbackSeedWindows(seed: *[Csprng.seed_len]u8) void { 17181 var pc: windows.LARGE_INTEGER = undefined; 17182 _ = windows.ntdll.RtlQueryPerformanceCounter(&pc); 17183 std.mem.writeInt(windows.LARGE_INTEGER, seed[0..@sizeOf(windows.LARGE_INTEGER)], pc, .native); 17184 } 17185 17186 fn fallbackSeedWasi(seed: *[Csprng.seed_len]u8) void { 17187 var ts: std.os.wasi.timestamp_t = undefined; 17188 if (std.os.wasi.clock_time_get(.REALTIME, 1, &ts) == .SUCCESS) { 17189 std.mem.writeInt(std.os.wasi.timestamp_t, seed[0..@sizeOf(std.os.wasi.timestamp_t)], ts, .native); 17190 } 17191 } 17192 17193 fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { 17194 const t: *Threaded = @ptrCast(@alignCast(userdata)); 17195 17196 if (is_windows) { 17197 if (buffer.len == 0) return; 17198 // ProcessPrng from bcryptprimitives.dll has the following properties: 17199 // * introduces a dependency on bcryptprimitives.dll, which apparently 17200 // runs a test suite every time it is loaded 17201 // * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL 17202 // despite the function being documented to always return TRUE 17203 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG 17204 // Therefore, that function is avoided in favor of using the device directly. 17205 const cng_device = try getCngDevice(t); 17206 var io_status_block: windows.IO_STATUS_BLOCK = undefined; 17207 var i: usize = 0; 17208 const syscall: Syscall = try .start(); 17209 while (true) { 17210 const remaining_len = std.math.lossyCast(u32, buffer.len - i); 17211 switch (windows.ntdll.NtDeviceIoControlFile( 17212 cng_device, 17213 null, 17214 null, 17215 null, 17216 &io_status_block, 17217 windows.IOCTL.KSEC.GEN_RANDOM, 17218 null, 17219 0, 17220 buffer[i..].ptr, 17221 remaining_len, 17222 )) { 17223 .SUCCESS => { 17224 i += remaining_len; 17225 if (buffer.len - i == 0) { 17226 return syscall.finish(); 17227 } else { 17228 try syscall.checkCancel(); 17229 continue; 17230 } 17231 }, 17232 .CANCELLED => { 17233 try syscall.checkCancel(); 17234 continue; 17235 }, 17236 else => return syscall.fail(error.EntropyUnavailable), 17237 } 17238 } 17239 } 17240 17241 if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) { 17242 if (buffer.len == 0) return; 17243 posix.system.arc4random_buf(buffer.ptr, buffer.len); 17244 return; 17245 } 17246 17247 if (native_os == .wasi) { 17248 if (buffer.len == 0) return; 17249 const syscall: Syscall = try .start(); 17250 while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) { 17251 .SUCCESS => return syscall.finish(), 17252 .INTR => { 17253 try syscall.checkCancel(); 17254 continue; 17255 }, 17256 else => return syscall.fail(error.EntropyUnavailable), 17257 }; 17258 } 17259 17260 if (@TypeOf(posix.system.getrandom) != void) { 17261 const getrandom = if (use_libc_getrandom) std.c.getrandom else std.os.linux.getrandom; 17262 var i: usize = 0; 17263 const syscall: Syscall = try .start(); 17264 while (buffer.len - i != 0) { 17265 const buf = buffer[i..]; 17266 const rc = getrandom(buf.ptr, buf.len, 0); 17267 switch (posix.errno(rc)) { 17268 .SUCCESS => { 17269 syscall.finish(); 17270 const n: usize = @intCast(rc); 17271 i += n; 17272 continue; 17273 }, 17274 .INTR => { 17275 try syscall.checkCancel(); 17276 continue; 17277 }, 17278 else => return syscall.fail(error.EntropyUnavailable), 17279 } 17280 } 17281 return; 17282 } 17283 17284 if (native_os == .emscripten) { 17285 if (buffer.len == 0) return; 17286 const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len)); 17287 switch (err) { 17288 .SUCCESS => return, 17289 else => return error.EntropyUnavailable, 17290 } 17291 } 17292 17293 if (native_os == .linux) { 17294 comptime assert(use_dev_urandom); 17295 const urandom_fd = try getRandomFd(t); 17296 17297 var i: usize = 0; 17298 while (buffer.len - i != 0) { 17299 const syscall: Syscall = try .start(); 17300 const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i); 17301 switch (posix.errno(rc)) { 17302 .SUCCESS => { 17303 syscall.finish(); 17304 const n: usize = @intCast(rc); 17305 if (n == 0) return error.EntropyUnavailable; 17306 i += n; 17307 continue; 17308 }, 17309 .INTR => { 17310 try syscall.checkCancel(); 17311 continue; 17312 }, 17313 else => return syscall.fail(error.EntropyUnavailable), 17314 } 17315 } 17316 } 17317 17318 return error.EntropyUnavailable; 17319 } 17320 17321 fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { 17322 { 17323 mutexLock(&t.mutex); 17324 defer mutexUnlock(&t.mutex); 17325 17326 if (t.random_file.fd == -2) return error.EntropyUnavailable; 17327 if (t.random_file.fd != -1) return t.random_file.fd; 17328 } 17329 17330 const mode: posix.mode_t = 0; 17331 17332 const fd: posix.fd_t = fd: { 17333 const syscall: Syscall = try .start(); 17334 while (true) { 17335 const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{ 17336 .ACCMODE = .RDONLY, 17337 .CLOEXEC = true, 17338 }, mode); 17339 switch (posix.errno(rc)) { 17340 .SUCCESS => { 17341 syscall.finish(); 17342 break :fd @intCast(rc); 17343 }, 17344 .INTR => { 17345 try syscall.checkCancel(); 17346 continue; 17347 }, 17348 else => return syscall.fail(error.EntropyUnavailable), 17349 } 17350 } 17351 }; 17352 errdefer closeFd(fd); 17353 17354 switch (native_os) { 17355 .linux => { 17356 const sys = if (statx_use_c) std.c else std.os.linux; 17357 const syscall: Syscall = try .start(); 17358 while (true) { 17359 var statx = std.mem.zeroes(std.os.linux.Statx); 17360 switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { 17361 .SUCCESS => { 17362 syscall.finish(); 17363 if (!statx.mask.TYPE) return error.EntropyUnavailable; 17364 mutexLock(&t.mutex); // Another thread might have won the race. 17365 defer mutexUnlock(&t.mutex); 17366 if (t.random_file.fd >= 0) { 17367 closeFd(fd); 17368 return t.random_file.fd; 17369 } else if (!posix.S.ISCHR(statx.mode)) { 17370 t.random_file.fd = -2; 17371 return error.EntropyUnavailable; 17372 } else { 17373 t.random_file.fd = fd; 17374 return fd; 17375 } 17376 }, 17377 .INTR => { 17378 try syscall.checkCancel(); 17379 continue; 17380 }, 17381 else => return syscall.fail(error.EntropyUnavailable), 17382 } 17383 } 17384 }, 17385 else => { 17386 const syscall: Syscall = try .start(); 17387 while (true) { 17388 var stat = std.mem.zeroes(posix.Stat); 17389 switch (posix.errno(fstat_sym(fd, &stat))) { 17390 .SUCCESS => { 17391 syscall.finish(); 17392 mutexLock(&t.mutex); // Another thread might have won the race. 17393 defer mutexUnlock(&t.mutex); 17394 if (t.random_file.fd >= 0) { 17395 closeFd(fd); 17396 return t.random_file.fd; 17397 } else if (!posix.S.ISCHR(stat.mode)) { 17398 t.random_file.fd = -2; 17399 return error.EntropyUnavailable; 17400 } else { 17401 t.random_file.fd = fd; 17402 return fd; 17403 } 17404 }, 17405 .INTR => { 17406 try syscall.checkCancel(); 17407 continue; 17408 }, 17409 else => return syscall.fail(error.EntropyUnavailable), 17410 } 17411 } 17412 }, 17413 } 17414 } 17415 17416 test { 17417 _ = @import("Threaded/test.zig"); 17418 } 17419 17420 const use_parking_futex = switch (native_os) { 17421 .windows => true, // RtlWaitOnAddress is a userland implementation anyway 17422 .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now. 17423 .illumos => true, // Illumos has no futex mechanism 17424 else => false, 17425 }; 17426 const use_parking_sleep = switch (native_os) { 17427 // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in 17428 // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the 17429 // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm 17430 // also more confident that it will always correctly handle the cancelation race (so "unpark" 17431 // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired 17432 // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might 17433 // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger 17434 // this behavior because `RtlWaitOnAddress` relies on it. 17435 .windows => true, 17436 17437 // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better 17438 // cancelation mechanism. 17439 .netbsd, 17440 .illumos, 17441 => true, 17442 17443 else => false, 17444 }; 17445 17446 const parking_futex = struct { 17447 comptime { 17448 assert(use_parking_futex); 17449 } 17450 17451 const Bucket = struct { 17452 /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no 17453 /// waiters. It is important for `wait` to increment this *before* checking the futex value to 17454 /// avoid a race. 17455 num_waiters: std.atomic.Value(u32), 17456 /// Protects `waiters`. 17457 mutex: ParkingMutex, 17458 waiters: std.DoublyLinkedList, 17459 17460 /// Prevent false sharing between buckets. 17461 _: void align(std.atomic.cache_line) = {}, 17462 17463 const init: Bucket = .{ .num_waiters = .init(0), .mutex = .init, .waiters = .{} }; 17464 }; 17465 17466 const Waiter = struct { 17467 node: std.DoublyLinkedList.Node, 17468 address: usize, 17469 tid: std.Thread.Id, 17470 /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread 17471 /// which atomically updates it (to `.none` or `.canceling`) is responsible for: 17472 /// 17473 /// * Removing the `Waiter` from `Bucket.waiters` 17474 /// * Decrementing `Bucket.num_waiters` 17475 /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope 17476 /// while it is still in the `Bucket`). 17477 thread_status: *std.atomic.Value(Thread.Status), 17478 unpark_flag: if (need_unpark_flag) *UnparkFlag else void, 17479 }; 17480 17481 fn bucketForAddress(address: usize) *Bucket { 17482 const global = struct { 17483 /// Length must be a power of two. The longer this array, the less likely contention is 17484 /// between different futexes. This length seems like it'll provide a reasonable balance 17485 /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line 17486 /// alignment), this uses 32 KiB of memory. 17487 var buckets: [256]Bucket = @splat(.init); 17488 }; 17489 17490 // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input 17491 // values across a range, giving a poor, but extremely quick to compute, hash. 17492 17493 // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The 17494 // shift then converts it to '2^b / phi', where 'b' is the pointer bit width. 17495 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize)); 17496 const hashed = address *% fibonacci_multiplier; 17497 17498 comptime assert(std.math.isPowerOfTwo(global.buckets.len)); 17499 // The high bits of `hashed` have better entropy than the low bits. 17500 const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len)); 17501 17502 return &global.buckets[index]; 17503 } 17504 17505 fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void { 17506 const bucket = bucketForAddress(@intFromPtr(ptr)); 17507 17508 // Put the threadlocal access outside of the critical section. 17509 const opt_thread = Thread.current; 17510 const self_tid = if (opt_thread) |thread| thread.id else std.Thread.getCurrentId(); 17511 17512 var waiter: Waiter = .{ 17513 .node = undefined, // populated by list append 17514 .address = @intFromPtr(ptr), 17515 .tid = self_tid, 17516 .thread_status = undefined, // populated in critical section 17517 .unpark_flag = undefined, // populated in critical section 17518 }; 17519 17520 var status_buf: std.atomic.Value(Thread.Status) = undefined; 17521 var unpark_flag_buf: UnparkFlag = unpark_flag_init; 17522 17523 { 17524 bucket.mutex.lock(); 17525 defer bucket.mutex.unlock(); 17526 17527 _ = bucket.num_waiters.fetchAdd(1, .acquire); 17528 17529 if (@atomicLoad(u32, ptr, .monotonic) != expect) { 17530 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); 17531 return; 17532 } 17533 17534 // This is in the critical section to avoid marking the thread as parked until we're 17535 // certain that we're actually going to park. 17536 waiter.thread_status, waiter.unpark_flag = status: { 17537 cancelable: { 17538 if (uncancelable) break :cancelable; 17539 const thread = opt_thread orelse break :cancelable; 17540 switch (thread.cancel_protection) { 17541 .blocked => break :cancelable, 17542 .unblocked => {}, 17543 } 17544 thread.futex_waiter = &waiter; 17545 const old_status = thread.status.fetchOr( 17546 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, 17547 .release, // release `thread.futex_waiter` 17548 ); 17549 switch (old_status.cancelation) { 17550 .none => {}, // status is now `.parked` 17551 .canceling => { 17552 // status is now `.canceled` 17553 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); 17554 return error.Canceled; 17555 }, 17556 .canceled => break :cancelable, // status is still `.canceled` 17557 .parked => unreachable, 17558 .blocked => unreachable, 17559 .blocked_alertable => unreachable, 17560 .blocked_alertable_canceling => unreachable, 17561 .blocked_canceling => unreachable, 17562 } 17563 // We could now be unparked for a cancelation at any time! 17564 break :status .{ &thread.status, if (need_unpark_flag) &thread.unpark_flag }; 17565 } 17566 // This is an uncancelable wait, so just use `status_buf`. Note that the value of 17567 // `status_buf.awaitable` is irrelevant because this is only visible to futex code, 17568 // while only cancelation cares about `awaitable`. 17569 status_buf.raw = .{ .cancelation = .parked, .awaitable = .null }; 17570 break :status .{ &status_buf, if (need_unpark_flag) &unpark_flag_buf }; 17571 }; 17572 17573 bucket.waiters.append(&waiter.node); 17574 } 17575 17576 if (park(timeout, ptr, waiter.unpark_flag)) { 17577 // We were unparked by either `wake` or cancelation, so our current status is either 17578 // `.none` or `.canceling`. In either case, they've already removed `waiter` from 17579 // `bucket`, so we have nothing more to do! 17580 } else |err| switch (err) { 17581 error.Timeout => { 17582 // We're not out of the woods yet: an unpark could race with the timeout. 17583 const old_status = waiter.thread_status.fetchAnd( 17584 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, 17585 .monotonic, 17586 ); 17587 switch (old_status.cancelation) { 17588 .parked => { 17589 // No race. It is our responsibility to remove `waiter` from `bucket`. 17590 // New status is `.none`. 17591 bucket.mutex.lock(); 17592 defer bucket.mutex.unlock(); 17593 bucket.waiters.remove(&waiter.node); 17594 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); 17595 }, 17596 .none, .canceling => { 17597 // Race condition: the timeout was reached, then `wake` or a canceler tried 17598 // to unpark us. Whoever did that will remove us from `bucket`. Wait for 17599 // that (and drop the unpark request in doing so). 17600 // New status is `.none` or `.canceling` respectively. 17601 park(.none, ptr, waiter.unpark_flag) catch |e| switch (e) { 17602 error.Timeout => unreachable, 17603 }; 17604 }, 17605 .canceled => unreachable, 17606 .blocked => unreachable, 17607 .blocked_alertable => unreachable, 17608 .blocked_canceling => unreachable, 17609 .blocked_alertable_canceling => unreachable, 17610 } 17611 }, 17612 } 17613 } 17614 17615 fn wake(ptr: *const u32, max_waiters: u32) void { 17616 if (max_waiters == 0) return; 17617 17618 const bucket = bucketForAddress(@intFromPtr(ptr)); 17619 17620 // To ensure the store to `ptr` is ordered before this check, we effectively want a `.release` 17621 // load, but that doesn't exist in the C11 memory model, so emulate it with a non-mutating rmw. 17622 if (bucket.num_waiters.fetchAdd(0, .release) == 0) { 17623 @branchHint(.likely); 17624 return; // no waiters 17625 } 17626 17627 // Waiters removed from the linked list under the mutex so we can unpark their threads outside 17628 // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. 17629 var waking_head: ?*std.DoublyLinkedList.Node = null; 17630 { 17631 bucket.mutex.lock(); 17632 defer bucket.mutex.unlock(); 17633 17634 var num_removed: u32 = 0; 17635 var it = bucket.waiters.first; 17636 while (num_removed < max_waiters) { 17637 const waiter: *Waiter = @fieldParentPtr("node", it orelse break); 17638 it = waiter.node.next; 17639 if (waiter.address != @intFromPtr(ptr)) continue; 17640 const old_status = waiter.thread_status.fetchAnd( 17641 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, 17642 .monotonic, 17643 ); 17644 switch (old_status.cancelation) { 17645 .parked => {}, // state updated to `.none` 17646 .none => continue, // race with timeout; they are about to lock `bucket.mutex` and remove themselves from the bucket 17647 .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet 17648 .canceled => unreachable, 17649 .blocked => unreachable, 17650 .blocked_alertable => unreachable, 17651 .blocked_alertable_canceling => unreachable, 17652 .blocked_canceling => unreachable, 17653 } 17654 // We're waking this waiter. Remove them from the bucket and add them to our local list. 17655 bucket.waiters.remove(&waiter.node); 17656 waiter.node.next = waking_head; 17657 waking_head = &waiter.node; 17658 num_removed += 1; 17659 } 17660 _ = bucket.num_waiters.fetchSub(num_removed, .monotonic); 17661 } 17662 17663 var unpark_buf: [128]UnparkTid = undefined; 17664 var unpark_len: usize = 0; 17665 17666 // Finally, unpark the threads. 17667 while (waking_head) |node| { 17668 waking_head = node.next; 17669 const waiter: *Waiter = @fieldParentPtr("node", node); 17670 unpark_buf[unpark_len] = waiter.tid; 17671 if (need_unpark_flag) setUnparkFlag(waiter.unpark_flag); 17672 unpark_len += 1; 17673 if (unpark_len == unpark_buf.len) { 17674 unpark(&unpark_buf, ptr); 17675 unpark_len = 0; 17676 } 17677 } 17678 if (unpark_len > 0) { 17679 unpark(unpark_buf[0..unpark_len], ptr); 17680 } 17681 } 17682 17683 fn removeCanceledWaiter(waiter: *Waiter) void { 17684 const bucket = bucketForAddress(waiter.address); 17685 bucket.mutex.lock(); 17686 defer bucket.mutex.unlock(); 17687 bucket.waiters.remove(&waiter.node); 17688 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); 17689 } 17690 }; 17691 const parking_sleep = struct { 17692 comptime { 17693 assert(use_parking_sleep); 17694 } 17695 fn sleep(timeout: Io.Timeout) Io.Cancelable!void { 17696 const opt_thread = Thread.current; 17697 cancelable: { 17698 const thread = opt_thread orelse break :cancelable; 17699 switch (thread.cancel_protection) { 17700 .blocked => break :cancelable, 17701 .unblocked => {}, 17702 } 17703 thread.futex_waiter = null; 17704 { 17705 const old_status = thread.status.fetchOr( 17706 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, 17707 .release, // release `thread.futex_waiter` 17708 ); 17709 switch (old_status.cancelation) { 17710 .none => {}, // status is now `.parked` 17711 .canceling => return error.Canceled, // status is now `.canceled` 17712 .canceled => break :cancelable, // status is still `.canceled` 17713 .parked => unreachable, 17714 .blocked => unreachable, 17715 .blocked_alertable => unreachable, 17716 .blocked_alertable_canceling => unreachable, 17717 .blocked_canceling => unreachable, 17718 } 17719 } 17720 if (park(timeout, null, if (need_unpark_flag) &thread.unpark_flag)) { 17721 // The only reason this could possibly happen is cancelation. 17722 const old_status = thread.status.load(.monotonic); 17723 assert(old_status.cancelation == .canceling); 17724 thread.status.store( 17725 .{ .cancelation = .canceled, .awaitable = old_status.awaitable }, 17726 .monotonic, 17727 ); 17728 return error.Canceled; 17729 } else |err| switch (err) { 17730 error.Timeout => { 17731 // We're not out of the woods yet: an unpark could race with the timeout. 17732 const old_status = thread.status.fetchAnd( 17733 .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, 17734 .monotonic, 17735 ); 17736 switch (old_status.cancelation) { 17737 .parked => return, // No race; new status is `.none` 17738 .canceling => { 17739 // Race condition: the timeout was reached, then someone tried to unpark 17740 // us for a cancelation. Whoever did that will have called `unpark`, so 17741 // drop that unpark request by waiting for it. 17742 // Status is still `.canceling`. 17743 park(.none, null, if (need_unpark_flag) &thread.unpark_flag) catch |e| switch (e) { 17744 error.Timeout => unreachable, 17745 }; 17746 return; 17747 }, 17748 .none => unreachable, 17749 .canceled => unreachable, 17750 .blocked => unreachable, 17751 .blocked_alertable => unreachable, 17752 .blocked_canceling => unreachable, 17753 .blocked_alertable_canceling => unreachable, 17754 } 17755 }, 17756 } 17757 } 17758 // Uncancelable sleep; we expect not to be manually unparked. 17759 var dummy_flag: UnparkFlag = unpark_flag_init; 17760 if (park(timeout, null, if (need_unpark_flag) &dummy_flag)) { 17761 unreachable; // unexpected unpark 17762 } else |err| switch (err) { 17763 error.Timeout => return, 17764 } 17765 } 17766 }; 17767 const ParkingMutex = struct { 17768 state: std.atomic.Value(State), 17769 17770 const init: ParkingMutex = .{ .state = .init(.unlocked) }; 17771 17772 comptime { 17773 assert(use_parking_futex); 17774 } 17775 17776 const State = enum(usize) { 17777 unlocked = 1, 17778 /// This value is intentionally 0 so that `waiter` returns `null`. 17779 locked_once = 0, 17780 /// Contended; value is a `*Waiter`. 17781 _, 17782 /// Returns the head of the waiter list. Illegal to call if `s == .unlocked`. 17783 fn waiter(s: State) ?*Waiter { 17784 return @ptrFromInt(@intFromEnum(s)); 17785 } 17786 /// Returns a locked state where `w` is contending the lock. 17787 /// If `w` is `null`, returns `.locked_once`. 17788 fn fromWaiter(w: ?*Waiter) State { 17789 return @enumFromInt(@intFromPtr(w)); 17790 } 17791 }; 17792 const Waiter = struct { 17793 unpark_flag: UnparkFlag, 17794 /// Never modified once the `Waiter` is in the linked list. 17795 next: ?*Waiter, 17796 /// Never modified once the `Waiter` is in the linked list. 17797 tid: std.Thread.Id, 17798 }; 17799 fn lock(m: *ParkingMutex) void { 17800 state: switch (State.unlocked) { // assume 'unlocked' to optimize for uncontended case 17801 .unlocked => continue :state m.state.cmpxchgWeak( 17802 .unlocked, 17803 .locked_once, 17804 .acquire, // acquire lock 17805 .monotonic, 17806 ) orelse { 17807 @branchHint(.likely); 17808 return; 17809 }, 17810 17811 .locked_once, _ => |last_state| { 17812 const old_waiter = last_state.waiter(); 17813 const self_tid = if (Thread.current) |t| t.id else std.Thread.getCurrentId(); 17814 var waiter: Waiter = .{ 17815 .next = old_waiter, 17816 .unpark_flag = unpark_flag_init, 17817 .tid = self_tid, 17818 }; 17819 if (m.state.cmpxchgWeak( 17820 .fromWaiter(old_waiter), 17821 .fromWaiter(&waiter), 17822 .release, // release `waiter` 17823 .monotonic, 17824 )) |new_state| { 17825 continue :state new_state; 17826 } 17827 // We're now in the list of waiters---park until we're given the lock. 17828 park(.none, m, if (need_unpark_flag) &waiter.unpark_flag) catch |err| switch (err) { 17829 error.Timeout => unreachable, 17830 }; 17831 return; 17832 }, 17833 } 17834 } 17835 fn unlock(m: *ParkingMutex) void { 17836 state: switch (State.locked_once) { // assume 'locked_once' to optimize for uncontended case 17837 .unlocked => unreachable, // we hold the lock 17838 17839 .locked_once => continue :state m.state.cmpxchgWeak( 17840 .locked_once, 17841 .unlocked, 17842 .release, // release lock 17843 .acquire, // acquire any `Waiter` memory 17844 ) orelse { 17845 @branchHint(.likely); 17846 return; 17847 }, 17848 17849 _ => |last_state| { 17850 // The logic here does not have ABA problems, and does some accesses non-atomically, 17851 // because `Waiter.next` is owned by the lock holder (that's us!) once the waiter is 17852 // in the linked list, up until we unpark the waiter. 17853 17854 // Run through the waiter list to the end to ensure fairness. This is obviously not 17855 // ideal, but it shouldn't be a big deal in practice provided the critical section 17856 // is fairly small (so we won't get too many threads contending the mutex at once). 17857 // There's a *chance* we could get away with a LIFO queue for our use case, but I 17858 // don't wanna risk that. 17859 var parent: ?*Waiter = null; 17860 var waiter: *Waiter = last_state.waiter().?; 17861 while (waiter.next) |next| { 17862 parent = waiter; 17863 waiter = next; 17864 } 17865 // `waiter` is next in line for the lock. Remove them from the list. 17866 if (parent) |p| { 17867 assert(p.next == waiter); 17868 p.next = null; 17869 } else { 17870 // We're waking the last waiter, so clear the list head. 17871 if (m.state.cmpxchgWeak( 17872 .fromWaiter(last_state.waiter().?), 17873 .locked_once, 17874 .acquire, 17875 .acquire, // acquire any new `Waiter` memory 17876 )) |new_state| { 17877 continue :state new_state; 17878 } 17879 } 17880 // Now we're ready to actually hand the lock over to them. 17881 const tid = waiter.tid; // load before the unpark below potentially invalidates `waiter` 17882 if (need_unpark_flag) setUnparkFlag(&waiter.unpark_flag); 17883 unpark(&.{tid}, m); 17884 return; 17885 }, 17886 } 17887 } 17888 }; 17889 17890 fn timeoutToWindowsInterval(timeout: Io.Timeout) ?windows.LARGE_INTEGER { 17891 // ntdll only supports two combinations: 17892 // * real-time (`.real`) sleeps with absolute deadlines 17893 // * monotonic (`.awake`/`.boot`) sleeps with relative durations 17894 const clock = switch (timeout) { 17895 .none => return null, 17896 .duration => |d| d.clock, 17897 .deadline => |d| d.clock, 17898 }; 17899 switch (clock) { 17900 .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time 17901 .real => { 17902 const deadline = switch (timeout) { 17903 .none => unreachable, 17904 .duration => |d| nowWindows(clock).addDuration(d.raw), 17905 .deadline => |d| d.raw, 17906 }; 17907 return @intCast(@max(@divTrunc(deadline.nanoseconds, 100), 0)); 17908 }, 17909 .awake, .boot => { 17910 const duration = switch (timeout) { 17911 .none => unreachable, 17912 .duration => |d| d.raw, 17913 .deadline => |d| nowWindows(clock).durationTo(d.raw), 17914 }; 17915 return @intCast(@min(@divTrunc(-duration.nanoseconds, 100), -1)); 17916 }, 17917 } 17918 } 17919 17920 /// The API on NetBSD and Illumos sucks and can unpark spuriously (well, it *can't*, but signals 17921 /// cause an indistinguishable unblock, and libpthread really likes to leave unparks pending). 17922 /// As such, on these targets only, we need to pass around a flag to track whether a thread is 17923 /// "actually" being unparked. 17924 const need_unpark_flag = switch (native_os) { 17925 .netbsd, .illumos => true, 17926 else => false, 17927 }; 17928 const UnparkFlag = if (need_unpark_flag) std.atomic.Value(bool) else void; 17929 const unpark_flag_init: UnparkFlag = if (need_unpark_flag) .init(false); 17930 /// Must be called before `unpark`. After this function is called, the thread may be unparked at any 17931 /// time, so the caller must not reference values on its stack. 17932 fn setUnparkFlag(f: *UnparkFlag) void { 17933 f.store(true, .release); 17934 } 17935 17936 /// The type passed into `unpark` for the thread ID. You'd think this was just a `std.Thread.Id`, 17937 /// but it seems that someone at Microsoft forgot how big their TIDs are supposed to be. 17938 const UnparkTid = switch (native_os) { 17939 .windows => usize, 17940 else => std.Thread.Id, 17941 }; 17942 17943 fn park( 17944 timeout: Io.Timeout, 17945 /// This value has no semantic effect, but may allow the OS to optimize the operation. 17946 addr_hint: ?*const anyopaque, 17947 unpark_flag: if (need_unpark_flag) *UnparkFlag else void, 17948 ) error{Timeout}!void { 17949 comptime assert(use_parking_futex or use_parking_sleep); 17950 switch (native_os) { 17951 .windows => { 17952 const raw_timeout = timeoutToWindowsInterval(timeout); 17953 // `RtlWaitOnAddress` passes the futex address in as the first argument to this call, 17954 // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId` 17955 // does *not* accept the address so the kernel can't really be using it as a hint. An 17956 // old Microsoft blog post discusses a more traditional futex-like mechanism in the 17957 // kernel which definitely isn't how `RtlWaitOnAddress` works today: 17958 // 17959 // https://devblogs.microsoft.com/oldnewthing/20160826-00/?p=94185 17960 // 17961 // ...so it's possible this argument is simply a remnant which no longer does anything 17962 // (perhaps the implementation changed during development but someone forgot to remove 17963 // this parameter). However, to err on the side of caution, let's match the behavior of 17964 // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something 17965 // stupid such as trying to dereference it. 17966 switch (windows.ntdll.NtWaitForAlertByThreadId( 17967 addr_hint, 17968 if (raw_timeout) |*t| t else null, 17969 )) { 17970 .ALERTED => return, 17971 .TIMEOUT => return error.Timeout, 17972 else => unreachable, 17973 } 17974 }, 17975 .netbsd => { 17976 var ts_buf: posix.timespec = undefined; 17977 const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) { 17978 .none => .{ null, false, false }, 17979 .deadline => |timestamp| timeout: { 17980 ts_buf = timestampToPosix(timestamp.raw.nanoseconds); 17981 break :timeout .{ &ts_buf, true, timestamp.clock == .real }; 17982 }, 17983 .duration => |duration| timeout: { 17984 ts_buf = timestampToPosix(duration.raw.nanoseconds); 17985 break :timeout .{ &ts_buf, false, duration.clock == .real }; 17986 }, 17987 }; 17988 // It's okay to pass the same timeout in a loop. If it's a duration, the OS actually 17989 // writes the remaining time into the buffer when the syscall returns. 17990 while (!unpark_flag.swap(false, .acquire)) { 17991 switch (posix.errno(std.c._lwp_park( 17992 if (clock_real) .REALTIME else .MONOTONIC, 17993 .{ .ABSTIME = abstime }, 17994 ts, 17995 0, 17996 addr_hint, 17997 null, 17998 ))) { 17999 .SUCCESS, .ALREADY, .INTR => {}, 18000 .TIMEDOUT => return error.Timeout, 18001 .INVAL => unreachable, 18002 .SRCH => unreachable, 18003 else => unreachable, 18004 } 18005 } 18006 }, 18007 .illumos => @panic("TODO: illumos lwp_park"), 18008 else => comptime unreachable, 18009 } 18010 } 18011 /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. 18012 fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { 18013 comptime assert(use_parking_futex or use_parking_sleep); 18014 switch (native_os) { 18015 .windows => { 18016 // TODO: this condition is currently disabled because mingw-w64 does not contain this 18017 // symbol. Once it's added, enable this check to use the new bulk API where possible. 18018 if (false and (builtin.os.version_range.windows.isAtLeast(.win11_dt) orelse false)) { 18019 _ = windows.ntdll.NtAlertMultipleThreadByThreadId(tids.ptr, @intCast(tids.len), null, null); 18020 } else { 18021 for (tids) |tid| { 18022 _ = windows.ntdll.NtAlertThreadByThreadId(@intCast(tid)); 18023 } 18024 } 18025 }, 18026 .netbsd => { 18027 switch (posix.errno(std.c._lwp_unpark_all(@ptrCast(tids.ptr), tids.len, addr_hint))) { 18028 .SUCCESS => return, 18029 // For errors, fall through to a loop over `tids`, though this is only expected to 18030 // be possible for ENOMEM (even that is questionable) and ESRCH (see comment below). 18031 .SRCH => {}, 18032 .FAULT => recoverableOsBugDetected(), 18033 .INVAL => recoverableOsBugDetected(), 18034 .NOMEM => {}, 18035 else => recoverableOsBugDetected(), 18036 } 18037 for (tids) |tid| { 18038 switch (posix.errno(std.c._lwp_unpark(@bitCast(tid), addr_hint))) { 18039 .SUCCESS => {}, 18040 .SRCH => { 18041 // This can happen in a rare race: the thread might have been spuriously 18042 // unparked, so already observed the changing status, and from there have 18043 // exited. That's okay, because the thread has woken up like we wanted. 18044 }, 18045 else => recoverableOsBugDetected(), 18046 } 18047 } 18048 }, 18049 .illumos => @panic("TODO: illumos lwp_unpark"), 18050 else => comptime unreachable, 18051 } 18052 } 18053 18054 pub const PipeError = error{ 18055 SystemFdQuotaExceeded, 18056 ProcessFdQuotaExceeded, 18057 } || Io.UnexpectedError; 18058 18059 pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t { 18060 var fds: [2]posix.fd_t = undefined; 18061 18062 if (@TypeOf(posix.system.pipe2) != void) { 18063 switch (posix.errno(posix.system.pipe2(&fds, flags))) { 18064 .SUCCESS => return fds, 18065 .INVAL => |err| return errnoBug(err), // Invalid flags 18066 .NFILE => return error.SystemFdQuotaExceeded, 18067 .MFILE => return error.ProcessFdQuotaExceeded, 18068 else => |err| return posix.unexpectedErrno(err), 18069 } 18070 } 18071 18072 switch (posix.errno(posix.system.pipe(&fds))) { 18073 .SUCCESS => {}, 18074 .NFILE => return error.SystemFdQuotaExceeded, 18075 .MFILE => return error.ProcessFdQuotaExceeded, 18076 else => |err| return posix.unexpectedErrno(err), 18077 } 18078 errdefer { 18079 closeFd(fds[0]); 18080 closeFd(fds[1]); 18081 } 18082 18083 // https://github.com/ziglang/zig/issues/18882 18084 if (@as(u32, @bitCast(flags)) == 0) return fds; 18085 18086 // CLOEXEC is special, it's a file descriptor flag and must be set using 18087 // F.SETFD. 18088 if (flags.CLOEXEC) for (fds) |fd| { 18089 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(u32, posix.FD_CLOEXEC)))) { 18090 .SUCCESS => {}, 18091 else => |err| return posix.unexpectedErrno(err), 18092 } 18093 }; 18094 18095 const new_flags: u32 = f: { 18096 var new_flags = flags; 18097 new_flags.CLOEXEC = false; 18098 break :f @bitCast(new_flags); 18099 }; 18100 18101 // Set every other flag affecting the file status using F.SETFL. 18102 if (new_flags != 0) for (fds) |fd| { 18103 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, new_flags))) { 18104 .SUCCESS => {}, 18105 .INVAL => |err| return errnoBug(err), 18106 else => |err| return posix.unexpectedErrno(err), 18107 } 18108 }; 18109 18110 return fds; 18111 } 18112 18113 pub const DupError = error{ 18114 ProcessFdQuotaExceeded, 18115 SystemResources, 18116 } || Io.UnexpectedError || Io.Cancelable; 18117 18118 pub fn dup2(old_fd: posix.fd_t, new_fd: posix.fd_t) DupError!void { 18119 const syscall: Syscall = try .start(); 18120 while (true) switch (posix.errno(posix.system.dup2(old_fd, new_fd))) { 18121 .SUCCESS => return syscall.finish(), 18122 .BUSY, .INTR => { 18123 try syscall.checkCancel(); 18124 continue; 18125 }, 18126 .INVAL => |err| return syscall.errnoBug(err), // invalid parameters 18127 .BADF => |err| return syscall.errnoBug(err), // use after free 18128 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), 18129 .NOMEM => return syscall.fail(error.SystemResources), 18130 else => |err| return syscall.unexpectedErrno(err), 18131 }; 18132 } 18133 18134 pub const FchdirError = error{ 18135 AccessDenied, 18136 NotDir, 18137 FileSystem, 18138 } || Io.Cancelable || Io.UnexpectedError; 18139 18140 pub fn fchdir(fd: posix.fd_t) FchdirError!void { 18141 if (fd == posix.AT.FDCWD) return; 18142 const syscall: Syscall = try .start(); 18143 while (true) switch (posix.errno(posix.system.fchdir(fd))) { 18144 .SUCCESS => return syscall.finish(), 18145 .INTR => { 18146 try syscall.checkCancel(); 18147 continue; 18148 }, 18149 .ACCES => return syscall.fail(error.AccessDenied), 18150 .NOTDIR => return syscall.fail(error.NotDir), 18151 .IO => return syscall.fail(error.FileSystem), 18152 .BADF => |err| return syscall.errnoBug(err), 18153 else => |err| return syscall.unexpectedErrno(err), 18154 }; 18155 } 18156 18157 pub const ChdirError = error{ 18158 AccessDenied, 18159 FileSystem, 18160 SymLinkLoop, 18161 NameTooLong, 18162 FileNotFound, 18163 SystemResources, 18164 NotDir, 18165 BadPathName, 18166 } || Io.Cancelable || Io.UnexpectedError; 18167 18168 pub fn chdir(dir_path: []const u8) ChdirError!void { 18169 var path_buffer: [posix.PATH_MAX]u8 = undefined; 18170 const dir_path_posix = try pathToPosix(dir_path, &path_buffer); 18171 const syscall: Syscall = try .start(); 18172 while (true) switch (posix.errno(posix.system.chdir(dir_path_posix))) { 18173 .SUCCESS => return syscall.finish(), 18174 .INTR => { 18175 try syscall.checkCancel(); 18176 continue; 18177 }, 18178 .ACCES => return syscall.fail(error.AccessDenied), 18179 .IO => return syscall.fail(error.FileSystem), 18180 .LOOP => return syscall.fail(error.SymLinkLoop), 18181 .NAMETOOLONG => return syscall.fail(error.NameTooLong), 18182 .NOENT => return syscall.fail(error.FileNotFound), 18183 .NOMEM => return syscall.fail(error.SystemResources), 18184 .NOTDIR => return syscall.fail(error.NotDir), 18185 .ILSEQ => return syscall.fail(error.BadPathName), 18186 .FAULT => |err| return syscall.errnoBug(err), 18187 else => |err| return syscall.unexpectedErrno(err), 18188 }; 18189 } 18190 18191 fn fileMemoryMapCreate( 18192 userdata: ?*anyopaque, 18193 file: File, 18194 options: File.MemoryMap.CreateOptions, 18195 ) File.MemoryMap.CreateError!File.MemoryMap { 18196 const t: *Threaded = @ptrCast(@alignCast(userdata)); 18197 const offset = options.offset; 18198 const len = options.len; 18199 18200 if (!t.disable_memory_mapping) { 18201 if (createFileMap(file, options.protection, offset, options.populate, len)) |result| { 18202 return result; 18203 } else |err| switch (err) { 18204 error.Unseekable, error.Canceled, error.AccessDenied => |e| return e, 18205 error.OperationUnsupported => {}, 18206 else => { 18207 if (builtin.mode == .Debug) 18208 std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err}); 18209 }, 18210 } 18211 } 18212 18213 const gpa = t.allocator; 18214 const page_size = std.heap.pageSize(); 18215 const alignment: Alignment = .fromByteUnits(page_size); 18216 const memory = m: { 18217 const ptr = gpa.rawAlloc(len, alignment, @returnAddress()) orelse return error.OutOfMemory; 18218 break :m ptr[0..len]; 18219 }; 18220 errdefer gpa.rawFree(memory, alignment, @returnAddress()); 18221 18222 if (!options.undefined_contents) try mmSyncRead(file, memory, offset); 18223 18224 return .{ 18225 .file = file, 18226 .offset = offset, 18227 .memory = @alignCast(memory), 18228 .section = null, 18229 }; 18230 } 18231 18232 const CreateFileMapError = error{ 18233 /// MaximumSize is greater than the system-defined maximum for sections, or 18234 /// greater than the specified file and the section is not writable. 18235 SectionOversize, 18236 /// A file descriptor refers to a non-regular file. Or a file mapping was requested, 18237 /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested 18238 /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode. 18239 /// Or `PROT_WRITE` is set, but the file is append-only. 18240 AccessDenied, 18241 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on 18242 /// a filesystem that was mounted no-exec. 18243 PermissionDenied, 18244 FileBusy, 18245 LockedMemoryLimitExceeded, 18246 OperationUnsupported, 18247 ProcessFdQuotaExceeded, 18248 SystemFdQuotaExceeded, 18249 OutOfMemory, 18250 MappingAlreadyExists, 18251 Unseekable, 18252 LockViolation, 18253 } || Io.Cancelable || Io.UnexpectedError; 18254 18255 fn createFileMap( 18256 file: File, 18257 protection: std.process.MemoryProtection, 18258 offset: u64, 18259 populate: bool, 18260 len: usize, 18261 ) CreateFileMapError!File.MemoryMap { 18262 if (is_windows) { 18263 try Thread.checkCancel(); 18264 18265 var section = windows.INVALID_HANDLE_VALUE; 18266 const section_size: windows.LARGE_INTEGER = @intCast(len); 18267 const page = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied; 18268 switch (windows.ntdll.NtCreateSection( 18269 §ion, 18270 .{ 18271 .SPECIFIC = .{ .SECTION = .{ 18272 .QUERY = true, 18273 .MAP_WRITE = protection.write, 18274 .MAP_READ = protection.read, 18275 .MAP_EXECUTE = protection.execute, 18276 .EXTEND_SIZE = true, 18277 } }, 18278 .STANDARD = .{ .RIGHTS = .REQUIRED }, 18279 }, 18280 null, 18281 §ion_size, 18282 page, 18283 .{ .COMMIT = populate }, 18284 file.handle, 18285 )) { 18286 .SUCCESS => {}, 18287 .FILE_LOCK_CONFLICT => return error.LockViolation, 18288 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported, 18289 .ACCESS_DENIED => return error.AccessDenied, 18290 .SECTION_TOO_BIG => return error.SectionOversize, 18291 else => |status| return windows.unexpectedStatus(status), 18292 } 18293 var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null; 18294 var contents_len = len; 18295 switch (windows.ntdll.NtMapViewOfSection( 18296 section, 18297 windows.current_process, 18298 @ptrCast(&contents_ptr), 18299 null, 18300 0, 18301 null, 18302 &contents_len, 18303 .Unmap, 18304 .{}, 18305 page, 18306 )) { 18307 .SUCCESS => {}, 18308 .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists, 18309 .SECTION_PROTECTION => return error.PermissionDenied, 18310 .ACCESS_DENIED => return error.AccessDenied, 18311 .INVALID_VIEW_SIZE => |status| return windows.statusBug(status), 18312 else => |status| return windows.unexpectedStatus(status), 18313 } 18314 if (builtin.mode == .Debug) { 18315 const page_size = std.heap.pageSize(); 18316 const alignment: Alignment = .fromByteUnits(page_size); 18317 assert(contents_len == alignment.forward(len)); 18318 } 18319 return .{ 18320 .file = file, 18321 .offset = offset, 18322 .memory = contents_ptr.?[0..len], 18323 .section = section, 18324 }; 18325 } else if (have_mmap) { 18326 const prot: posix.PROT = .{ 18327 .READ = protection.read, 18328 .WRITE = protection.write, 18329 .EXEC = protection.execute, 18330 }; 18331 const flags: posix.MAP = switch (native_os) { 18332 .linux => .{ 18333 .TYPE = .SHARED_VALIDATE, 18334 .POPULATE = populate, 18335 }, 18336 else => .{ 18337 .TYPE = .SHARED, 18338 }, 18339 }; 18340 18341 const page_align = std.heap.page_size_min; 18342 18343 const contents = while (true) { 18344 const syscall: Syscall = try .start(); 18345 const casted_offset = std.math.cast(i64, offset) orelse return error.Unseekable; 18346 const rc = mmap_sym(null, len, prot, flags, file.handle, casted_offset); 18347 syscall.finish(); 18348 const err: posix.E = if (builtin.link_libc) e: { 18349 if (rc != std.c.MAP_FAILED) { 18350 break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..len]; 18351 } 18352 break :e @enumFromInt(posix.system._errno().*); 18353 } else e: { 18354 const err = posix.errno(rc); 18355 if (err == .SUCCESS) { 18356 break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..len]; 18357 } 18358 break :e err; 18359 }; 18360 switch (err) { 18361 .SUCCESS => unreachable, 18362 .INTR => continue, 18363 .ACCES => return error.AccessDenied, 18364 .AGAIN => return error.LockedMemoryLimitExceeded, 18365 .EXIST => return error.MappingAlreadyExists, 18366 .MFILE => return error.ProcessFdQuotaExceeded, 18367 .NFILE => return error.SystemFdQuotaExceeded, 18368 .NODEV => return error.OperationUnsupported, 18369 .NOMEM => return error.OutOfMemory, 18370 .PERM => return error.PermissionDenied, 18371 .TXTBSY => return error.FileBusy, 18372 .OVERFLOW => return error.Unseekable, 18373 .BADF => return errnoBug(err), // Always a race condition. 18374 .INVAL => return errnoBug(err), // Invalid parameters to mmap() 18375 .OPNOTSUPP => return errnoBug(err), // Bad flags with MAP.SHARED_VALIDATE on Linux. 18376 else => return posix.unexpectedErrno(err), 18377 } 18378 }; 18379 return .{ 18380 .file = file, 18381 .offset = offset, 18382 .memory = contents, 18383 .section = {}, 18384 }; 18385 } 18386 18387 return error.OperationUnsupported; 18388 } 18389 18390 fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void { 18391 const t: *Threaded = @ptrCast(@alignCast(userdata)); 18392 const memory = mm.memory; 18393 if (mm.section) |section| switch (native_os) { 18394 .windows => { 18395 if (section == windows.INVALID_HANDLE_VALUE) return; 18396 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr); 18397 windows.CloseHandle(section); 18398 }, 18399 .wasi => unreachable, 18400 else => { 18401 if (memory.len == 0) return; 18402 switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) { 18403 .SUCCESS => {}, 18404 else => |e| { 18405 if (builtin.mode == .Debug) 18406 std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e }); 18407 }, 18408 } 18409 }, 18410 } else { 18411 const gpa = t.allocator; 18412 gpa.rawFree(memory, .fromByteUnits(std.heap.pageSize()), @returnAddress()); 18413 } 18414 mm.* = undefined; 18415 } 18416 18417 fn fileMemoryMapSetLength( 18418 userdata: ?*anyopaque, 18419 mm: *File.MemoryMap, 18420 new_len: usize, 18421 ) File.MemoryMap.SetLengthError!void { 18422 const t: *Threaded = @ptrCast(@alignCast(userdata)); 18423 const page_size = std.heap.pageSize(); 18424 const alignment: Alignment = .fromByteUnits(page_size); 18425 const page_align = std.heap.page_size_min; 18426 const old_memory = mm.memory; 18427 18428 if (mm.section) |section| { 18429 _ = section; 18430 if (alignment.forward(new_len) == alignment.forward(old_memory.len)) { 18431 mm.memory.len = new_len; 18432 return; 18433 } 18434 switch (native_os) { 18435 .wasi => unreachable, 18436 .linux => { 18437 const flags: posix.MREMAP = .{ .MAYMOVE = true }; 18438 const addr_hint: ?[*]const u8 = null; 18439 const new_memory = while (true) { 18440 const syscall: Syscall = try .start(); 18441 const rc = posix.system.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint); 18442 syscall.finish(); 18443 const err: posix.E = if (builtin.link_libc) e: { 18444 if (rc != std.c.MAP_FAILED) break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..new_len]; 18445 break :e @enumFromInt(posix.system._errno().*); 18446 } else e: { 18447 const err = posix.errno(rc); 18448 if (err == .SUCCESS) break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len]; 18449 break :e err; 18450 }; 18451 switch (err) { 18452 .SUCCESS => unreachable, 18453 .INTR => continue, 18454 .AGAIN => return error.LockedMemoryLimitExceeded, 18455 .NOMEM => return error.OutOfMemory, 18456 .INVAL => return errnoBug(err), 18457 .FAULT => return errnoBug(err), 18458 else => return posix.unexpectedErrno(err), 18459 } 18460 }; 18461 mm.memory = new_memory; 18462 return; 18463 }, 18464 else => return error.OperationUnsupported, 18465 } 18466 } else { 18467 const gpa = t.allocator; 18468 if (gpa.rawRemap(old_memory, alignment, new_len, @returnAddress())) |new_ptr| { 18469 mm.memory = @alignCast(new_ptr[0..new_len]); 18470 } else { 18471 const new_ptr: [*]align(page_align) u8 = @alignCast( 18472 gpa.rawAlloc(new_len, alignment, @returnAddress()) orelse return error.OutOfMemory, 18473 ); 18474 const copy_len = @min(new_len, old_memory.len); 18475 @memcpy(new_ptr[0..copy_len], old_memory[0..copy_len]); 18476 mm.memory = new_ptr[0..new_len]; 18477 gpa.rawFree(old_memory, alignment, @returnAddress()); 18478 } 18479 } 18480 } 18481 18482 fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositionalError!void { 18483 const t: *Threaded = @ptrCast(@alignCast(userdata)); 18484 _ = t; 18485 const section = mm.section orelse return mmSyncRead(mm.file, mm.memory, mm.offset); 18486 _ = section; 18487 } 18488 18489 fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void { 18490 const t: *Threaded = @ptrCast(@alignCast(userdata)); 18491 _ = t; 18492 const section = mm.section orelse return mmSyncWrite(mm.file, mm.memory, mm.offset); 18493 _ = section; 18494 } 18495 18496 fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void { 18497 if (is_windows) { 18498 var i: usize = 0; 18499 while (true) { 18500 const buf = memory[i..]; 18501 if (buf.len == 0) break; 18502 const n = try readFilePositionalWindows(file, buf, offset + i); 18503 if (n == 0) { 18504 @memset(memory[i..], 0); 18505 break; 18506 } 18507 i += n; 18508 } 18509 } else if (native_os == .wasi and !builtin.link_libc) { 18510 var i: usize = 0; 18511 const syscall: Syscall = try .start(); 18512 while (true) { 18513 const buf = memory[i..]; 18514 if (buf.len == 0) { 18515 syscall.finish(); 18516 break; 18517 } 18518 var n: usize = undefined; 18519 const vec: std.os.wasi.iovec_t = .{ .base = buf.ptr, .len = buf.len }; 18520 switch (std.os.wasi.fd_pread(file.handle, (&vec)[0..1], 1, offset + i, &n)) { 18521 .SUCCESS => { 18522 if (n == 0) { 18523 syscall.finish(); 18524 @memset(memory[i..], 0); 18525 break; 18526 } 18527 i += n; 18528 try syscall.checkCancel(); 18529 continue; 18530 }, 18531 .INTR, .TIMEDOUT => { 18532 try syscall.checkCancel(); 18533 continue; 18534 }, 18535 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket 18536 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket 18537 .BADF => |err| return syscall.errnoBug(err), // use after free 18538 .INVAL => |err| return syscall.errnoBug(err), 18539 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault 18540 .AGAIN => |err| return syscall.errnoBug(err), 18541 .IO => return syscall.fail(error.InputOutput), 18542 .ISDIR => return syscall.fail(error.IsDir), 18543 .NOBUFS => return syscall.fail(error.SystemResources), 18544 .NOMEM => return syscall.fail(error.SystemResources), 18545 .NXIO => return syscall.fail(error.Unseekable), 18546 .SPIPE => return syscall.fail(error.Unseekable), 18547 .OVERFLOW => return syscall.fail(error.Unseekable), 18548 .NOTCAPABLE => return syscall.fail(error.AccessDenied), 18549 else => |err| return syscall.unexpectedErrno(err), 18550 } 18551 } 18552 } else { 18553 var i: usize = 0; 18554 const syscall: Syscall = try .start(); 18555 while (true) { 18556 const buf = memory[i..]; 18557 if (buf.len == 0) { 18558 syscall.finish(); 18559 break; 18560 } 18561 const rc = pread_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i)); 18562 switch (posix.errno(rc)) { 18563 .SUCCESS => { 18564 const n: usize = @intCast(rc); 18565 if (n == 0) { 18566 syscall.finish(); 18567 @memset(memory[i..], 0); 18568 break; 18569 } 18570 i += n; 18571 try syscall.checkCancel(); 18572 continue; 18573 }, 18574 .INTR, .TIMEDOUT => { 18575 try syscall.checkCancel(); 18576 continue; 18577 }, 18578 .NXIO => return syscall.fail(error.Unseekable), 18579 .SPIPE => return syscall.fail(error.Unseekable), 18580 .OVERFLOW => return syscall.fail(error.Unseekable), 18581 .NOBUFS => return syscall.fail(error.SystemResources), 18582 .NOMEM => return syscall.fail(error.SystemResources), 18583 .AGAIN => return syscall.fail(error.WouldBlock), 18584 .IO => return syscall.fail(error.InputOutput), 18585 .ISDIR => return syscall.fail(error.IsDir), 18586 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket 18587 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket 18588 .INVAL => |err| return syscall.errnoBug(err), 18589 .FAULT => |err| return syscall.errnoBug(err), 18590 .BADF => |err| return syscall.errnoBug(err), // use after free 18591 else => |err| return syscall.unexpectedErrno(err), 18592 } 18593 } 18594 } 18595 } 18596 18597 fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError!void { 18598 if (is_windows) { 18599 var i: usize = 0; 18600 while (true) { 18601 const buf = memory[i..]; 18602 if (buf.len == 0) break; 18603 i += try writeFilePositionalWindows(file, memory[i..], offset + i); 18604 } 18605 } else if (native_os == .wasi and !builtin.link_libc) { 18606 var i: usize = 0; 18607 var n: usize = undefined; 18608 const syscall: Syscall = try .start(); 18609 while (true) { 18610 const buf = memory[i..]; 18611 if (buf.len == 0) { 18612 syscall.finish(); 18613 break; 18614 } 18615 const iovec: std.os.wasi.ciovec_t = .{ .base = buf.ptr, .len = buf.len }; 18616 switch (std.os.wasi.fd_pwrite(file.handle, (&iovec)[0..1], 1, offset + i, &n)) { 18617 .SUCCESS => { 18618 i += n; 18619 try syscall.checkCancel(); 18620 continue; 18621 }, 18622 .INTR => { 18623 try syscall.checkCancel(); 18624 continue; 18625 }, 18626 .DQUOT => return syscall.fail(error.DiskQuota), 18627 .FBIG => return syscall.fail(error.FileTooBig), 18628 .IO => return syscall.fail(error.InputOutput), 18629 .NOSPC => return syscall.fail(error.NoSpaceLeft), 18630 .PERM => return syscall.fail(error.PermissionDenied), 18631 .PIPE => return syscall.fail(error.BrokenPipe), 18632 .NOTCAPABLE => return syscall.fail(error.AccessDenied), 18633 .NXIO => return syscall.fail(error.Unseekable), 18634 .SPIPE => return syscall.fail(error.Unseekable), 18635 .OVERFLOW => return syscall.fail(error.Unseekable), 18636 .INVAL => |err| return syscall.errnoBug(err), 18637 .FAULT => |err| return syscall.errnoBug(err), 18638 .AGAIN => |err| return syscall.errnoBug(err), 18639 .BADF => |err| return syscall.errnoBug(err), // use after free 18640 .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket 18641 else => |err| return syscall.unexpectedErrno(err), 18642 } 18643 } 18644 } else { 18645 var i: usize = 0; 18646 const syscall: Syscall = try .start(); 18647 while (true) { 18648 const buf = memory[i..]; 18649 if (buf.len == 0) { 18650 syscall.finish(); 18651 break; 18652 } 18653 const rc = pwrite_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i)); 18654 switch (posix.errno(rc)) { 18655 .SUCCESS => { 18656 const n: usize = @bitCast(rc); 18657 i += n; 18658 try syscall.checkCancel(); 18659 continue; 18660 }, 18661 .INTR => { 18662 try syscall.checkCancel(); 18663 continue; 18664 }, 18665 .INVAL => |err| return syscall.errnoBug(err), 18666 .FAULT => |err| return syscall.errnoBug(err), 18667 .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket 18668 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket 18669 .BADF => return syscall.fail(error.NotOpenForWriting), 18670 .AGAIN => return syscall.fail(error.WouldBlock), 18671 .DQUOT => return syscall.fail(error.DiskQuota), 18672 .FBIG => return syscall.fail(error.FileTooBig), 18673 .IO => return syscall.fail(error.InputOutput), 18674 .NOSPC => return syscall.fail(error.NoSpaceLeft), 18675 .PERM => return syscall.fail(error.PermissionDenied), 18676 .PIPE => return syscall.fail(error.BrokenPipe), 18677 .BUSY => return syscall.fail(error.DeviceBusy), 18678 .TXTBSY => return syscall.fail(error.FileBusy), 18679 .NXIO => return syscall.fail(error.Unseekable), 18680 .SPIPE => return syscall.fail(error.Unseekable), 18681 .OVERFLOW => return syscall.fail(error.Unseekable), 18682 else => |err| return syscall.unexpectedErrno(err), 18683 } 18684 } 18685 } 18686 } 18687 18688 fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { 18689 if (is_windows) { 18690 const NtControlFile = switch (o.code.DeviceType) { 18691 .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, 18692 else => &windows.ntdll.NtDeviceIoControlFile, 18693 }; 18694 var iosb: windows.IO_STATUS_BLOCK = undefined; 18695 if (o.file.flags.nonblocking) { 18696 var done: bool = false; 18697 switch (NtControlFile( 18698 o.file.handle, 18699 null, // event 18700 flagApc, 18701 &done, // APC context 18702 &iosb, 18703 o.code, 18704 if (o.in.len > 0) o.in.ptr else null, 18705 @intCast(o.in.len), 18706 if (o.out.len > 0) o.out.ptr else null, 18707 @intCast(o.out.len), 18708 )) { 18709 // We must wait for the APC routine. 18710 .PENDING, .SUCCESS => while (!done) { 18711 // Once we get here we must not return from the function until the 18712 // operation completes, thereby releasing reference to io_status_block. 18713 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { 18714 error.Canceled => |e| { 18715 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; 18716 _ = windows.ntdll.NtCancelIoFileEx(o.file.handle, &iosb, &cancel_iosb); 18717 while (!done) waitForApcOrAlert(); 18718 return e; 18719 }, 18720 }; 18721 waitForApcOrAlert(); 18722 alertable_syscall.finish(); 18723 }, 18724 else => |status| iosb.u.Status = status, 18725 } 18726 } else { 18727 const syscall: Syscall = try .start(); 18728 while (true) switch (NtControlFile( 18729 o.file.handle, 18730 null, // event 18731 null, // APC routine 18732 null, // APC context 18733 &iosb, 18734 o.code, 18735 if (o.in.len > 0) o.in.ptr else null, 18736 @intCast(o.in.len), 18737 if (o.out.len > 0) o.out.ptr else null, 18738 @intCast(o.out.len), 18739 )) { 18740 .PENDING => unreachable, // unrecoverable: wrong asynchronous flag 18741 .CANCELLED => { 18742 try syscall.checkCancel(); 18743 continue; 18744 }, 18745 else => |status| { 18746 syscall.finish(); 18747 iosb.u.Status = status; 18748 break; 18749 }, 18750 }; 18751 } 18752 return iosb; 18753 } else { 18754 const syscall: Syscall = try .start(); 18755 while (true) { 18756 const rc = posix.system.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg)); 18757 switch (posix.errno(rc)) { 18758 .SUCCESS => { 18759 syscall.finish(); 18760 if (@TypeOf(rc) == usize) return @bitCast(@as(u32, @truncate(rc))); 18761 return rc; 18762 }, 18763 .INTR => { 18764 try syscall.checkCancel(); 18765 continue; 18766 }, 18767 else => |err| { 18768 syscall.finish(); 18769 return -@as(i32, @intFromEnum(err)); 18770 }, 18771 } 18772 } 18773 } 18774 } 18775 18776 const WaitGroup = struct { 18777 state: std.atomic.Value(usize), 18778 event: Io.Event, 18779 18780 const init: WaitGroup = .{ .state = .{ .raw = 0 }, .event = .unset }; 18781 18782 const is_waiting: usize = 1 << 0; 18783 const one_pending: usize = 1 << 1; 18784 18785 fn start(wg: *WaitGroup) void { 18786 const prev_state = wg.state.fetchAdd(one_pending, .monotonic); 18787 assert((prev_state / one_pending) < (std.math.maxInt(usize) / one_pending)); 18788 } 18789 18790 fn value(wg: *WaitGroup) usize { 18791 return wg.state.load(.monotonic) / one_pending; 18792 } 18793 18794 fn wait(wg: *WaitGroup) void { 18795 const prev_state = wg.state.fetchAdd(is_waiting, .acquire); 18796 assert(prev_state & is_waiting == 0); 18797 if ((prev_state / one_pending) > 0) eventWait(&wg.event); 18798 } 18799 18800 fn finish(wg: *WaitGroup) void { 18801 const state = wg.state.fetchSub(one_pending, .acq_rel); 18802 assert((state / one_pending) > 0); 18803 18804 if (state == (one_pending | is_waiting)) { 18805 eventSet(&wg.event); 18806 } 18807 } 18808 }; 18809 18810 /// Same as `Io.Event.wait` but avoids the VTable. 18811 fn eventWait(event: *Io.Event) void { 18812 if (@cmpxchgStrong(Io.Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) { 18813 .unset => unreachable, 18814 .waiting => {}, 18815 .is_set => return, 18816 }; 18817 while (true) { 18818 Thread.futexWaitUncancelable(@ptrCast(event), @intFromEnum(Io.Event.waiting), null); 18819 switch (@atomicLoad(Io.Event, event, .acquire)) { 18820 .unset => unreachable, // `reset` called before pending `wait` returned 18821 .waiting => continue, 18822 .is_set => return, 18823 } 18824 } 18825 } 18826 18827 /// Same as `Io.Event.set` but avoids the VTable. 18828 fn eventSet(event: *Io.Event) void { 18829 switch (@atomicRmw(Io.Event, event, .Xchg, .is_set, .release)) { 18830 .unset, .is_set => {}, 18831 .waiting => Thread.futexWake(@ptrCast(event), std.math.maxInt(u32)), 18832 } 18833 } 18834 18835 /// Same as `Io.Condition.broadcast` but avoids the VTable. 18836 fn condBroadcast(cond: *Io.Condition) void { 18837 var prev_state = cond.state.load(.monotonic); 18838 while (prev_state.waiters > prev_state.signals) { 18839 @branchHint(.unlikely); 18840 prev_state = cond.state.cmpxchgWeak(prev_state, .{ 18841 .waiters = prev_state.waiters, 18842 .signals = prev_state.waiters, 18843 }, .release, .monotonic) orelse { 18844 // Update the epoch to tell the waiting threads that there are new signals for them. 18845 // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen 18846 // between it observing the epoch and sleeping on it, but this is extraordinarily 18847 // unlikely due to the precise number of calls required. 18848 _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update 18849 Thread.futexWake(&cond.epoch.raw, prev_state.waiters - prev_state.signals); 18850 return; 18851 }; 18852 } 18853 } 18854 18855 /// Same as `Io.Condition.signal` but avoids the VTable. 18856 fn condSignal(cond: *Io.Condition) void { 18857 var prev_state = cond.state.load(.monotonic); 18858 while (prev_state.waiters > prev_state.signals) { 18859 @branchHint(.unlikely); 18860 prev_state = cond.state.cmpxchgWeak(prev_state, .{ 18861 .waiters = prev_state.waiters, 18862 .signals = prev_state.signals + 1, 18863 }, .release, .monotonic) orelse { 18864 // Update the epoch to tell the waiting threads that there are new signals for them. 18865 // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen 18866 // between it observing the epoch and sleeping on it, but this is extraordinarily 18867 // unlikely due to the precise number of calls required. 18868 _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update 18869 Thread.futexWake(&cond.epoch.raw, 1); 18870 return; 18871 }; 18872 } 18873 } 18874 18875 /// Same as `Io.Condition.waitUncancelable` but avoids the VTable. 18876 fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { 18877 var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load 18878 18879 { 18880 const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic); 18881 assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters 18882 } 18883 18884 mutexUnlock(mutex); 18885 defer mutexLock(mutex); 18886 18887 while (true) { 18888 Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null); 18889 18890 epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod 18891 18892 var prev_state = cond.state.load(.monotonic); 18893 while (prev_state.signals > 0) { 18894 prev_state = cond.state.cmpxchgWeak(prev_state, .{ 18895 .waiters = prev_state.waiters - 1, 18896 .signals = prev_state.signals - 1, 18897 }, .acquire, .monotonic) orelse { 18898 // We successfully consumed a signal. 18899 return; 18900 }; 18901 } 18902 } 18903 } 18904 18905 /// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. 18906 pub fn mutexLock(m: *Io.Mutex) void { 18907 const initial_state = m.state.cmpxchgWeak( 18908 .unlocked, 18909 .locked_once, 18910 .acquire, 18911 .monotonic, 18912 ) orelse { 18913 @branchHint(.likely); 18914 return; 18915 }; 18916 if (initial_state == .contended) { 18917 Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); 18918 } 18919 while (m.state.swap(.contended, .acquire) != .unlocked) { 18920 Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); 18921 } 18922 } 18923 18924 /// Same as `Io.Mutex.unlock` but avoids the VTable. 18925 pub fn mutexUnlock(m: *Io.Mutex) void { 18926 switch (m.state.swap(.unlocked, .release)) { 18927 .unlocked => unreachable, 18928 .locked_once => {}, 18929 .contended => { 18930 @branchHint(.unlikely); 18931 Thread.futexWake(@ptrCast(&m.state.raw), 1); 18932 }, 18933 } 18934 } 18935 18936 const OpenError = error{ 18937 IsDir, 18938 NotDir, 18939 FileNotFound, 18940 NoDevice, 18941 AccessDenied, 18942 PipeBusy, 18943 PathAlreadyExists, 18944 WouldBlock, 18945 NetworkNotFound, 18946 AntivirusInterference, 18947 FileBusy, 18948 } || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; 18949 18950 const OpenFileOptions = struct { 18951 access_mask: windows.ACCESS_MASK, 18952 dir: ?windows.HANDLE = null, 18953 sa: ?*const windows.SECURITY_ATTRIBUTES = null, 18954 share_access: windows.FILE.SHARE = .VALID_FLAGS, 18955 creation: windows.FILE.CREATE_DISPOSITION, 18956 filter: Filter = .non_directory_only, 18957 /// If false, tries to open path as a reparse point without dereferencing it. 18958 /// Defaults to true. 18959 follow_symlinks: bool = true, 18960 18961 pub const Filter = enum { 18962 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory. 18963 non_directory_only, 18964 /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory. 18965 dir_only, 18966 /// `OpenFile` does not discriminate between opening files and directories. 18967 any, 18968 }; 18969 }; 18970 18971 /// TODO: inline this logic everywhere and delete this function 18972 fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows.HANDLE { 18973 if (std.mem.eql(u16, sub_path_w, &.{'.'}) and options.filter == .non_directory_only) { 18974 return error.IsDir; 18975 } 18976 if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' }) and options.filter == .non_directory_only) { 18977 return error.IsDir; 18978 } 18979 18980 var result: windows.HANDLE = undefined; 18981 18982 const attr: windows.OBJECT.ATTRIBUTES = .{ 18983 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, 18984 .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != windows.FALSE else false }, 18985 .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w)), 18986 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, 18987 }; 18988 18989 var iosb: windows.IO_STATUS_BLOCK = undefined; 18990 var attempt: u5 = 0; 18991 var syscall: Syscall = try .start(); 18992 while (true) { 18993 switch (windows.ntdll.NtCreateFile( 18994 &result, 18995 options.access_mask, 18996 &attr, 18997 &iosb, 18998 null, 18999 .{ .NORMAL = true }, 19000 options.share_access, 19001 options.creation, 19002 .{ 19003 .DIRECTORY_FILE = options.filter == .dir_only, 19004 .NON_DIRECTORY_FILE = options.filter == .non_directory_only, 19005 .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, 19006 .OPEN_REPARSE_POINT = !options.follow_symlinks, 19007 }, 19008 null, 19009 0, 19010 )) { 19011 .SUCCESS => { 19012 syscall.finish(); 19013 return result; 19014 }, 19015 .CANCELLED => { 19016 try syscall.checkCancel(); 19017 continue; 19018 }, 19019 .SHARING_VIOLATION => { 19020 // This occurs if the file attempting to be opened is a running 19021 // executable. However, there's a kernel bug: the error may be 19022 // incorrectly returned for an indeterminate amount of time 19023 // after an executable file is closed. Here we work around the 19024 // kernel bug with retry attempts. 19025 syscall.finish(); 19026 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 19027 try parking_sleep.sleep(.{ .duration = .{ 19028 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 19029 .clock = .awake, 19030 } }); 19031 attempt += 1; 19032 syscall = try .start(); 19033 continue; 19034 }, 19035 .DELETE_PENDING => { 19036 // This error means that there *was* a file in this location on 19037 // the file system, but it was deleted. However, the OS is not 19038 // finished with the deletion operation, and so this CreateFile 19039 // call has failed. There is not really a sane way to handle 19040 // this other than retrying the creation after the OS finishes 19041 // the deletion. 19042 syscall.finish(); 19043 if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; 19044 try parking_sleep.sleep(.{ .duration = .{ 19045 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), 19046 .clock = .awake, 19047 } }); 19048 attempt += 1; 19049 syscall = try .start(); 19050 continue; 19051 }, 19052 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), 19053 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), 19054 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), 19055 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found 19056 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't 19057 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), 19058 .ACCESS_DENIED => return syscall.fail(error.AccessDenied), 19059 .PIPE_BUSY => return syscall.fail(error.PipeBusy), 19060 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), 19061 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), 19062 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), 19063 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), 19064 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), 19065 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), 19066 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), 19067 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), 19068 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), 19069 else => |status| return syscall.unexpectedNtstatus(status), 19070 } 19071 } 19072 } 19073 19074 pub fn closeFd(fd: posix.fd_t) void { 19075 if (native_os == .wasi and !builtin.link_libc) { 19076 switch (std.os.wasi.fd_close(fd)) { 19077 .SUCCESS, .INTR => {}, 19078 .BADF => recoverableOsBugDetected(), // use after free 19079 else => recoverableOsBugDetected(), // unexpected failure 19080 } 19081 } else switch (posix.errno(posix.system.close(fd))) { 19082 .SUCCESS, .INTR => {}, // INTR still a success, see https://github.com/ziglang/zig/issues/2425 19083 .BADF => recoverableOsBugDetected(), // use after free 19084 else => recoverableOsBugDetected(), // unexpected failure 19085 } 19086 }