| 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 = net.HostName; |
| 15 | const IpAddress = 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 | have_signal_handler: bool, |
| 51 | old_sig_io: if (have_sig_io) posix.Sigaction else void, |
| 52 | old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, |
| 53 | |
| 54 | use_sendfile: UseSendfile = .default, |
| 55 | use_copy_file_range: UseCopyFileRange = .default, |
| 56 | use_fcopyfile: UseFcopyfile = .default, |
| 57 | use_fchmodat2: UseFchmodat2 = .default, |
| 58 | disable_memory_mapping: bool, |
| 59 | |
| 60 | stderr_writer: File.Writer = .{ |
| 61 | .io = undefined, |
| 62 | .interface = File.Writer.initInterface(&.{}), |
| 63 | .file = if (is_windows) undefined else .stderr(), |
| 64 | .mode = .streaming, |
| 65 | }, |
| 66 | stderr_mode: Io.Terminal.Mode = .no_color, |
| 67 | stderr_writer_initialized: bool = false, |
| 68 | stderr_mutex: Io.Mutex = .init, |
| 69 | stderr_mutex_locker: std.Thread.Id = Thread.invalid_id, |
| 70 | stderr_mutex_lock_count: usize = 0, |
| 71 | |
| 72 | argv0: Argv0, |
| 73 | /// Protected by `mutex`. Determines whether `environ` has been |
| 74 | /// memoized based on `process_environ`. |
| 75 | environ_initialized: bool, |
| 76 | environ: Environ, |
| 77 | |
| 78 | dl: Dl = .init, |
| 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 | const Dl = switch (native_os) { |
| 94 | .windows => struct { |
| 95 | iphlpapi_dll: std.atomic.Value(?*anyopaque), |
| 96 | ConvertInterfaceNameToLuidW: std.atomic.Value(?*const fn ( |
| 97 | InterfaceName: [*:0]const windows.WCHAR, |
| 98 | InterfaceLuid: *windows.NET.LUID, |
| 99 | ) callconv(.winapi) windows.Win32Error), |
| 100 | ConvertInterfaceLuidToIndex: std.atomic.Value(?*const fn ( |
| 101 | InterfaceLuid: *const windows.NET.LUID, |
| 102 | InterfaceIndex: *windows.NET.IFINDEX, |
| 103 | ) callconv(.winapi) windows.Win32Error), |
| 104 | ConvertInterfaceIndexToLuid: std.atomic.Value(?*const fn ( |
| 105 | InterfaceIndex: std.os.windows.NET.IFINDEX, |
| 106 | InterfaceLuid: *std.os.windows.NET.LUID, |
| 107 | ) callconv(.winapi) windows.Win32Error), |
| 108 | ConvertInterfaceLuidToNameW: std.atomic.Value(?*const fn ( |
| 109 | InterfaceLuid: *const std.os.windows.NET.LUID, |
| 110 | InterfaceName: std.os.windows.PWSTR, |
| 111 | Length: std.os.windows.SIZE_T, |
| 112 | ) callconv(.winapi) std.os.windows.Win32Error), |
| 113 | |
| 114 | dnsapi_dll: std.atomic.Value(?*anyopaque), |
| 115 | DnsQueryEx: std.atomic.Value(?*const fn ( |
| 116 | pQueryRequest: *const windows.DNS.QUERY.REQUEST, |
| 117 | pQueryResults: *windows.DNS.QUERY.RESULT, |
| 118 | pCancelHandle: ?*windows.DNS.QUERY.CANCEL, |
| 119 | ) callconv(.winapi) windows.DNS.STATUS), |
| 120 | //DnsCancelQuery: std.atomic.Value(?*const fn ( |
| 121 | // pCancelHandle: *const windows.DNS.QUERY.CANCEL, |
| 122 | //) callconv(.winapi) windows.DNS.STATUS), |
| 123 | DnsFree: std.atomic.Value(?*const fn ( |
| 124 | pRecordList: ?*anyopaque, |
| 125 | FreeType: windows.DNS.FREE_TYPE, |
| 126 | ) callconv(.winapi) void), |
| 127 | |
| 128 | const init: Dl = .{ |
| 129 | .iphlpapi_dll = .init(null), |
| 130 | .ConvertInterfaceNameToLuidW = .init(null), |
| 131 | .ConvertInterfaceLuidToIndex = .init(null), |
| 132 | .ConvertInterfaceIndexToLuid = .init(null), |
| 133 | .ConvertInterfaceLuidToNameW = .init(null), |
| 134 | |
| 135 | .dnsapi_dll = .init(null), |
| 136 | .DnsQueryEx = .init(null), |
| 137 | //.DnsCancelQuery = .init(null), |
| 138 | .DnsFree = .init(null), |
| 139 | }; |
| 140 | fn deinit(dl: *Dl) void { |
| 141 | if (dl.iphlpapi_dll.raw) |iphlpapi_dll| switch (windows.ntdll.LdrUnloadDll(iphlpapi_dll)) { |
| 142 | .SUCCESS => {}, |
| 143 | else => |status| windows.unexpectedStatus(status) catch {}, |
| 144 | }; |
| 145 | dl.* = .init; |
| 146 | } |
| 147 | }, |
| 148 | else => struct { |
| 149 | const init: Dl = .{}; |
| 150 | fn deinit(_: Dl) void {} |
| 151 | }, |
| 152 | }; |
| 153 | |
| 154 | pub const Csprng = struct { |
| 155 | rng: std.Random.DefaultCsprng, |
| 156 | |
| 157 | pub const uninitialized: Csprng = .{ .rng = .{ |
| 158 | .state = undefined, |
| 159 | .offset = std.math.maxInt(usize), |
| 160 | } }; |
| 161 | |
| 162 | pub const seed_len = std.Random.DefaultCsprng.secret_seed_length; |
| 163 | |
| 164 | pub fn isInitialized(c: *const Csprng) bool { |
| 165 | return c.rng.offset != std.math.maxInt(usize); |
| 166 | } |
| 167 | }; |
| 168 | |
| 169 | pub const Argv0 = switch (native_os) { |
| 170 | .openbsd, .haiku => struct { |
| 171 | value: ?[*:0]const u8, |
| 172 | |
| 173 | pub const empty: Argv0 = .{ .value = null }; |
| 174 | |
| 175 | pub fn init(args: process.Args) Argv0 { |
| 176 | return .{ .value = args.vector[0] }; |
| 177 | } |
| 178 | }, |
| 179 | else => struct { |
| 180 | pub const empty: Argv0 = .{}; |
| 181 | |
| 182 | pub fn init(args: process.Args) Argv0 { |
| 183 | _ = args; |
| 184 | return .{}; |
| 185 | } |
| 186 | }, |
| 187 | }; |
| 188 | |
| 189 | pub const Environ = struct { |
| 190 | /// Unmodified data directly from the OS. |
| 191 | process_environ: process.Environ, |
| 192 | /// Protected by `mutex`. Memoized based on `process_environ`. Tracks whether the |
| 193 | /// environment variables are present, ignoring their value. |
| 194 | exist: Exist = .{}, |
| 195 | /// Protected by `mutex`. Memoized based on `process_environ`. |
| 196 | string: String = .{}, |
| 197 | /// ZIG_PROGRESS |
| 198 | zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing, |
| 199 | /// Protected by `mutex`. Tracks the problem, if any, that occurred when |
| 200 | /// trying to scan environment variables. |
| 201 | /// |
| 202 | /// Errors are only possible on WASI. |
| 203 | err: ?Error = null, |
| 204 | |
| 205 | pub const empty: Environ = .{ .process_environ = .empty }; |
| 206 | |
| 207 | pub const Error = Allocator.Error || Io.UnexpectedError; |
| 208 | |
| 209 | pub const Exist = struct { |
| 210 | NO_COLOR: bool = false, |
| 211 | CLICOLOR_FORCE: bool = false, |
| 212 | }; |
| 213 | |
| 214 | pub const String = switch (native_os) { |
| 215 | .windows, .wasi => struct {}, |
| 216 | else => struct { |
| 217 | PATH: ?[:0]const u8 = null, |
| 218 | DEBUGINFOD_CACHE_PATH: ?[:0]const u8 = null, |
| 219 | XDG_CACHE_HOME: ?[:0]const u8 = null, |
| 220 | HOME: ?[:0]const u8 = null, |
| 221 | TERM: ?[:0]const u8 = null, |
| 222 | }, |
| 223 | }; |
| 224 | |
| 225 | pub fn scan(environ: *Environ, allocator: Allocator) void { |
| 226 | if (is_windows) { |
| 227 | // This value expires with any call that modifies the environment, |
| 228 | // which is outside of this Io implementation's control, so references |
| 229 | // must be short-lived. |
| 230 | const peb = windows.peb(); |
| 231 | assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); |
| 232 | defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); |
| 233 | const ptr = peb.ProcessParameters.Environment; |
| 234 | |
| 235 | var i: usize = 0; |
| 236 | while (ptr[i] != 0) { |
| 237 | // There are some special environment variables that start with =, |
| 238 | // so we need a special case to not treat = as a key/value separator |
| 239 | // if it's the first character. |
| 240 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 241 | const key_start = i; |
| 242 | if (ptr[i] == '=') i += 1; |
| 243 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} |
| 244 | const key_w = ptr[key_start..i]; |
| 245 | |
| 246 | const value_start = i + 1; |
| 247 | while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value |
| 248 | const value_w = ptr[value_start..i]; |
| 249 | i += 1; // skip over null byte |
| 250 | |
| 251 | if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) { |
| 252 | environ.exist.NO_COLOR = true; |
| 253 | } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) { |
| 254 | environ.exist.CLICOLOR_FORCE = true; |
| 255 | } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) { |
| 256 | environ.zig_progress_file = file: { |
| 257 | var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; |
| 258 | const len = std.unicode.calcWtf8Len(value_w); |
| 259 | if (len > value_buf.len) break :file error.UnrecognizedFormat; |
| 260 | assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len); |
| 261 | break :file .{ |
| 262 | .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch |
| 263 | break :file error.UnrecognizedFormat), |
| 264 | .flags = .{ .nonblocking = true }, |
| 265 | }; |
| 266 | }; |
| 267 | } |
| 268 | comptime assert(@sizeOf(String) == 0); |
| 269 | } |
| 270 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 271 | var environ_size: usize = undefined; |
| 272 | var environ_buf_size: usize = undefined; |
| 273 | |
| 274 | switch (std.os.wasi.environ_sizes_get(&environ_size, &environ_buf_size)) { |
| 275 | .SUCCESS => {}, |
| 276 | else => |err| { |
| 277 | environ.err = posix.unexpectedErrno(err); |
| 278 | return; |
| 279 | }, |
| 280 | } |
| 281 | if (environ_size == 0) return; |
| 282 | |
| 283 | const wasi_environ = allocator.alloc([*:0]u8, environ_size) catch |err| { |
| 284 | environ.err = err; |
| 285 | return; |
| 286 | }; |
| 287 | defer allocator.free(wasi_environ); |
| 288 | const wasi_environ_buf = allocator.alloc(u8, environ_buf_size) catch |err| { |
| 289 | environ.err = err; |
| 290 | return; |
| 291 | }; |
| 292 | defer allocator.free(wasi_environ_buf); |
| 293 | |
| 294 | switch (std.os.wasi.environ_get(wasi_environ.ptr, wasi_environ_buf.ptr)) { |
| 295 | .SUCCESS => {}, |
| 296 | else => |err| { |
| 297 | environ.err = posix.unexpectedErrno(err); |
| 298 | return; |
| 299 | }, |
| 300 | } |
| 301 | |
| 302 | for (wasi_environ) |env| { |
| 303 | const pair = std.mem.sliceTo(env, 0); |
| 304 | var parts = std.mem.splitScalar(u8, pair, '='); |
| 305 | const key = parts.first(); |
| 306 | if (std.mem.eql(u8, key, "NO_COLOR")) { |
| 307 | environ.exist.NO_COLOR = true; |
| 308 | } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { |
| 309 | environ.exist.CLICOLOR_FORCE = true; |
| 310 | } |
| 311 | comptime assert(@sizeOf(String) == 0); |
| 312 | } |
| 313 | } else { |
| 314 | for (environ.process_environ.block.slice) |opt_entry| { |
| 315 | const entry = opt_entry.?; |
| 316 | var entry_i: usize = 0; |
| 317 | while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {} |
| 318 | const key = entry[0..entry_i]; |
| 319 | |
| 320 | var end_i: usize = entry_i; |
| 321 | while (entry[end_i] != 0) : (end_i += 1) {} |
| 322 | const value = entry[entry_i + 1 .. end_i :0]; |
| 323 | |
| 324 | if (std.mem.eql(u8, key, "NO_COLOR")) { |
| 325 | environ.exist.NO_COLOR = true; |
| 326 | } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { |
| 327 | environ.exist.CLICOLOR_FORCE = true; |
| 328 | } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { |
| 329 | environ.zig_progress_file = file: { |
| 330 | break :file .{ |
| 331 | .handle = std.fmt.parseInt(u31, value, 10) catch |
| 332 | break :file error.UnrecognizedFormat, |
| 333 | .flags = .{ .nonblocking = true }, |
| 334 | }; |
| 335 | }; |
| 336 | } else inline for (@typeInfo(String).@"struct".field_names) |field_name| { |
| 337 | if (std.mem.eql(u8, key, field_name)) @field(environ.string, field_name) = value; |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | } |
| 342 | }; |
| 343 | |
| 344 | pub const NullFile = switch (native_os) { |
| 345 | .windows => struct { |
| 346 | handle: ?windows.HANDLE = null, |
| 347 | |
| 348 | fn deinit(this: *@This()) void { |
| 349 | if (this.handle) |handle| { |
| 350 | windows.CloseHandle(handle); |
| 351 | this.handle = null; |
| 352 | } |
| 353 | } |
| 354 | }, |
| 355 | .wasi, .ios, .tvos, .visionos, .watchos => struct { |
| 356 | fn deinit(this: @This()) void { |
| 357 | _ = this; |
| 358 | } |
| 359 | }, |
| 360 | else => struct { |
| 361 | fd: posix.fd_t = -1, |
| 362 | |
| 363 | fn deinit(this: *@This()) void { |
| 364 | if (this.fd >= 0) { |
| 365 | closeFd(this.fd); |
| 366 | this.fd = -1; |
| 367 | } |
| 368 | } |
| 369 | }, |
| 370 | }; |
| 371 | |
| 372 | pub const RandomFile = switch (native_os) { |
| 373 | .windows => NullFile, |
| 374 | else => if (use_dev_urandom) NullFile else struct { |
| 375 | fn deinit(this: @This()) void { |
| 376 | _ = this; |
| 377 | } |
| 378 | }, |
| 379 | }; |
| 380 | |
| 381 | pub const PipeFile = switch (native_os) { |
| 382 | .windows => struct { |
| 383 | handle: ?windows.HANDLE = null, |
| 384 | |
| 385 | fn deinit(this: *@This()) void { |
| 386 | if (this.handle) |handle| { |
| 387 | windows.CloseHandle(handle); |
| 388 | this.handle = null; |
| 389 | } |
| 390 | } |
| 391 | }, |
| 392 | else => struct { |
| 393 | fn deinit(this: @This()) void { |
| 394 | _ = this; |
| 395 | } |
| 396 | }, |
| 397 | }; |
| 398 | |
| 399 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 400 | unknown = 0, |
| 401 | _, |
| 402 | } else enum(u0) { unknown = 0 }; |
| 403 | |
| 404 | pub const UseSendfile = if (have_sendfile) enum { |
| 405 | enabled, |
| 406 | disabled, |
| 407 | pub const default: UseSendfile = .enabled; |
| 408 | } else enum { |
| 409 | disabled, |
| 410 | pub const default: UseSendfile = .disabled; |
| 411 | }; |
| 412 | |
| 413 | pub const UseCopyFileRange = if (have_copy_file_range) enum { |
| 414 | enabled, |
| 415 | disabled, |
| 416 | pub const default: UseCopyFileRange = .enabled; |
| 417 | } else enum { |
| 418 | disabled, |
| 419 | pub const default: UseCopyFileRange = .disabled; |
| 420 | }; |
| 421 | |
| 422 | pub const UseFcopyfile = if (have_fcopyfile) enum { |
| 423 | enabled, |
| 424 | disabled, |
| 425 | pub const default: UseFcopyfile = .enabled; |
| 426 | } else enum { |
| 427 | disabled, |
| 428 | pub const default: UseFcopyfile = .disabled; |
| 429 | }; |
| 430 | |
| 431 | pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum { |
| 432 | enabled, |
| 433 | disabled, |
| 434 | pub const default: UseFchmodat2 = .enabled; |
| 435 | } else enum { |
| 436 | disabled, |
| 437 | pub const default: UseFchmodat2 = .disabled; |
| 438 | }; |
| 439 | |
| 440 | pub const apc_align = @max(default_fn_align, 2); |
| 441 | |
| 442 | const default_fn_align = switch (builtin.mode) { |
| 443 | .debug, .safe, .fast => switch (builtin.cpu.arch) { |
| 444 | else => |arch| @compileError("Unsupported architecture: " ++ @tagName(arch)), |
| 445 | .arm, .thumb => 4, |
| 446 | .aarch64, .x86, .x86_64 => 16, |
| 447 | }, |
| 448 | .small => 1, |
| 449 | }; |
| 450 | |
| 451 | const Runnable = struct { |
| 452 | node: std.SinglyLinkedList.Node, |
| 453 | startFn: *const fn (*Runnable, *Thread, *Threaded) void, |
| 454 | }; |
| 455 | |
| 456 | const Group = struct { |
| 457 | ptr: *Io.Group, |
| 458 | |
| 459 | /// Returns a correctly-typed pointer to the `Io.Group.token` field. |
| 460 | /// |
| 461 | /// The status indicates how many pending tasks are in the group, whether the group has been |
| 462 | /// canceled, and whether the group has been awaited. |
| 463 | /// |
| 464 | /// Note that the zero value of `Status` intentionally represents the initial group state (empty |
| 465 | /// with no awaiters). This is a requirement of `Io.Group`. |
| 466 | fn status(g: Group) *std.atomic.Value(Status) { |
| 467 | return @ptrCast(&g.ptr.token); |
| 468 | } |
| 469 | /// Returns a correctly-typed pointer to the `Io.Group.state` field. The double-pointer here is |
| 470 | /// intentional, because the `state` field itself stores a pointer, and this function returns a |
| 471 | /// pointer to that field. |
| 472 | /// |
| 473 | /// On completion of the whole group, if `status` indicates that there is an awaiter, the last |
| 474 | /// task must increment this `u32` and do a futex wake on it to signal that awaiter. |
| 475 | fn awaiter(g: Group) **std.atomic.Value(u32) { |
| 476 | return @ptrCast(&g.ptr.state); |
| 477 | } |
| 478 | |
| 479 | const Status = packed struct(usize) { |
| 480 | num_running: @Int(.unsigned, @bitSizeOf(usize) - 2), |
| 481 | have_awaiter: bool, |
| 482 | canceled: bool, |
| 483 | }; |
| 484 | |
| 485 | const Task = struct { |
| 486 | runnable: Runnable, |
| 487 | group: *Io.Group, |
| 488 | func: *const fn (context: *const anyopaque) void, |
| 489 | context_alignment: Alignment, |
| 490 | alloc_len: usize, |
| 491 | |
| 492 | /// `Task.runnable.node` is `undefined` in the created `Task`. |
| 493 | fn create( |
| 494 | gpa: Allocator, |
| 495 | group: Group, |
| 496 | context: []const u8, |
| 497 | context_alignment: Alignment, |
| 498 | func: *const fn (context: *const anyopaque) void, |
| 499 | ) Allocator.Error!*Task { |
| 500 | const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Task); |
| 501 | const worst_case_context_offset = context_alignment.forward(@sizeOf(Task) + max_context_misalignment); |
| 502 | const alloc_len = worst_case_context_offset + context.len; |
| 503 | |
| 504 | const task: *Task = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Task), alloc_len))); |
| 505 | errdefer comptime unreachable; |
| 506 | |
| 507 | task.* = .{ |
| 508 | .runnable = .{ |
| 509 | .node = undefined, |
| 510 | .startFn = &start, |
| 511 | }, |
| 512 | .group = group.ptr, |
| 513 | .func = func, |
| 514 | .context_alignment = context_alignment, |
| 515 | .alloc_len = alloc_len, |
| 516 | }; |
| 517 | @memcpy(task.contextPointer()[0..context.len], context); |
| 518 | return task; |
| 519 | } |
| 520 | |
| 521 | fn destroy(task: *Task, gpa: Allocator) void { |
| 522 | const base: [*]align(@alignOf(Task)) u8 = @ptrCast(task); |
| 523 | gpa.free(base[0..task.alloc_len]); |
| 524 | } |
| 525 | |
| 526 | fn contextPointer(task: *Task) [*]u8 { |
| 527 | const base: [*]u8 = @ptrCast(task); |
| 528 | const offset = task.context_alignment.forward(@intFromPtr(base) + @sizeOf(Task)) - @intFromPtr(base); |
| 529 | return base + offset; |
| 530 | } |
| 531 | |
| 532 | fn start(r: *Runnable, thread: *Thread, t: *Threaded) void { |
| 533 | const task: *Task = @fieldParentPtr("runnable", r); |
| 534 | const group: Group = .{ .ptr = task.group }; |
| 535 | |
| 536 | // This would be a simple store, but it's upgraded to an RMW so we can use `.acquire` to |
| 537 | // enforce the ordering between this and the `group.status().load` below. Paired with |
| 538 | // the `.release` rmw on `Thread.status` in `cancelThreads`, this creates a StoreLoad |
| 539 | // barrier which guarantees that when a group is canceled, either we see the cancelation |
| 540 | // in the group status, or the canceler sees our thread status so can directly notify us |
| 541 | // of the cancelation. |
| 542 | _ = thread.status.swap(.{ |
| 543 | .cancelation = .none, |
| 544 | .awaitable = .fromGroup(group.ptr), |
| 545 | }, .acquire); |
| 546 | if (group.status().load(.monotonic).canceled) { |
| 547 | thread.status.store(.{ |
| 548 | .cancelation = .canceling, |
| 549 | .awaitable = .fromGroup(group.ptr), |
| 550 | }, .monotonic); |
| 551 | } |
| 552 | |
| 553 | task.func(task.contextPointer()); |
| 554 | |
| 555 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); |
| 556 | const old_status = group.status().fetchSub(.{ |
| 557 | .num_running = 1, |
| 558 | .have_awaiter = false, |
| 559 | .canceled = false, |
| 560 | }, .acq_rel); // acquire `group.awaiter()`, release task results |
| 561 | assert(old_status.num_running > 0); |
| 562 | if (old_status.have_awaiter and old_status.num_running == 1) { |
| 563 | const to_signal = group.awaiter().*; |
| 564 | // `awaiter` should only be modified by us. For another thread to see `num_running` |
| 565 | // drop to 0 after this point would indicate that another task started up, meaning |
| 566 | // `async`/`cancel` was racing with awaited group completion. |
| 567 | group.awaiter().* = undefined; |
| 568 | _ = to_signal.fetchAdd(1, .release); // release results |
| 569 | Thread.futexWake(&to_signal.raw, 1); |
| 570 | } |
| 571 | |
| 572 | // Task completed. Self-destruct sequence initiated. |
| 573 | task.destroy(t.allocator); |
| 574 | } |
| 575 | }; |
| 576 | |
| 577 | /// Assumes the caller has already atomically updated the group status to indicate cancelation, |
| 578 | /// and notifies any already-running threads of this cancelation. |
| 579 | fn cancelThreads(g: Group, t: *Threaded) bool { |
| 580 | var any_blocked = false; |
| 581 | var it = t.worker_threads.load(.acquire); // acquire `Thread` values |
| 582 | while (it) |thread| : (it = thread.next) { |
| 583 | // This non-mutating RMW exists for ordering reasons: see comment in `Group.Task.start` for reasons. |
| 584 | _ = thread.status.fetchOr(.{ .cancelation = @fromBackingInt(@intCast(0)), .awaitable = .null }, .release); |
| 585 | if (thread.cancelAwaitable(.fromGroup(g.ptr))) any_blocked = true; |
| 586 | } |
| 587 | return any_blocked; |
| 588 | } |
| 589 | |
| 590 | /// Uses `Thread.signalCanceledSyscall` to signal any threads which are still blocked in a |
| 591 | /// syscall for this group and have not observed a cancelation request yet. Returns `true` if |
| 592 | /// more signals may be necessary, in which case the caller must call this again after a delay. |
| 593 | fn signalAllCanceledSyscalls(g: Group, t: *Threaded) bool { |
| 594 | var any_signaled = false; |
| 595 | var it = t.worker_threads.load(.acquire); // acquire `Thread` values |
| 596 | while (it) |thread| : (it = thread.next) { |
| 597 | if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr))) any_signaled = true; |
| 598 | } |
| 599 | return any_signaled; |
| 600 | } |
| 601 | |
| 602 | /// The caller has canceled `g`. Inform any threads working on that group of the cancelation if |
| 603 | /// necessary, and wait for `g` to finish (indicated by `num_completed` being incremented from 0 |
| 604 | /// to 1), while sending regular signals to threads if necessary for them to unblock from any |
| 605 | /// cancelable syscalls. |
| 606 | /// |
| 607 | /// `skip_signals` means it is already known that no threads are currently working on the group |
| 608 | /// so no notifications or signals are necessary. |
| 609 | fn waitForCancelWithSignaling( |
| 610 | g: Group, |
| 611 | t: *Threaded, |
| 612 | num_completed: *std.atomic.Value(u32), |
| 613 | skip_signals: bool, |
| 614 | ) void { |
| 615 | var need_signal: bool = !skip_signals and g.cancelThreads(t); |
| 616 | var timeout_ns: u64 = 1 << 10; |
| 617 | while (true) { |
| 618 | need_signal = need_signal and g.signalAllCanceledSyscalls(t); |
| 619 | Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); |
| 620 | switch (num_completed.load(.acquire)) { // acquire task results |
| 621 | 0 => {}, |
| 622 | 1 => break, |
| 623 | else => unreachable, |
| 624 | } |
| 625 | timeout_ns <<|= 1; |
| 626 | } |
| 627 | } |
| 628 | }; |
| 629 | |
| 630 | /// Trailing data: |
| 631 | /// 1. context |
| 632 | /// 2. result |
| 633 | const Future = struct { |
| 634 | runnable: Runnable, |
| 635 | func: *const fn (context: *const anyopaque, result: *anyopaque) void, |
| 636 | status: std.atomic.Value(Status), |
| 637 | /// On completion, increment this `u32` and do a futex wake on it. |
| 638 | awaiter: *std.atomic.Value(u32), |
| 639 | context_alignment: Alignment, |
| 640 | result_offset: usize, |
| 641 | alloc_len: usize, |
| 642 | |
| 643 | const Status = packed struct(usize) { |
| 644 | /// The values of this enum are chosen so that await/cancel can just OR with 0b01 and 0b11 |
| 645 | /// respectively. That *does* clobber `.done`, but that's actually fine, because if the tag |
| 646 | /// is `.done` then only the awaiter is referencing this `Future` anyway. |
| 647 | tag: enum(u2) { |
| 648 | /// The future is queued or running (depending on whether `thread` is set). |
| 649 | pending = 0b00, |
| 650 | /// Like `pending`, but the future is being awaited. `Future.awaiter` is populated. |
| 651 | pending_awaited = 0b01, |
| 652 | /// Like `pending`, but the future is being canceled. `Future.awaiter` is populated. |
| 653 | pending_canceled = 0b11, |
| 654 | /// The future has already completed. `thread` is `.null`, unless the future terminated |
| 655 | /// with an acknowledged cancel request, in which case `thread` is `.all_ones`. |
| 656 | done = 0b10, |
| 657 | }, |
| 658 | /// When the future begins execution, this is atomically updated from `null` to the thread running the |
| 659 | /// `Future`, so that cancelation knows which thread to cancel. |
| 660 | thread: Thread.PackedPtr, |
| 661 | }; |
| 662 | |
| 663 | /// `Future.runnable.node` is `undefined` in the created `Future`. |
| 664 | fn create( |
| 665 | gpa: Allocator, |
| 666 | result_len: usize, |
| 667 | result_alignment: Alignment, |
| 668 | context: []const u8, |
| 669 | context_alignment: Alignment, |
| 670 | func: *const fn (context: *const anyopaque, result: *anyopaque) void, |
| 671 | ) Allocator.Error!*Future { |
| 672 | const max_context_misalignment = context_alignment.toByteUnits() -| @alignOf(Future); |
| 673 | const worst_case_context_offset = context_alignment.forward(@sizeOf(Future) + max_context_misalignment); |
| 674 | const worst_case_result_offset = result_alignment.forward(worst_case_context_offset + context.len); |
| 675 | const alloc_len = worst_case_result_offset + result_len; |
| 676 | |
| 677 | const future: *Future = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(Future), alloc_len))); |
| 678 | errdefer comptime unreachable; |
| 679 | |
| 680 | const actual_context_addr = context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)); |
| 681 | const actual_result_addr = result_alignment.forward(actual_context_addr + context.len); |
| 682 | const actual_result_offset = actual_result_addr - @intFromPtr(future); |
| 683 | future.* = .{ |
| 684 | .runnable = .{ |
| 685 | .node = undefined, |
| 686 | .startFn = &start, |
| 687 | }, |
| 688 | .func = func, |
| 689 | .status = .init(.{ |
| 690 | .tag = .pending, |
| 691 | .thread = .null, |
| 692 | }), |
| 693 | .awaiter = undefined, |
| 694 | .context_alignment = context_alignment, |
| 695 | .result_offset = actual_result_offset, |
| 696 | .alloc_len = alloc_len, |
| 697 | }; |
| 698 | @memcpy(future.contextPointer()[0..context.len], context); |
| 699 | return future; |
| 700 | } |
| 701 | |
| 702 | fn destroy(future: *Future, gpa: Allocator) void { |
| 703 | const base: [*]align(@alignOf(Future)) u8 = @ptrCast(future); |
| 704 | gpa.free(base[0..future.alloc_len]); |
| 705 | } |
| 706 | |
| 707 | fn resultPointer(future: *Future) [*]u8 { |
| 708 | const base: [*]u8 = @ptrCast(future); |
| 709 | return base + future.result_offset; |
| 710 | } |
| 711 | |
| 712 | fn contextPointer(future: *Future) [*]u8 { |
| 713 | const base: [*]u8 = @ptrCast(future); |
| 714 | const context_offset = future.context_alignment.forward(@intFromPtr(future) + @sizeOf(Future)) - @intFromPtr(future); |
| 715 | return base + context_offset; |
| 716 | } |
| 717 | |
| 718 | fn start(r: *Runnable, thread: *Thread, t: *Threaded) void { |
| 719 | _ = t; |
| 720 | const future: *Future = @fieldParentPtr("runnable", r); |
| 721 | |
| 722 | thread.status.store(.{ |
| 723 | .cancelation = .none, |
| 724 | .awaitable = .fromFuture(future), |
| 725 | }, .monotonic); |
| 726 | { |
| 727 | const old_status = future.status.fetchOr(.{ |
| 728 | .tag = .pending, |
| 729 | .thread = .pack(thread), |
| 730 | }, .release); |
| 731 | assert(old_status.thread == .null); |
| 732 | switch (old_status.tag) { |
| 733 | .pending, .pending_awaited => {}, |
| 734 | .pending_canceled => thread.status.store(.{ |
| 735 | .cancelation = .canceling, |
| 736 | .awaitable = .fromFuture(future), |
| 737 | }, .monotonic), |
| 738 | .done => unreachable, |
| 739 | } |
| 740 | } |
| 741 | |
| 742 | future.func(future.contextPointer(), future.resultPointer()); |
| 743 | |
| 744 | const had_acknowledged_cancel = switch (thread.status.load(.monotonic).cancelation) { |
| 745 | .none, .canceling => false, |
| 746 | .canceled => true, |
| 747 | .parked => unreachable, |
| 748 | .blocked => unreachable, |
| 749 | .blocked_alertable => unreachable, |
| 750 | .blocked_alertable_canceling => unreachable, |
| 751 | .blocked_canceling => unreachable, |
| 752 | }; |
| 753 | thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); |
| 754 | const old_status = future.status.swap(.{ |
| 755 | .tag = .done, |
| 756 | .thread = if (had_acknowledged_cancel) .all_ones else .null, |
| 757 | }, .acq_rel); // acquire `future.awaiter`, release results |
| 758 | switch (old_status.tag) { |
| 759 | .pending => {}, |
| 760 | .pending_awaited, .pending_canceled => { |
| 761 | const to_signal = future.awaiter; |
| 762 | _ = to_signal.fetchAdd(1, .release); // release results |
| 763 | Thread.futexWake(&to_signal.raw, 1); |
| 764 | }, |
| 765 | .done => unreachable, |
| 766 | } |
| 767 | } |
| 768 | |
| 769 | /// The caller has canceled `future`. `thread` is the thread currently running that future. |
| 770 | /// Inform `thread` of the cancelation if necessary, and wait for `future` to finish (indicated |
| 771 | /// by `num_completed` being incremented from 0 to 1), while sending regular signals to `thread` |
| 772 | /// if necessary for it to unblock from a cancelable syscall. |
| 773 | fn waitForCancelWithSignaling( |
| 774 | future: *Future, |
| 775 | t: *Threaded, |
| 776 | num_completed: *std.atomic.Value(u32), |
| 777 | thread: ?*Thread, |
| 778 | ) void { |
| 779 | var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false; |
| 780 | var timeout_ns: u64 = 1 << 10; |
| 781 | while (true) { |
| 782 | need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)); |
| 783 | Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); |
| 784 | switch (num_completed.load(.acquire)) { // acquire task results |
| 785 | 0 => {}, |
| 786 | 1 => break, |
| 787 | else => unreachable, |
| 788 | } |
| 789 | timeout_ns <<|= 1; |
| 790 | } |
| 791 | } |
| 792 | }; |
| 793 | |
| 794 | /// A sequence of (ptr_bit_width - 3) bits which uniquely identifies a group or future. The bits are |
| 795 | /// the MSBs of the `*Io.Group` or `*Future`. These things do not necessarily have 3 zero bits at |
| 796 | /// the end (they are pointer-aligned, so on 32-bit targets only have 2), but because they both have |
| 797 | /// a *size* of at least 8 bytes, no two groups/futures in memory at the same time will have the |
| 798 | /// same value for all of these bits. In other words, given a group/future pointer, the next group |
| 799 | /// or future must be at least 8 bytes later, so its address will have a different value for one of |
| 800 | /// the top (ptr_bit_width - 3) bits. |
| 801 | const AwaitableId = enum(@Int(.unsigned, @bitSizeOf(usize) - 3)) { |
| 802 | comptime { |
| 803 | assert(@sizeOf(Future) >= 8); |
| 804 | assert(@sizeOf(Io.Group) >= 8); |
| 805 | } |
| 806 | null = 0, |
| 807 | all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 3)), |
| 808 | _, |
| 809 | const Split = packed struct(usize) { low: u3, high: AwaitableId }; |
| 810 | fn fromGroup(g: *Io.Group) AwaitableId { |
| 811 | const split: Split = @bitCast(@intFromPtr(g)); |
| 812 | return split.high; |
| 813 | } |
| 814 | fn fromFuture(f: *Future) AwaitableId { |
| 815 | const split: Split = @bitCast(@intFromPtr(f)); |
| 816 | return split.high; |
| 817 | } |
| 818 | }; |
| 819 | |
| 820 | const Thread = struct { |
| 821 | next: ?*Thread, |
| 822 | |
| 823 | id: std.Thread.Id, |
| 824 | handle: Handle, |
| 825 | |
| 826 | status: std.atomic.Value(Status), |
| 827 | |
| 828 | cancel_protection: Io.CancelProtection, |
| 829 | /// Always released when `Status.cancelation` is set to `.parked`. |
| 830 | futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, |
| 831 | unpark_flag: UnparkFlag, |
| 832 | park_tid: if (ParkTid == std.Thread.Id) void else ParkTid, |
| 833 | |
| 834 | csprng: Csprng, |
| 835 | |
| 836 | const Handle = Handle: { |
| 837 | if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; |
| 838 | if (is_windows) break :Handle windows.HANDLE; |
| 839 | break :Handle void; |
| 840 | }; |
| 841 | |
| 842 | const Status = packed struct(usize) { |
| 843 | /// The specific values of these enum fields are chosen to simplify the implementation of |
| 844 | /// the transformations we need to apply to this state. |
| 845 | cancelation: enum(u3) { |
| 846 | /// The thread has not yet been canceled, and is not in a cancelable operation. |
| 847 | /// To request cancelation, just set the status to `.canceling`. |
| 848 | none = 0b000, |
| 849 | |
| 850 | /// The thread is parked in a cancelable futex wait or sleep. |
| 851 | /// Only applicable if `use_parking_futex` or `use_parking_sleep`. |
| 852 | /// To request cancelation, set the status to `.canceling` and unpark the thread. |
| 853 | /// To unpark for another reason (futex wake), set the status to `.none` and unpark the thread. |
| 854 | parked = 0b001, |
| 855 | |
| 856 | /// The thread is blocked in a cancelable system call. |
| 857 | /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. |
| 858 | blocked = 0b011, |
| 859 | |
| 860 | /// Windows-only: the thread is blocked in an alertable wait via |
| 861 | /// `NtDelayExecution`. To request cancelation, set the status to |
| 862 | /// `blocked_alertable_canceling` and repeatedly alert the thread |
| 863 | /// until the status changes. |
| 864 | blocked_alertable = 0b010, |
| 865 | |
| 866 | /// The thread has an outstanding cancelation request but is not in a cancelable operation. |
| 867 | /// When it acknowledges the cancelation, it will set the status to `.canceled`. |
| 868 | canceling = 0b110, |
| 869 | |
| 870 | /// The thread has received and acknowledged a cancelation request. |
| 871 | /// If `recancel` is called, the status will revert to `.canceling`, but otherwise, the status |
| 872 | /// will not change for the remainder of this task's execution. |
| 873 | canceled = 0b111, |
| 874 | |
| 875 | /// The thread is blocked in a cancelable system call, and is being |
| 876 | /// canceled. The thread which triggered the cancelation will send |
| 877 | /// signals to this thread until its status changes. |
| 878 | blocked_canceling = 0b101, |
| 879 | |
| 880 | /// Windows-only: the thread is blocked in an alertable wait via |
| 881 | /// `NtDelayExecution`, and is being canceled. The thread which |
| 882 | /// triggered the cancelation will send signals to this thread |
| 883 | /// until its status changes. |
| 884 | blocked_alertable_canceling = 0b100, |
| 885 | }, |
| 886 | |
| 887 | /// We cannot turn this value back into a pointer. Instead, it exists so that a task can be |
| 888 | /// canceled by a cmpxchg on thread status: if it is running the task we want to cancel, |
| 889 | /// then update the `cancelation` field. |
| 890 | awaitable: AwaitableId, |
| 891 | }; |
| 892 | |
| 893 | const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; |
| 894 | |
| 895 | threadlocal var current: ?*Thread = null; |
| 896 | |
| 897 | /// A value that does not alias any other thread id. |
| 898 | const invalid_id: std.Thread.Id = std.math.maxInt(std.Thread.Id); |
| 899 | |
| 900 | fn currentId() std.Thread.Id { |
| 901 | return if (current) |t| t.id else std.Thread.getCurrentId(); |
| 902 | } |
| 903 | |
| 904 | /// The thread is neither in a syscall nor entering one, but we want to check for cancelation |
| 905 | /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`. |
| 906 | fn checkCancel() Io.Cancelable!void { |
| 907 | const thread = Thread.current orelse return; |
| 908 | switch (thread.cancel_protection) { |
| 909 | .blocked => return, |
| 910 | .unblocked => {}, |
| 911 | } |
| 912 | // Here, unlike `Syscall.checkCancel`, it's not particularly likely that we're canceled, so |
| 913 | // it seems preferable to do a cheap atomic load and, in the unlikely case, a separate store |
| 914 | // to acknowledge. Besides, the state transitions we need here can't be done with one atomic |
| 915 | // OR/AND/XOR on `Status.cancelation`, so we don't actually have any other option. |
| 916 | const status = thread.status.load(.monotonic); |
| 917 | switch (status.cancelation) { |
| 918 | .parked => unreachable, |
| 919 | .blocked => unreachable, |
| 920 | .blocked_alertable => unreachable, |
| 921 | .blocked_alertable_canceling => unreachable, |
| 922 | .blocked_canceling => unreachable, |
| 923 | .none, .canceled => {}, |
| 924 | .canceling => { |
| 925 | thread.status.store(.{ |
| 926 | .cancelation = .canceled, |
| 927 | .awaitable = status.awaitable, |
| 928 | }, .monotonic); |
| 929 | return error.Canceled; |
| 930 | }, |
| 931 | } |
| 932 | } |
| 933 | |
| 934 | fn futexWaitUncancelable(ptr: *const u32, expect: u32, timeout_ns: ?u64) void { |
| 935 | return Thread.futexWaitInner(ptr, expect, true, timeout_ns) catch unreachable; |
| 936 | } |
| 937 | |
| 938 | fn futexWait(ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void { |
| 939 | return Thread.futexWaitInner(ptr, expect, false, timeout_ns); |
| 940 | } |
| 941 | |
| 942 | fn futexWaitInner(ptr: *const u32, expect: u32, uncancelable: bool, timeout_ns: ?u64) Io.Cancelable!void { |
| 943 | @branchHint(.cold); |
| 944 | |
| 945 | if (builtin.single_threaded) unreachable; // nobody would ever wake us |
| 946 | |
| 947 | if (use_parking_futex) { |
| 948 | return parking_futex.wait( |
| 949 | ptr, |
| 950 | expect, |
| 951 | uncancelable, |
| 952 | if (timeout_ns) |ns| .{ .duration = .{ |
| 953 | .raw = .fromNanoseconds(ns), |
| 954 | .clock = .boot, |
| 955 | } } else .none, |
| 956 | ); |
| 957 | } else if (builtin.cpu.arch.isWasm()) { |
| 958 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 959 | // TODO implement cancelation for WASM futex waits by signaling the futex |
| 960 | if (!uncancelable) try Thread.checkCancel(); |
| 961 | const to: i64 = if (timeout_ns) |ns| std.math.cast(i64, ns) orelse std.math.maxInt(i64) else -1; |
| 962 | const signed_expect: i32 = @bitCast(expect); |
| 963 | const result = asm volatile ( |
| 964 | \\local.get %[ptr] |
| 965 | \\local.get %[expected] |
| 966 | \\local.get %[timeout] |
| 967 | \\memory.atomic.wait32 0 |
| 968 | \\local.set %[ret] |
| 969 | : [ret] "=r" (-> u32), |
| 970 | : [ptr] "r" (ptr), |
| 971 | [expected] "r" (signed_expect), |
| 972 | [timeout] "r" (to), |
| 973 | ); |
| 974 | switch (result) { |
| 975 | 0 => {}, // ok |
| 976 | 1 => {}, // expected != loaded |
| 977 | 2 => {}, // timeout |
| 978 | else => assert(!is_debug), |
| 979 | } |
| 980 | } else switch (native_os) { |
| 981 | .linux => { |
| 982 | const linux = std.os.linux; |
| 983 | var ts_buffer: linux.timespec = undefined; |
| 984 | const ts: ?*linux.timespec = if (timeout_ns) |ns| ts: { |
| 985 | ts_buffer = timestampToPosix(ns); |
| 986 | break :ts &ts_buffer; |
| 987 | } else null; |
| 988 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 989 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts); |
| 990 | syscall.finish(); |
| 991 | switch (linux.errno(rc)) { |
| 992 | .SUCCESS => {}, // notified by `wake()` |
| 993 | .INTR => {}, // caller's responsibility to retry |
| 994 | .AGAIN => {}, // ptr.* != expect |
| 995 | .INVAL => {}, // possibly timeout overflow |
| 996 | .TIMEDOUT => {}, |
| 997 | .FAULT => recoverableOsBugDetected(), // ptr was invalid |
| 998 | else => recoverableOsBugDetected(), |
| 999 | } |
| 1000 | }, |
| 1001 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 1002 | const c = std.c; |
| 1003 | const flags: c.UL = .{ |
| 1004 | .op = .COMPARE_AND_WAIT, |
| 1005 | .NO_ERRNO = true, |
| 1006 | }; |
| 1007 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 1008 | const status = switch (darwin_supports_ulock_wait2) { |
| 1009 | true => c.__ulock_wait2(flags, ptr, expect, ns: { |
| 1010 | const ns = timeout_ns orelse break :ns 0; |
| 1011 | if (ns == 0) break :ns 1; |
| 1012 | break :ns ns; |
| 1013 | }, 0), |
| 1014 | false => c.__ulock_wait(flags, ptr, expect, us: { |
| 1015 | const ns = timeout_ns orelse break :us 0; |
| 1016 | const us = std.math.lossyCast(u32, ns / std.time.ns_per_us); |
| 1017 | if (us == 0) break :us 1; |
| 1018 | break :us us; |
| 1019 | }), |
| 1020 | }; |
| 1021 | syscall.finish(); |
| 1022 | if (status >= 0) return; |
| 1023 | switch (@as(c.E, @fromBackingInt(@intCast(-status)))) { |
| 1024 | .INTR => {}, // spurious wake |
| 1025 | // Address of the futex was paged out. This is unlikely, but possible in theory, and |
| 1026 | // pthread/libdispatch on darwin bother to handle it. In this case we'll return |
| 1027 | // without waiting, but the caller should retry anyway. |
| 1028 | .FAULT => {}, |
| 1029 | .TIMEDOUT => {}, // timeout |
| 1030 | else => recoverableOsBugDetected(), |
| 1031 | } |
| 1032 | }, |
| 1033 | .freebsd => { |
| 1034 | const flags = @backingInt(std.c.UMTX_OP.WAIT_UINT_PRIVATE); |
| 1035 | var tm_size: usize = 0; |
| 1036 | var tm: std.c._umtx_time = undefined; |
| 1037 | var tm_ptr: ?*const std.c._umtx_time = null; |
| 1038 | if (timeout_ns) |ns| { |
| 1039 | tm_ptr = &tm; |
| 1040 | tm_size = @sizeOf(@TypeOf(tm)); |
| 1041 | tm.flags = 0; // use relative time not UMTX_ABSTIME |
| 1042 | tm.clockid = .MONOTONIC; |
| 1043 | tm.timeout = timestampToPosix(ns); |
| 1044 | } |
| 1045 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 1046 | const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr)); |
| 1047 | syscall.finish(); |
| 1048 | if (is_debug) switch (posix.errno(rc)) { |
| 1049 | .SUCCESS => {}, |
| 1050 | .FAULT => unreachable, // one of the args points to invalid memory |
| 1051 | .INVAL => unreachable, // arguments should be correct |
| 1052 | .TIMEDOUT => {}, // timeout |
| 1053 | .INTR => {}, // spurious wake |
| 1054 | else => unreachable, |
| 1055 | }; |
| 1056 | }, |
| 1057 | .openbsd => { |
| 1058 | var tm: std.c.timespec = undefined; |
| 1059 | var tm_ptr: ?*const std.c.timespec = null; |
| 1060 | if (timeout_ns) |ns| { |
| 1061 | tm_ptr = &tm; |
| 1062 | tm = timestampToPosix(ns); |
| 1063 | } |
| 1064 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 1065 | const rc = std.c.futex( |
| 1066 | ptr, |
| 1067 | std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG, |
| 1068 | @as(c_int, @bitCast(expect)), |
| 1069 | tm_ptr, |
| 1070 | null, // uaddr2 is ignored |
| 1071 | ); |
| 1072 | syscall.finish(); |
| 1073 | if (is_debug) switch (posix.errno(rc)) { |
| 1074 | .SUCCESS => {}, |
| 1075 | .NOSYS => unreachable, // constant op known good value |
| 1076 | .AGAIN => {}, // contents of uaddr != val |
| 1077 | .INVAL => unreachable, // invalid timeout |
| 1078 | .TIMEDOUT => {}, // timeout |
| 1079 | .INTR => {}, // a signal arrived |
| 1080 | .CANCELED => {}, // a signal arrived and SA_RESTART was set |
| 1081 | else => unreachable, |
| 1082 | }; |
| 1083 | }, |
| 1084 | .dragonfly => { |
| 1085 | var timeout_us: c_int = undefined; |
| 1086 | if (timeout_ns) |ns| { |
| 1087 | timeout_us = std.math.cast(c_int, ns / std.time.ns_per_us) orelse std.math.maxInt(c_int); |
| 1088 | } else { |
| 1089 | timeout_us = 0; |
| 1090 | } |
| 1091 | const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start(); |
| 1092 | const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us); |
| 1093 | syscall.finish(); |
| 1094 | if (is_debug) switch (std.posix.errno(rc)) { |
| 1095 | .SUCCESS => {}, |
| 1096 | .BUSY => {}, // ptr != expect |
| 1097 | .AGAIN => {}, // maybe timed out, or paged out, or hit 2s kernel refresh |
| 1098 | .INTR => {}, // spurious wake |
| 1099 | .INVAL => unreachable, // invalid timeout |
| 1100 | else => unreachable, |
| 1101 | }; |
| 1102 | }, |
| 1103 | else => @compileError("unimplemented: futexWait"), |
| 1104 | } |
| 1105 | } |
| 1106 | |
| 1107 | fn futexWake(ptr: *const u32, max_waiters: u32) void { |
| 1108 | @branchHint(.cold); |
| 1109 | assert(max_waiters != 0); |
| 1110 | |
| 1111 | if (builtin.single_threaded) return; // nothing to wake up |
| 1112 | |
| 1113 | if (use_parking_futex) { |
| 1114 | return parking_futex.wake(ptr, max_waiters); |
| 1115 | } else if (builtin.cpu.arch.isWasm()) { |
| 1116 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 1117 | const woken_count = asm volatile ( |
| 1118 | \\local.get %[ptr] |
| 1119 | \\local.get %[waiters] |
| 1120 | \\memory.atomic.notify 0 |
| 1121 | \\local.set %[ret] |
| 1122 | : [ret] "=r" (-> u32), |
| 1123 | : [ptr] "r" (ptr), |
| 1124 | [waiters] "r" (max_waiters), |
| 1125 | ); |
| 1126 | _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled |
| 1127 | } else switch (native_os) { |
| 1128 | .linux => { |
| 1129 | const linux = std.os.linux; |
| 1130 | switch (linux.errno(linux.futex_3arg( |
| 1131 | ptr, |
| 1132 | .{ .cmd = .WAKE, .private = true }, |
| 1133 | @min(max_waiters, std.math.maxInt(i32)), |
| 1134 | ))) { |
| 1135 | .SUCCESS => return, // successful wake up |
| 1136 | .INVAL => return, // invalid futex_wait() on ptr done elsewhere |
| 1137 | .FAULT => return, // pointer became invalid while doing the wake |
| 1138 | else => return recoverableOsBugDetected(), // deadlock due to operating system bug |
| 1139 | } |
| 1140 | }, |
| 1141 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 1142 | const c = std.c; |
| 1143 | const flags: c.UL = .{ |
| 1144 | .op = .COMPARE_AND_WAIT, |
| 1145 | .NO_ERRNO = true, |
| 1146 | .WAKE_ALL = max_waiters > 1, |
| 1147 | }; |
| 1148 | while (true) { |
| 1149 | const status = c.__ulock_wake(flags, ptr, 0); |
| 1150 | if (status >= 0) return; |
| 1151 | switch (@as(c.E, @fromBackingInt(@intCast(-status)))) { |
| 1152 | .INTR, .CANCELED => continue, // spurious wake() |
| 1153 | .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t |
| 1154 | .NOENT => return, // nothing was woken up |
| 1155 | .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD |
| 1156 | else => unreachable, // deadlock due to operating system bug |
| 1157 | } |
| 1158 | } |
| 1159 | }, |
| 1160 | .freebsd => { |
| 1161 | const rc = std.c._umtx_op( |
| 1162 | @intFromPtr(ptr), |
| 1163 | @backingInt(std.c.UMTX_OP.WAKE_PRIVATE), |
| 1164 | @as(c_ulong, @min(max_waiters, std.math.maxInt(c_int))), |
| 1165 | 0, // there is no timeout struct |
| 1166 | 0, // there is no timeout struct pointer |
| 1167 | ); |
| 1168 | switch (posix.errno(rc)) { |
| 1169 | .SUCCESS => {}, |
| 1170 | .FAULT => {}, // it's ok if the ptr doesn't point to valid memory |
| 1171 | .INVAL => unreachable, // arguments should be correct |
| 1172 | else => unreachable, // deadlock due to operating system bug |
| 1173 | } |
| 1174 | }, |
| 1175 | .openbsd => { |
| 1176 | const rc = std.c.futex( |
| 1177 | ptr, |
| 1178 | std.c.FUTEX.WAKE | std.c.FUTEX.PRIVATE_FLAG, |
| 1179 | @min(max_waiters, std.math.maxInt(c_int)), |
| 1180 | null, // timeout is ignored |
| 1181 | null, // uaddr2 is ignored |
| 1182 | ); |
| 1183 | assert(rc >= 0); |
| 1184 | }, |
| 1185 | .dragonfly => { |
| 1186 | // will generally return 0 unless the address is bad |
| 1187 | _ = std.c.umtx_wakeup( |
| 1188 | @ptrCast(ptr), |
| 1189 | @min(max_waiters, std.math.maxInt(c_int)), |
| 1190 | ); |
| 1191 | }, |
| 1192 | else => @compileError("unimplemented: futexWake"), |
| 1193 | } |
| 1194 | } |
| 1195 | |
| 1196 | /// Cancels `thread` if it is working on `awaitable`. |
| 1197 | /// |
| 1198 | /// It is possible that `thread` gets canceled by this function, but is blocked in a syscall. In |
| 1199 | /// that case, the thread may need to be sent a signal to interrupt the call. This function will |
| 1200 | /// return `true` to indicate this, in which case the caller must call `signalCanceledSyscall`. |
| 1201 | fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) bool { |
| 1202 | var status = thread.status.load(.monotonic); |
| 1203 | while (true) { |
| 1204 | if (status.awaitable != awaitable) return false; // thread is working on something else |
| 1205 | status = switch (status.cancelation) { |
| 1206 | .none => thread.status.cmpxchgWeak( |
| 1207 | .{ .cancelation = .none, .awaitable = awaitable }, |
| 1208 | .{ .cancelation = .canceling, .awaitable = awaitable }, |
| 1209 | .monotonic, |
| 1210 | .monotonic, |
| 1211 | ) orelse return false, |
| 1212 | |
| 1213 | .parked => thread.status.cmpxchgWeak( |
| 1214 | .{ .cancelation = .parked, .awaitable = awaitable }, |
| 1215 | .{ .cancelation = .canceling, .awaitable = awaitable }, |
| 1216 | .acquire, // acquire `thread.futex_waiter` |
| 1217 | .monotonic, |
| 1218 | ) orelse { |
| 1219 | if (!use_parking_futex and !use_parking_sleep) unreachable; |
| 1220 | if (thread.futex_waiter) |futex_waiter| { |
| 1221 | parking_futex.removeCanceledWaiter(futex_waiter); |
| 1222 | } |
| 1223 | if (need_unpark_flag) setUnparkFlag(&thread.unpark_flag); |
| 1224 | unpark(&.{if (ParkTid == std.Thread.Id) thread.id else thread.park_tid}, null); |
| 1225 | return false; |
| 1226 | }, |
| 1227 | |
| 1228 | .blocked => thread.status.cmpxchgWeak( |
| 1229 | .{ .cancelation = .blocked, .awaitable = awaitable }, |
| 1230 | .{ .cancelation = .blocked_canceling, .awaitable = awaitable }, |
| 1231 | .monotonic, |
| 1232 | .monotonic, |
| 1233 | ) orelse return true, |
| 1234 | |
| 1235 | .blocked_alertable => thread.status.cmpxchgWeak( |
| 1236 | .{ .cancelation = .blocked_alertable, .awaitable = awaitable }, |
| 1237 | .{ .cancelation = .blocked_alertable_canceling, .awaitable = awaitable }, |
| 1238 | .monotonic, |
| 1239 | .monotonic, |
| 1240 | ) orelse { |
| 1241 | if (!is_windows) unreachable; |
| 1242 | return true; |
| 1243 | }, |
| 1244 | |
| 1245 | .canceling, .canceled => { |
| 1246 | // This can happen when the task start raced with the cancelation, so the thread |
| 1247 | // saw the cancelation on the future/group *and* we are trying to signal the |
| 1248 | // thread here. |
| 1249 | return false; |
| 1250 | }, |
| 1251 | |
| 1252 | .blocked_canceling => unreachable, // `awaitable` has not been canceled before now |
| 1253 | .blocked_alertable_canceling => unreachable, // `awaitable` has not been canceled before now |
| 1254 | }; |
| 1255 | } |
| 1256 | } |
| 1257 | |
| 1258 | /// Sends a signal to `thread` if it is still blocked in a syscall (i.e. has not yet observed |
| 1259 | /// the cancelation request from `cancelAwaitable`). |
| 1260 | /// |
| 1261 | /// Unfortunately, the signal could arrive before the syscall actually starts, so the interrupt |
| 1262 | /// is missed. To handle this, we may need to send multiple signals. As such, if this function |
| 1263 | /// returns `true`, then it should be called again after a short delay to send another signal if |
| 1264 | /// the thread is still blocked. For the implementation, `Future.waitForCancelWithSignaling` and |
| 1265 | /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and |
| 1266 | /// doubling each call. In practice, it is rare to send more than one signal. |
| 1267 | fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool { |
| 1268 | const status = thread.status.load(.monotonic); |
| 1269 | if (status.awaitable != awaitable) { |
| 1270 | // The thread has moved on and is working on something totally different. |
| 1271 | return false; |
| 1272 | } |
| 1273 | |
| 1274 | // The thread ID and/or handle can be read non-atomically because they never change and were |
| 1275 | // released by the store that made `thread` available to us. |
| 1276 | |
| 1277 | switch (status.cancelation) { |
| 1278 | .blocked_canceling => if (std.Thread.use_pthreads) { |
| 1279 | return switch (std.c.pthread_kill(thread.handle, .IO)) { |
| 1280 | 0 => true, |
| 1281 | else => false, |
| 1282 | }; |
| 1283 | } else switch (native_os) { |
| 1284 | .linux => { |
| 1285 | const pid: posix.pid_t = pid: { |
| 1286 | const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); |
| 1287 | if (cached_pid != .unknown) break :pid @backingInt(cached_pid); |
| 1288 | const pid = std.os.linux.getpid(); |
| 1289 | @atomicStore(Pid, &t.pid, @fromBackingInt(@intCast(pid)), .monotonic); |
| 1290 | break :pid pid; |
| 1291 | }; |
| 1292 | return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) { |
| 1293 | 0 => true, |
| 1294 | else => false, |
| 1295 | }; |
| 1296 | }, |
| 1297 | .windows => { |
| 1298 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 1299 | return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) { |
| 1300 | .NOT_FOUND => true, // this might mean the operation hasn't started yet |
| 1301 | .SUCCESS => false, // the OS confirmed that our cancelation worked |
| 1302 | else => false, |
| 1303 | }; |
| 1304 | }, |
| 1305 | else => return false, |
| 1306 | }, |
| 1307 | |
| 1308 | .blocked_alertable_canceling => { |
| 1309 | if (!is_windows) unreachable; |
| 1310 | return switch (windows.ntdll.NtAlertThread(thread.handle)) { |
| 1311 | .SUCCESS => true, |
| 1312 | else => false, |
| 1313 | }; |
| 1314 | }, |
| 1315 | |
| 1316 | else => { |
| 1317 | // The thread is working on `awaitable`, but no longer needs signaling (they already |
| 1318 | // woke up and saw the cancelation). |
| 1319 | return false; |
| 1320 | }, |
| 1321 | } |
| 1322 | } |
| 1323 | |
| 1324 | /// Like a `*Thread`, but 2 bits smaller than a pointer (because the LSBs are always 0 due to |
| 1325 | /// alignment) so that those two bits can be used in a `packed struct`. |
| 1326 | const PackedPtr = enum(@Int(.unsigned, @bitSizeOf(usize) - 2)) { |
| 1327 | null = 0, |
| 1328 | all_ones = std.math.maxInt(@Int(.unsigned, @bitSizeOf(usize) - 2)), |
| 1329 | _, |
| 1330 | |
| 1331 | const Split = packed struct(usize) { low: u2, high: PackedPtr }; |
| 1332 | fn pack(ptr: *Thread) PackedPtr { |
| 1333 | const split: Split = @bitCast(@intFromPtr(ptr)); |
| 1334 | assert(split.low == 0); |
| 1335 | return split.high; |
| 1336 | } |
| 1337 | fn unpack(ptr: PackedPtr) ?*Thread { |
| 1338 | const split: Split = .{ .low = 0, .high = ptr }; |
| 1339 | return @ptrFromInt(@as(usize, @bitCast(split))); |
| 1340 | } |
| 1341 | }; |
| 1342 | }; |
| 1343 | |
| 1344 | const Syscall = struct { |
| 1345 | thread: ?*Thread, |
| 1346 | /// Marks entry to a syscall region. This should be tightly scoped around the actual syscall |
| 1347 | /// to minimize races. The syscall must be marked as "finished" by `checkCancel`, `finish`, |
| 1348 | /// or one of the wrappers of `finish`. |
| 1349 | fn start() Io.Cancelable!Syscall { |
| 1350 | const thread = Thread.current orelse return .{ .thread = null }; |
| 1351 | switch (thread.cancel_protection) { |
| 1352 | .blocked => return .{ .thread = null }, |
| 1353 | .unblocked => {}, |
| 1354 | } |
| 1355 | switch (thread.status.fetchOr(.{ |
| 1356 | .cancelation = @fromBackingInt(@intCast(0b011)), |
| 1357 | .awaitable = .null, |
| 1358 | }, .monotonic).cancelation) { |
| 1359 | .parked => unreachable, |
| 1360 | .blocked => unreachable, |
| 1361 | .blocked_alertable => unreachable, |
| 1362 | .blocked_alertable_canceling => unreachable, |
| 1363 | .blocked_canceling => unreachable, |
| 1364 | .none => return .{ .thread = thread }, // new status is `.blocked` |
| 1365 | .canceling => return error.Canceled, // new status is `.canceled` |
| 1366 | .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged) |
| 1367 | } |
| 1368 | } |
| 1369 | /// Checks whether this syscall has been canceled. This should be called when a syscall is |
| 1370 | /// interrupted through a mechanism which may indicate cancelation, or may be spurious. If |
| 1371 | /// the syscall was canceled, it is finished and `error.Canceled` is returned. Otherwise, |
| 1372 | /// the syscall is not marked finished, and the caller should retry. |
| 1373 | fn checkCancel(s: Syscall) Io.Cancelable!void { |
| 1374 | const thread = s.thread orelse return; |
| 1375 | switch (thread.status.fetchOr(.{ |
| 1376 | .cancelation = @fromBackingInt(@intCast(0b010)), |
| 1377 | .awaitable = .null, |
| 1378 | }, .monotonic).cancelation) { |
| 1379 | .none => unreachable, |
| 1380 | .parked => unreachable, |
| 1381 | .blocked_alertable => unreachable, |
| 1382 | .blocked_alertable_canceling => unreachable, |
| 1383 | .canceling => unreachable, |
| 1384 | .canceled => unreachable, |
| 1385 | .blocked => {}, // new status is `.blocked` (unchanged) |
| 1386 | .blocked_canceling => return error.Canceled, // new status is `.canceled` |
| 1387 | } |
| 1388 | } |
| 1389 | /// Marks this syscall as finished. |
| 1390 | fn finish(s: Syscall) void { |
| 1391 | const thread = s.thread orelse return; |
| 1392 | switch (thread.status.fetchXor(.{ |
| 1393 | .cancelation = @fromBackingInt(@intCast(0b011)), |
| 1394 | .awaitable = .null, |
| 1395 | }, .monotonic).cancelation) { |
| 1396 | .none => unreachable, |
| 1397 | .parked => unreachable, |
| 1398 | .blocked_alertable => unreachable, |
| 1399 | .blocked_alertable_canceling => unreachable, |
| 1400 | .canceling => unreachable, |
| 1401 | .canceled => unreachable, |
| 1402 | .blocked => {}, // new status is `.none` |
| 1403 | .blocked_canceling => {}, // new status is `.canceling` |
| 1404 | } |
| 1405 | } |
| 1406 | /// Indicates instead of `NtCancelSynchronousIoFile` we need to use |
| 1407 | /// `NtAlertThread` to interrupt the wait. |
| 1408 | /// |
| 1409 | /// Windows only, called from blocked state only. |
| 1410 | fn toAlertable(s: Syscall) Io.Cancelable!AlertableSyscall { |
| 1411 | comptime assert(is_windows); |
| 1412 | const thread = s.thread orelse return .{ .thread = null }; |
| 1413 | var prev = thread.status.load(.monotonic); |
| 1414 | while (true) prev = switch (prev.cancelation) { |
| 1415 | .none => unreachable, |
| 1416 | .parked => unreachable, |
| 1417 | .blocked_alertable => unreachable, |
| 1418 | .blocked_alertable_canceling => unreachable, |
| 1419 | .canceling => unreachable, |
| 1420 | .canceled => unreachable, |
| 1421 | |
| 1422 | .blocked => thread.status.cmpxchgWeak(prev, .{ |
| 1423 | .cancelation = .blocked_alertable, |
| 1424 | .awaitable = prev.awaitable, |
| 1425 | }, .monotonic, .monotonic) orelse return .{ .thread = thread }, |
| 1426 | |
| 1427 | .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ |
| 1428 | .cancelation = .canceled, |
| 1429 | .awaitable = prev.awaitable, |
| 1430 | }, .monotonic, .monotonic) orelse return error.Canceled, |
| 1431 | }; |
| 1432 | } |
| 1433 | /// Convenience wrapper which calls `finish`, then returns `err`. |
| 1434 | fn fail(s: Syscall, err: anytype) @TypeOf(err) { |
| 1435 | s.finish(); |
| 1436 | return err; |
| 1437 | } |
| 1438 | /// Convenience wrapper which calls `finish`, then calls `Threaded.errnoBug`. |
| 1439 | fn errnoBug(s: Syscall, err: posix.E) Io.UnexpectedError { |
| 1440 | @branchHint(.cold); |
| 1441 | s.finish(); |
| 1442 | return Threaded.errnoBug(err); |
| 1443 | } |
| 1444 | /// Convenience wrapper which calls `finish`, then calls `posix.unexpectedErrno`. |
| 1445 | fn unexpectedErrno(s: Syscall, err: posix.E) Io.UnexpectedError { |
| 1446 | @branchHint(.cold); |
| 1447 | s.finish(); |
| 1448 | return posix.unexpectedErrno(err); |
| 1449 | } |
| 1450 | /// Convenience wrapper which calls `finish`, then calls `windows.statusBug`. |
| 1451 | fn ntstatusBug(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| 1452 | @branchHint(.cold); |
| 1453 | s.finish(); |
| 1454 | return windows.statusBug(status); |
| 1455 | } |
| 1456 | /// Convenience wrapper which calls `finish`, then calls `windows.unexpectedStatus`. |
| 1457 | fn unexpectedNtstatus(s: Syscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| 1458 | @branchHint(.cold); |
| 1459 | s.finish(); |
| 1460 | return windows.unexpectedStatus(status); |
| 1461 | } |
| 1462 | }; |
| 1463 | |
| 1464 | const AlertableSyscall = struct { |
| 1465 | thread: ?*Thread, |
| 1466 | |
| 1467 | comptime { |
| 1468 | assert(is_windows); |
| 1469 | } |
| 1470 | |
| 1471 | fn start() Io.Cancelable!AlertableSyscall { |
| 1472 | const thread = Thread.current orelse return .{ .thread = null }; |
| 1473 | switch (thread.cancel_protection) { |
| 1474 | .blocked => return .{ .thread = null }, |
| 1475 | .unblocked => {}, |
| 1476 | } |
| 1477 | const old_status = thread.status.fetchOr(.{ |
| 1478 | .cancelation = @fromBackingInt(@intCast(0b010)), |
| 1479 | .awaitable = .null, |
| 1480 | }, .monotonic); |
| 1481 | switch (old_status.cancelation) { |
| 1482 | .parked => unreachable, |
| 1483 | .blocked => unreachable, |
| 1484 | .blocked_alertable => unreachable, |
| 1485 | .blocked_canceling => unreachable, |
| 1486 | .blocked_alertable_canceling => unreachable, |
| 1487 | .none => return .{ .thread = thread }, // new status is `.blocked_alertable` |
| 1488 | .canceling => { |
| 1489 | // Status is unchanged (still `.canceling`)---change to `.canceled` before return. |
| 1490 | thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic); |
| 1491 | return error.Canceled; |
| 1492 | }, |
| 1493 | .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged) |
| 1494 | } |
| 1495 | } |
| 1496 | |
| 1497 | fn checkCancel(s: AlertableSyscall) Io.Cancelable!void { |
| 1498 | comptime assert(is_windows); |
| 1499 | const thread = s.thread orelse return; |
| 1500 | const old_status = thread.status.fetchOr(.{ |
| 1501 | .cancelation = @fromBackingInt(@intCast(0b010)), |
| 1502 | .awaitable = .null, |
| 1503 | }, .monotonic); |
| 1504 | switch (old_status.cancelation) { |
| 1505 | .none => unreachable, |
| 1506 | .parked => unreachable, |
| 1507 | .blocked => unreachable, |
| 1508 | .blocked_canceling => unreachable, |
| 1509 | .canceling => unreachable, |
| 1510 | .canceled => unreachable, |
| 1511 | .blocked_alertable => {}, // new status is `.blocked_alertable` (unchanged) |
| 1512 | .blocked_alertable_canceling => { |
| 1513 | // New status is `.canceling`---change to `.canceled` before return. |
| 1514 | thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic); |
| 1515 | return error.Canceled; |
| 1516 | }, |
| 1517 | } |
| 1518 | } |
| 1519 | |
| 1520 | fn finish(s: AlertableSyscall) void { |
| 1521 | comptime assert(is_windows); |
| 1522 | const thread = s.thread orelse return; |
| 1523 | switch (thread.status.fetchXor(.{ |
| 1524 | .cancelation = @fromBackingInt(@intCast(0b010)), |
| 1525 | .awaitable = .null, |
| 1526 | }, .monotonic).cancelation) { |
| 1527 | .none => unreachable, |
| 1528 | .parked => unreachable, |
| 1529 | .blocked => unreachable, |
| 1530 | .blocked_canceling => unreachable, |
| 1531 | .canceling => unreachable, |
| 1532 | .canceled => unreachable, |
| 1533 | .blocked_alertable => {}, // new status is `.none` |
| 1534 | .blocked_alertable_canceling => {}, // new status is `.canceling` |
| 1535 | } |
| 1536 | } |
| 1537 | |
| 1538 | fn fail(s: AlertableSyscall, err: anytype) @TypeOf(err) { |
| 1539 | s.finish(); |
| 1540 | return err; |
| 1541 | } |
| 1542 | |
| 1543 | fn ntstatusBug(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| 1544 | @branchHint(.cold); |
| 1545 | s.finish(); |
| 1546 | return windows.statusBug(status); |
| 1547 | } |
| 1548 | |
| 1549 | fn unexpectedNtstatus(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError { |
| 1550 | @branchHint(.cold); |
| 1551 | s.finish(); |
| 1552 | return windows.unexpectedStatus(status); |
| 1553 | } |
| 1554 | }; |
| 1555 | |
| 1556 | pub fn waitForApcOrAlert() void { |
| 1557 | const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); |
| 1558 | _ = windows.ntdll.NtDelayExecution(.TRUE, &infinite_timeout); |
| 1559 | } |
| 1560 | |
| 1561 | pub const max_iovecs_len = 8; |
| 1562 | pub const splat_buffer_size = 64; |
| 1563 | /// Happens to be the same number that matches maximum number of handles that |
| 1564 | /// NtWaitForMultipleObjects accepts. We use this value also for poll() on |
| 1565 | /// posix systems. |
| 1566 | const poll_buffer_len = 64; |
| 1567 | pub const default_PATH = "/usr/local/bin:/bin:/usr/bin"; |
| 1568 | /// There are multiple kernel bugs being worked around with retries. |
| 1569 | const max_windows_kernel_bug_retries = 13; |
| 1570 | |
| 1571 | comptime { |
| 1572 | if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX); |
| 1573 | } |
| 1574 | |
| 1575 | pub const InitOptions = struct { |
| 1576 | /// Affects how many bytes are memory-mapped for threads. |
| 1577 | stack_size: usize = std.Thread.SpawnConfig.default_stack_size, |
| 1578 | /// Maximum thread pool size (excluding main thread) when dispatching async |
| 1579 | /// tasks. Until this limit, calls to `Io.async` when all threads are busy will |
| 1580 | /// cause a new thread to be spawned and permanently added to the pool. After |
| 1581 | /// this limit, calls to `Io.async` when all threads are busy run the task |
| 1582 | /// immediately. |
| 1583 | /// |
| 1584 | /// Defaults to one less than the number of logical CPU cores. |
| 1585 | /// |
| 1586 | /// Protected by `Threaded.mutex` once the I/O instance is already in use. See |
| 1587 | /// `setAsyncLimit`. |
| 1588 | async_limit: ?Io.Limit = null, |
| 1589 | /// Maximum thread pool size (excluding main thread) for dispatching concurrent |
| 1590 | /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread |
| 1591 | /// pool size. |
| 1592 | /// |
| 1593 | /// After this number, calls to `Io.concurrent` return `error.ConcurrencyUnavailable`. |
| 1594 | concurrent_limit: Io.Limit = .unlimited, |
| 1595 | /// Affects the following operations: |
| 1596 | /// * `processExecutablePath` on OpenBSD and Haiku. |
| 1597 | argv0: Argv0 = .empty, |
| 1598 | /// Affects the following operations: |
| 1599 | /// * `fileIsTty` |
| 1600 | /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH"). |
| 1601 | /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath` |
| 1602 | environ: process.Environ = .empty, |
| 1603 | /// If set to `true`, `File.MemoryMap` APIs will always take the fallback path. |
| 1604 | disable_memory_mapping: bool = false, |
| 1605 | }; |
| 1606 | |
| 1607 | /// Related: |
| 1608 | /// * `init_single_threaded` |
| 1609 | pub fn init( |
| 1610 | /// Must be threadsafe. Only used for the following functions: |
| 1611 | /// * `Io.VTable.async` |
| 1612 | /// * `Io.VTable.concurrent` |
| 1613 | /// * `Io.VTable.groupAsync` |
| 1614 | /// * `Io.VTable.groupConcurrent` |
| 1615 | /// If these functions are avoided, then `Allocator.failing` may be passed |
| 1616 | /// here. |
| 1617 | gpa: Allocator, |
| 1618 | options: InitOptions, |
| 1619 | ) Threaded { |
| 1620 | if (builtin.single_threaded) return .{ |
| 1621 | .allocator = gpa, |
| 1622 | .stack_size = options.stack_size, |
| 1623 | .async_limit = options.async_limit orelse init_single_threaded.async_limit, |
| 1624 | .cpu_count_error = init_single_threaded.cpu_count_error, |
| 1625 | .concurrent_limit = options.concurrent_limit, |
| 1626 | .old_sig_io = undefined, |
| 1627 | .old_sig_pipe = undefined, |
| 1628 | .have_signal_handler = init_single_threaded.have_signal_handler, |
| 1629 | .argv0 = options.argv0, |
| 1630 | .environ_initialized = options.environ.block.isEmpty(), |
| 1631 | .environ = .{ .process_environ = options.environ }, |
| 1632 | .worker_threads = init_single_threaded.worker_threads, |
| 1633 | .disable_memory_mapping = options.disable_memory_mapping, |
| 1634 | }; |
| 1635 | |
| 1636 | const cpu_count = std.Thread.getCpuCount(); |
| 1637 | |
| 1638 | var t: Threaded = .{ |
| 1639 | .allocator = gpa, |
| 1640 | .stack_size = options.stack_size, |
| 1641 | .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing, |
| 1642 | .concurrent_limit = options.concurrent_limit, |
| 1643 | .cpu_count_error = if (cpu_count) |_| null else |e| e, |
| 1644 | .old_sig_io = undefined, |
| 1645 | .old_sig_pipe = undefined, |
| 1646 | .have_signal_handler = false, |
| 1647 | .argv0 = options.argv0, |
| 1648 | .environ_initialized = options.environ.block.isEmpty(), |
| 1649 | .environ = .{ .process_environ = options.environ }, |
| 1650 | .worker_threads = .init(null), |
| 1651 | .disable_memory_mapping = options.disable_memory_mapping, |
| 1652 | }; |
| 1653 | |
| 1654 | if (posix.Sigaction != void) { |
| 1655 | // This causes sending `posix.SIG.IO` to thread to interrupt blocking |
| 1656 | // syscalls, returning `posix.E.INTR`. |
| 1657 | const act: posix.Sigaction = .{ |
| 1658 | .handler = .{ .handler = doNothingSignalHandler }, |
| 1659 | .mask = posix.sigemptyset(), |
| 1660 | .flags = 0, |
| 1661 | }; |
| 1662 | if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); |
| 1663 | if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe); |
| 1664 | t.have_signal_handler = true; |
| 1665 | } |
| 1666 | |
| 1667 | return t; |
| 1668 | } |
| 1669 | |
| 1670 | /// Statically initialize such that calls to `Io.VTable.concurrent` will fail |
| 1671 | /// with `error.ConcurrencyUnavailable`. |
| 1672 | /// |
| 1673 | /// When initialized this way: |
| 1674 | /// * cancel requests have no effect. |
| 1675 | /// * `deinit` is safe, but unnecessary to call. |
| 1676 | pub const init_single_threaded: Threaded = init: { |
| 1677 | const env_block: process.Environ.Block = if (is_windows) .global else .empty; |
| 1678 | break :init .{ |
| 1679 | .allocator = .failing, |
| 1680 | .stack_size = std.Thread.SpawnConfig.default_stack_size, |
| 1681 | .async_limit = .nothing, |
| 1682 | .cpu_count_error = null, |
| 1683 | .concurrent_limit = .nothing, |
| 1684 | .old_sig_io = undefined, |
| 1685 | .old_sig_pipe = undefined, |
| 1686 | .have_signal_handler = false, |
| 1687 | .argv0 = .empty, |
| 1688 | .environ_initialized = env_block.isEmpty(), |
| 1689 | .environ = .{ .process_environ = .{ .block = env_block } }, |
| 1690 | .worker_threads = .init(null), |
| 1691 | .disable_memory_mapping = false, |
| 1692 | }; |
| 1693 | }; |
| 1694 | |
| 1695 | var global_single_threaded_instance: Threaded = .init_single_threaded; |
| 1696 | |
| 1697 | /// In general, the application is responsible for choosing the `Io` |
| 1698 | /// implementation and library code should accept an `Io` parameter rather than |
| 1699 | /// accessing this declaration. Most code should avoid referencing this |
| 1700 | /// declaration entirely. |
| 1701 | /// |
| 1702 | /// However, in some cases such as debugging, it is desirable to hardcode a |
| 1703 | /// reference to this `Io` implementation. |
| 1704 | /// |
| 1705 | /// This instance does not support concurrency or cancelation. |
| 1706 | pub const global_single_threaded: *Threaded = &global_single_threaded_instance; |
| 1707 | |
| 1708 | pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { |
| 1709 | mutexLock(&t.mutex); |
| 1710 | defer mutexUnlock(&t.mutex); |
| 1711 | t.async_limit = new_limit; |
| 1712 | } |
| 1713 | |
| 1714 | pub fn deinit(t: *Threaded) void { |
| 1715 | t.join(); |
| 1716 | if (posix.Sigaction != void and t.have_signal_handler) { |
| 1717 | if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); |
| 1718 | if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); |
| 1719 | } |
| 1720 | t.dl.deinit(); |
| 1721 | t.null_file.deinit(); |
| 1722 | t.random_file.deinit(); |
| 1723 | t.pipe_file.deinit(); |
| 1724 | t.* = undefined; |
| 1725 | } |
| 1726 | |
| 1727 | fn join(t: *Threaded) void { |
| 1728 | if (builtin.single_threaded) return; |
| 1729 | { |
| 1730 | mutexLock(&t.mutex); |
| 1731 | defer mutexUnlock(&t.mutex); |
| 1732 | t.join_requested = true; |
| 1733 | } |
| 1734 | condBroadcast(&t.cond); |
| 1735 | t.wait_group.wait(); |
| 1736 | } |
| 1737 | |
| 1738 | fn worker(t: *Threaded) void { |
| 1739 | var thread: Thread = .{ |
| 1740 | .next = undefined, |
| 1741 | .id = std.Thread.getCurrentId(), |
| 1742 | .handle = handle: { |
| 1743 | if (std.Thread.use_pthreads) break :handle std.c.pthread_self(); |
| 1744 | if (is_windows) break :handle undefined; // populated below |
| 1745 | }, |
| 1746 | .status = .init(.{ |
| 1747 | .cancelation = .none, |
| 1748 | .awaitable = .null, |
| 1749 | }), |
| 1750 | .cancel_protection = .unblocked, |
| 1751 | .futex_waiter = undefined, |
| 1752 | .unpark_flag = unpark_flag_init, |
| 1753 | .park_tid = if (ParkTid == std.Thread.Id) {} else getParkTid(), |
| 1754 | .csprng = .uninitialized, |
| 1755 | }; |
| 1756 | Thread.current = &thread; |
| 1757 | |
| 1758 | if (is_windows) { |
| 1759 | assert(windows.ntdll.NtOpenThread( |
| 1760 | &thread.handle, |
| 1761 | .{ |
| 1762 | .SPECIFIC = .{ |
| 1763 | .THREAD = .{ |
| 1764 | .TERMINATE = true, // for `NtCancelSynchronousIoFile` |
| 1765 | .ALERT = true, // for `NtAlertThread` |
| 1766 | }, |
| 1767 | }, |
| 1768 | }, |
| 1769 | &.{ .ObjectName = null }, |
| 1770 | &windows.teb().ClientId, |
| 1771 | ) == .SUCCESS); |
| 1772 | } |
| 1773 | defer if (is_windows) { |
| 1774 | windows.CloseHandle(thread.handle); |
| 1775 | }; |
| 1776 | |
| 1777 | { |
| 1778 | var head = t.worker_threads.load(.monotonic); |
| 1779 | while (true) { |
| 1780 | thread.next = head; |
| 1781 | head = t.worker_threads.cmpxchgWeak( |
| 1782 | head, |
| 1783 | &thread, |
| 1784 | .release, |
| 1785 | .monotonic, |
| 1786 | ) orelse break; |
| 1787 | } |
| 1788 | } |
| 1789 | |
| 1790 | defer t.wait_group.finish(); |
| 1791 | |
| 1792 | mutexLock(&t.mutex); |
| 1793 | defer mutexUnlock(&t.mutex); |
| 1794 | |
| 1795 | while (true) { |
| 1796 | while (t.run_queue.popFirst()) |runnable_node| { |
| 1797 | mutexUnlock(&t.mutex); |
| 1798 | thread.cancel_protection = .unblocked; |
| 1799 | const runnable: *Runnable = @fieldParentPtr("node", runnable_node); |
| 1800 | runnable.startFn(runnable, &thread, t); |
| 1801 | mutexLock(&t.mutex); |
| 1802 | t.busy_count -= 1; |
| 1803 | } |
| 1804 | if (t.join_requested) break; |
| 1805 | condWait(&t.cond, &t.mutex); |
| 1806 | } |
| 1807 | } |
| 1808 | |
| 1809 | pub fn io(t: *Threaded) Io { |
| 1810 | return .{ |
| 1811 | .userdata = t, |
| 1812 | .vtable = &.{ |
| 1813 | .crashHandler = crashHandler, |
| 1814 | |
| 1815 | .async = async, |
| 1816 | .concurrent = concurrent, |
| 1817 | .await = await, |
| 1818 | .cancel = cancel, |
| 1819 | |
| 1820 | .groupAsync = groupAsync, |
| 1821 | .groupConcurrent = groupConcurrent, |
| 1822 | .groupAwait = groupAwait, |
| 1823 | .groupCancel = groupCancel, |
| 1824 | |
| 1825 | .recancel = recancel, |
| 1826 | .swapCancelProtection = swapCancelProtection, |
| 1827 | .checkCancel = checkCancel, |
| 1828 | |
| 1829 | .futexWait = futexWait, |
| 1830 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1831 | .futexWake = futexWake, |
| 1832 | |
| 1833 | .operate = operate, |
| 1834 | .batchAwaitAsync = batchAwaitAsync, |
| 1835 | .batchAwaitConcurrent = batchAwaitConcurrent, |
| 1836 | .batchCancel = batchCancel, |
| 1837 | |
| 1838 | .dirCreateDir = dirCreateDir, |
| 1839 | .dirCreateDirPath = dirCreateDirPath, |
| 1840 | .dirCreateDirPathOpen = dirCreateDirPathOpen, |
| 1841 | .dirStat = dirStat, |
| 1842 | .dirStatFile = dirStatFile, |
| 1843 | .dirAccess = dirAccess, |
| 1844 | .dirCreateFile = dirCreateFile, |
| 1845 | .dirCreateFileAtomic = dirCreateFileAtomic, |
| 1846 | .dirOpenFile = dirOpenFile, |
| 1847 | .dirOpenDir = dirOpenDir, |
| 1848 | .dirClose = dirClose, |
| 1849 | .dirRead = dirRead, |
| 1850 | .dirRealPath = dirRealPath, |
| 1851 | .dirRealPathFile = dirRealPathFile, |
| 1852 | .dirDeleteFile = dirDeleteFile, |
| 1853 | .dirDeleteDir = dirDeleteDir, |
| 1854 | .dirRename = dirRename, |
| 1855 | .dirRenamePreserve = dirRenamePreserve, |
| 1856 | .dirSymLink = dirSymLink, |
| 1857 | .dirReadLink = dirReadLink, |
| 1858 | .dirSetOwner = dirSetOwner, |
| 1859 | .dirSetFileOwner = dirSetFileOwner, |
| 1860 | .dirSetPermissions = dirSetPermissions, |
| 1861 | .dirSetFilePermissions = dirSetFilePermissions, |
| 1862 | .dirSetTimestamps = dirSetTimestamps, |
| 1863 | .dirHardLink = dirHardLink, |
| 1864 | |
| 1865 | .fileStat = fileStat, |
| 1866 | .fileLength = fileLength, |
| 1867 | .fileClose = fileClose, |
| 1868 | .fileWritePositional = fileWritePositional, |
| 1869 | .fileWriteFileStreaming = fileWriteFileStreaming, |
| 1870 | .fileWriteFilePositional = fileWriteFilePositional, |
| 1871 | .fileReadPositional = fileReadPositional, |
| 1872 | .fileSeekBy = fileSeekBy, |
| 1873 | .fileSeekTo = fileSeekTo, |
| 1874 | .fileSync = fileSync, |
| 1875 | .fileIsTty = fileIsTty, |
| 1876 | .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, |
| 1877 | .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes, |
| 1878 | .fileSetLength = fileSetLength, |
| 1879 | .fileSetOwner = fileSetOwner, |
| 1880 | .fileSetPermissions = fileSetPermissions, |
| 1881 | .fileSetTimestamps = fileSetTimestamps, |
| 1882 | .fileLock = fileLock, |
| 1883 | .fileTryLock = fileTryLock, |
| 1884 | .fileUnlock = fileUnlock, |
| 1885 | .fileDowngradeLock = fileDowngradeLock, |
| 1886 | .fileRealPath = fileRealPath, |
| 1887 | .fileHardLink = fileHardLink, |
| 1888 | |
| 1889 | .fileMemoryMapCreate = fileMemoryMapCreate, |
| 1890 | .fileMemoryMapDestroy = fileMemoryMapDestroy, |
| 1891 | .fileMemoryMapSetLength = fileMemoryMapSetLength, |
| 1892 | .fileMemoryMapRead = fileMemoryMapRead, |
| 1893 | .fileMemoryMapWrite = fileMemoryMapWrite, |
| 1894 | |
| 1895 | .processExecutableOpen = processExecutableOpen, |
| 1896 | .processExecutablePath = processExecutablePath, |
| 1897 | .lockStderr = lockStderr, |
| 1898 | .tryLockStderr = tryLockStderr, |
| 1899 | .unlockStderr = unlockStderr, |
| 1900 | .processCurrentPath = processCurrentPath, |
| 1901 | .processSetCurrentDir = processSetCurrentDir, |
| 1902 | .processSetCurrentPath = processSetCurrentPath, |
| 1903 | .processReplace = processReplace, |
| 1904 | .processReplacePath = processReplacePath, |
| 1905 | .processSpawn = processSpawn, |
| 1906 | .processSpawnPath = processSpawnPath, |
| 1907 | .childWait = childWait, |
| 1908 | .childKill = childKill, |
| 1909 | |
| 1910 | .progressParentFile = progressParentFile, |
| 1911 | |
| 1912 | .now = now, |
| 1913 | .clockResolution = clockResolution, |
| 1914 | .sleep = sleep, |
| 1915 | |
| 1916 | .random = random, |
| 1917 | .randomSecure = randomSecure, |
| 1918 | |
| 1919 | .netListenIp = switch (native_os) { |
| 1920 | .windows => netListenIpWindows, |
| 1921 | else => netListenIpPosix, |
| 1922 | }, |
| 1923 | .netListenUnix = switch (native_os) { |
| 1924 | .windows => netListenUnixWindows, |
| 1925 | else => netListenUnixPosix, |
| 1926 | }, |
| 1927 | .netAccept = switch (native_os) { |
| 1928 | .windows => netAcceptWindows, |
| 1929 | else => netAcceptPosix, |
| 1930 | }, |
| 1931 | .netBindIp = switch (native_os) { |
| 1932 | .windows => netBindIpWindows, |
| 1933 | else => netBindIpPosix, |
| 1934 | }, |
| 1935 | .netConnectIp = switch (native_os) { |
| 1936 | .windows => netConnectIpWindows, |
| 1937 | else => netConnectIpPosix, |
| 1938 | }, |
| 1939 | .netConnectUnix = switch (native_os) { |
| 1940 | .windows => netConnectUnixWindows, |
| 1941 | else => netConnectUnixPosix, |
| 1942 | }, |
| 1943 | .netSocketCreatePair = netSocketCreatePair, |
| 1944 | .netClose = netClose, |
| 1945 | .netShutdown = switch (native_os) { |
| 1946 | .windows => netShutdownWindows, |
| 1947 | else => netShutdownPosix, |
| 1948 | }, |
| 1949 | .netWriteFile = netWriteFile, |
| 1950 | .netInterfaceNameResolve = netInterfaceNameResolve, |
| 1951 | .netInterfaceName = netInterfaceName, |
| 1952 | .netLookup = netLookup, |
| 1953 | }, |
| 1954 | }; |
| 1955 | } |
| 1956 | |
| 1957 | pub const socket_flags_unsupported = is_darwin or native_os == .haiku; |
| 1958 | const have_accept4 = !socket_flags_unsupported; |
| 1959 | const have_flock_open_flags = @hasField(posix.O, "EXLOCK"); |
| 1960 | const have_networking = std.options.networking and native_os != .wasi; |
| 1961 | const have_flock = @TypeOf(posix.system.flock) != void; |
| 1962 | const have_sendmmsg = native_os == .linux; |
| 1963 | const have_futex = switch (builtin.cpu.arch) { |
| 1964 | .wasm32, .wasm64 => builtin.cpu.has(.wasm, .atomics), |
| 1965 | else => true, |
| 1966 | }; |
| 1967 | const have_preadv = switch (native_os) { |
| 1968 | .windows, .haiku => false, |
| 1969 | else => true, |
| 1970 | }; |
| 1971 | const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO"); |
| 1972 | const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE"); |
| 1973 | const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux; |
| 1974 | const have_copy_file_range = switch (native_os) { |
| 1975 | .linux, .freebsd => true, |
| 1976 | else => false, |
| 1977 | }; |
| 1978 | const have_fcopyfile = is_darwin; |
| 1979 | const have_fchmodat2 = native_os == .linux and |
| 1980 | (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and |
| 1981 | (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); |
| 1982 | const have_fchmodat_flags = native_os != .linux or |
| 1983 | (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); |
| 1984 | |
| 1985 | const have_fchown = switch (native_os) { |
| 1986 | .wasi, .windows => false, |
| 1987 | else => true, |
| 1988 | }; |
| 1989 | |
| 1990 | const have_fchmod = switch (native_os) { |
| 1991 | .windows => false, |
| 1992 | .wasi => builtin.link_libc, |
| 1993 | else => true, |
| 1994 | }; |
| 1995 | |
| 1996 | const have_waitid = switch (native_os) { |
| 1997 | .linux => @hasField(std.os.linux.SYS, "waitid"), |
| 1998 | else => false, |
| 1999 | }; |
| 2000 | |
| 2001 | const have_wait4 = switch (native_os) { |
| 2002 | .linux => @hasField(std.os.linux.SYS, "wait4"), |
| 2003 | .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, |
| 2004 | else => false, |
| 2005 | }; |
| 2006 | |
| 2007 | const have_mmap = switch (native_os) { |
| 2008 | .wasi, .windows => false, |
| 2009 | else => true, |
| 2010 | }; |
| 2011 | const have_poll = switch (native_os) { |
| 2012 | .wasi, .windows => false, |
| 2013 | else => true, |
| 2014 | }; |
| 2015 | |
| 2016 | const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open; |
| 2017 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; |
| 2018 | const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; |
| 2019 | const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; |
| 2020 | const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; |
| 2021 | const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; |
| 2022 | const pread_sym = if (posix.lfs64_abi) posix.system.pread64 else posix.system.pread; |
| 2023 | const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate; |
| 2024 | const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev; |
| 2025 | const pwrite_sym = if (posix.lfs64_abi) posix.system.pwrite64 else posix.system.pwrite; |
| 2026 | const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile; |
| 2027 | const mmap_sym = if (posix.lfs64_abi) posix.system.mmap64 else posix.system.mmap; |
| 2028 | |
| 2029 | const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ |
| 2030 | .major = 34, |
| 2031 | .minor = 0, |
| 2032 | .patch = 0, |
| 2033 | } else .{ |
| 2034 | .major = 2, |
| 2035 | .minor = 27, |
| 2036 | .patch = 0, |
| 2037 | }); |
| 2038 | const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; |
| 2039 | |
| 2040 | const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) |
| 2041 | .{ .major = 30, .minor = 0, .patch = 0 } |
| 2042 | else |
| 2043 | .{ .major = 2, .minor = 28, .patch = 0 }); |
| 2044 | |
| 2045 | const use_libc_getrandom = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ |
| 2046 | .major = 28, |
| 2047 | .minor = 0, |
| 2048 | .patch = 0, |
| 2049 | } else .{ |
| 2050 | .major = 2, |
| 2051 | .minor = 25, |
| 2052 | .patch = 0, |
| 2053 | }); |
| 2054 | |
| 2055 | const use_dev_urandom = @TypeOf(posix.system.getrandom) == void and native_os == .linux; |
| 2056 | |
| 2057 | fn crashHandler(userdata: ?*anyopaque) void { |
| 2058 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2059 | _ = t; |
| 2060 | const thread = Thread.current orelse return; |
| 2061 | thread.status.store(.{ .cancelation = .canceled, .awaitable = .null }, .monotonic); |
| 2062 | thread.cancel_protection = .blocked; |
| 2063 | } |
| 2064 | |
| 2065 | fn async( |
| 2066 | userdata: ?*anyopaque, |
| 2067 | result: []u8, |
| 2068 | result_alignment: Alignment, |
| 2069 | context: []const u8, |
| 2070 | context_alignment: Alignment, |
| 2071 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, |
| 2072 | ) ?*Io.AnyFuture { |
| 2073 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2074 | if (builtin.single_threaded) { |
| 2075 | start(context.ptr, result.ptr); |
| 2076 | return null; |
| 2077 | } |
| 2078 | |
| 2079 | const gpa = t.allocator; |
| 2080 | const future = Future.create(gpa, result.len, result_alignment, context, context_alignment, start) catch |err| switch (err) { |
| 2081 | error.OutOfMemory => { |
| 2082 | start(context.ptr, result.ptr); |
| 2083 | return null; |
| 2084 | }, |
| 2085 | }; |
| 2086 | |
| 2087 | mutexLock(&t.mutex); |
| 2088 | |
| 2089 | const busy_count = t.busy_count; |
| 2090 | |
| 2091 | if (busy_count >= @backingInt(t.async_limit)) { |
| 2092 | mutexUnlock(&t.mutex); |
| 2093 | future.destroy(gpa); |
| 2094 | start(context.ptr, result.ptr); |
| 2095 | return null; |
| 2096 | } |
| 2097 | |
| 2098 | t.busy_count = busy_count + 1; |
| 2099 | |
| 2100 | const pool_size = t.wait_group.value(); |
| 2101 | if (pool_size - busy_count == 0) { |
| 2102 | t.wait_group.start(); |
| 2103 | const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { |
| 2104 | t.wait_group.finish(); |
| 2105 | t.busy_count = busy_count; |
| 2106 | mutexUnlock(&t.mutex); |
| 2107 | future.destroy(gpa); |
| 2108 | start(context.ptr, result.ptr); |
| 2109 | return null; |
| 2110 | }; |
| 2111 | thread.detach(); |
| 2112 | } |
| 2113 | |
| 2114 | t.run_queue.prepend(&future.runnable.node); |
| 2115 | |
| 2116 | mutexUnlock(&t.mutex); |
| 2117 | condSignal(&t.cond); |
| 2118 | return @ptrCast(future); |
| 2119 | } |
| 2120 | |
| 2121 | fn concurrent( |
| 2122 | userdata: ?*anyopaque, |
| 2123 | result_len: usize, |
| 2124 | result_alignment: Alignment, |
| 2125 | context: []const u8, |
| 2126 | context_alignment: Alignment, |
| 2127 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, |
| 2128 | ) Io.ConcurrentError!*Io.AnyFuture { |
| 2129 | if (builtin.single_threaded) return error.ConcurrencyUnavailable; |
| 2130 | |
| 2131 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2132 | |
| 2133 | const gpa = t.allocator; |
| 2134 | const future = Future.create(gpa, result_len, result_alignment, context, context_alignment, start) catch |err| switch (err) { |
| 2135 | error.OutOfMemory => return error.ConcurrencyUnavailable, |
| 2136 | }; |
| 2137 | errdefer future.destroy(gpa); |
| 2138 | |
| 2139 | mutexLock(&t.mutex); |
| 2140 | defer mutexUnlock(&t.mutex); |
| 2141 | |
| 2142 | const busy_count = t.busy_count; |
| 2143 | |
| 2144 | if (busy_count >= @backingInt(t.concurrent_limit)) |
| 2145 | return error.ConcurrencyUnavailable; |
| 2146 | |
| 2147 | t.busy_count = busy_count + 1; |
| 2148 | errdefer t.busy_count = busy_count; |
| 2149 | |
| 2150 | const pool_size = t.wait_group.value(); |
| 2151 | if (pool_size - busy_count == 0) { |
| 2152 | t.wait_group.start(); |
| 2153 | errdefer t.wait_group.finish(); |
| 2154 | |
| 2155 | const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch |
| 2156 | return error.ConcurrencyUnavailable; |
| 2157 | |
| 2158 | thread.detach(); |
| 2159 | } |
| 2160 | |
| 2161 | t.run_queue.prepend(&future.runnable.node); |
| 2162 | |
| 2163 | condSignal(&t.cond); |
| 2164 | return @ptrCast(future); |
| 2165 | } |
| 2166 | |
| 2167 | fn groupAsync( |
| 2168 | userdata: ?*anyopaque, |
| 2169 | type_erased: *Io.Group, |
| 2170 | context: []const u8, |
| 2171 | context_alignment: Alignment, |
| 2172 | start: *const fn (context: *const anyopaque) void, |
| 2173 | ) void { |
| 2174 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2175 | const g: Group = .{ .ptr = type_erased }; |
| 2176 | |
| 2177 | if (builtin.single_threaded) return groupAsyncEager(start, context.ptr); |
| 2178 | |
| 2179 | const gpa = t.allocator; |
| 2180 | const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) { |
| 2181 | error.OutOfMemory => return groupAsyncEager(start, context.ptr), |
| 2182 | }; |
| 2183 | |
| 2184 | mutexLock(&t.mutex); |
| 2185 | |
| 2186 | const busy_count = t.busy_count; |
| 2187 | |
| 2188 | if (busy_count >= @backingInt(t.async_limit)) { |
| 2189 | mutexUnlock(&t.mutex); |
| 2190 | task.destroy(gpa); |
| 2191 | return groupAsyncEager(start, context.ptr); |
| 2192 | } |
| 2193 | |
| 2194 | t.busy_count = busy_count + 1; |
| 2195 | |
| 2196 | const pool_size = t.wait_group.value(); |
| 2197 | if (pool_size - busy_count == 0) { |
| 2198 | t.wait_group.start(); |
| 2199 | const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { |
| 2200 | t.wait_group.finish(); |
| 2201 | t.busy_count = busy_count; |
| 2202 | mutexUnlock(&t.mutex); |
| 2203 | task.destroy(gpa); |
| 2204 | return groupAsyncEager(start, context.ptr); |
| 2205 | }; |
| 2206 | thread.detach(); |
| 2207 | } |
| 2208 | |
| 2209 | // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue |
| 2210 | // prepend so that the task doesn't finish without observing this and try to decrement the count |
| 2211 | // below zero. |
| 2212 | _ = g.status().fetchAdd(.{ |
| 2213 | .num_running = 1, |
| 2214 | .have_awaiter = false, |
| 2215 | .canceled = false, |
| 2216 | }, .monotonic); |
| 2217 | t.run_queue.prepend(&task.runnable.node); |
| 2218 | |
| 2219 | mutexUnlock(&t.mutex); |
| 2220 | condSignal(&t.cond); |
| 2221 | } |
| 2222 | fn groupAsyncEager( |
| 2223 | start: *const fn (context: *const anyopaque) void, |
| 2224 | context: *const anyopaque, |
| 2225 | ) void { |
| 2226 | start(context); |
| 2227 | } |
| 2228 | |
| 2229 | fn groupConcurrent( |
| 2230 | userdata: ?*anyopaque, |
| 2231 | type_erased: *Io.Group, |
| 2232 | context: []const u8, |
| 2233 | context_alignment: Alignment, |
| 2234 | start: *const fn (context: *const anyopaque) void, |
| 2235 | ) Io.ConcurrentError!void { |
| 2236 | if (builtin.single_threaded) return error.ConcurrencyUnavailable; |
| 2237 | |
| 2238 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2239 | const g: Group = .{ .ptr = type_erased }; |
| 2240 | |
| 2241 | const gpa = t.allocator; |
| 2242 | const task = Group.Task.create(gpa, g, context, context_alignment, start) catch |err| switch (err) { |
| 2243 | error.OutOfMemory => return error.ConcurrencyUnavailable, |
| 2244 | }; |
| 2245 | errdefer task.destroy(gpa); |
| 2246 | |
| 2247 | mutexLock(&t.mutex); |
| 2248 | defer mutexUnlock(&t.mutex); |
| 2249 | |
| 2250 | const busy_count = t.busy_count; |
| 2251 | |
| 2252 | if (busy_count >= @backingInt(t.concurrent_limit)) |
| 2253 | return error.ConcurrencyUnavailable; |
| 2254 | |
| 2255 | t.busy_count = busy_count + 1; |
| 2256 | errdefer t.busy_count = busy_count; |
| 2257 | |
| 2258 | const pool_size = t.wait_group.value(); |
| 2259 | if (pool_size - busy_count == 0) { |
| 2260 | t.wait_group.start(); |
| 2261 | errdefer t.wait_group.finish(); |
| 2262 | |
| 2263 | const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch |
| 2264 | return error.ConcurrencyUnavailable; |
| 2265 | |
| 2266 | thread.detach(); |
| 2267 | } |
| 2268 | |
| 2269 | // TODO: if this logic is changed to be lock-free, this `fetchAdd` must be released by the queue |
| 2270 | // prepend so that the task doesn't finish without observing this and try to decrement the count |
| 2271 | // below zero. |
| 2272 | _ = g.status().fetchAdd(.{ |
| 2273 | .num_running = 1, |
| 2274 | .have_awaiter = false, |
| 2275 | .canceled = false, |
| 2276 | }, .monotonic); |
| 2277 | t.run_queue.prepend(&task.runnable.node); |
| 2278 | |
| 2279 | condSignal(&t.cond); |
| 2280 | } |
| 2281 | |
| 2282 | fn groupAwait(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void { |
| 2283 | _ = initial_token; // we need to load `token` *after* the group finishes |
| 2284 | if (builtin.single_threaded) unreachable; // nothing to await |
| 2285 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2286 | const g: Group = .{ .ptr = type_erased }; |
| 2287 | |
| 2288 | var num_completed: std.atomic.Value(u32) = .init(0); |
| 2289 | g.awaiter().* = &num_completed; |
| 2290 | |
| 2291 | const pre_await_status = g.status().fetchOr(.{ |
| 2292 | .num_running = 0, |
| 2293 | .have_awaiter = true, |
| 2294 | .canceled = false, |
| 2295 | }, .acq_rel); // acquire results if complete; release `g.awaiter()` |
| 2296 | |
| 2297 | assert(!pre_await_status.have_awaiter); |
| 2298 | assert(!pre_await_status.canceled); |
| 2299 | if (pre_await_status.num_running == 0) { |
| 2300 | // Already done. Since the group is finished, it's illegal to spawn more tasks in it |
| 2301 | // until we return, so we can access `g.status()` non-atomically. |
| 2302 | g.status().raw.have_awaiter = false; |
| 2303 | return; |
| 2304 | } |
| 2305 | |
| 2306 | while (Thread.futexWait(&num_completed.raw, 0, null)) { |
| 2307 | switch (num_completed.load(.acquire)) { // acquire task results |
| 2308 | 0 => continue, |
| 2309 | 1 => break, |
| 2310 | else => unreachable, // group was reused before `await` returned |
| 2311 | } |
| 2312 | } else |err| switch (err) { |
| 2313 | error.Canceled => { |
| 2314 | const pre_cancel_status = g.status().fetchOr(.{ |
| 2315 | .num_running = 0, |
| 2316 | .have_awaiter = false, |
| 2317 | .canceled = true, |
| 2318 | }, .acq_rel); // acquire results if complete; release `g.awaiter()` |
| 2319 | assert(pre_cancel_status.have_awaiter); |
| 2320 | assert(!pre_cancel_status.canceled); |
| 2321 | |
| 2322 | // Even if `pre_cancel_status.num_running == 0`, we still need to wait for the signal, |
| 2323 | // because in that case the last member of the group is already trying to modify it. |
| 2324 | // However, if we know everything is done, we *can* skip signaling blocked threads. |
| 2325 | const skip_signals = pre_cancel_status.num_running == 0; |
| 2326 | g.waitForCancelWithSignaling(t, &num_completed, skip_signals); |
| 2327 | |
| 2328 | // The group is finished, so it's illegal to spawn more tasks in it until we return, so |
| 2329 | // we can access `g.status()` non-atomically. |
| 2330 | g.status().raw.canceled = false; |
| 2331 | g.status().raw.have_awaiter = false; |
| 2332 | return error.Canceled; |
| 2333 | }, |
| 2334 | } |
| 2335 | |
| 2336 | // The group is finished, so it's illegal to spawn more tasks in it until we return, so |
| 2337 | // we can access `g.status()` non-atomically. |
| 2338 | g.status().raw.have_awaiter = false; |
| 2339 | } |
| 2340 | |
| 2341 | fn groupCancel(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) void { |
| 2342 | _ = initial_token; |
| 2343 | if (builtin.single_threaded) unreachable; // nothing to cancel |
| 2344 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2345 | const g: Group = .{ .ptr = type_erased }; |
| 2346 | |
| 2347 | var num_completed: std.atomic.Value(u32) = .init(0); |
| 2348 | g.awaiter().* = &num_completed; |
| 2349 | |
| 2350 | const pre_cancel_status = g.status().fetchOr(.{ |
| 2351 | .num_running = 0, |
| 2352 | .have_awaiter = true, |
| 2353 | .canceled = true, |
| 2354 | }, .acq_rel); // acquire results if complete; release `g.awaiter()` |
| 2355 | |
| 2356 | assert(!pre_cancel_status.have_awaiter); |
| 2357 | assert(!pre_cancel_status.canceled); |
| 2358 | if (pre_cancel_status.num_running == 0) { |
| 2359 | // Already done. Since the group is finished, it's illegal to spawn more tasks in it |
| 2360 | // until we return, so we can access `g.status()` non-atomically. |
| 2361 | g.status().raw.have_awaiter = false; |
| 2362 | g.status().raw.canceled = false; |
| 2363 | return; |
| 2364 | } |
| 2365 | |
| 2366 | g.waitForCancelWithSignaling(t, &num_completed, false); |
| 2367 | |
| 2368 | g.status().raw = .{ .num_running = 0, .have_awaiter = false, .canceled = false }; |
| 2369 | } |
| 2370 | |
| 2371 | fn recancel(userdata: ?*anyopaque) void { |
| 2372 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2373 | _ = t; |
| 2374 | recancelInner(); |
| 2375 | } |
| 2376 | fn recancelInner() void { |
| 2377 | const thread = Thread.current.?; // called `recancel` but was not canceled |
| 2378 | switch (thread.status.fetchXor(.{ |
| 2379 | .cancelation = @fromBackingInt(@intCast(0b001)), |
| 2380 | .awaitable = .null, |
| 2381 | }, .monotonic).cancelation) { |
| 2382 | .canceled => {}, |
| 2383 | .none => unreachable, // called `recancel` but was not canceled |
| 2384 | .canceling => unreachable, // called `recancel` but cancelation was already pending |
| 2385 | .parked => unreachable, |
| 2386 | .blocked => unreachable, |
| 2387 | .blocked_alertable => unreachable, |
| 2388 | .blocked_alertable_canceling => unreachable, |
| 2389 | .blocked_canceling => unreachable, |
| 2390 | } |
| 2391 | } |
| 2392 | |
| 2393 | fn swapCancelProtection(userdata: ?*anyopaque, new: Io.CancelProtection) Io.CancelProtection { |
| 2394 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2395 | _ = t; |
| 2396 | const thread = Thread.current orelse return .unblocked; |
| 2397 | const old = thread.cancel_protection; |
| 2398 | thread.cancel_protection = new; |
| 2399 | return old; |
| 2400 | } |
| 2401 | |
| 2402 | fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void { |
| 2403 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2404 | _ = t; |
| 2405 | return Thread.checkCancel(); |
| 2406 | } |
| 2407 | |
| 2408 | fn await( |
| 2409 | userdata: ?*anyopaque, |
| 2410 | any_future: *Io.AnyFuture, |
| 2411 | result: []u8, |
| 2412 | result_alignment: Alignment, |
| 2413 | ) void { |
| 2414 | _ = result_alignment; |
| 2415 | if (builtin.single_threaded) unreachable; // nothing to await |
| 2416 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2417 | const future: *Future = @ptrCast(@alignCast(any_future)); |
| 2418 | |
| 2419 | var num_completed: std.atomic.Value(u32) = .init(0); |
| 2420 | future.awaiter = &num_completed; |
| 2421 | |
| 2422 | const pre_await_status = future.status.fetchOr(.{ |
| 2423 | .tag = .pending_awaited, |
| 2424 | .thread = .null, |
| 2425 | }, .acq_rel); // acquire results if complete; release `future.awaiter` |
| 2426 | switch (pre_await_status.tag) { |
| 2427 | .pending => while (Thread.futexWait(&num_completed.raw, 0, null)) { |
| 2428 | switch (num_completed.load(.acquire)) { // acquire task results |
| 2429 | 0 => continue, |
| 2430 | 1 => break, |
| 2431 | else => unreachable, // group was reused before `await` returned |
| 2432 | } |
| 2433 | } else |err| switch (err) { |
| 2434 | error.Canceled => { |
| 2435 | const pre_cancel_status = future.status.fetchOr(.{ |
| 2436 | .tag = .pending_canceled, |
| 2437 | .thread = .null, |
| 2438 | }, .acq_rel); // acquire results if complete; release `future.awaiter` |
| 2439 | const done_status = switch (pre_cancel_status.tag) { |
| 2440 | .pending => unreachable, // invalid state: we already awaited |
| 2441 | .pending_awaited => done_status: { |
| 2442 | const working_thread = pre_cancel_status.thread.unpack(); |
| 2443 | future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread)); |
| 2444 | break :done_status future.status.load(.monotonic); |
| 2445 | }, |
| 2446 | .pending_canceled => unreachable, // `await` raced with `cancel` |
| 2447 | .done => done_status: { |
| 2448 | // The task just finished, but we still need to wait for the signal, because the |
| 2449 | // task thread already figured out that they need to update `future.awaiter`. |
| 2450 | future.waitForCancelWithSignaling(t, &num_completed, null); |
| 2451 | // Also, we have clobbered `future.status.tag` to `.pending_canceled`, but that's |
| 2452 | // not actually a problem for the logic below. |
| 2453 | break :done_status pre_cancel_status; |
| 2454 | }, |
| 2455 | }; |
| 2456 | // If the future did not acknowledge the cancelation, we need to mark it outstanding |
| 2457 | // for us. Because `done_status.tag == .done`, the information about whether there |
| 2458 | // was an acknowledged cancelation is encoded in `done_status.thread`. |
| 2459 | assert(done_status.tag == .done); |
| 2460 | switch (done_status.thread) { |
| 2461 | .null => recancelInner(), // cancelation was not acknowledged, so it's ours |
| 2462 | .all_ones => {}, // cancelation was acknowledged, so it was this task's job to propagate it |
| 2463 | _ => unreachable, |
| 2464 | } |
| 2465 | }, |
| 2466 | }, |
| 2467 | .pending_awaited => unreachable, // `await` raced with `await` |
| 2468 | .pending_canceled => unreachable, // `await` raced with `cancel` |
| 2469 | .done => {}, |
| 2470 | } |
| 2471 | @memcpy(result, future.resultPointer()); |
| 2472 | future.destroy(t.allocator); |
| 2473 | } |
| 2474 | |
| 2475 | fn cancel( |
| 2476 | userdata: ?*anyopaque, |
| 2477 | any_future: *Io.AnyFuture, |
| 2478 | result: []u8, |
| 2479 | result_alignment: Alignment, |
| 2480 | ) void { |
| 2481 | _ = result_alignment; |
| 2482 | if (builtin.single_threaded) unreachable; // nothing to cancel |
| 2483 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2484 | const future: *Future = @ptrCast(@alignCast(any_future)); |
| 2485 | |
| 2486 | var num_completed: std.atomic.Value(u32) = .init(0); |
| 2487 | future.awaiter = &num_completed; |
| 2488 | |
| 2489 | const pre_cancel_status = future.status.fetchOr(.{ |
| 2490 | .tag = .pending_canceled, |
| 2491 | .thread = .null, |
| 2492 | }, .acq_rel); // acquire results if complete; release `future.awaiter` |
| 2493 | switch (pre_cancel_status.tag) { |
| 2494 | .pending => { |
| 2495 | const working_thread = pre_cancel_status.thread.unpack(); |
| 2496 | future.waitForCancelWithSignaling(t, &num_completed, @alignCast(working_thread)); |
| 2497 | }, |
| 2498 | .pending_awaited => unreachable, // `await` raced with `await` |
| 2499 | .pending_canceled => unreachable, // `await` raced with `cancel` |
| 2500 | .done => {}, |
| 2501 | } |
| 2502 | @memcpy(result, future.resultPointer()); |
| 2503 | future.destroy(t.allocator); |
| 2504 | } |
| 2505 | |
| 2506 | fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void { |
| 2507 | if (builtin.single_threaded) { |
| 2508 | assert(timeout != .none); // Deadlock. |
| 2509 | return; |
| 2510 | } |
| 2511 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2512 | const t_io = io(t); |
| 2513 | const timeout_ns: ?u64 = ns: { |
| 2514 | const d = timeout.toDurationFromNow(t_io) orelse break :ns null; |
| 2515 | break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); |
| 2516 | }; |
| 2517 | return Thread.futexWait(ptr, expected, timeout_ns); |
| 2518 | } |
| 2519 | |
| 2520 | fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { |
| 2521 | if (builtin.single_threaded) unreachable; // Deadlock. |
| 2522 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2523 | _ = t; |
| 2524 | Thread.futexWaitUncancelable(ptr, expected, null); |
| 2525 | } |
| 2526 | |
| 2527 | fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2528 | if (builtin.single_threaded) return; // Nothing to wake up. |
| 2529 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2530 | _ = t; |
| 2531 | Thread.futexWake(ptr, max_waiters); |
| 2532 | } |
| 2533 | |
| 2534 | fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result { |
| 2535 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2536 | switch (operation) { |
| 2537 | .file_read_streaming => |o| return .{ |
| 2538 | .file_read_streaming = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { |
| 2539 | error.Canceled => |e| return e, |
| 2540 | else => |e| e, |
| 2541 | }, |
| 2542 | }, |
| 2543 | .file_write_streaming => |o| return .{ |
| 2544 | .file_write_streaming = fileWriteStreaming(t, o.file, o.header, o.data, o.splat) catch |err| switch (err) { |
| 2545 | error.Canceled => |e| return e, |
| 2546 | else => |e| e, |
| 2547 | }, |
| 2548 | }, |
| 2549 | .device_io_control => |*o| return .{ .device_io_control = try deviceIoControl(o) }, |
| 2550 | .net_receive => |*o| return .{ .net_receive = o: { |
| 2551 | if (!have_networking) break :o .{ error.NetworkDown, 0 }; |
| 2552 | if (is_windows) break :o netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags); |
| 2553 | netReceivePosix(o.socket_handle, &o.message_buffer[0], o.data_buffer, o.flags, false) catch |err| switch (err) { |
| 2554 | error.Canceled => |e| return e, |
| 2555 | error.WouldBlock => unreachable, |
| 2556 | else => |e| break :o .{ e, 0 }, |
| 2557 | }; |
| 2558 | break :o .{ null, 1 }; |
| 2559 | } }, |
| 2560 | .net_send => |*o| return .{ |
| 2561 | .net_send = o: { |
| 2562 | if (!have_networking) break :o .{ error.NetworkDown, 0 }; |
| 2563 | if (is_windows) break :o netSendWindows(t, o.socket_handle, o.messages, o.flags); |
| 2564 | const send_err, const sent = netSendPosix(t, o.socket_handle, o.messages, o.flags, false); |
| 2565 | if (send_err) |err| switch (err) { |
| 2566 | error.Canceled => |e| if (sent == 0) { |
| 2567 | return e; |
| 2568 | } else { |
| 2569 | // Leave the `error.Canceled` for later, but don't try to send any more messages. |
| 2570 | recancelInner(); |
| 2571 | break :o .{ null, sent }; |
| 2572 | }, |
| 2573 | error.WouldBlock => unreachable, |
| 2574 | else => |e| break :o .{ e, sent }, |
| 2575 | }; |
| 2576 | break :o .{ null, sent }; |
| 2577 | }, |
| 2578 | }, |
| 2579 | .net_read => |o| return .{ |
| 2580 | .net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) { |
| 2581 | error.Canceled => |e| return e, |
| 2582 | else => |e| e, |
| 2583 | }, |
| 2584 | }, |
| 2585 | .net_write => |o| return .{ |
| 2586 | .net_write = (if (is_windows) |
| 2587 | netWriteWindows(o.socket_handle, o.header, o.data, o.splat) |
| 2588 | else |
| 2589 | netWritePosix(o.socket_handle, o.header, o.data, o.splat)) catch |err| switch (err) { |
| 2590 | error.Canceled => |e| return e, |
| 2591 | else => |e| e, |
| 2592 | }, |
| 2593 | }, |
| 2594 | } |
| 2595 | } |
| 2596 | |
| 2597 | fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { |
| 2598 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2599 | if (is_windows) { |
| 2600 | batchDrainSubmittedWindows(t, b, false) catch |err| switch (err) { |
| 2601 | error.ConcurrencyUnavailable => unreachable, // passed concurrency=false |
| 2602 | else => |e| return e, |
| 2603 | }; |
| 2604 | const alertable_syscall = try AlertableSyscall.start(); |
| 2605 | while (b.pending.head != .none and b.completed.head == .none) waitForApcOrAlert(); |
| 2606 | alertable_syscall.finish(); |
| 2607 | return; |
| 2608 | } |
| 2609 | if (have_poll) { |
| 2610 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2611 | var poll_len: u32 = 0; |
| 2612 | { |
| 2613 | var index = b.submitted.head; |
| 2614 | while (index != .none and poll_len < poll_buffer_len) { |
| 2615 | const submission = &b.storage[index.toIndex()].submission; |
| 2616 | switch (submission.operation) { |
| 2617 | .file_read_streaming => |o| { |
| 2618 | poll_buffer[poll_len] = .{ |
| 2619 | .fd = o.file.handle, |
| 2620 | .events = posix.POLL.IN | posix.POLL.ERR, |
| 2621 | }; |
| 2622 | poll_len += 1; |
| 2623 | }, |
| 2624 | .file_write_streaming => |o| { |
| 2625 | poll_buffer[poll_len] = .{ |
| 2626 | .fd = o.file.handle, |
| 2627 | .events = posix.POLL.OUT | posix.POLL.ERR, |
| 2628 | }; |
| 2629 | poll_len += 1; |
| 2630 | }, |
| 2631 | .device_io_control => |o| { |
| 2632 | poll_buffer[poll_len] = .{ |
| 2633 | .fd = o.file.handle, |
| 2634 | .events = posix.POLL.OUT | posix.POLL.IN | posix.POLL.ERR, |
| 2635 | }; |
| 2636 | poll_len += 1; |
| 2637 | }, |
| 2638 | .net_receive => |*o| { |
| 2639 | poll_buffer[poll_len] = .{ |
| 2640 | .fd = o.socket_handle, |
| 2641 | .events = posix.POLL.IN | posix.POLL.ERR, |
| 2642 | }; |
| 2643 | poll_len += 1; |
| 2644 | }, |
| 2645 | .net_send => |*o| { |
| 2646 | poll_buffer[poll_len] = .{ |
| 2647 | .fd = o.socket_handle, |
| 2648 | .events = posix.POLL.OUT | posix.POLL.ERR, |
| 2649 | }; |
| 2650 | poll_len += 1; |
| 2651 | }, |
| 2652 | .net_read => |o| { |
| 2653 | poll_buffer[poll_len] = .{ |
| 2654 | .fd = o.socket_handle, |
| 2655 | .events = posix.POLL.IN | posix.POLL.ERR, |
| 2656 | }; |
| 2657 | poll_len += 1; |
| 2658 | }, |
| 2659 | .net_write => |o| { |
| 2660 | poll_buffer[poll_len] = .{ |
| 2661 | .fd = o.socket_handle, |
| 2662 | .events = posix.POLL.OUT | posix.POLL.ERR, |
| 2663 | }; |
| 2664 | poll_len += 1; |
| 2665 | }, |
| 2666 | } |
| 2667 | index = submission.node.next; |
| 2668 | } |
| 2669 | } |
| 2670 | switch (poll_len) { |
| 2671 | 0 => return, |
| 2672 | 1 => {}, |
| 2673 | else => while (true) { |
| 2674 | const timeout_ms: i32 = t: { |
| 2675 | if (b.completed.head != .none) { |
| 2676 | // It is legal to call batchWait with already completed |
| 2677 | // operations in the ring. In such case, we need to avoid |
| 2678 | // blocking in the poll syscall, but we can still take this |
| 2679 | // opportunity to find additional ready operations. |
| 2680 | break :t 0; |
| 2681 | } |
| 2682 | break :t std.math.maxInt(i32); |
| 2683 | }; |
| 2684 | const syscall = try Syscall.start(); |
| 2685 | const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); |
| 2686 | syscall.finish(); |
| 2687 | switch (posix.errno(rc)) { |
| 2688 | .SUCCESS => { |
| 2689 | if (rc == 0) { |
| 2690 | if (b.completed.head != .none) { |
| 2691 | // Since there are already completions available in the |
| 2692 | // queue, this is neither a timeout nor a case for |
| 2693 | // retrying. |
| 2694 | return; |
| 2695 | } |
| 2696 | continue; |
| 2697 | } |
| 2698 | var prev_index: Io.Operation.OptionalIndex = .none; |
| 2699 | var index = b.submitted.head; |
| 2700 | for (poll_buffer[0..poll_len]) |poll_entry| { |
| 2701 | const storage = &b.storage[index.toIndex()]; |
| 2702 | const submission = &storage.submission; |
| 2703 | const next_index = submission.node.next; |
| 2704 | if (poll_entry.revents != 0) { |
| 2705 | const result = try operate(t, submission.operation); |
| 2706 | |
| 2707 | switch (prev_index) { |
| 2708 | .none => b.submitted.head = next_index, |
| 2709 | else => b.storage[prev_index.toIndex()].submission.node.next = next_index, |
| 2710 | } |
| 2711 | if (next_index == .none) b.submitted.tail = prev_index; |
| 2712 | |
| 2713 | switch (b.completed.tail) { |
| 2714 | .none => b.completed.head = index, |
| 2715 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| 2716 | } |
| 2717 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 2718 | b.completed.tail = index; |
| 2719 | } else prev_index = index; |
| 2720 | index = next_index; |
| 2721 | } |
| 2722 | assert(index == .none); |
| 2723 | return; |
| 2724 | }, |
| 2725 | .INTR => continue, |
| 2726 | else => break, |
| 2727 | } |
| 2728 | }, |
| 2729 | } |
| 2730 | } |
| 2731 | |
| 2732 | var tail_index = b.completed.tail; |
| 2733 | defer b.completed.tail = tail_index; |
| 2734 | var index = b.submitted.head; |
| 2735 | errdefer b.submitted.head = index; |
| 2736 | while (index != .none) { |
| 2737 | const storage = &b.storage[index.toIndex()]; |
| 2738 | const submission = &storage.submission; |
| 2739 | const next_index = submission.node.next; |
| 2740 | const result = try operate(t, submission.operation); |
| 2741 | |
| 2742 | switch (tail_index) { |
| 2743 | .none => b.completed.head = index, |
| 2744 | else => b.storage[tail_index.toIndex()].completion.node.next = index, |
| 2745 | } |
| 2746 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 2747 | tail_index = index; |
| 2748 | index = next_index; |
| 2749 | } |
| 2750 | b.submitted = .{ .head = .none, .tail = .none }; |
| 2751 | } |
| 2752 | |
| 2753 | fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { |
| 2754 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2755 | if (is_windows) { |
| 2756 | const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(io(t)); |
| 2757 | try batchDrainSubmittedWindows(t, b, true); |
| 2758 | while (b.pending.head != .none and b.completed.head == .none) { |
| 2759 | var delay_interval: windows.LARGE_INTEGER = interval: { |
| 2760 | const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); |
| 2761 | break :interval timeoutToWindowsInterval(.{ .deadline = d }).?; |
| 2762 | }; |
| 2763 | const alertable_syscall = try AlertableSyscall.start(); |
| 2764 | const delay_rc = windows.ntdll.NtDelayExecution(.TRUE, &delay_interval); |
| 2765 | alertable_syscall.finish(); |
| 2766 | switch (delay_rc) { |
| 2767 | .SUCCESS, .TIMEOUT => { |
| 2768 | // The thread woke due to the timeout. Although spurious |
| 2769 | // timeouts are OK, when no deadline is passed we must not |
| 2770 | // return `error.Timeout`. |
| 2771 | if (timeout != .none and b.completed.head == .none) return error.Timeout; |
| 2772 | }, |
| 2773 | else => {}, |
| 2774 | } |
| 2775 | } |
| 2776 | return; |
| 2777 | } |
| 2778 | if (native_os == .wasi) { |
| 2779 | // TODO call poll_oneoff |
| 2780 | return error.ConcurrencyUnavailable; |
| 2781 | } |
| 2782 | if (!have_poll) return error.ConcurrencyUnavailable; |
| 2783 | var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; |
| 2784 | var poll_storage: struct { |
| 2785 | gpa: Allocator, |
| 2786 | batch: *Io.Batch, |
| 2787 | slice: []posix.pollfd, |
| 2788 | len: u32, |
| 2789 | |
| 2790 | fn add(storage: *@This(), fd: File.Handle, events: @FieldType(posix.pollfd, "events")) Io.ConcurrentError!void { |
| 2791 | const len = storage.len; |
| 2792 | if (len == poll_buffer_len) { |
| 2793 | const slice: []posix.pollfd = if (storage.batch.userdata) |batch_userdata| |
| 2794 | @as([*]posix.pollfd, @ptrCast(@alignCast(batch_userdata)))[0..storage.batch.storage.len] |
| 2795 | else allocation: { |
| 2796 | const allocation = storage.gpa.alloc(posix.pollfd, storage.batch.storage.len) catch |
| 2797 | return error.ConcurrencyUnavailable; |
| 2798 | storage.batch.userdata = allocation.ptr; |
| 2799 | break :allocation allocation; |
| 2800 | }; |
| 2801 | @memcpy(slice[0..poll_buffer_len], storage.slice); |
| 2802 | storage.slice = slice; |
| 2803 | } |
| 2804 | storage.slice[len] = .{ |
| 2805 | .fd = fd, |
| 2806 | .events = events, |
| 2807 | }; |
| 2808 | storage.len = len + 1; |
| 2809 | } |
| 2810 | } = .{ .gpa = t.allocator, .batch = b, .slice = &poll_buffer, .len = 0 }; |
| 2811 | { |
| 2812 | var index = b.submitted.head; |
| 2813 | while (index != .none) { |
| 2814 | const storage = &b.storage[index.toIndex()]; |
| 2815 | const submission = storage.submission; |
| 2816 | switch (submission.operation) { |
| 2817 | .file_read_streaming => |o| try poll_storage.add(o.file.handle, posix.POLL.IN | posix.POLL.ERR), |
| 2818 | .file_write_streaming => |o| try poll_storage.add(o.file.handle, posix.POLL.OUT | posix.POLL.ERR), |
| 2819 | .device_io_control => |o| try poll_storage.add(o.file.handle, posix.POLL.IN | posix.POLL.OUT | posix.POLL.ERR), |
| 2820 | .net_receive => |*o| nb: { |
| 2821 | var data_i: usize = 0; |
| 2822 | const result: Io.Operation.Result = .{ .net_receive = for (o.message_buffer, 0..) |*msg, msg_i| { |
| 2823 | const remaining_data_buffer = o.data_buffer[data_i..]; |
| 2824 | netReceivePosix(o.socket_handle, msg, remaining_data_buffer, o.flags, true) catch |err| switch (err) { |
| 2825 | error.Canceled => |e| return e, |
| 2826 | error.WouldBlock => { |
| 2827 | if (msg_i != 0) break .{ null, msg_i }; |
| 2828 | try poll_storage.add(o.socket_handle, posix.POLL.IN | posix.POLL.ERR); |
| 2829 | break :nb; |
| 2830 | }, |
| 2831 | else => |e| break .{ e, 0 }, |
| 2832 | }; |
| 2833 | data_i += msg.data.len; |
| 2834 | } else .{ null, o.message_buffer.len } }; |
| 2835 | switch (b.completed.tail) { |
| 2836 | .none => b.completed.head = index, |
| 2837 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| 2838 | } |
| 2839 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 2840 | b.completed.tail = index; |
| 2841 | }, |
| 2842 | .net_send => |*o| nb: { |
| 2843 | const result: Io.Operation.Result = .{ |
| 2844 | .net_send = o: { |
| 2845 | const send_err, const sent = netSendPosix(t, o.socket_handle, o.messages, o.flags, true); |
| 2846 | if (send_err) |err| switch (err) { |
| 2847 | error.Canceled => |e| if (sent == 0) { |
| 2848 | return e; |
| 2849 | } else { |
| 2850 | // Leave the `error.Canceled` for later, but don't try to send any more messages. |
| 2851 | recancelInner(); |
| 2852 | break :o .{ null, sent }; |
| 2853 | }, |
| 2854 | error.WouldBlock => { |
| 2855 | if (sent != 0) break :o .{ null, sent }; |
| 2856 | try poll_storage.add(o.socket_handle, posix.POLL.OUT | posix.POLL.ERR); |
| 2857 | break :nb; |
| 2858 | }, |
| 2859 | else => |e| break :o .{ e, sent }, |
| 2860 | }; |
| 2861 | break :o .{ null, sent }; |
| 2862 | }, |
| 2863 | }; |
| 2864 | switch (b.completed.tail) { |
| 2865 | .none => b.completed.head = index, |
| 2866 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| 2867 | } |
| 2868 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 2869 | b.completed.tail = index; |
| 2870 | }, |
| 2871 | .net_read => |o| try poll_storage.add(o.socket_handle, posix.POLL.IN | posix.POLL.ERR), |
| 2872 | .net_write => |o| try poll_storage.add(o.socket_handle, posix.POLL.OUT | posix.POLL.ERR), |
| 2873 | } |
| 2874 | index = submission.node.next; |
| 2875 | } |
| 2876 | } |
| 2877 | switch (poll_storage.len) { |
| 2878 | 0 => return, |
| 2879 | 1 => if (timeout == .none and b.completed.head == .none) { |
| 2880 | const index = b.submitted.head; |
| 2881 | const storage = &b.storage[index.toIndex()]; |
| 2882 | const result = try operate(t, storage.submission.operation); |
| 2883 | |
| 2884 | b.submitted = .{ .head = .none, .tail = .none }; |
| 2885 | |
| 2886 | switch (b.completed.tail) { |
| 2887 | .none => b.completed.head = index, |
| 2888 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| 2889 | } |
| 2890 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 2891 | b.completed.tail = index; |
| 2892 | return; |
| 2893 | }, |
| 2894 | else => {}, |
| 2895 | } |
| 2896 | const t_io = io(t); |
| 2897 | const deadline = timeout.toTimestamp(t_io); |
| 2898 | while (true) { |
| 2899 | const timeout_ms: i32 = t: { |
| 2900 | if (b.completed.head != .none) { |
| 2901 | // It is legal to call batchWait with already completed |
| 2902 | // operations in the ring. In such case, we need to avoid |
| 2903 | // blocking in the poll syscall, but we can still take this |
| 2904 | // opportunity to find additional ready operations. |
| 2905 | break :t 0; |
| 2906 | } |
| 2907 | const d = deadline orelse break :t -1; |
| 2908 | const duration = d.durationFromNow(t_io); |
| 2909 | break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32)); |
| 2910 | }; |
| 2911 | const syscall = try Syscall.start(); |
| 2912 | const rc = posix.system.poll(poll_storage.slice.ptr, poll_storage.len, timeout_ms); |
| 2913 | syscall.finish(); |
| 2914 | switch (posix.errno(rc)) { |
| 2915 | .SUCCESS => { |
| 2916 | if (rc == 0) { |
| 2917 | if (b.completed.head != .none) { |
| 2918 | // Since there are already completions available in the |
| 2919 | // queue, this is neither a timeout nor a case for |
| 2920 | // retrying. |
| 2921 | return; |
| 2922 | } |
| 2923 | // Although spurious timeouts are OK, when no deadline is |
| 2924 | // passed we must not return `error.Timeout`. |
| 2925 | if (deadline == null) continue; |
| 2926 | return error.Timeout; |
| 2927 | } |
| 2928 | var prev_index: Io.Operation.OptionalIndex = .none; |
| 2929 | var index = b.submitted.head; |
| 2930 | for (poll_storage.slice[0..poll_storage.len]) |poll_entry| { |
| 2931 | const submission = &b.storage[index.toIndex()].submission; |
| 2932 | const next_index = submission.node.next; |
| 2933 | if (poll_entry.revents != 0) { |
| 2934 | const result = try operate(t, submission.operation); |
| 2935 | |
| 2936 | switch (prev_index) { |
| 2937 | .none => b.submitted.head = next_index, |
| 2938 | else => b.storage[prev_index.toIndex()].submission.node.next = next_index, |
| 2939 | } |
| 2940 | if (next_index == .none) b.submitted.tail = prev_index; |
| 2941 | |
| 2942 | switch (b.completed.tail) { |
| 2943 | .none => b.completed.head = index, |
| 2944 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| 2945 | } |
| 2946 | b.completed.tail = index; |
| 2947 | b.storage[index.toIndex()] = .{ .completion = .{ |
| 2948 | .node = .{ .next = .none }, |
| 2949 | .result = result, |
| 2950 | } }; |
| 2951 | } else prev_index = index; |
| 2952 | index = next_index; |
| 2953 | } |
| 2954 | assert(index == .none); |
| 2955 | return; |
| 2956 | }, |
| 2957 | .INTR => continue, |
| 2958 | else => return error.ConcurrencyUnavailable, |
| 2959 | } |
| 2960 | } |
| 2961 | } |
| 2962 | |
| 2963 | const WindowsBatchOperationUserdata = extern struct { |
| 2964 | file: windows.HANDLE, |
| 2965 | iosb: windows.IO_STATUS_BLOCK, |
| 2966 | |
| 2967 | const Erased = Io.Operation.Storage.Pending.Userdata; |
| 2968 | |
| 2969 | comptime { |
| 2970 | assert(@sizeOf(WindowsBatchOperationUserdata) <= @sizeOf(Erased)); |
| 2971 | } |
| 2972 | |
| 2973 | fn toErased(userdata: *WindowsBatchOperationUserdata) *Erased { |
| 2974 | return @ptrCast(userdata); |
| 2975 | } |
| 2976 | |
| 2977 | fn fromErased(erased: *Erased) *WindowsBatchOperationUserdata { |
| 2978 | return @ptrCast(erased); |
| 2979 | } |
| 2980 | }; |
| 2981 | |
| 2982 | fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { |
| 2983 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2984 | if (is_windows) { |
| 2985 | if (b.pending.head == .none) return; |
| 2986 | waitForApcOrAlert(); |
| 2987 | var index = b.pending.head; |
| 2988 | while (index != .none) { |
| 2989 | const pending = &b.storage[index.toIndex()].pending; |
| 2990 | const operation_userdata: *WindowsBatchOperationUserdata = .fromErased(&pending.userdata); |
| 2991 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; |
| 2992 | _ = windows.ntdll.NtCancelIoFileEx(operation_userdata.file, &operation_userdata.iosb, &cancel_iosb); |
| 2993 | index = pending.node.next; |
| 2994 | } |
| 2995 | while (b.pending.head != .none) waitForApcOrAlert(); |
| 2996 | } else if (b.userdata) |batch_userdata| { |
| 2997 | const poll_storage: [*]posix.pollfd = @ptrCast(@alignCast(batch_userdata)); |
| 2998 | t.allocator.free(poll_storage[0..b.storage.len]); |
| 2999 | b.userdata = null; |
| 3000 | } |
| 3001 | } |
| 3002 | |
| 3003 | fn batchCompleteBlockingWindows( |
| 3004 | b: *Io.Batch, |
| 3005 | operation_userdata: *WindowsBatchOperationUserdata, |
| 3006 | result: Io.Operation.Result, |
| 3007 | ) void { |
| 3008 | const erased_userdata = operation_userdata.toErased(); |
| 3009 | const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("userdata", erased_userdata); |
| 3010 | switch (pending.node.prev) { |
| 3011 | .none => b.pending.head = pending.node.next, |
| 3012 | else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next, |
| 3013 | } |
| 3014 | switch (pending.node.next) { |
| 3015 | .none => b.pending.tail = pending.node.prev, |
| 3016 | else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev, |
| 3017 | } |
| 3018 | const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending); |
| 3019 | const index: Io.Operation.OptionalIndex = .fromIndex(storage - b.storage.ptr); |
| 3020 | switch (b.completed.tail) { |
| 3021 | .none => b.completed.head = index, |
| 3022 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| 3023 | } |
| 3024 | b.completed.tail = index; |
| 3025 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 3026 | } |
| 3027 | |
| 3028 | fn batchApc( |
| 3029 | apc_context: ?*anyopaque, |
| 3030 | iosb: *windows.IO_STATUS_BLOCK, |
| 3031 | _: windows.ULONG, |
| 3032 | ) align(apc_align) callconv(.winapi) void { |
| 3033 | const b: *Io.Batch = @ptrCast(@alignCast(apc_context)); |
| 3034 | const operation_userdata: *WindowsBatchOperationUserdata = @fieldParentPtr("iosb", iosb); |
| 3035 | const erased_userdata = operation_userdata.toErased(); |
| 3036 | const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("userdata", erased_userdata); |
| 3037 | switch (pending.node.prev) { |
| 3038 | .none => b.pending.head = pending.node.next, |
| 3039 | else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next, |
| 3040 | } |
| 3041 | switch (pending.node.next) { |
| 3042 | .none => b.pending.tail = pending.node.prev, |
| 3043 | else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev, |
| 3044 | } |
| 3045 | const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending); |
| 3046 | const index: Io.Operation.OptionalIndex = .fromIndex(storage - b.storage.ptr); |
| 3047 | switch (iosb.u.Status) { |
| 3048 | .CANCELLED => { |
| 3049 | const tail_index = b.unused.tail; |
| 3050 | switch (tail_index) { |
| 3051 | .none => b.unused.head = index, |
| 3052 | else => b.storage[tail_index.toIndex()].unused.next = index, |
| 3053 | } |
| 3054 | storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } }; |
| 3055 | b.unused.tail = index; |
| 3056 | }, |
| 3057 | else => { |
| 3058 | switch (b.completed.tail) { |
| 3059 | .none => b.completed.head = index, |
| 3060 | else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, |
| 3061 | } |
| 3062 | b.completed.tail = index; |
| 3063 | const result: Io.Operation.Result = switch (pending.tag) { |
| 3064 | .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, |
| 3065 | .file_write_streaming => .{ .file_write_streaming = ntWriteFileResult(iosb) }, |
| 3066 | .device_io_control => .{ .device_io_control = iosb.* }, |
| 3067 | .net_receive => unreachable, |
| 3068 | .net_send => unreachable, |
| 3069 | .net_read => unreachable, |
| 3070 | .net_write => unreachable, |
| 3071 | }; |
| 3072 | storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; |
| 3073 | }, |
| 3074 | } |
| 3075 | } |
| 3076 | |
| 3077 | /// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable. |
| 3078 | fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io.ConcurrentError || Io.Cancelable)!void { |
| 3079 | var index = b.submitted.head; |
| 3080 | errdefer b.submitted.head = index; |
| 3081 | while (index != .none) { |
| 3082 | const storage = &b.storage[index.toIndex()]; |
| 3083 | const submission = storage.submission; |
| 3084 | storage.* = .{ .pending = .{ |
| 3085 | .node = .{ .prev = b.pending.tail, .next = .none }, |
| 3086 | .tag = submission.operation, |
| 3087 | .userdata = undefined, |
| 3088 | } }; |
| 3089 | switch (b.pending.tail) { |
| 3090 | .none => b.pending.head = index, |
| 3091 | else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index, |
| 3092 | } |
| 3093 | b.pending.tail = index; |
| 3094 | const operation_userdata: *WindowsBatchOperationUserdata = .fromErased(&storage.pending.userdata); |
| 3095 | errdefer { |
| 3096 | operation_userdata.iosb = .{ .u = .{ .Status = .CANCELLED }, .Information = undefined }; |
| 3097 | batchApc(b, &operation_userdata.iosb, 0); |
| 3098 | } |
| 3099 | switch (submission.operation) { |
| 3100 | .file_read_streaming => |o| o: { |
| 3101 | var data_index: usize = 0; |
| 3102 | while (o.data.len - data_index != 0 and o.data[data_index].len == 0) data_index += 1; |
| 3103 | if (o.data.len - data_index == 0) { |
| 3104 | operation_userdata.iosb = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; |
| 3105 | batchApc(b, &operation_userdata.iosb, 0); |
| 3106 | break :o; |
| 3107 | } |
| 3108 | const buffer = o.data[data_index]; |
| 3109 | const short_buffer_len = std.math.lossyCast(u32, buffer.len); |
| 3110 | |
| 3111 | if (o.file.flags.nonblocking) { |
| 3112 | operation_userdata.file = o.file.handle; |
| 3113 | switch (windows.ntdll.NtReadFile( |
| 3114 | o.file.handle, |
| 3115 | null, // event |
| 3116 | &batchApc, |
| 3117 | b, |
| 3118 | &operation_userdata.iosb, |
| 3119 | buffer.ptr, |
| 3120 | short_buffer_len, |
| 3121 | null, // byte offset |
| 3122 | null, // key |
| 3123 | )) { |
| 3124 | .PENDING, .SUCCESS => {}, |
| 3125 | .CANCELLED => unreachable, |
| 3126 | else => |status| { |
| 3127 | operation_userdata.iosb.u.Status = status; |
| 3128 | batchApc(b, &operation_userdata.iosb, 0); |
| 3129 | }, |
| 3130 | } |
| 3131 | } else { |
| 3132 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3133 | |
| 3134 | const syscall: Syscall = try .start(); |
| 3135 | while (true) switch (windows.ntdll.NtReadFile( |
| 3136 | o.file.handle, |
| 3137 | null, // event |
| 3138 | null, // APC routine |
| 3139 | null, // APC context |
| 3140 | &operation_userdata.iosb, |
| 3141 | buffer.ptr, |
| 3142 | short_buffer_len, |
| 3143 | null, // byte offset |
| 3144 | null, // key |
| 3145 | )) { |
| 3146 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 3147 | .CANCELLED => { |
| 3148 | try syscall.checkCancel(); |
| 3149 | continue; |
| 3150 | }, |
| 3151 | else => |status| { |
| 3152 | syscall.finish(); |
| 3153 | operation_userdata.iosb.u.Status = status; |
| 3154 | batchApc(b, &operation_userdata.iosb, 0); |
| 3155 | break; |
| 3156 | }, |
| 3157 | }; |
| 3158 | } |
| 3159 | }, |
| 3160 | .file_write_streaming => |o| o: { |
| 3161 | const buffer = windowsWriteBuffer(o.header, o.data, o.splat); |
| 3162 | if (buffer.len == 0) { |
| 3163 | operation_userdata.iosb = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; |
| 3164 | batchApc(b, &operation_userdata.iosb, 0); |
| 3165 | break :o; |
| 3166 | } |
| 3167 | if (o.file.flags.nonblocking) { |
| 3168 | operation_userdata.file = o.file.handle; |
| 3169 | switch (windows.ntdll.NtWriteFile( |
| 3170 | o.file.handle, |
| 3171 | null, // event |
| 3172 | &batchApc, |
| 3173 | b, |
| 3174 | &operation_userdata.iosb, |
| 3175 | buffer.ptr, |
| 3176 | @intCast(buffer.len), |
| 3177 | null, // byte offset |
| 3178 | null, // key |
| 3179 | )) { |
| 3180 | .PENDING, .SUCCESS => {}, |
| 3181 | .CANCELLED => unreachable, |
| 3182 | else => |status| { |
| 3183 | operation_userdata.iosb.u.Status = status; |
| 3184 | batchApc(b, &operation_userdata.iosb, 0); |
| 3185 | }, |
| 3186 | } |
| 3187 | } else { |
| 3188 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3189 | |
| 3190 | const syscall: Syscall = try .start(); |
| 3191 | while (true) switch (windows.ntdll.NtWriteFile( |
| 3192 | o.file.handle, |
| 3193 | null, // event |
| 3194 | null, // APC routine |
| 3195 | null, // APC context |
| 3196 | &operation_userdata.iosb, |
| 3197 | buffer.ptr, |
| 3198 | @intCast(buffer.len), |
| 3199 | null, // byte offset |
| 3200 | null, // key |
| 3201 | )) { |
| 3202 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 3203 | .CANCELLED => { |
| 3204 | try syscall.checkCancel(); |
| 3205 | continue; |
| 3206 | }, |
| 3207 | else => |status| { |
| 3208 | syscall.finish(); |
| 3209 | operation_userdata.iosb.u.Status = status; |
| 3210 | batchApc(b, &operation_userdata.iosb, 0); |
| 3211 | break; |
| 3212 | }, |
| 3213 | }; |
| 3214 | } |
| 3215 | }, |
| 3216 | .device_io_control => |o| { |
| 3217 | const NtControlFile = switch (o.code.DeviceType) { |
| 3218 | .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, |
| 3219 | else => &windows.ntdll.NtDeviceIoControlFile, |
| 3220 | }; |
| 3221 | if (o.file.flags.nonblocking) { |
| 3222 | operation_userdata.file = o.file.handle; |
| 3223 | switch (NtControlFile( |
| 3224 | o.file.handle, |
| 3225 | null, // event |
| 3226 | &batchApc, |
| 3227 | b, |
| 3228 | &operation_userdata.iosb, |
| 3229 | o.code, |
| 3230 | if (o.in.len > 0) o.in.ptr else null, |
| 3231 | @intCast(o.in.len), |
| 3232 | if (o.out.len > 0) o.out.ptr else null, |
| 3233 | @intCast(o.out.len), |
| 3234 | )) { |
| 3235 | .PENDING, .SUCCESS => {}, |
| 3236 | .CANCELLED => unreachable, |
| 3237 | else => |status| { |
| 3238 | operation_userdata.iosb.u.Status = status; |
| 3239 | batchApc(b, &operation_userdata.iosb, 0); |
| 3240 | }, |
| 3241 | } |
| 3242 | } else { |
| 3243 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3244 | |
| 3245 | const syscall: Syscall = try .start(); |
| 3246 | while (true) switch (NtControlFile( |
| 3247 | o.file.handle, |
| 3248 | null, // event |
| 3249 | null, // APC routine |
| 3250 | null, // APC context |
| 3251 | &operation_userdata.iosb, |
| 3252 | o.code, |
| 3253 | if (o.in.len > 0) o.in.ptr else null, |
| 3254 | @intCast(o.in.len), |
| 3255 | if (o.out.len > 0) o.out.ptr else null, |
| 3256 | @intCast(o.out.len), |
| 3257 | )) { |
| 3258 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 3259 | .CANCELLED => { |
| 3260 | try syscall.checkCancel(); |
| 3261 | continue; |
| 3262 | }, |
| 3263 | else => |status| { |
| 3264 | syscall.finish(); |
| 3265 | operation_userdata.iosb.u.Status = status; |
| 3266 | batchApc(b, &operation_userdata.iosb, 0); |
| 3267 | break; |
| 3268 | }, |
| 3269 | }; |
| 3270 | } |
| 3271 | }, |
| 3272 | .net_receive => |*o| { |
| 3273 | // TODO integrate with overlapped I/O or equivalent to avoid this error |
| 3274 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3275 | batchCompleteBlockingWindows(b, operation_userdata, .{ |
| 3276 | .net_receive = netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags), |
| 3277 | }); |
| 3278 | }, |
| 3279 | .net_send => |*o| { |
| 3280 | // TODO integrate with overlapped I/O or equivalent to avoid this error |
| 3281 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3282 | batchCompleteBlockingWindows(b, operation_userdata, .{ |
| 3283 | .net_send = netSendWindows(t, o.socket_handle, o.messages, o.flags), |
| 3284 | }); |
| 3285 | }, |
| 3286 | .net_read => |*o| { |
| 3287 | // TODO integrate with overlapped I/O or equivalent to avoid this error |
| 3288 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3289 | batchCompleteBlockingWindows(b, operation_userdata, .{ |
| 3290 | .net_read = netRead(o.socket_handle, o.data) catch |err| switch (err) { |
| 3291 | error.Canceled => |e| return e, |
| 3292 | else => |e| e, |
| 3293 | }, |
| 3294 | }); |
| 3295 | }, |
| 3296 | .net_write => |*o| { |
| 3297 | // TODO integrate with overlapped I/O or equivalent to avoid this error |
| 3298 | if (concurrency) return error.ConcurrencyUnavailable; |
| 3299 | batchCompleteBlockingWindows(b, operation_userdata, .{ |
| 3300 | .net_write = netWriteWindows(o.socket_handle, o.header, o.data, o.splat) catch |err| switch (err) { |
| 3301 | error.Canceled => |e| return e, |
| 3302 | else => |e| e, |
| 3303 | }, |
| 3304 | }); |
| 3305 | }, |
| 3306 | } |
| 3307 | index = submission.node.next; |
| 3308 | } |
| 3309 | b.submitted = .{ .head = .none, .tail = .none }; |
| 3310 | } |
| 3311 | |
| 3312 | /// Since Windows only supports writing one contiguous buffer, returns the |
| 3313 | /// first one, while also limiting it to a length representable by 32-bit |
| 3314 | /// unsigned integer. |
| 3315 | fn windowsWriteBuffer(header: []const u8, data: []const []const u8, splat: usize) []const u8 { |
| 3316 | const buffer = b: { |
| 3317 | if (header.len != 0) break :b header; |
| 3318 | for (data[0 .. data.len - 1]) |buffer| { |
| 3319 | if (buffer.len != 0) break :b buffer; |
| 3320 | } |
| 3321 | if (splat == 0) return &.{}; |
| 3322 | break :b data[data.len - 1]; |
| 3323 | }; |
| 3324 | return buffer[0..std.math.lossyCast(u32, buffer.len)]; |
| 3325 | } |
| 3326 | |
| 3327 | fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { |
| 3328 | const ct = complete_tail.*; |
| 3329 | const len: u31 = @intCast(ring.len); |
| 3330 | ring[ct.index(len)] = op; |
| 3331 | complete_tail.* = ct.next(len); |
| 3332 | } |
| 3333 | |
| 3334 | const dirCreateDir = switch (native_os) { |
| 3335 | .windows => dirCreateDirWindows, |
| 3336 | .wasi => dirCreateDirWasi, |
| 3337 | else => dirCreateDirPosix, |
| 3338 | }; |
| 3339 | |
| 3340 | fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { |
| 3341 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3342 | _ = t; |
| 3343 | |
| 3344 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 3345 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 3346 | |
| 3347 | const syscall: Syscall = try .start(); |
| 3348 | while (true) { |
| 3349 | switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) { |
| 3350 | .SUCCESS => { |
| 3351 | syscall.finish(); |
| 3352 | return; |
| 3353 | }, |
| 3354 | .INTR => { |
| 3355 | try syscall.checkCancel(); |
| 3356 | continue; |
| 3357 | }, |
| 3358 | .ACCES => return syscall.fail(error.AccessDenied), |
| 3359 | .PERM => return syscall.fail(error.PermissionDenied), |
| 3360 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 3361 | .EXIST => return syscall.fail(error.PathAlreadyExists), |
| 3362 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 3363 | .MLINK => return syscall.fail(error.LinkQuotaExceeded), |
| 3364 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 3365 | .NOENT => return syscall.fail(error.FileNotFound), |
| 3366 | .NOMEM => return syscall.fail(error.SystemResources), |
| 3367 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 3368 | .NOTDIR => return syscall.fail(error.NotDir), |
| 3369 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 3370 | // dragonfly: when dir_fd is unlinked from filesystem |
| 3371 | .NOTCONN => return syscall.fail(error.FileNotFound), |
| 3372 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 3373 | .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. |
| 3374 | .FAULT => |err| return syscall.errnoBug(err), |
| 3375 | else => |err| return syscall.unexpectedErrno(err), |
| 3376 | } |
| 3377 | } |
| 3378 | } |
| 3379 | |
| 3380 | fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { |
| 3381 | if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions); |
| 3382 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3383 | _ = t; |
| 3384 | const syscall: Syscall = try .start(); |
| 3385 | while (true) { |
| 3386 | switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) { |
| 3387 | .SUCCESS => { |
| 3388 | syscall.finish(); |
| 3389 | return; |
| 3390 | }, |
| 3391 | .INTR => { |
| 3392 | try syscall.checkCancel(); |
| 3393 | continue; |
| 3394 | }, |
| 3395 | else => |e| { |
| 3396 | syscall.finish(); |
| 3397 | switch (e) { |
| 3398 | .ACCES => return error.AccessDenied, |
| 3399 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 3400 | .PERM => return error.PermissionDenied, |
| 3401 | .DQUOT => return error.DiskQuota, |
| 3402 | .EXIST => return error.PathAlreadyExists, |
| 3403 | .FAULT => |err| return errnoBug(err), |
| 3404 | .LOOP => return error.SymLinkLoop, |
| 3405 | .MLINK => return error.LinkQuotaExceeded, |
| 3406 | .NAMETOOLONG => return error.NameTooLong, |
| 3407 | .NOENT => return error.FileNotFound, |
| 3408 | .NOMEM => return error.SystemResources, |
| 3409 | .NOSPC => return error.NoSpaceLeft, |
| 3410 | .NOTDIR => return error.NotDir, |
| 3411 | .ROFS => return error.ReadOnlyFileSystem, |
| 3412 | .NOTCAPABLE => return error.AccessDenied, |
| 3413 | .ILSEQ => return error.BadPathName, |
| 3414 | else => |err| return posix.unexpectedErrno(err), |
| 3415 | } |
| 3416 | }, |
| 3417 | } |
| 3418 | } |
| 3419 | } |
| 3420 | |
| 3421 | fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { |
| 3422 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3423 | _ = t; |
| 3424 | _ = permissions; // TODO use this value |
| 3425 | |
| 3426 | const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 3427 | const attr: windows.OBJECT.ATTRIBUTES = .{ |
| 3428 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, |
| 3429 | .Attributes = .{ .INHERIT = false }, |
| 3430 | .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w.span())), |
| 3431 | .SecurityDescriptor = null, |
| 3432 | .SecurityQualityOfService = null, |
| 3433 | }; |
| 3434 | |
| 3435 | var sub_dir_handle: windows.HANDLE = undefined; |
| 3436 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 3437 | var attempt: u5 = 0; |
| 3438 | var syscall: Syscall = try .start(); |
| 3439 | while (true) switch (windows.ntdll.NtCreateFile( |
| 3440 | &sub_dir_handle, |
| 3441 | .{ |
| 3442 | .GENERIC = .{ .READ = true }, |
| 3443 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 3444 | }, |
| 3445 | &attr, |
| 3446 | &io_status_block, |
| 3447 | null, |
| 3448 | .{ .NORMAL = true }, |
| 3449 | .VALID_FLAGS, |
| 3450 | .CREATE, |
| 3451 | .{ |
| 3452 | .DIRECTORY_FILE = true, |
| 3453 | .NON_DIRECTORY_FILE = false, |
| 3454 | .IO = .SYNCHRONOUS_NONALERT, |
| 3455 | .OPEN_REPARSE_POINT = false, |
| 3456 | }, |
| 3457 | null, |
| 3458 | 0, |
| 3459 | )) { |
| 3460 | .SUCCESS => { |
| 3461 | syscall.finish(); |
| 3462 | windows.CloseHandle(sub_dir_handle); |
| 3463 | return; |
| 3464 | }, |
| 3465 | .CANCELLED => { |
| 3466 | try syscall.checkCancel(); |
| 3467 | continue; |
| 3468 | }, |
| 3469 | .SHARING_VIOLATION => { |
| 3470 | // This occurs if the file attempting to be opened is a running |
| 3471 | // executable. However, there's a kernel bug: the error may be |
| 3472 | // incorrectly returned for an indeterminate amount of time |
| 3473 | // after an executable file is closed. Here we work around the |
| 3474 | // kernel bug with retry attempts. |
| 3475 | syscall.finish(); |
| 3476 | if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected; |
| 3477 | try parking_sleep.sleep(.{ .duration = .{ |
| 3478 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 3479 | .clock = .awake, |
| 3480 | } }); |
| 3481 | attempt += 1; |
| 3482 | syscall = try .start(); |
| 3483 | continue; |
| 3484 | }, |
| 3485 | .DELETE_PENDING => { |
| 3486 | // This error means that there *was* a file in this location on |
| 3487 | // the file system, but it was deleted. However, the OS is not |
| 3488 | // finished with the deletion operation, and so this CreateFile |
| 3489 | // call has failed. There is not really a sane way to handle |
| 3490 | // this other than retrying the creation after the OS finishes |
| 3491 | // the deletion. |
| 3492 | syscall.finish(); |
| 3493 | if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected; |
| 3494 | try parking_sleep.sleep(.{ .duration = .{ |
| 3495 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 3496 | .clock = .awake, |
| 3497 | } }); |
| 3498 | attempt += 1; |
| 3499 | syscall = try .start(); |
| 3500 | continue; |
| 3501 | }, |
| 3502 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 3503 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 3504 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 3505 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 3506 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| 3507 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 3508 | .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), |
| 3509 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 3510 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 3511 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 3512 | .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), |
| 3513 | .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), |
| 3514 | else => |status| return syscall.unexpectedNtstatus(status), |
| 3515 | }; |
| 3516 | } |
| 3517 | |
| 3518 | fn dirCreateDirPath( |
| 3519 | userdata: ?*anyopaque, |
| 3520 | dir: Dir, |
| 3521 | sub_path: []const u8, |
| 3522 | permissions: Dir.Permissions, |
| 3523 | ) Dir.CreateDirPathError!Dir.CreatePathStatus { |
| 3524 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3525 | |
| 3526 | var it = Dir.path.componentIterator(sub_path); |
| 3527 | var status: Dir.CreatePathStatus = .existed; |
| 3528 | var component = it.last() orelse return error.BadPathName; |
| 3529 | while (true) { |
| 3530 | if (dirCreateDir(t, dir, component.path, permissions)) |_| { |
| 3531 | status = .created; |
| 3532 | } else |err| switch (err) { |
| 3533 | error.PathAlreadyExists => { |
| 3534 | // It is important to return an error if it's not a directory |
| 3535 | // because otherwise a dangling symlink could cause an infinite |
| 3536 | // loop. |
| 3537 | const kind = try filePathKind(t, dir, component.path); |
| 3538 | if (kind != .directory) return error.NotDir; |
| 3539 | }, |
| 3540 | error.FileNotFound => |e| { |
| 3541 | component = it.previous() orelse return e; |
| 3542 | continue; |
| 3543 | }, |
| 3544 | else => |e| return e, |
| 3545 | } |
| 3546 | component = it.next() orelse return status; |
| 3547 | } |
| 3548 | } |
| 3549 | |
| 3550 | const dirCreateDirPathOpen = switch (native_os) { |
| 3551 | .windows => dirCreateDirPathOpenWindows, |
| 3552 | .wasi => dirCreateDirPathOpenWasi, |
| 3553 | else => dirCreateDirPathOpenPosix, |
| 3554 | }; |
| 3555 | |
| 3556 | fn dirCreateDirPathOpenPosix( |
| 3557 | userdata: ?*anyopaque, |
| 3558 | dir: Dir, |
| 3559 | sub_path: []const u8, |
| 3560 | permissions: Dir.Permissions, |
| 3561 | options: Dir.OpenOptions, |
| 3562 | ) Dir.CreateDirPathOpenError!Dir { |
| 3563 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3564 | const t_io = io(t); |
| 3565 | return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) { |
| 3566 | error.FileNotFound => { |
| 3567 | _ = try dir.createDirPathStatus(t_io, sub_path, permissions); |
| 3568 | return dirOpenDirPosix(t, dir, sub_path, options); |
| 3569 | }, |
| 3570 | else => |e| return e, |
| 3571 | }; |
| 3572 | } |
| 3573 | |
| 3574 | fn dirCreateDirPathOpenWindows( |
| 3575 | userdata: ?*anyopaque, |
| 3576 | dir: Dir, |
| 3577 | sub_path: []const u8, |
| 3578 | permissions: Dir.Permissions, |
| 3579 | options: Dir.OpenOptions, |
| 3580 | ) Dir.CreateDirPathOpenError!Dir { |
| 3581 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3582 | const w = windows; |
| 3583 | |
| 3584 | _ = permissions; // TODO apply these permissions |
| 3585 | |
| 3586 | var it = Dir.path.componentIterator(sub_path); |
| 3587 | // If there are no components in the path, then create a dummy component with the full path. |
| 3588 | var component: Dir.path.NativeComponentIterator.Component = it.last() orelse .{ |
| 3589 | .name = "", |
| 3590 | .path = sub_path, |
| 3591 | }; |
| 3592 | |
| 3593 | components: while (true) { |
| 3594 | const sub_path_w = try sliceToPrefixedFileW(dir.handle, component.path, .{}); |
| 3595 | const attr: windows.OBJECT.ATTRIBUTES = .{ |
| 3596 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, |
| 3597 | .ObjectName = @constCast(&sub_path_w.string()), |
| 3598 | }; |
| 3599 | const is_last = it.peekNext() == null; |
| 3600 | var result: Dir = .{ .handle = undefined }; |
| 3601 | var iosb: w.IO_STATUS_BLOCK = undefined; |
| 3602 | const syscall: Syscall = try .start(); |
| 3603 | while (true) switch (w.ntdll.NtCreateFile( |
| 3604 | &result.handle, |
| 3605 | .{ |
| 3606 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ |
| 3607 | .LIST = options.iterate, |
| 3608 | .READ_EA = true, |
| 3609 | .READ_ATTRIBUTES = true, |
| 3610 | .TRAVERSE = true, |
| 3611 | } }, |
| 3612 | .STANDARD = .{ |
| 3613 | .RIGHTS = .READ, |
| 3614 | .SYNCHRONIZE = true, |
| 3615 | }, |
| 3616 | }, |
| 3617 | &attr, |
| 3618 | &iosb, |
| 3619 | null, |
| 3620 | .{ .NORMAL = true }, |
| 3621 | .VALID_FLAGS, |
| 3622 | if (is_last) .OPEN_IF else .CREATE, |
| 3623 | .{ |
| 3624 | .DIRECTORY_FILE = true, |
| 3625 | .IO = .SYNCHRONOUS_NONALERT, |
| 3626 | .OPEN_FOR_BACKUP_INTENT = true, |
| 3627 | .OPEN_REPARSE_POINT = !options.follow_symlinks, |
| 3628 | }, |
| 3629 | null, |
| 3630 | 0, |
| 3631 | )) { |
| 3632 | .SUCCESS => { |
| 3633 | syscall.finish(); |
| 3634 | component = it.next() orelse return result; |
| 3635 | w.CloseHandle(result.handle); |
| 3636 | continue :components; |
| 3637 | }, |
| 3638 | .CANCELLED => { |
| 3639 | try syscall.checkCancel(); |
| 3640 | continue; |
| 3641 | }, |
| 3642 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 3643 | .OBJECT_NAME_COLLISION => { |
| 3644 | syscall.finish(); |
| 3645 | assert(!is_last); |
| 3646 | // stat the file and return an error if it's not a directory |
| 3647 | // this is important because otherwise a dangling symlink |
| 3648 | // could cause an infinite loop |
| 3649 | const fstat = try dirStatFileWindows(t, dir, component.path, .{ |
| 3650 | .follow_symlinks = options.follow_symlinks, |
| 3651 | }); |
| 3652 | if (fstat.kind != .directory) return error.NotDir; |
| 3653 | |
| 3654 | component = it.next().?; |
| 3655 | continue :components; |
| 3656 | }, |
| 3657 | |
| 3658 | .OBJECT_NAME_NOT_FOUND, |
| 3659 | .OBJECT_PATH_NOT_FOUND, |
| 3660 | => { |
| 3661 | syscall.finish(); |
| 3662 | component = it.previous() orelse return error.FileNotFound; |
| 3663 | continue :components; |
| 3664 | }, |
| 3665 | |
| 3666 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 3667 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' |
| 3668 | // and the directory is trying to be opened for iteration. |
| 3669 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 3670 | .DISK_FULL => return syscall.fail(error.NoSpaceLeft), |
| 3671 | .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s), |
| 3672 | else => |s| return syscall.unexpectedNtstatus(s), |
| 3673 | }; |
| 3674 | } |
| 3675 | } |
| 3676 | |
| 3677 | fn dirCreateDirPathOpenWasi( |
| 3678 | userdata: ?*anyopaque, |
| 3679 | dir: Dir, |
| 3680 | sub_path: []const u8, |
| 3681 | permissions: Dir.Permissions, |
| 3682 | options: Dir.OpenOptions, |
| 3683 | ) Dir.CreateDirPathOpenError!Dir { |
| 3684 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3685 | const t_io = io(t); |
| 3686 | return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) { |
| 3687 | error.FileNotFound => { |
| 3688 | _ = try dir.createDirPathStatus(t_io, sub_path, permissions); |
| 3689 | return dirOpenDirWasi(t, dir, sub_path, options); |
| 3690 | }, |
| 3691 | else => |e| return e, |
| 3692 | }; |
| 3693 | } |
| 3694 | |
| 3695 | fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { |
| 3696 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3697 | return fileStat(t, .{ |
| 3698 | .handle = dir.handle, |
| 3699 | .flags = .{ .nonblocking = false }, |
| 3700 | }); |
| 3701 | } |
| 3702 | |
| 3703 | const dirStatFile = switch (native_os) { |
| 3704 | .linux => dirStatFileLinux, |
| 3705 | .windows => dirStatFileWindows, |
| 3706 | .wasi => dirStatFileWasi, |
| 3707 | else => dirStatFilePosix, |
| 3708 | }; |
| 3709 | |
| 3710 | fn dirStatFileLinux( |
| 3711 | userdata: ?*anyopaque, |
| 3712 | dir: Dir, |
| 3713 | sub_path: []const u8, |
| 3714 | options: Dir.StatFileOptions, |
| 3715 | ) Dir.StatFileError!File.Stat { |
| 3716 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3717 | _ = t; |
| 3718 | const linux = std.os.linux; |
| 3719 | const sys = if (statx_use_c) std.c else std.os.linux; |
| 3720 | |
| 3721 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 3722 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 3723 | |
| 3724 | const flags: u32 = linux.AT.NO_AUTOMOUNT | |
| 3725 | @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0); |
| 3726 | |
| 3727 | const syscall: Syscall = try .start(); |
| 3728 | while (true) { |
| 3729 | var statx = std.mem.zeroes(linux.Statx); |
| 3730 | switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_request, &statx))) { |
| 3731 | .SUCCESS => { |
| 3732 | syscall.finish(); |
| 3733 | return statFromLinux(&statx); |
| 3734 | }, |
| 3735 | .INTR => { |
| 3736 | try syscall.checkCancel(); |
| 3737 | continue; |
| 3738 | }, |
| 3739 | else => |e| { |
| 3740 | syscall.finish(); |
| 3741 | switch (e) { |
| 3742 | .ACCES => return error.AccessDenied, |
| 3743 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 3744 | .FAULT => |err| return errnoBug(err), |
| 3745 | .INVAL => |err| return errnoBug(err), |
| 3746 | .LOOP => return error.SymLinkLoop, |
| 3747 | .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above. |
| 3748 | .NOENT => return error.FileNotFound, |
| 3749 | .NOTDIR => return error.NotDir, |
| 3750 | .NOMEM => return error.SystemResources, |
| 3751 | else => |err| return posix.unexpectedErrno(err), |
| 3752 | } |
| 3753 | }, |
| 3754 | } |
| 3755 | } |
| 3756 | } |
| 3757 | |
| 3758 | fn dirStatFilePosix( |
| 3759 | userdata: ?*anyopaque, |
| 3760 | dir: Dir, |
| 3761 | sub_path: []const u8, |
| 3762 | options: Dir.StatFileOptions, |
| 3763 | ) Dir.StatFileError!File.Stat { |
| 3764 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3765 | _ = t; |
| 3766 | |
| 3767 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 3768 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 3769 | |
| 3770 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| 3771 | |
| 3772 | return posixStatFile(dir.handle, sub_path_posix, flags); |
| 3773 | } |
| 3774 | |
| 3775 | fn posixStatFile(dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat { |
| 3776 | const syscall: Syscall = try .start(); |
| 3777 | while (true) { |
| 3778 | var stat = std.mem.zeroes(posix.Stat); |
| 3779 | switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) { |
| 3780 | .SUCCESS => { |
| 3781 | syscall.finish(); |
| 3782 | return statFromPosix(&stat); |
| 3783 | }, |
| 3784 | .INTR => { |
| 3785 | try syscall.checkCancel(); |
| 3786 | continue; |
| 3787 | }, |
| 3788 | else => |e| { |
| 3789 | syscall.finish(); |
| 3790 | switch (e) { |
| 3791 | .INVAL => |err| return errnoBug(err), |
| 3792 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 3793 | .NOMEM => return error.SystemResources, |
| 3794 | .ACCES => return error.AccessDenied, |
| 3795 | .PERM => return error.PermissionDenied, |
| 3796 | .FAULT => |err| return errnoBug(err), |
| 3797 | .NAMETOOLONG => return error.NameTooLong, |
| 3798 | .LOOP => return error.SymLinkLoop, |
| 3799 | .NOENT => return error.FileNotFound, |
| 3800 | .NOTDIR => return error.FileNotFound, |
| 3801 | .ILSEQ => return error.BadPathName, |
| 3802 | else => |err| return posix.unexpectedErrno(err), |
| 3803 | } |
| 3804 | }, |
| 3805 | } |
| 3806 | } |
| 3807 | } |
| 3808 | |
| 3809 | fn dirStatFileWindows( |
| 3810 | userdata: ?*anyopaque, |
| 3811 | dir: Dir, |
| 3812 | sub_path: []const u8, |
| 3813 | options: Dir.StatFileOptions, |
| 3814 | ) Dir.StatFileError!File.Stat { |
| 3815 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3816 | const file = try dirOpenFileWindows(t, dir, sub_path, .{ |
| 3817 | .follow_symlinks = options.follow_symlinks, |
| 3818 | }); |
| 3819 | defer windows.CloseHandle(file.handle); |
| 3820 | return fileStatWindows(t, file); |
| 3821 | } |
| 3822 | |
| 3823 | fn dirStatFileWasi( |
| 3824 | userdata: ?*anyopaque, |
| 3825 | dir: Dir, |
| 3826 | sub_path: []const u8, |
| 3827 | options: Dir.StatFileOptions, |
| 3828 | ) Dir.StatFileError!File.Stat { |
| 3829 | if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options); |
| 3830 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3831 | _ = t; |
| 3832 | const wasi = std.os.wasi; |
| 3833 | const flags: wasi.lookupflags_t = .{ |
| 3834 | .SYMLINK_FOLLOW = options.follow_symlinks, |
| 3835 | }; |
| 3836 | var stat: wasi.filestat_t = undefined; |
| 3837 | const syscall: Syscall = try .start(); |
| 3838 | while (true) { |
| 3839 | switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { |
| 3840 | .SUCCESS => { |
| 3841 | syscall.finish(); |
| 3842 | return statFromWasi(&stat); |
| 3843 | }, |
| 3844 | .INTR => { |
| 3845 | try syscall.checkCancel(); |
| 3846 | continue; |
| 3847 | }, |
| 3848 | else => |e| { |
| 3849 | syscall.finish(); |
| 3850 | switch (e) { |
| 3851 | .INVAL => |err| return errnoBug(err), |
| 3852 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 3853 | .NOMEM => return error.SystemResources, |
| 3854 | .ACCES => return error.AccessDenied, |
| 3855 | .FAULT => |err| return errnoBug(err), |
| 3856 | .NAMETOOLONG => return error.NameTooLong, |
| 3857 | .NOENT => return error.FileNotFound, |
| 3858 | .NOTDIR => return error.FileNotFound, |
| 3859 | .NOTCAPABLE => return error.AccessDenied, |
| 3860 | .ILSEQ => return error.BadPathName, |
| 3861 | else => |err| return posix.unexpectedErrno(err), |
| 3862 | } |
| 3863 | }, |
| 3864 | } |
| 3865 | } |
| 3866 | } |
| 3867 | |
| 3868 | fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind { |
| 3869 | if (native_os == .linux) { |
| 3870 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 3871 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 3872 | |
| 3873 | const linux = std.os.linux; |
| 3874 | const syscall: Syscall = try .start(); |
| 3875 | while (true) { |
| 3876 | var statx = std.mem.zeroes(linux.Statx); |
| 3877 | switch (linux.errno(linux.statx( |
| 3878 | dir.handle, |
| 3879 | sub_path_posix, |
| 3880 | linux.AT.NO_AUTOMOUNT | linux.AT.SYMLINK_NOFOLLOW, |
| 3881 | .{ .TYPE = true }, |
| 3882 | &statx, |
| 3883 | ))) { |
| 3884 | .SUCCESS => { |
| 3885 | syscall.finish(); |
| 3886 | if (!statx.mask.TYPE) return error.Unexpected; |
| 3887 | return statxKind(statx.mode); |
| 3888 | }, |
| 3889 | .INTR => { |
| 3890 | try syscall.checkCancel(); |
| 3891 | continue; |
| 3892 | }, |
| 3893 | .NOMEM => return syscall.fail(error.SystemResources), |
| 3894 | else => |err| return syscall.unexpectedErrno(err), |
| 3895 | } |
| 3896 | } |
| 3897 | } |
| 3898 | |
| 3899 | const stat = try dirStatFile(t, dir, sub_path, .{ .follow_symlinks = false }); |
| 3900 | return stat.kind; |
| 3901 | } |
| 3902 | |
| 3903 | fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { |
| 3904 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3905 | |
| 3906 | if (native_os == .linux) { |
| 3907 | const linux = std.os.linux; |
| 3908 | |
| 3909 | const syscall: Syscall = try .start(); |
| 3910 | while (true) { |
| 3911 | var statx = std.mem.zeroes(linux.Statx); |
| 3912 | switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) { |
| 3913 | .SUCCESS => { |
| 3914 | syscall.finish(); |
| 3915 | if (!statx.mask.SIZE) return error.Unexpected; |
| 3916 | return statx.size; |
| 3917 | }, |
| 3918 | .INTR => { |
| 3919 | try syscall.checkCancel(); |
| 3920 | continue; |
| 3921 | }, |
| 3922 | else => |e| { |
| 3923 | syscall.finish(); |
| 3924 | switch (e) { |
| 3925 | .ACCES => |err| return errnoBug(err), |
| 3926 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 3927 | .FAULT => |err| return errnoBug(err), |
| 3928 | .INVAL => |err| return errnoBug(err), |
| 3929 | .LOOP => |err| return errnoBug(err), |
| 3930 | .NAMETOOLONG => |err| return errnoBug(err), |
| 3931 | .NOENT => |err| return errnoBug(err), |
| 3932 | .NOMEM => return error.SystemResources, |
| 3933 | .NOTDIR => |err| return errnoBug(err), |
| 3934 | else => |err| return posix.unexpectedErrno(err), |
| 3935 | } |
| 3936 | }, |
| 3937 | } |
| 3938 | } |
| 3939 | } else if (is_windows) { |
| 3940 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 3941 | var info: windows.FILE.STANDARD_INFORMATION = undefined; |
| 3942 | const syscall: Syscall = try .start(); |
| 3943 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 3944 | file.handle, |
| 3945 | &io_status_block, |
| 3946 | &info, |
| 3947 | @sizeOf(windows.FILE.STANDARD_INFORMATION), |
| 3948 | .Standard, |
| 3949 | )) { |
| 3950 | .SUCCESS => break syscall.finish(), |
| 3951 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 3952 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 3953 | .CANCELLED => { |
| 3954 | try syscall.checkCancel(); |
| 3955 | continue; |
| 3956 | }, |
| 3957 | else => |s| return syscall.unexpectedNtstatus(s), |
| 3958 | }; |
| 3959 | return @as(u64, @bitCast(info.EndOfFile)); |
| 3960 | } |
| 3961 | |
| 3962 | const stat = try fileStat(t, file); |
| 3963 | return stat.size; |
| 3964 | } |
| 3965 | |
| 3966 | const fileStat = switch (native_os) { |
| 3967 | .linux => fileStatLinux, |
| 3968 | .windows => fileStatWindows, |
| 3969 | .wasi => fileStatWasi, |
| 3970 | else => fileStatPosix, |
| 3971 | }; |
| 3972 | |
| 3973 | fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 3974 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3975 | _ = t; |
| 3976 | |
| 3977 | if (posix.Stat == void) return error.Streaming; |
| 3978 | |
| 3979 | const syscall: Syscall = try .start(); |
| 3980 | while (true) { |
| 3981 | var stat = std.mem.zeroes(posix.Stat); |
| 3982 | switch (posix.errno(fstat_sym(file.handle, &stat))) { |
| 3983 | .SUCCESS => { |
| 3984 | syscall.finish(); |
| 3985 | return statFromPosix(&stat); |
| 3986 | }, |
| 3987 | .INTR => { |
| 3988 | try syscall.checkCancel(); |
| 3989 | continue; |
| 3990 | }, |
| 3991 | else => |e| { |
| 3992 | syscall.finish(); |
| 3993 | switch (e) { |
| 3994 | .INVAL => |err| return errnoBug(err), |
| 3995 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 3996 | .NOMEM => return error.SystemResources, |
| 3997 | .ACCES => return error.AccessDenied, |
| 3998 | else => |err| return posix.unexpectedErrno(err), |
| 3999 | } |
| 4000 | }, |
| 4001 | } |
| 4002 | } |
| 4003 | } |
| 4004 | |
| 4005 | fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 4006 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4007 | _ = t; |
| 4008 | const linux = std.os.linux; |
| 4009 | const sys = if (statx_use_c) std.c else std.os.linux; |
| 4010 | |
| 4011 | const syscall: Syscall = try .start(); |
| 4012 | while (true) { |
| 4013 | var statx = std.mem.zeroes(linux.Statx); |
| 4014 | switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_request, &statx))) { |
| 4015 | .SUCCESS => { |
| 4016 | syscall.finish(); |
| 4017 | return statFromLinux(&statx); |
| 4018 | }, |
| 4019 | .INTR => { |
| 4020 | try syscall.checkCancel(); |
| 4021 | continue; |
| 4022 | }, |
| 4023 | else => |e| { |
| 4024 | syscall.finish(); |
| 4025 | switch (e) { |
| 4026 | .ACCES => |err| return errnoBug(err), |
| 4027 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 4028 | .FAULT => |err| return errnoBug(err), |
| 4029 | .INVAL => |err| return errnoBug(err), |
| 4030 | .LOOP => |err| return errnoBug(err), |
| 4031 | .NAMETOOLONG => |err| return errnoBug(err), |
| 4032 | .NOENT => |err| return errnoBug(err), |
| 4033 | .NOMEM => return error.SystemResources, |
| 4034 | .NOTDIR => |err| return errnoBug(err), |
| 4035 | else => |err| return posix.unexpectedErrno(err), |
| 4036 | } |
| 4037 | }, |
| 4038 | } |
| 4039 | } |
| 4040 | } |
| 4041 | |
| 4042 | fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 4043 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4044 | |
| 4045 | const block_size: u32 = if (t.systemBasicInformation()) |sbi| |
| 4046 | @intCast(@max(sbi.PageSize, sbi.AllocationGranularity)) |
| 4047 | else |
| 4048 | std.heap.page_size_max; |
| 4049 | |
| 4050 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 4051 | var info: windows.FILE.ALL_INFORMATION = undefined; |
| 4052 | { |
| 4053 | const syscall: Syscall = try .start(); |
| 4054 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 4055 | file.handle, |
| 4056 | &io_status_block, |
| 4057 | &info, |
| 4058 | @sizeOf(windows.FILE.ALL_INFORMATION), |
| 4059 | .All, |
| 4060 | )) { |
| 4061 | .SUCCESS => break syscall.finish(), |
| 4062 | // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer |
| 4063 | // size provided. This is treated as success because the type of variable-length information that this would be relevant for |
| 4064 | // (name, volume name, etc) we don't care about. |
| 4065 | .BUFFER_OVERFLOW => break syscall.finish(), |
| 4066 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 4067 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 4068 | .CANCELLED => { |
| 4069 | try syscall.checkCancel(); |
| 4070 | continue; |
| 4071 | }, |
| 4072 | else => |s| return syscall.unexpectedNtstatus(s), |
| 4073 | }; |
| 4074 | } |
| 4075 | return .{ |
| 4076 | .inode = info.InternalInformation.IndexNumber, |
| 4077 | .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)), |
| 4078 | .permissions = .default_file, |
| 4079 | .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: { |
| 4080 | var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined; |
| 4081 | const syscall: Syscall = try .start(); |
| 4082 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 4083 | file.handle, |
| 4084 | &io_status_block, |
| 4085 | &tag_info, |
| 4086 | @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), |
| 4087 | .AttributeTag, |
| 4088 | )) { |
| 4089 | .SUCCESS => break syscall.finish(), |
| 4090 | // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors |
| 4091 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e |
| 4092 | .INFO_LENGTH_MISMATCH => |err| return syscall.ntstatusBug(err), |
| 4093 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 4094 | .CANCELLED => { |
| 4095 | try syscall.checkCancel(); |
| 4096 | continue; |
| 4097 | }, |
| 4098 | else => |s| return syscall.unexpectedNtstatus(s), |
| 4099 | }; |
| 4100 | if (tag_info.ReparseTag.IsSurrogate) break :reparse_point .sym_link; |
| 4101 | // Unknown reparse point |
| 4102 | break :reparse_point .unknown; |
| 4103 | } else if (info.BasicInformation.FileAttributes.DIRECTORY) |
| 4104 | .directory |
| 4105 | else |
| 4106 | .file, |
| 4107 | .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), |
| 4108 | .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), |
| 4109 | .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), |
| 4110 | .nlink = info.StandardInformation.NumberOfLinks, |
| 4111 | .block_size = block_size, |
| 4112 | }; |
| 4113 | } |
| 4114 | |
| 4115 | fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION { |
| 4116 | if (!t.system_basic_information.initialized.load(.acquire)) { |
| 4117 | mutexLock(&t.mutex); |
| 4118 | defer mutexUnlock(&t.mutex); |
| 4119 | |
| 4120 | switch (windows.ntdll.NtQuerySystemInformation( |
| 4121 | .Basic, |
| 4122 | &t.system_basic_information.buffer, |
| 4123 | @sizeOf(windows.SYSTEM.BASIC_INFORMATION), |
| 4124 | null, |
| 4125 | )) { |
| 4126 | .SUCCESS => {}, |
| 4127 | else => return null, |
| 4128 | } |
| 4129 | |
| 4130 | t.system_basic_information.initialized.store(true, .release); |
| 4131 | } |
| 4132 | return &t.system_basic_information.buffer; |
| 4133 | } |
| 4134 | |
| 4135 | fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 4136 | if (builtin.link_libc) return fileStatPosix(userdata, file); |
| 4137 | |
| 4138 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4139 | _ = t; |
| 4140 | |
| 4141 | const syscall: Syscall = try .start(); |
| 4142 | while (true) { |
| 4143 | var stat: std.os.wasi.filestat_t = undefined; |
| 4144 | switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) { |
| 4145 | .SUCCESS => { |
| 4146 | syscall.finish(); |
| 4147 | return statFromWasi(&stat); |
| 4148 | }, |
| 4149 | .INTR => { |
| 4150 | try syscall.checkCancel(); |
| 4151 | continue; |
| 4152 | }, |
| 4153 | else => |e| { |
| 4154 | syscall.finish(); |
| 4155 | switch (e) { |
| 4156 | .INVAL => |err| return errnoBug(err), |
| 4157 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 4158 | .NOMEM => return error.SystemResources, |
| 4159 | .ACCES => return error.AccessDenied, |
| 4160 | .NOTCAPABLE => return error.AccessDenied, |
| 4161 | else => |err| return posix.unexpectedErrno(err), |
| 4162 | } |
| 4163 | }, |
| 4164 | } |
| 4165 | } |
| 4166 | } |
| 4167 | |
| 4168 | const dirAccess = switch (native_os) { |
| 4169 | .windows => dirAccessWindows, |
| 4170 | .wasi => dirAccessWasi, |
| 4171 | else => dirAccessPosix, |
| 4172 | }; |
| 4173 | |
| 4174 | fn dirAccessPosix( |
| 4175 | userdata: ?*anyopaque, |
| 4176 | dir: Dir, |
| 4177 | sub_path: []const u8, |
| 4178 | options: Dir.AccessOptions, |
| 4179 | ) Dir.AccessError!void { |
| 4180 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4181 | _ = t; |
| 4182 | |
| 4183 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 4184 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 4185 | |
| 4186 | const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0); |
| 4187 | |
| 4188 | const mode: u32 = |
| 4189 | @as(u32, if (options.read) posix.R_OK else 0) | |
| 4190 | @as(u32, if (options.write) posix.W_OK else 0) | |
| 4191 | @as(u32, if (options.execute) posix.X_OK else 0); |
| 4192 | |
| 4193 | const syscall: Syscall = try .start(); |
| 4194 | while (true) { |
| 4195 | switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) { |
| 4196 | .SUCCESS => { |
| 4197 | syscall.finish(); |
| 4198 | return; |
| 4199 | }, |
| 4200 | .INTR => { |
| 4201 | try syscall.checkCancel(); |
| 4202 | continue; |
| 4203 | }, |
| 4204 | else => |e| { |
| 4205 | syscall.finish(); |
| 4206 | switch (e) { |
| 4207 | .ACCES => return error.AccessDenied, |
| 4208 | .PERM => return error.PermissionDenied, |
| 4209 | .ROFS => return error.ReadOnlyFileSystem, |
| 4210 | .LOOP => return error.SymLinkLoop, |
| 4211 | .TXTBSY => return error.FileBusy, |
| 4212 | .NOTDIR => return error.FileNotFound, |
| 4213 | .NOENT => return error.FileNotFound, |
| 4214 | .NAMETOOLONG => return error.NameTooLong, |
| 4215 | .INVAL => |err| return errnoBug(err), |
| 4216 | .FAULT => |err| return errnoBug(err), |
| 4217 | .IO => return error.InputOutput, |
| 4218 | .NOMEM => return error.SystemResources, |
| 4219 | .ILSEQ => return error.BadPathName, |
| 4220 | else => |err| return posix.unexpectedErrno(err), |
| 4221 | } |
| 4222 | }, |
| 4223 | } |
| 4224 | } |
| 4225 | } |
| 4226 | |
| 4227 | fn dirAccessWasi( |
| 4228 | userdata: ?*anyopaque, |
| 4229 | dir: Dir, |
| 4230 | sub_path: []const u8, |
| 4231 | options: Dir.AccessOptions, |
| 4232 | ) Dir.AccessError!void { |
| 4233 | if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options); |
| 4234 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4235 | _ = t; |
| 4236 | const wasi = std.os.wasi; |
| 4237 | const flags: wasi.lookupflags_t = .{ |
| 4238 | .SYMLINK_FOLLOW = options.follow_symlinks, |
| 4239 | }; |
| 4240 | var stat: wasi.filestat_t = undefined; |
| 4241 | |
| 4242 | const syscall: Syscall = try .start(); |
| 4243 | while (true) { |
| 4244 | switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { |
| 4245 | .SUCCESS => { |
| 4246 | syscall.finish(); |
| 4247 | break; |
| 4248 | }, |
| 4249 | .INTR => { |
| 4250 | try syscall.checkCancel(); |
| 4251 | continue; |
| 4252 | }, |
| 4253 | else => |e| { |
| 4254 | syscall.finish(); |
| 4255 | switch (e) { |
| 4256 | .INVAL => |err| return errnoBug(err), |
| 4257 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 4258 | .NOMEM => return error.SystemResources, |
| 4259 | .ACCES => return error.AccessDenied, |
| 4260 | .FAULT => |err| return errnoBug(err), |
| 4261 | .NAMETOOLONG => return error.NameTooLong, |
| 4262 | .NOENT => return error.FileNotFound, |
| 4263 | .NOTDIR => return error.FileNotFound, |
| 4264 | .NOTCAPABLE => return error.AccessDenied, |
| 4265 | .ILSEQ => return error.BadPathName, |
| 4266 | else => |err| return posix.unexpectedErrno(err), |
| 4267 | } |
| 4268 | }, |
| 4269 | } |
| 4270 | } |
| 4271 | |
| 4272 | if (!options.read and !options.write and !options.execute) |
| 4273 | return; |
| 4274 | |
| 4275 | var directory: wasi.fdstat_t = undefined; |
| 4276 | if (wasi.fd_fdstat_get(dir.handle, &directory) != .SUCCESS) |
| 4277 | return error.AccessDenied; |
| 4278 | |
| 4279 | var rights: wasi.rights_t = .{}; |
| 4280 | if (options.read) { |
| 4281 | if (stat.filetype == .DIRECTORY) { |
| 4282 | rights.FD_READDIR = true; |
| 4283 | } else { |
| 4284 | rights.FD_READ = true; |
| 4285 | } |
| 4286 | } |
| 4287 | if (options.write) |
| 4288 | rights.FD_WRITE = true; |
| 4289 | |
| 4290 | // No validation for execution. |
| 4291 | |
| 4292 | // https://github.com/ziglang/zig/issues/18882 |
| 4293 | const rights_int: u64 = @bitCast(rights); |
| 4294 | const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting); |
| 4295 | if ((rights_int & inheriting_int) != rights_int) |
| 4296 | return error.AccessDenied; |
| 4297 | } |
| 4298 | |
| 4299 | fn dirAccessWindows( |
| 4300 | userdata: ?*anyopaque, |
| 4301 | dir: Dir, |
| 4302 | sub_path: []const u8, |
| 4303 | options: Dir.AccessOptions, |
| 4304 | ) Dir.AccessError!void { |
| 4305 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4306 | _ = t; |
| 4307 | |
| 4308 | _ = options; // TODO |
| 4309 | |
| 4310 | if (std.mem.eql(u8, sub_path, ".") or std.mem.eql(u8, sub_path, "..")) return; |
| 4311 | const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 4312 | const attr: windows.OBJECT.ATTRIBUTES = .{ |
| 4313 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, |
| 4314 | .ObjectName = @constCast(&sub_path_w.string()), |
| 4315 | }; |
| 4316 | var basic_info: windows.FILE.BASIC_INFORMATION = undefined; |
| 4317 | const syscall: Syscall = try .start(); |
| 4318 | while (true) switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) { |
| 4319 | .SUCCESS => return syscall.finish(), |
| 4320 | .CANCELLED => { |
| 4321 | try syscall.checkCancel(); |
| 4322 | continue; |
| 4323 | }, |
| 4324 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 4325 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 4326 | .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err), |
| 4327 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 4328 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 4329 | .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), |
| 4330 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| 4331 | }; |
| 4332 | } |
| 4333 | |
| 4334 | const dirCreateFile = switch (native_os) { |
| 4335 | .windows => dirCreateFileWindows, |
| 4336 | .wasi => dirCreateFileWasi, |
| 4337 | else => dirCreateFilePosix, |
| 4338 | }; |
| 4339 | |
| 4340 | fn dirCreateFilePosix( |
| 4341 | userdata: ?*anyopaque, |
| 4342 | dir: Dir, |
| 4343 | sub_path: []const u8, |
| 4344 | options: Dir.CreateFileOptions, |
| 4345 | ) File.OpenError!File { |
| 4346 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4347 | _ = t; |
| 4348 | |
| 4349 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 4350 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 4351 | |
| 4352 | var flags: posix.O = .{ |
| 4353 | .ACCMODE = if (options.read) .RDWR else .WRONLY, |
| 4354 | .CREAT = true, |
| 4355 | .TRUNC = options.truncate, |
| 4356 | .EXCL = options.exclusive, |
| 4357 | }; |
| 4358 | if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true; |
| 4359 | if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; |
| 4360 | if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath; |
| 4361 | |
| 4362 | // Use the O locking flags if the os supports them to acquire the lock |
| 4363 | // atomically. Note that the NONBLOCK flag is removed after the openat() |
| 4364 | // call is successful. |
| 4365 | if (have_flock_open_flags) switch (options.lock) { |
| 4366 | .none => {}, |
| 4367 | .shared => { |
| 4368 | flags.SHLOCK = true; |
| 4369 | flags.NONBLOCK = options.lock_nonblocking; |
| 4370 | }, |
| 4371 | .exclusive => { |
| 4372 | flags.EXLOCK = true; |
| 4373 | flags.NONBLOCK = options.lock_nonblocking; |
| 4374 | }, |
| 4375 | }; |
| 4376 | |
| 4377 | const fd: posix.fd_t = fd: { |
| 4378 | const syscall: Syscall = try .start(); |
| 4379 | while (true) { |
| 4380 | const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode()); |
| 4381 | switch (posix.errno(rc)) { |
| 4382 | .SUCCESS => { |
| 4383 | syscall.finish(); |
| 4384 | break :fd @intCast(rc); |
| 4385 | }, |
| 4386 | .INTR => { |
| 4387 | try syscall.checkCancel(); |
| 4388 | continue; |
| 4389 | }, |
| 4390 | else => |e| { |
| 4391 | syscall.finish(); |
| 4392 | switch (e) { |
| 4393 | .FAULT => |err| return errnoBug(err), |
| 4394 | .INVAL => return error.BadPathName, |
| 4395 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 4396 | .ACCES => return error.AccessDenied, |
| 4397 | .FBIG => return error.FileTooBig, |
| 4398 | .OVERFLOW => return error.FileTooBig, |
| 4399 | .ISDIR => return error.IsDir, |
| 4400 | .LOOP => return error.SymLinkLoop, |
| 4401 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 4402 | .NAMETOOLONG => return error.NameTooLong, |
| 4403 | .NFILE => return error.SystemFdQuotaExceeded, |
| 4404 | .NODEV => return error.NoDevice, |
| 4405 | .NOENT => return error.FileNotFound, |
| 4406 | .SRCH => return error.FileNotFound, // Linux when accessing procfs. |
| 4407 | .NOMEM => return error.SystemResources, |
| 4408 | .NOSPC => return error.NoSpaceLeft, |
| 4409 | .NOTDIR => return error.NotDir, |
| 4410 | .PERM => return error.PermissionDenied, |
| 4411 | .EXIST => return error.PathAlreadyExists, |
| 4412 | .BUSY => return error.DeviceBusy, |
| 4413 | .OPNOTSUPP => return error.FileLocksUnsupported, |
| 4414 | .AGAIN => return error.WouldBlock, |
| 4415 | .TXTBSY => return error.FileBusy, |
| 4416 | .NXIO => return error.NoDevice, |
| 4417 | .ROFS => return error.ReadOnlyFileSystem, |
| 4418 | .ILSEQ => return error.BadPathName, |
| 4419 | else => |err| return posix.unexpectedErrno(err), |
| 4420 | } |
| 4421 | }, |
| 4422 | } |
| 4423 | } |
| 4424 | }; |
| 4425 | errdefer closeFd(fd); |
| 4426 | |
| 4427 | if (have_flock and !have_flock_open_flags and options.lock != .none) { |
| 4428 | const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0; |
| 4429 | const lock_flags = switch (options.lock) { |
| 4430 | .none => unreachable, |
| 4431 | .shared => posix.LOCK.SH | lock_nonblocking, |
| 4432 | .exclusive => posix.LOCK.EX | lock_nonblocking, |
| 4433 | }; |
| 4434 | |
| 4435 | const syscall: Syscall = try .start(); |
| 4436 | while (true) { |
| 4437 | switch (posix.errno(posix.system.flock(fd, lock_flags))) { |
| 4438 | .SUCCESS => { |
| 4439 | syscall.finish(); |
| 4440 | break; |
| 4441 | }, |
| 4442 | .INTR => { |
| 4443 | try syscall.checkCancel(); |
| 4444 | continue; |
| 4445 | }, |
| 4446 | else => |e| { |
| 4447 | syscall.finish(); |
| 4448 | switch (e) { |
| 4449 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 4450 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 4451 | .NOLCK => return error.SystemResources, |
| 4452 | .AGAIN => return error.WouldBlock, |
| 4453 | .OPNOTSUPP => return error.FileLocksUnsupported, |
| 4454 | else => |err| return posix.unexpectedErrno(err), |
| 4455 | } |
| 4456 | }, |
| 4457 | } |
| 4458 | } |
| 4459 | } |
| 4460 | |
| 4461 | if (have_flock_open_flags and options.lock_nonblocking) { |
| 4462 | var fl_flags: usize = fl: { |
| 4463 | const syscall: Syscall = try .start(); |
| 4464 | while (true) { |
| 4465 | const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); |
| 4466 | switch (posix.errno(rc)) { |
| 4467 | .SUCCESS => { |
| 4468 | syscall.finish(); |
| 4469 | break :fl @intCast(rc); |
| 4470 | }, |
| 4471 | .INTR => { |
| 4472 | try syscall.checkCancel(); |
| 4473 | continue; |
| 4474 | }, |
| 4475 | else => |err| { |
| 4476 | syscall.finish(); |
| 4477 | return posix.unexpectedErrno(err); |
| 4478 | }, |
| 4479 | } |
| 4480 | } |
| 4481 | }; |
| 4482 | |
| 4483 | fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); |
| 4484 | |
| 4485 | const syscall: Syscall = try .start(); |
| 4486 | while (true) { |
| 4487 | switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { |
| 4488 | .SUCCESS => { |
| 4489 | syscall.finish(); |
| 4490 | break; |
| 4491 | }, |
| 4492 | .INTR => { |
| 4493 | try syscall.checkCancel(); |
| 4494 | continue; |
| 4495 | }, |
| 4496 | else => |err| { |
| 4497 | syscall.finish(); |
| 4498 | return posix.unexpectedErrno(err); |
| 4499 | }, |
| 4500 | } |
| 4501 | } |
| 4502 | } |
| 4503 | |
| 4504 | return .{ |
| 4505 | .handle = fd, |
| 4506 | .flags = .{ .nonblocking = false }, |
| 4507 | }; |
| 4508 | } |
| 4509 | |
| 4510 | fn dirCreateFileWindows( |
| 4511 | userdata: ?*anyopaque, |
| 4512 | dir: Dir, |
| 4513 | sub_path: []const u8, |
| 4514 | flags: Dir.CreateFileOptions, |
| 4515 | ) File.OpenError!File { |
| 4516 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4517 | _ = t; |
| 4518 | |
| 4519 | if (std.mem.eql(u8, sub_path, ".")) return error.IsDir; |
| 4520 | if (std.mem.eql(u8, sub_path, "..")) return error.IsDir; |
| 4521 | |
| 4522 | const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 4523 | const attr: windows.OBJECT.ATTRIBUTES = .{ |
| 4524 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, |
| 4525 | .ObjectName = @constCast(&sub_path_w.string()), |
| 4526 | }; |
| 4527 | const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) |
| 4528 | .CREATE |
| 4529 | else if (flags.truncate) |
| 4530 | .OVERWRITE_IF |
| 4531 | else |
| 4532 | .OPEN_IF; |
| 4533 | |
| 4534 | const access_mask: windows.ACCESS_MASK = .{ |
| 4535 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 4536 | .GENERIC = .{ |
| 4537 | .WRITE = true, |
| 4538 | .READ = flags.read, |
| 4539 | }, |
| 4540 | }; |
| 4541 | |
| 4542 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 4543 | var attempt: u5 = 0; |
| 4544 | var handle: windows.HANDLE = undefined; |
| 4545 | var syscall: Syscall = try .start(); |
| 4546 | while (true) switch (windows.ntdll.NtCreateFile( |
| 4547 | &handle, |
| 4548 | access_mask, |
| 4549 | &attr, |
| 4550 | &io_status_block, |
| 4551 | null, |
| 4552 | .{ .NORMAL = true }, |
| 4553 | .VALID_FLAGS, // share access |
| 4554 | create_disposition, |
| 4555 | .{ |
| 4556 | .NON_DIRECTORY_FILE = true, |
| 4557 | .IO = .SYNCHRONOUS_NONALERT, |
| 4558 | }, |
| 4559 | null, |
| 4560 | 0, |
| 4561 | )) { |
| 4562 | .SUCCESS => { |
| 4563 | syscall.finish(); |
| 4564 | break; |
| 4565 | }, |
| 4566 | .CANCELLED => { |
| 4567 | try syscall.checkCancel(); |
| 4568 | continue; |
| 4569 | }, |
| 4570 | .SHARING_VIOLATION => { |
| 4571 | // This occurs if the file attempting to be opened is a running |
| 4572 | // executable. However, there's a kernel bug: the error may be |
| 4573 | // incorrectly returned for an indeterminate amount of time |
| 4574 | // after an executable file is closed. Here we work around the |
| 4575 | // kernel bug with retry attempts. |
| 4576 | syscall.finish(); |
| 4577 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 4578 | try parking_sleep.sleep(.{ .duration = .{ |
| 4579 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4580 | .clock = .awake, |
| 4581 | } }); |
| 4582 | attempt += 1; |
| 4583 | syscall = try .start(); |
| 4584 | continue; |
| 4585 | }, |
| 4586 | .DELETE_PENDING => { |
| 4587 | // This error means that there *was* a file in this location on |
| 4588 | // the file system, but it was deleted. However, the OS is not |
| 4589 | // finished with the deletion operation, and so this CreateFile |
| 4590 | // call has failed. Here, we simulate the kernel bug being |
| 4591 | // fixed by sleeping and retrying until the error goes away. |
| 4592 | syscall.finish(); |
| 4593 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 4594 | try parking_sleep.sleep(.{ .duration = .{ |
| 4595 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 4596 | .clock = .awake, |
| 4597 | } }); |
| 4598 | attempt += 1; |
| 4599 | syscall = try .start(); |
| 4600 | continue; |
| 4601 | }, |
| 4602 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 4603 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 4604 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 4605 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 4606 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| 4607 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 4608 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 4609 | .PIPE_BUSY => return syscall.fail(error.PipeBusy), |
| 4610 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), |
| 4611 | .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), |
| 4612 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 4613 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 4614 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 4615 | .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), |
| 4616 | .DISK_FULL => return syscall.fail(error.NoSpaceLeft), |
| 4617 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 4618 | .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), |
| 4619 | .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), |
| 4620 | else => |status| return syscall.unexpectedNtstatus(status), |
| 4621 | }; |
| 4622 | errdefer windows.CloseHandle(handle); |
| 4623 | |
| 4624 | const exclusive = switch (flags.lock) { |
| 4625 | .none => return .{ |
| 4626 | .handle = handle, |
| 4627 | .flags = .{ .nonblocking = false }, |
| 4628 | }, |
| 4629 | .shared => false, |
| 4630 | .exclusive => true, |
| 4631 | }; |
| 4632 | |
| 4633 | syscall = try .start(); |
| 4634 | while (true) switch (windows.ntdll.NtLockFile( |
| 4635 | handle, |
| 4636 | null, |
| 4637 | null, |
| 4638 | null, |
| 4639 | &io_status_block, |
| 4640 | &windows_lock_range_off, |
| 4641 | &windows_lock_range_len, |
| 4642 | null, |
| 4643 | .fromBool(flags.lock_nonblocking), |
| 4644 | .fromBool(exclusive), |
| 4645 | )) { |
| 4646 | .SUCCESS => { |
| 4647 | syscall.finish(); |
| 4648 | return .{ |
| 4649 | .handle = handle, |
| 4650 | .flags = .{ .nonblocking = false }, |
| 4651 | }; |
| 4652 | }, |
| 4653 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 4654 | .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), |
| 4655 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 4656 | else => |status| return syscall.unexpectedNtstatus(status), |
| 4657 | }; |
| 4658 | } |
| 4659 | |
| 4660 | fn dirCreateFileWasi( |
| 4661 | userdata: ?*anyopaque, |
| 4662 | dir: Dir, |
| 4663 | sub_path: []const u8, |
| 4664 | flags: Dir.CreateFileOptions, |
| 4665 | ) File.OpenError!File { |
| 4666 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4667 | _ = t; |
| 4668 | const wasi = std.os.wasi; |
| 4669 | const lookup_flags: wasi.lookupflags_t = .{}; |
| 4670 | const oflags: wasi.oflags_t = .{ |
| 4671 | .CREAT = true, |
| 4672 | .TRUNC = flags.truncate, |
| 4673 | .EXCL = flags.exclusive, |
| 4674 | }; |
| 4675 | const fdflags: wasi.fdflags_t = .{}; |
| 4676 | const base: wasi.rights_t = .{ |
| 4677 | .FD_READ = flags.read, |
| 4678 | .FD_WRITE = true, |
| 4679 | .FD_DATASYNC = true, |
| 4680 | .FD_SEEK = true, |
| 4681 | .FD_TELL = true, |
| 4682 | .FD_FDSTAT_SET_FLAGS = true, |
| 4683 | .FD_SYNC = true, |
| 4684 | .FD_ALLOCATE = true, |
| 4685 | .FD_ADVISE = true, |
| 4686 | .FD_FILESTAT_SET_TIMES = true, |
| 4687 | .FD_FILESTAT_SET_SIZE = true, |
| 4688 | .FD_FILESTAT_GET = true, |
| 4689 | // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or |
| 4690 | // FD_WRITE is also set. |
| 4691 | .POLL_FD_READWRITE = true, |
| 4692 | }; |
| 4693 | const inheriting: wasi.rights_t = .{}; |
| 4694 | var fd: posix.fd_t = undefined; |
| 4695 | const syscall: Syscall = try .start(); |
| 4696 | while (true) { |
| 4697 | switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { |
| 4698 | .SUCCESS => { |
| 4699 | syscall.finish(); |
| 4700 | return .{ |
| 4701 | .handle = fd, |
| 4702 | .flags = .{ .nonblocking = false }, |
| 4703 | }; |
| 4704 | }, |
| 4705 | .INTR => { |
| 4706 | try syscall.checkCancel(); |
| 4707 | continue; |
| 4708 | }, |
| 4709 | else => |e| { |
| 4710 | syscall.finish(); |
| 4711 | switch (e) { |
| 4712 | .FAULT => |err| return errnoBug(err), |
| 4713 | .INVAL => return error.BadPathName, |
| 4714 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 4715 | .ACCES => return error.AccessDenied, |
| 4716 | .FBIG => return error.FileTooBig, |
| 4717 | .OVERFLOW => return error.FileTooBig, |
| 4718 | .ISDIR => return error.IsDir, |
| 4719 | .LOOP => return error.SymLinkLoop, |
| 4720 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 4721 | .NAMETOOLONG => return error.NameTooLong, |
| 4722 | .NFILE => return error.SystemFdQuotaExceeded, |
| 4723 | .NODEV => return error.NoDevice, |
| 4724 | .NOENT => return error.FileNotFound, |
| 4725 | .NOMEM => return error.SystemResources, |
| 4726 | .NOSPC => return error.NoSpaceLeft, |
| 4727 | .NOTDIR => return error.NotDir, |
| 4728 | .PERM => return error.PermissionDenied, |
| 4729 | .EXIST => return error.PathAlreadyExists, |
| 4730 | .BUSY => return error.DeviceBusy, |
| 4731 | .NOTCAPABLE => return error.AccessDenied, |
| 4732 | .ILSEQ => return error.BadPathName, |
| 4733 | else => |err| return posix.unexpectedErrno(err), |
| 4734 | } |
| 4735 | }, |
| 4736 | } |
| 4737 | } |
| 4738 | } |
| 4739 | |
| 4740 | fn dirCreateFileAtomic( |
| 4741 | userdata: ?*anyopaque, |
| 4742 | dir: Dir, |
| 4743 | dest_path: []const u8, |
| 4744 | options: Dir.CreateFileAtomicOptions, |
| 4745 | ) Dir.CreateFileAtomicError!File.Atomic { |
| 4746 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4747 | const t_io = io(t); |
| 4748 | |
| 4749 | // Linux has O_TMPFILE, but linkat() does not support AT_REPLACE, so it's |
| 4750 | // useless when we have to make up a bogus path name to do the rename() |
| 4751 | // anyway. |
| 4752 | if (native_os == .linux and !options.replace) tmpfile: { |
| 4753 | const flags: posix.O = if (@hasField(posix.O, "TMPFILE")) .{ |
| 4754 | .ACCMODE = .RDWR, |
| 4755 | .TMPFILE = true, |
| 4756 | .DIRECTORY = true, |
| 4757 | .CLOEXEC = true, |
| 4758 | } else if (@hasField(posix.O, "TMPFILE0") and !@hasField(posix.O, "TMPFILE2")) .{ |
| 4759 | .ACCMODE = .RDWR, |
| 4760 | .TMPFILE0 = true, |
| 4761 | .TMPFILE1 = true, |
| 4762 | .DIRECTORY = true, |
| 4763 | .CLOEXEC = true, |
| 4764 | } else break :tmpfile; |
| 4765 | |
| 4766 | const dest_dirname = Dir.path.dirname(dest_path); |
| 4767 | if (dest_dirname) |dirname| { |
| 4768 | // This has a nice side effect of preemptively triggering EISDIR or |
| 4769 | // ENOENT, avoiding the ambiguity below. |
| 4770 | if (options.make_path) dir.createDirPath(t_io, dirname) catch |err| switch (err) { |
| 4771 | // None of these make sense in this context. |
| 4772 | error.IsDir, |
| 4773 | error.Streaming, |
| 4774 | error.DiskQuota, |
| 4775 | error.PathAlreadyExists, |
| 4776 | error.LinkQuotaExceeded, |
| 4777 | error.PipeBusy, |
| 4778 | error.FileTooBig, |
| 4779 | error.DeviceBusy, |
| 4780 | error.FileLocksUnsupported, |
| 4781 | error.FileBusy, |
| 4782 | => return error.Unexpected, |
| 4783 | |
| 4784 | else => |e| return e, |
| 4785 | }; |
| 4786 | } |
| 4787 | |
| 4788 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 4789 | const sub_path_posix = try pathToPosix(dest_dirname orelse ".", &path_buffer); |
| 4790 | |
| 4791 | const syscall: Syscall = try .start(); |
| 4792 | while (true) { |
| 4793 | const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode()); |
| 4794 | switch (posix.errno(rc)) { |
| 4795 | .SUCCESS => { |
| 4796 | syscall.finish(); |
| 4797 | return .{ |
| 4798 | .file = .{ |
| 4799 | .handle = @intCast(rc), |
| 4800 | .flags = .{ .nonblocking = false }, |
| 4801 | }, |
| 4802 | .file_basename_hex = 0, |
| 4803 | .dest_sub_path = dest_path, |
| 4804 | .file_open = true, |
| 4805 | .file_exists = false, |
| 4806 | .close_dir_on_deinit = false, |
| 4807 | .dir = dir, |
| 4808 | }; |
| 4809 | }, |
| 4810 | .INTR => { |
| 4811 | try syscall.checkCancel(); |
| 4812 | continue; |
| 4813 | }, |
| 4814 | .ISDIR, .NOENT, .OPNOTSUPP => { |
| 4815 | // Ambiguous error code. It might mean the file system |
| 4816 | // does not support O_TMPFILE. Therefore, we must fall |
| 4817 | // back to not using O_TMPFILE. |
| 4818 | syscall.finish(); |
| 4819 | break :tmpfile; |
| 4820 | }, |
| 4821 | .INVAL => return syscall.fail(error.BadPathName), |
| 4822 | .ACCES => return syscall.fail(error.AccessDenied), |
| 4823 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 4824 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| 4825 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 4826 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), |
| 4827 | .NODEV => return syscall.fail(error.NoDevice), |
| 4828 | .NOMEM => return syscall.fail(error.SystemResources), |
| 4829 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 4830 | .NOTDIR => return syscall.fail(error.NotDir), |
| 4831 | .PERM => return syscall.fail(error.PermissionDenied), |
| 4832 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 4833 | .NXIO => return syscall.fail(error.NoDevice), |
| 4834 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 4835 | else => |err| return syscall.unexpectedErrno(err), |
| 4836 | } |
| 4837 | } |
| 4838 | } |
| 4839 | |
| 4840 | if (Dir.path.dirname(dest_path)) |dirname| { |
| 4841 | const new_dir = if (options.make_path) |
| 4842 | dir.createDirPathOpen(t_io, dirname, .{}) catch |err| switch (err) { |
| 4843 | // None of these make sense in this context. |
| 4844 | error.IsDir, |
| 4845 | error.Streaming, |
| 4846 | error.DiskQuota, |
| 4847 | error.PathAlreadyExists, |
| 4848 | error.LinkQuotaExceeded, |
| 4849 | error.PipeBusy, |
| 4850 | error.FileTooBig, |
| 4851 | error.FileLocksUnsupported, |
| 4852 | error.DeviceBusy, |
| 4853 | => return error.Unexpected, |
| 4854 | |
| 4855 | else => |e| return e, |
| 4856 | } |
| 4857 | else |
| 4858 | try dir.openDir(t_io, dirname, .{}); |
| 4859 | |
| 4860 | return atomicFileInit(t_io, Dir.path.basename(dest_path), options.permissions, new_dir, true); |
| 4861 | } |
| 4862 | |
| 4863 | return atomicFileInit(t_io, dest_path, options.permissions, dir, false); |
| 4864 | } |
| 4865 | |
| 4866 | fn atomicFileInit( |
| 4867 | t_io: Io, |
| 4868 | dest_basename: []const u8, |
| 4869 | permissions: File.Permissions, |
| 4870 | dir: Dir, |
| 4871 | close_dir_on_deinit: bool, |
| 4872 | ) Dir.CreateFileAtomicError!File.Atomic { |
| 4873 | while (true) { |
| 4874 | var random_integer: u64 = undefined; |
| 4875 | t_io.random(@ptrCast(&random_integer)); |
| 4876 | const tmp_sub_path = std.fmt.hex(random_integer); |
| 4877 | const file = dir.createFile(t_io, &tmp_sub_path, .{ |
| 4878 | .permissions = permissions, |
| 4879 | .exclusive = true, |
| 4880 | }) catch |err| switch (err) { |
| 4881 | error.PathAlreadyExists => continue, |
| 4882 | error.DeviceBusy => continue, |
| 4883 | error.FileBusy => continue, |
| 4884 | |
| 4885 | error.IsDir => return error.Unexpected, // No path components. |
| 4886 | error.FileTooBig => return error.Unexpected, // Creating, not opening. |
| 4887 | error.FileLocksUnsupported => return error.Unexpected, // Not asking for locks. |
| 4888 | error.PipeBusy => return error.Unexpected, // Not opening a pipe. |
| 4889 | |
| 4890 | else => |e| return e, |
| 4891 | }; |
| 4892 | return .{ |
| 4893 | .file = file, |
| 4894 | .file_basename_hex = random_integer, |
| 4895 | .dest_sub_path = dest_basename, |
| 4896 | .file_open = true, |
| 4897 | .file_exists = true, |
| 4898 | .close_dir_on_deinit = close_dir_on_deinit, |
| 4899 | .dir = dir, |
| 4900 | }; |
| 4901 | } |
| 4902 | } |
| 4903 | |
| 4904 | const dirOpenFile = switch (native_os) { |
| 4905 | .windows => dirOpenFileWindows, |
| 4906 | .wasi => dirOpenFileWasi, |
| 4907 | else => dirOpenFilePosix, |
| 4908 | }; |
| 4909 | |
| 4910 | fn dirOpenFilePosix( |
| 4911 | userdata: ?*anyopaque, |
| 4912 | dir: Dir, |
| 4913 | sub_path: []const u8, |
| 4914 | options: Dir.OpenFileOptions, |
| 4915 | ) File.OpenError!File { |
| 4916 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4917 | |
| 4918 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 4919 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 4920 | |
| 4921 | var flags: posix.O = switch (native_os) { |
| 4922 | .wasi => .{ |
| 4923 | .read = options.mode != .write_only, |
| 4924 | .write = options.mode != .read_only, |
| 4925 | .NOFOLLOW = !options.follow_symlinks, |
| 4926 | }, |
| 4927 | else => .{ |
| 4928 | .ACCMODE = switch (options.mode) { |
| 4929 | .read_only => .RDONLY, |
| 4930 | .write_only => .WRONLY, |
| 4931 | .read_write => .RDWR, |
| 4932 | }, |
| 4933 | .NOFOLLOW = !options.follow_symlinks, |
| 4934 | }, |
| 4935 | }; |
| 4936 | if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; |
| 4937 | if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true; |
| 4938 | if (@hasField(posix.O, "NOCTTY")) flags.NOCTTY = !options.allow_ctty; |
| 4939 | if (@hasField(posix.O, "PATH")) flags.PATH = options.path_only; |
| 4940 | if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath; |
| 4941 | |
| 4942 | // Use the O locking options if the os supports them to acquire the lock |
| 4943 | // atomically. Note that the NONBLOCK flag is removed after the openat() |
| 4944 | // call is successful. |
| 4945 | if (have_flock_open_flags) switch (options.lock) { |
| 4946 | .none => {}, |
| 4947 | .shared => { |
| 4948 | flags.SHLOCK = true; |
| 4949 | flags.NONBLOCK = options.lock_nonblocking; |
| 4950 | }, |
| 4951 | .exclusive => { |
| 4952 | flags.EXLOCK = true; |
| 4953 | flags.NONBLOCK = options.lock_nonblocking; |
| 4954 | }, |
| 4955 | }; |
| 4956 | |
| 4957 | const mode: posix.mode_t = 0; |
| 4958 | |
| 4959 | const fd: posix.fd_t = fd: { |
| 4960 | const syscall: Syscall = try .start(); |
| 4961 | while (true) { |
| 4962 | const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); |
| 4963 | switch (posix.errno(rc)) { |
| 4964 | .SUCCESS => { |
| 4965 | syscall.finish(); |
| 4966 | break :fd @intCast(rc); |
| 4967 | }, |
| 4968 | .INTR => { |
| 4969 | try syscall.checkCancel(); |
| 4970 | continue; |
| 4971 | }, |
| 4972 | else => |e| { |
| 4973 | syscall.finish(); |
| 4974 | switch (e) { |
| 4975 | .FAULT => |err| return errnoBug(err), |
| 4976 | .INVAL => return error.BadPathName, |
| 4977 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 4978 | .ACCES => return error.AccessDenied, |
| 4979 | .FBIG => return error.FileTooBig, |
| 4980 | .OVERFLOW => return error.FileTooBig, |
| 4981 | .ISDIR => return error.IsDir, |
| 4982 | .LOOP => return error.SymLinkLoop, |
| 4983 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 4984 | .NAMETOOLONG => return error.NameTooLong, |
| 4985 | .NFILE => return error.SystemFdQuotaExceeded, |
| 4986 | .NODEV => return error.NoDevice, |
| 4987 | .NOENT => return error.FileNotFound, |
| 4988 | .SRCH => return error.FileNotFound, // Linux when opening procfs files. |
| 4989 | .NOMEM => return error.SystemResources, |
| 4990 | .NOSPC => return error.NoSpaceLeft, |
| 4991 | .NOTDIR => return error.NotDir, |
| 4992 | .PERM => return error.PermissionDenied, |
| 4993 | .EXIST => return error.PathAlreadyExists, |
| 4994 | .BUSY => return error.DeviceBusy, |
| 4995 | .OPNOTSUPP => return error.FileLocksUnsupported, |
| 4996 | .AGAIN => return error.WouldBlock, |
| 4997 | .TXTBSY => return error.FileBusy, |
| 4998 | .NXIO => return error.NoDevice, |
| 4999 | .ROFS => return error.ReadOnlyFileSystem, |
| 5000 | .ILSEQ => return error.BadPathName, |
| 5001 | else => |err| return posix.unexpectedErrno(err), |
| 5002 | } |
| 5003 | }, |
| 5004 | } |
| 5005 | } |
| 5006 | }; |
| 5007 | errdefer closeFd(fd); |
| 5008 | |
| 5009 | if (!options.allow_directory) { |
| 5010 | const is_dir = is_dir: { |
| 5011 | const stat = fileStat(t, .{ |
| 5012 | .handle = fd, |
| 5013 | .flags = .{ .nonblocking = false }, |
| 5014 | }) catch |err| switch (err) { |
| 5015 | // The directory-ness is either unknown or unknowable |
| 5016 | error.Streaming => break :is_dir false, |
| 5017 | else => |e| return e, |
| 5018 | }; |
| 5019 | break :is_dir stat.kind == .directory; |
| 5020 | }; |
| 5021 | if (is_dir) return error.IsDir; |
| 5022 | } |
| 5023 | |
| 5024 | if (have_flock and !have_flock_open_flags and options.lock != .none) { |
| 5025 | const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0; |
| 5026 | const lock_flags = switch (options.lock) { |
| 5027 | .none => unreachable, |
| 5028 | .shared => posix.LOCK.SH | lock_nonblocking, |
| 5029 | .exclusive => posix.LOCK.EX | lock_nonblocking, |
| 5030 | }; |
| 5031 | const syscall: Syscall = try .start(); |
| 5032 | while (true) { |
| 5033 | switch (posix.errno(posix.system.flock(fd, lock_flags))) { |
| 5034 | .SUCCESS => { |
| 5035 | syscall.finish(); |
| 5036 | break; |
| 5037 | }, |
| 5038 | .INTR => { |
| 5039 | try syscall.checkCancel(); |
| 5040 | continue; |
| 5041 | }, |
| 5042 | else => |e| { |
| 5043 | syscall.finish(); |
| 5044 | switch (e) { |
| 5045 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 5046 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 5047 | .NOLCK => return error.SystemResources, |
| 5048 | .AGAIN => return error.WouldBlock, |
| 5049 | .OPNOTSUPP => return error.FileLocksUnsupported, |
| 5050 | else => |err| return posix.unexpectedErrno(err), |
| 5051 | } |
| 5052 | }, |
| 5053 | } |
| 5054 | } |
| 5055 | } |
| 5056 | |
| 5057 | if (have_flock_open_flags and options.lock_nonblocking) { |
| 5058 | var fl_flags: usize = fl: { |
| 5059 | const syscall: Syscall = try .start(); |
| 5060 | while (true) { |
| 5061 | const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); |
| 5062 | switch (posix.errno(rc)) { |
| 5063 | .SUCCESS => { |
| 5064 | syscall.finish(); |
| 5065 | break :fl @intCast(rc); |
| 5066 | }, |
| 5067 | .INTR => { |
| 5068 | try syscall.checkCancel(); |
| 5069 | continue; |
| 5070 | }, |
| 5071 | else => |err| { |
| 5072 | syscall.finish(); |
| 5073 | return posix.unexpectedErrno(err); |
| 5074 | }, |
| 5075 | } |
| 5076 | } |
| 5077 | }; |
| 5078 | |
| 5079 | fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); |
| 5080 | |
| 5081 | const syscall: Syscall = try .start(); |
| 5082 | while (true) { |
| 5083 | switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { |
| 5084 | .SUCCESS => { |
| 5085 | syscall.finish(); |
| 5086 | break; |
| 5087 | }, |
| 5088 | .INTR => { |
| 5089 | try syscall.checkCancel(); |
| 5090 | continue; |
| 5091 | }, |
| 5092 | else => |err| { |
| 5093 | syscall.finish(); |
| 5094 | return posix.unexpectedErrno(err); |
| 5095 | }, |
| 5096 | } |
| 5097 | } |
| 5098 | } |
| 5099 | |
| 5100 | return .{ |
| 5101 | .handle = fd, |
| 5102 | .flags = .{ .nonblocking = false }, |
| 5103 | }; |
| 5104 | } |
| 5105 | |
| 5106 | fn dirOpenFileWindows( |
| 5107 | userdata: ?*anyopaque, |
| 5108 | dir: Dir, |
| 5109 | sub_path: []const u8, |
| 5110 | flags: Dir.OpenFileOptions, |
| 5111 | ) File.OpenError!File { |
| 5112 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5113 | _ = t; |
| 5114 | const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 5115 | const sub_path_w = sub_path_w_array.span(); |
| 5116 | const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; |
| 5117 | return dirOpenFileWtf16(dir_handle, sub_path_w, flags); |
| 5118 | } |
| 5119 | |
| 5120 | pub fn dirOpenFileWtf16( |
| 5121 | dir_handle: ?windows.HANDLE, |
| 5122 | sub_path_w: []const u16, |
| 5123 | flags: Dir.OpenFileOptions, |
| 5124 | ) File.OpenError!File { |
| 5125 | const allow_directory = flags.allow_directory and !flags.isWrite(); |
| 5126 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; |
| 5127 | if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; |
| 5128 | const w = windows; |
| 5129 | |
| 5130 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 5131 | var attempt: u5 = 0; |
| 5132 | var syscall: Syscall = try .start(); |
| 5133 | const handle = while (true) { |
| 5134 | var result: w.HANDLE = undefined; |
| 5135 | switch (w.ntdll.NtCreateFile( |
| 5136 | &result, |
| 5137 | .{ |
| 5138 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 5139 | .GENERIC = .{ |
| 5140 | .READ = flags.isRead(), |
| 5141 | .WRITE = flags.isWrite(), |
| 5142 | }, |
| 5143 | }, |
| 5144 | &.{ |
| 5145 | .RootDirectory = dir_handle, |
| 5146 | .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)), |
| 5147 | }, |
| 5148 | &io_status_block, |
| 5149 | null, |
| 5150 | .{ .NORMAL = true }, |
| 5151 | .VALID_FLAGS, |
| 5152 | .OPEN, |
| 5153 | .{ |
| 5154 | .IO = .SYNCHRONOUS_NONALERT, |
| 5155 | .NON_DIRECTORY_FILE = !allow_directory, |
| 5156 | .OPEN_REPARSE_POINT = !flags.follow_symlinks, |
| 5157 | }, |
| 5158 | null, |
| 5159 | 0, |
| 5160 | )) { |
| 5161 | .SUCCESS => { |
| 5162 | syscall.finish(); |
| 5163 | break result; |
| 5164 | }, |
| 5165 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 5166 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 5167 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 5168 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 5169 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| 5170 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 5171 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 5172 | .CANCELLED => { |
| 5173 | try syscall.checkCancel(); |
| 5174 | continue; |
| 5175 | }, |
| 5176 | .SHARING_VIOLATION => { |
| 5177 | // This occurs if the file attempting to be opened is a running |
| 5178 | // executable. However, there's a kernel bug: the error may be |
| 5179 | // incorrectly returned for an indeterminate amount of time |
| 5180 | // after an executable file is closed. Here we work around the |
| 5181 | // kernel bug with retry attempts. |
| 5182 | syscall.finish(); |
| 5183 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 5184 | try parking_sleep.sleep(.{ .duration = .{ |
| 5185 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 5186 | .clock = .awake, |
| 5187 | } }); |
| 5188 | attempt += 1; |
| 5189 | syscall = try .start(); |
| 5190 | continue; |
| 5191 | }, |
| 5192 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 5193 | .PIPE_BUSY => return syscall.fail(error.PipeBusy), |
| 5194 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), |
| 5195 | .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), |
| 5196 | .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), |
| 5197 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 5198 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 5199 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 5200 | .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), |
| 5201 | .DELETE_PENDING => { |
| 5202 | // This error means that there *was* a file in this location on |
| 5203 | // the file system, but it was deleted. However, the OS is not |
| 5204 | // finished with the deletion operation, and so this CreateFile |
| 5205 | // call has failed. Here, we simulate the kernel bug being |
| 5206 | // fixed by sleeping and retrying until the error goes away. |
| 5207 | syscall.finish(); |
| 5208 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 5209 | try parking_sleep.sleep(.{ .duration = .{ |
| 5210 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 5211 | .clock = .awake, |
| 5212 | } }); |
| 5213 | attempt += 1; |
| 5214 | syscall = try .start(); |
| 5215 | continue; |
| 5216 | }, |
| 5217 | .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), |
| 5218 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| 5219 | } |
| 5220 | }; |
| 5221 | errdefer w.CloseHandle(handle); |
| 5222 | |
| 5223 | const exclusive = switch (flags.lock) { |
| 5224 | .none => return .{ |
| 5225 | .handle = handle, |
| 5226 | .flags = .{ .nonblocking = false }, |
| 5227 | }, |
| 5228 | .shared => false, |
| 5229 | .exclusive => true, |
| 5230 | }; |
| 5231 | syscall = try .start(); |
| 5232 | while (true) switch (w.ntdll.NtLockFile( |
| 5233 | handle, |
| 5234 | null, |
| 5235 | null, |
| 5236 | null, |
| 5237 | &io_status_block, |
| 5238 | &windows_lock_range_off, |
| 5239 | &windows_lock_range_len, |
| 5240 | null, |
| 5241 | .fromBool(flags.lock_nonblocking), |
| 5242 | .fromBool(exclusive), |
| 5243 | )) { |
| 5244 | .SUCCESS => break syscall.finish(), |
| 5245 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 5246 | .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), |
| 5247 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 5248 | else => |status| return syscall.unexpectedNtstatus(status), |
| 5249 | }; |
| 5250 | return .{ |
| 5251 | .handle = handle, |
| 5252 | .flags = .{ .nonblocking = false }, |
| 5253 | }; |
| 5254 | } |
| 5255 | |
| 5256 | fn dirOpenFileWasi( |
| 5257 | userdata: ?*anyopaque, |
| 5258 | dir: Dir, |
| 5259 | sub_path: []const u8, |
| 5260 | flags: Dir.OpenFileOptions, |
| 5261 | ) File.OpenError!File { |
| 5262 | if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags); |
| 5263 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5264 | const wasi = std.os.wasi; |
| 5265 | var base: std.os.wasi.rights_t = .{}; |
| 5266 | // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE |
| 5267 | // is also set. |
| 5268 | if (flags.isRead()) { |
| 5269 | base.FD_READ = true; |
| 5270 | base.FD_TELL = true; |
| 5271 | base.FD_SEEK = true; |
| 5272 | base.FD_FILESTAT_GET = true; |
| 5273 | base.POLL_FD_READWRITE = true; |
| 5274 | } |
| 5275 | if (flags.isWrite()) { |
| 5276 | base.FD_WRITE = true; |
| 5277 | base.FD_TELL = true; |
| 5278 | base.FD_SEEK = true; |
| 5279 | base.FD_DATASYNC = true; |
| 5280 | base.FD_FDSTAT_SET_FLAGS = true; |
| 5281 | base.FD_SYNC = true; |
| 5282 | base.FD_ALLOCATE = true; |
| 5283 | base.FD_ADVISE = true; |
| 5284 | base.FD_FILESTAT_SET_TIMES = true; |
| 5285 | base.FD_FILESTAT_SET_SIZE = true; |
| 5286 | base.POLL_FD_READWRITE = true; |
| 5287 | } |
| 5288 | const lookup_flags: wasi.lookupflags_t = .{}; |
| 5289 | const oflags: wasi.oflags_t = .{}; |
| 5290 | const inheriting: wasi.rights_t = .{}; |
| 5291 | const fdflags: wasi.fdflags_t = .{}; |
| 5292 | var fd: posix.fd_t = undefined; |
| 5293 | const syscall: Syscall = try .start(); |
| 5294 | while (true) { |
| 5295 | switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { |
| 5296 | .SUCCESS => { |
| 5297 | syscall.finish(); |
| 5298 | break; |
| 5299 | }, |
| 5300 | .INTR => { |
| 5301 | try syscall.checkCancel(); |
| 5302 | continue; |
| 5303 | }, |
| 5304 | else => |e| { |
| 5305 | syscall.finish(); |
| 5306 | switch (e) { |
| 5307 | .FAULT => |err| return errnoBug(err), |
| 5308 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 5309 | .ACCES => return error.AccessDenied, |
| 5310 | .FBIG => return error.FileTooBig, |
| 5311 | .OVERFLOW => return error.FileTooBig, |
| 5312 | .ISDIR => return error.IsDir, |
| 5313 | .LOOP => return error.SymLinkLoop, |
| 5314 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 5315 | .NFILE => return error.SystemFdQuotaExceeded, |
| 5316 | .NODEV => return error.NoDevice, |
| 5317 | .NOENT => return error.FileNotFound, |
| 5318 | .NOMEM => return error.SystemResources, |
| 5319 | .NOTDIR => return error.NotDir, |
| 5320 | .PERM => return error.PermissionDenied, |
| 5321 | .BUSY => return error.DeviceBusy, |
| 5322 | .NOTCAPABLE => return error.AccessDenied, |
| 5323 | .NAMETOOLONG => return error.NameTooLong, |
| 5324 | .INVAL => return error.BadPathName, |
| 5325 | .ILSEQ => return error.BadPathName, |
| 5326 | else => |err| return posix.unexpectedErrno(err), |
| 5327 | } |
| 5328 | }, |
| 5329 | } |
| 5330 | } |
| 5331 | errdefer closeFd(fd); |
| 5332 | |
| 5333 | if (!flags.allow_directory) { |
| 5334 | const is_dir = is_dir: { |
| 5335 | const stat = fileStat(t, .{ .handle = fd, .flags = .{ .nonblocking = false } }) catch |err| switch (err) { |
| 5336 | // The directory-ness is either unknown or unknowable |
| 5337 | error.Streaming => break :is_dir false, |
| 5338 | else => |e| return e, |
| 5339 | }; |
| 5340 | break :is_dir stat.kind == .directory; |
| 5341 | }; |
| 5342 | if (is_dir) return error.IsDir; |
| 5343 | } |
| 5344 | |
| 5345 | return .{ |
| 5346 | .handle = fd, |
| 5347 | .flags = .{ .nonblocking = false }, |
| 5348 | }; |
| 5349 | } |
| 5350 | |
| 5351 | const dirOpenDir = switch (native_os) { |
| 5352 | .wasi => dirOpenDirWasi, |
| 5353 | .haiku => dirOpenDirHaiku, |
| 5354 | else => dirOpenDirPosix, |
| 5355 | }; |
| 5356 | |
| 5357 | /// This function is also used for WASI when libc is linked. |
| 5358 | fn dirOpenDirPosix( |
| 5359 | userdata: ?*anyopaque, |
| 5360 | dir: Dir, |
| 5361 | sub_path: []const u8, |
| 5362 | options: Dir.OpenOptions, |
| 5363 | ) Dir.OpenError!Dir { |
| 5364 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5365 | _ = t; |
| 5366 | |
| 5367 | if (is_windows) { |
| 5368 | const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 5369 | return dirOpenDirWindows(dir, sub_path_w.span(), options); |
| 5370 | } |
| 5371 | |
| 5372 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 5373 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 5374 | |
| 5375 | var flags: posix.O = switch (native_os) { |
| 5376 | .wasi => .{ |
| 5377 | .read = true, |
| 5378 | .NOFOLLOW = !options.follow_symlinks, |
| 5379 | .DIRECTORY = true, |
| 5380 | }, |
| 5381 | else => .{ |
| 5382 | .ACCMODE = .RDONLY, |
| 5383 | .NOFOLLOW = !options.follow_symlinks, |
| 5384 | .DIRECTORY = true, |
| 5385 | .CLOEXEC = true, |
| 5386 | }, |
| 5387 | }; |
| 5388 | |
| 5389 | if (@hasField(posix.O, "PATH") and !options.iterate) |
| 5390 | flags.PATH = true; |
| 5391 | |
| 5392 | const mode: posix.mode_t = 0; |
| 5393 | |
| 5394 | const syscall: Syscall = try .start(); |
| 5395 | while (true) { |
| 5396 | const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); |
| 5397 | switch (posix.errno(rc)) { |
| 5398 | .SUCCESS => { |
| 5399 | syscall.finish(); |
| 5400 | return .{ .handle = @intCast(rc) }; |
| 5401 | }, |
| 5402 | .INTR => { |
| 5403 | try syscall.checkCancel(); |
| 5404 | continue; |
| 5405 | }, |
| 5406 | .INVAL => return syscall.fail(error.BadPathName), |
| 5407 | .ACCES => return syscall.fail(error.AccessDenied), |
| 5408 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 5409 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| 5410 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 5411 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), |
| 5412 | .NODEV => return syscall.fail(error.NoDevice), |
| 5413 | .NOENT => return syscall.fail(error.FileNotFound), |
| 5414 | .NOMEM => return syscall.fail(error.SystemResources), |
| 5415 | .NOTDIR => return syscall.fail(error.NotDir), |
| 5416 | .PERM => return syscall.fail(error.PermissionDenied), |
| 5417 | .NXIO => return syscall.fail(error.NoDevice), |
| 5418 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 5419 | .FAULT => |err| return syscall.errnoBug(err), |
| 5420 | .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. |
| 5421 | .BUSY => |err| return syscall.errnoBug(err), // O_EXCL not passed |
| 5422 | else => |err| return syscall.unexpectedErrno(err), |
| 5423 | } |
| 5424 | } |
| 5425 | } |
| 5426 | |
| 5427 | fn dirOpenDirHaiku( |
| 5428 | userdata: ?*anyopaque, |
| 5429 | dir: Dir, |
| 5430 | sub_path: []const u8, |
| 5431 | options: Dir.OpenOptions, |
| 5432 | ) Dir.OpenError!Dir { |
| 5433 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5434 | _ = t; |
| 5435 | |
| 5436 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 5437 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 5438 | |
| 5439 | _ = options; |
| 5440 | |
| 5441 | const syscall: Syscall = try .start(); |
| 5442 | while (true) { |
| 5443 | const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix); |
| 5444 | if (rc >= 0) { |
| 5445 | syscall.finish(); |
| 5446 | return .{ .handle = rc }; |
| 5447 | } |
| 5448 | switch (@as(posix.E, @fromBackingInt(@intCast(rc)))) { |
| 5449 | .INTR => { |
| 5450 | try syscall.checkCancel(); |
| 5451 | continue; |
| 5452 | }, |
| 5453 | else => |e| { |
| 5454 | syscall.finish(); |
| 5455 | switch (e) { |
| 5456 | .FAULT => |err| return errnoBug(err), |
| 5457 | .INVAL => |err| return errnoBug(err), |
| 5458 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 5459 | .ACCES => return error.AccessDenied, |
| 5460 | .LOOP => return error.SymLinkLoop, |
| 5461 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 5462 | .NAMETOOLONG => return error.NameTooLong, |
| 5463 | .NFILE => return error.SystemFdQuotaExceeded, |
| 5464 | .NODEV => return error.NoDevice, |
| 5465 | .NOENT => return error.FileNotFound, |
| 5466 | .NOMEM => return error.SystemResources, |
| 5467 | .NOTDIR => return error.NotDir, |
| 5468 | .PERM => return error.PermissionDenied, |
| 5469 | .BUSY => |err| return errnoBug(err), |
| 5470 | else => |err| return posix.unexpectedErrno(err), |
| 5471 | } |
| 5472 | }, |
| 5473 | } |
| 5474 | } |
| 5475 | } |
| 5476 | |
| 5477 | pub fn dirOpenDirWindows( |
| 5478 | dir: Dir, |
| 5479 | sub_path_w: []const u16, |
| 5480 | options: Dir.OpenOptions, |
| 5481 | ) Dir.OpenError!Dir { |
| 5482 | const w = windows; |
| 5483 | |
| 5484 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 5485 | var result: Dir = .{ .handle = undefined }; |
| 5486 | |
| 5487 | const attr: w.OBJECT.ATTRIBUTES = .{ |
| 5488 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, |
| 5489 | .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)), |
| 5490 | }; |
| 5491 | |
| 5492 | const syscall: Syscall = try .start(); |
| 5493 | while (true) switch (w.ntdll.NtCreateFile( |
| 5494 | &result.handle, |
| 5495 | // TODO remove some of these flags if options.access_sub_paths is false |
| 5496 | .{ |
| 5497 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ |
| 5498 | .LIST = options.iterate, |
| 5499 | .READ_EA = true, |
| 5500 | .TRAVERSE = true, |
| 5501 | .READ_ATTRIBUTES = true, |
| 5502 | } }, |
| 5503 | .STANDARD = .{ |
| 5504 | .RIGHTS = .READ, |
| 5505 | .SYNCHRONIZE = true, |
| 5506 | }, |
| 5507 | }, |
| 5508 | &attr, |
| 5509 | &io_status_block, |
| 5510 | null, |
| 5511 | .{ .NORMAL = true }, |
| 5512 | .VALID_FLAGS, |
| 5513 | .OPEN, |
| 5514 | .{ |
| 5515 | .DIRECTORY_FILE = true, |
| 5516 | .IO = .SYNCHRONOUS_NONALERT, |
| 5517 | .OPEN_FOR_BACKUP_INTENT = true, |
| 5518 | .OPEN_REPARSE_POINT = !options.follow_symlinks, |
| 5519 | }, |
| 5520 | null, |
| 5521 | 0, |
| 5522 | )) { |
| 5523 | .SUCCESS => { |
| 5524 | syscall.finish(); |
| 5525 | return result; |
| 5526 | }, |
| 5527 | .CANCELLED => { |
| 5528 | try syscall.checkCancel(); |
| 5529 | continue; |
| 5530 | }, |
| 5531 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 5532 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 5533 | .OBJECT_NAME_COLLISION => |err| return w.statusBug(err), |
| 5534 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 5535 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 5536 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' |
| 5537 | // and the directory is trying to be opened for iteration. |
| 5538 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 5539 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 5540 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| 5541 | }; |
| 5542 | } |
| 5543 | |
| 5544 | fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void { |
| 5545 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5546 | _ = t; |
| 5547 | for (dirs) |dir| { |
| 5548 | if (is_windows) { |
| 5549 | windows.CloseHandle(dir.handle); |
| 5550 | } else { |
| 5551 | closeFd(dir.handle); |
| 5552 | } |
| 5553 | } |
| 5554 | } |
| 5555 | |
| 5556 | const dirRead = switch (native_os) { |
| 5557 | .linux => dirReadLinux, |
| 5558 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin, |
| 5559 | .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd, |
| 5560 | .illumos => dirReadIllumos, |
| 5561 | .haiku => dirReadHaiku, |
| 5562 | .windows => dirReadWindows, |
| 5563 | .wasi => dirReadWasi, |
| 5564 | else => dirReadUnimplemented, |
| 5565 | }; |
| 5566 | |
| 5567 | fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 5568 | const linux = std.os.linux; |
| 5569 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5570 | _ = t; |
| 5571 | var buffer_index: usize = 0; |
| 5572 | while (buffer.len - buffer_index != 0) { |
| 5573 | if (dr.end - dr.index == 0) { |
| 5574 | // Refill the buffer, unless we've already created references to |
| 5575 | // buffered data. |
| 5576 | if (buffer_index != 0) break; |
| 5577 | if (dr.state == .reset) { |
| 5578 | posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { |
| 5579 | error.Unseekable => return error.Unexpected, |
| 5580 | else => |e| return e, |
| 5581 | }; |
| 5582 | dr.state = .reading; |
| 5583 | } |
| 5584 | const syscall: Syscall = try .start(); |
| 5585 | const n = while (true) { |
| 5586 | const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, @min(dr.buffer.len, std.math.maxInt(c_uint))); |
| 5587 | switch (linux.errno(rc)) { |
| 5588 | .SUCCESS => { |
| 5589 | syscall.finish(); |
| 5590 | break rc; |
| 5591 | }, |
| 5592 | .INTR => { |
| 5593 | try syscall.checkCancel(); |
| 5594 | continue; |
| 5595 | }, |
| 5596 | else => |e| { |
| 5597 | syscall.finish(); |
| 5598 | switch (e) { |
| 5599 | .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. |
| 5600 | .FAULT => |err| return errnoBug(err), |
| 5601 | .NOTDIR => |err| return errnoBug(err), |
| 5602 | // To be consistent across platforms, iteration |
| 5603 | // ends if the directory being iterated is deleted |
| 5604 | // during iteration. This matches the behavior of |
| 5605 | // non-Linux, non-WASI UNIX platforms. |
| 5606 | .NOENT => { |
| 5607 | dr.state = .finished; |
| 5608 | return 0; |
| 5609 | }, |
| 5610 | // This can occur when reading /proc/$PID/net, or |
| 5611 | // if the provided buffer is too small. Neither |
| 5612 | // scenario is intended to be handled by this API. |
| 5613 | .INVAL => return error.Unexpected, |
| 5614 | .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory. |
| 5615 | else => |err| return posix.unexpectedErrno(err), |
| 5616 | } |
| 5617 | }, |
| 5618 | } |
| 5619 | }; |
| 5620 | if (n == 0) { |
| 5621 | dr.state = .finished; |
| 5622 | return 0; |
| 5623 | } |
| 5624 | dr.index = 0; |
| 5625 | dr.end = n; |
| 5626 | } |
| 5627 | // Linux aligns the header by padding after the null byte of the name |
| 5628 | // to align the next entry. This means we can find the end of the name |
| 5629 | // by looking at only the 8 bytes before the next record. However since |
| 5630 | // file names are usually short it's better to keep the machine code |
| 5631 | // simpler. |
| 5632 | // |
| 5633 | // Furthermore, I observed qemu user mode to not align this struct, so |
| 5634 | // this code makes the conservative choice to not assume alignment. |
| 5635 | const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]); |
| 5636 | const next_index = dr.index + linux_entry.reclen; |
| 5637 | dr.index = next_index; |
| 5638 | const name_ptr: [*]u8 = &linux_entry.name; |
| 5639 | const padded_name = name_ptr[0 .. linux_entry.reclen - @offsetOf(linux.dirent64, "name")]; |
| 5640 | const name_len = std.mem.findScalar(u8, padded_name, 0).?; |
| 5641 | const name = name_ptr[0..name_len :0]; |
| 5642 | |
| 5643 | if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; |
| 5644 | |
| 5645 | const entry_kind: File.Kind = switch (linux_entry.type) { |
| 5646 | linux.DT.BLK => .block_device, |
| 5647 | linux.DT.CHR => .character_device, |
| 5648 | linux.DT.DIR => .directory, |
| 5649 | linux.DT.FIFO => .named_pipe, |
| 5650 | linux.DT.LNK => .sym_link, |
| 5651 | linux.DT.REG => .file, |
| 5652 | linux.DT.SOCK => .unix_domain_socket, |
| 5653 | else => .unknown, |
| 5654 | }; |
| 5655 | buffer[buffer_index] = .{ |
| 5656 | .name = name, |
| 5657 | .kind = entry_kind, |
| 5658 | .inode = linux_entry.ino, |
| 5659 | }; |
| 5660 | buffer_index += 1; |
| 5661 | } |
| 5662 | return buffer_index; |
| 5663 | } |
| 5664 | |
| 5665 | fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 5666 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5667 | _ = t; |
| 5668 | const Header = extern struct { |
| 5669 | seek: i64, |
| 5670 | }; |
| 5671 | const header: *Header = @ptrCast(dr.buffer.ptr); |
| 5672 | const header_end: usize = @sizeOf(Header); |
| 5673 | if (dr.index < header_end) { |
| 5674 | // Initialize header. |
| 5675 | dr.index = header_end; |
| 5676 | dr.end = header_end; |
| 5677 | header.* = .{ .seek = 0 }; |
| 5678 | } |
| 5679 | var buffer_index: usize = 0; |
| 5680 | while (buffer.len - buffer_index != 0) { |
| 5681 | if (dr.end - dr.index == 0) { |
| 5682 | // Refill the buffer, unless we've already created references to |
| 5683 | // buffered data. |
| 5684 | if (buffer_index != 0) break; |
| 5685 | if (dr.state == .reset) { |
| 5686 | posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { |
| 5687 | error.Unseekable => return error.Unexpected, |
| 5688 | else => |e| return e, |
| 5689 | }; |
| 5690 | dr.state = .reading; |
| 5691 | } |
| 5692 | const dents_buffer = dr.buffer[header_end..]; |
| 5693 | const syscall: Syscall = try .start(); |
| 5694 | const n: usize = while (true) { |
| 5695 | const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek); |
| 5696 | switch (posix.errno(rc)) { |
| 5697 | .SUCCESS => { |
| 5698 | syscall.finish(); |
| 5699 | break @intCast(rc); |
| 5700 | }, |
| 5701 | .INTR => { |
| 5702 | try syscall.checkCancel(); |
| 5703 | continue; |
| 5704 | }, |
| 5705 | else => |e| { |
| 5706 | syscall.finish(); |
| 5707 | switch (e) { |
| 5708 | .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. |
| 5709 | .FAULT => |err| return errnoBug(err), |
| 5710 | .NOTDIR => |err| return errnoBug(err), |
| 5711 | .INVAL => |err| return errnoBug(err), |
| 5712 | else => |err| return posix.unexpectedErrno(err), |
| 5713 | } |
| 5714 | }, |
| 5715 | } |
| 5716 | }; |
| 5717 | if (n == 0) { |
| 5718 | dr.state = .finished; |
| 5719 | return 0; |
| 5720 | } |
| 5721 | dr.index = header_end; |
| 5722 | dr.end = header_end + n; |
| 5723 | } |
| 5724 | const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); |
| 5725 | const next_index = dr.index + darwin_entry.reclen; |
| 5726 | dr.index = next_index; |
| 5727 | |
| 5728 | const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen]; |
| 5729 | if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) |
| 5730 | continue; |
| 5731 | |
| 5732 | const entry_kind: File.Kind = switch (darwin_entry.type) { |
| 5733 | posix.DT.BLK => .block_device, |
| 5734 | posix.DT.CHR => .character_device, |
| 5735 | posix.DT.DIR => .directory, |
| 5736 | posix.DT.FIFO => .named_pipe, |
| 5737 | posix.DT.LNK => .sym_link, |
| 5738 | posix.DT.REG => .file, |
| 5739 | posix.DT.SOCK => .unix_domain_socket, |
| 5740 | posix.DT.WHT => .whiteout, |
| 5741 | else => .unknown, |
| 5742 | }; |
| 5743 | buffer[buffer_index] = .{ |
| 5744 | .name = name, |
| 5745 | .kind = entry_kind, |
| 5746 | .inode = darwin_entry.ino, |
| 5747 | }; |
| 5748 | buffer_index += 1; |
| 5749 | } |
| 5750 | return buffer_index; |
| 5751 | } |
| 5752 | |
| 5753 | fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 5754 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5755 | _ = t; |
| 5756 | var buffer_index: usize = 0; |
| 5757 | while (buffer.len - buffer_index != 0) { |
| 5758 | if (dr.end - dr.index == 0) { |
| 5759 | // Refill the buffer, unless we've already created references to |
| 5760 | // buffered data. |
| 5761 | if (buffer_index != 0) break; |
| 5762 | if (dr.state == .reset) { |
| 5763 | posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { |
| 5764 | error.Unseekable => return error.Unexpected, |
| 5765 | else => |e| return e, |
| 5766 | }; |
| 5767 | dr.state = .reading; |
| 5768 | } |
| 5769 | const syscall: Syscall = try .start(); |
| 5770 | const n: usize = while (true) { |
| 5771 | const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); |
| 5772 | switch (posix.errno(rc)) { |
| 5773 | .SUCCESS => { |
| 5774 | syscall.finish(); |
| 5775 | break @intCast(rc); |
| 5776 | }, |
| 5777 | .INTR => { |
| 5778 | try syscall.checkCancel(); |
| 5779 | continue; |
| 5780 | }, |
| 5781 | else => |e| { |
| 5782 | syscall.finish(); |
| 5783 | switch (e) { |
| 5784 | .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability |
| 5785 | .FAULT => |err| return errnoBug(err), |
| 5786 | .NOTDIR => |err| return errnoBug(err), |
| 5787 | .INVAL => |err| return errnoBug(err), |
| 5788 | // Introduced in freebsd 13.2: directory unlinked |
| 5789 | // but still open. To be consistent, iteration ends |
| 5790 | // if the directory being iterated is deleted |
| 5791 | // during iteration. |
| 5792 | .NOENT => { |
| 5793 | dr.state = .finished; |
| 5794 | return 0; |
| 5795 | }, |
| 5796 | else => |err| return posix.unexpectedErrno(err), |
| 5797 | } |
| 5798 | }, |
| 5799 | } |
| 5800 | }; |
| 5801 | if (n == 0) { |
| 5802 | dr.state = .finished; |
| 5803 | return 0; |
| 5804 | } |
| 5805 | dr.index = 0; |
| 5806 | dr.end = n; |
| 5807 | } |
| 5808 | const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); |
| 5809 | const next_index = dr.index + |
| 5810 | if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen(); |
| 5811 | dr.index = next_index; |
| 5812 | |
| 5813 | const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen]; |
| 5814 | |
| 5815 | const skip_zero_fileno = switch (native_os) { |
| 5816 | // fileno=0 is used to mark invalid entries or deleted files. |
| 5817 | .openbsd, .netbsd => true, |
| 5818 | else => false, |
| 5819 | }; |
| 5820 | if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or |
| 5821 | (skip_zero_fileno and bsd_entry.fileno == 0)) |
| 5822 | { |
| 5823 | continue; |
| 5824 | } |
| 5825 | |
| 5826 | const entry_kind: File.Kind = switch (bsd_entry.type) { |
| 5827 | posix.DT.BLK => .block_device, |
| 5828 | posix.DT.CHR => .character_device, |
| 5829 | posix.DT.DIR => .directory, |
| 5830 | posix.DT.FIFO => .named_pipe, |
| 5831 | posix.DT.LNK => .sym_link, |
| 5832 | posix.DT.REG => .file, |
| 5833 | posix.DT.SOCK => .unix_domain_socket, |
| 5834 | posix.DT.WHT => .whiteout, |
| 5835 | else => .unknown, |
| 5836 | }; |
| 5837 | buffer[buffer_index] = .{ |
| 5838 | .name = name, |
| 5839 | .kind = entry_kind, |
| 5840 | .inode = bsd_entry.fileno, |
| 5841 | }; |
| 5842 | buffer_index += 1; |
| 5843 | } |
| 5844 | return buffer_index; |
| 5845 | } |
| 5846 | |
| 5847 | fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 5848 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5849 | _ = t; |
| 5850 | var buffer_index: usize = 0; |
| 5851 | while (buffer.len - buffer_index != 0) { |
| 5852 | if (dr.end - dr.index == 0) { |
| 5853 | // Refill the buffer, unless we've already created references to |
| 5854 | // buffered data. |
| 5855 | if (buffer_index != 0) break; |
| 5856 | if (dr.state == .reset) { |
| 5857 | posixSeekTo(dr.dir.handle, 0) catch |err| switch (err) { |
| 5858 | error.Unseekable => return error.Unexpected, |
| 5859 | else => |e| return e, |
| 5860 | }; |
| 5861 | dr.state = .reading; |
| 5862 | } |
| 5863 | const syscall: Syscall = try .start(); |
| 5864 | const n: usize = while (true) { |
| 5865 | const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len); |
| 5866 | switch (posix.errno(rc)) { |
| 5867 | .SUCCESS => { |
| 5868 | syscall.finish(); |
| 5869 | break rc; |
| 5870 | }, |
| 5871 | .INTR => { |
| 5872 | try syscall.checkCancel(); |
| 5873 | continue; |
| 5874 | }, |
| 5875 | else => |e| { |
| 5876 | syscall.finish(); |
| 5877 | switch (e) { |
| 5878 | .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability |
| 5879 | .FAULT => |err| return errnoBug(err), |
| 5880 | .NOTDIR => |err| return errnoBug(err), |
| 5881 | .INVAL => |err| return errnoBug(err), |
| 5882 | else => |err| return posix.unexpectedErrno(err), |
| 5883 | } |
| 5884 | }, |
| 5885 | } |
| 5886 | }; |
| 5887 | if (n == 0) { |
| 5888 | dr.state = .finished; |
| 5889 | return 0; |
| 5890 | } |
| 5891 | dr.index = 0; |
| 5892 | dr.end = n; |
| 5893 | } |
| 5894 | const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index])); |
| 5895 | const next_index = dr.index + entry.reclen; |
| 5896 | dr.index = next_index; |
| 5897 | |
| 5898 | const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0); |
| 5899 | if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue; |
| 5900 | |
| 5901 | // illumos dirent doesn't expose type, so we have to call stat to get it. |
| 5902 | const stat = try posixStatFile(dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW); |
| 5903 | |
| 5904 | buffer[buffer_index] = .{ |
| 5905 | .name = name, |
| 5906 | .kind = stat.kind, |
| 5907 | .inode = entry.ino, |
| 5908 | }; |
| 5909 | buffer_index += 1; |
| 5910 | } |
| 5911 | return buffer_index; |
| 5912 | } |
| 5913 | |
| 5914 | fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 5915 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5916 | _ = t; |
| 5917 | var buffer_index: usize = 0; |
| 5918 | while (buffer.len - buffer_index != 0) { |
| 5919 | if (dr.end - dr.index == 0) { |
| 5920 | // Refill the buffer, unless we've already created references to |
| 5921 | // buffered data. |
| 5922 | if (buffer_index != 0) break; |
| 5923 | if (dr.state == .reset) { |
| 5924 | const syscall: Syscall = try .start(); |
| 5925 | while (true) { |
| 5926 | const rc = posix.system._kern_rewind_dir(dr.dir.handle); |
| 5927 | switch (@as(posix.E, @fromBackingInt(@intCast(@min(rc, 0))))) { |
| 5928 | .SUCCESS => { |
| 5929 | syscall.finish(); |
| 5930 | break; |
| 5931 | }, |
| 5932 | .INTR => { |
| 5933 | try syscall.checkCancel(); |
| 5934 | continue; |
| 5935 | }, |
| 5936 | else => |e| { |
| 5937 | syscall.finish(); |
| 5938 | switch (e) { |
| 5939 | else => |err| return posix.unexpectedErrno(err), |
| 5940 | } |
| 5941 | }, |
| 5942 | } |
| 5943 | } |
| 5944 | dr.state = .reading; |
| 5945 | } |
| 5946 | const syscall: Syscall = try .start(); |
| 5947 | const n: usize = while (true) { |
| 5948 | const rc = posix.system._kern_read_dir(dr.dir.handle, dr.buffer.ptr, dr.buffer.len, @truncate(dr.buffer.len / @sizeOf(posix.system.DirEnt))); |
| 5949 | switch (@as(posix.E, @fromBackingInt(@intCast(@min(rc, 0))))) { |
| 5950 | .SUCCESS => { |
| 5951 | syscall.finish(); |
| 5952 | break @intCast(rc); |
| 5953 | }, |
| 5954 | .INTR => { |
| 5955 | try syscall.checkCancel(); |
| 5956 | continue; |
| 5957 | }, |
| 5958 | else => |e| { |
| 5959 | syscall.finish(); |
| 5960 | switch (e) { |
| 5961 | else => |err| return posix.unexpectedErrno(err), |
| 5962 | } |
| 5963 | }, |
| 5964 | } |
| 5965 | }; |
| 5966 | if (n == 0) { |
| 5967 | dr.state = .finished; |
| 5968 | return 0; |
| 5969 | } |
| 5970 | dr.index = 0; |
| 5971 | // _kern_read_dir returns entry count, but Dir.Reader is designed for byte count |
| 5972 | dr.end = 0; |
| 5973 | var i: usize = 0; |
| 5974 | while (i < n) : (i += 1) { |
| 5975 | const entry = @as(*align(1) posix.system.DirEnt, @ptrCast(&dr.buffer[dr.end])); |
| 5976 | dr.end += entry.reclen; |
| 5977 | } |
| 5978 | } |
| 5979 | const entry = @as(*align(1) posix.system.DirEnt, @ptrCast(&dr.buffer[dr.index])); |
| 5980 | const next_index = dr.index + entry.reclen; |
| 5981 | dr.index = next_index; |
| 5982 | |
| 5983 | const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0); |
| 5984 | if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or entry.ino == 0) continue; |
| 5985 | |
| 5986 | // haiku dirent doesn't expose type, so we have to call stat to get it. |
| 5987 | var stat: std.c.Stat = undefined; |
| 5988 | { |
| 5989 | const syscall: Syscall = try .start(); |
| 5990 | while (true) { |
| 5991 | const rc = posix.system._kern_read_stat(dr.dir.handle, name, false, &stat, @sizeOf(std.c.Stat)); |
| 5992 | switch (@as(posix.E, @fromBackingInt(@intCast(@min(rc, 0))))) { |
| 5993 | .SUCCESS => { |
| 5994 | syscall.finish(); |
| 5995 | break; |
| 5996 | }, |
| 5997 | .INTR => { |
| 5998 | try syscall.checkCancel(); |
| 5999 | continue; |
| 6000 | }, |
| 6001 | else => |e| { |
| 6002 | syscall.finish(); |
| 6003 | switch (e) { |
| 6004 | else => |err| return posix.unexpectedErrno(err), |
| 6005 | } |
| 6006 | }, |
| 6007 | } |
| 6008 | } |
| 6009 | } |
| 6010 | |
| 6011 | const entry_kind: File.Kind = switch (stat.mode & posix.S.IFMT) { |
| 6012 | posix.S.IFBLK => .block_device, |
| 6013 | posix.S.IFCHR => .character_device, |
| 6014 | posix.S.IFDIR => .directory, |
| 6015 | posix.S.IFIFO => .named_pipe, |
| 6016 | posix.S.IFLNK => .sym_link, |
| 6017 | posix.S.IFREG => .file, |
| 6018 | else => .unknown, |
| 6019 | }; |
| 6020 | buffer[buffer_index] = .{ |
| 6021 | .name = name, |
| 6022 | .kind = entry_kind, |
| 6023 | .inode = entry.ino, |
| 6024 | }; |
| 6025 | buffer_index += 1; |
| 6026 | } |
| 6027 | return buffer_index; |
| 6028 | } |
| 6029 | |
| 6030 | fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 6031 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6032 | _ = t; |
| 6033 | const w = windows; |
| 6034 | |
| 6035 | // We want to be able to use the `dr.buffer` for both the NtQueryDirectoryFile call (which |
| 6036 | // returns WTF-16 names) *and* as a buffer for storing those WTF-16 names as WTF-8 to be able |
| 6037 | // to return them in `Dir.Entry.name`. However, the problem that needs to be overcome in order to do |
| 6038 | // that is that each WTF-16 code unit can be encoded as a maximum of 3 WTF-8 bytes, which means |
| 6039 | // that it's not guaranteed that the memory used for the WTF-16 name will be sufficient |
| 6040 | // for the WTF-8 encoding of the same name (for example, € is encoded as one WTF-16 code unit, |
| 6041 | // [2 bytes] but encoded in WTF-8 as 3 bytes). |
| 6042 | // |
| 6043 | // The approach taken here is to "reserve" enough space in the `dr.buffer` to ensure that |
| 6044 | // at least one entry with the maximum possible WTF-8 name length can be stored without clobbering |
| 6045 | // any entries that follow it. That is, we determine how much space is needed to allow that, |
| 6046 | // and then only provide the remaining portion of `dr.buffer` to the NtQueryDirectoryFile |
| 6047 | // call. The WTF-16 names can then be safely converted using the full `dr.buffer` slice, making |
| 6048 | // sure that each name can only potentially overwrite the data of its own entry. |
| 6049 | // |
| 6050 | // The worst case, where an entry's name is both the maximum length of a component and |
| 6051 | // made up entirely of code points that are encoded as one WTF-16 code unit/three WTF-8 bytes, |
| 6052 | // would therefore look like the diagram below, and only one entry would be able to be returned: |
| 6053 | // |
| 6054 | // | reserved | remaining unreserved buffer | |
| 6055 | // | entry 1 | entry 2 | ... | |
| 6056 | // | wtf-8 name of entry 1 | |
| 6057 | // |
| 6058 | // However, in the average case we will be able to store more than one WTF-8 name at a time in the |
| 6059 | // available buffer and therefore we will be able to populate more than one `Dir.Entry` at a time. |
| 6060 | // That might look something like this (where name 1, name 2, etc are the converted WTF-8 names): |
| 6061 | // |
| 6062 | // | reserved | remaining unreserved buffer | |
| 6063 | // | entry 1 | entry 2 | ... | |
| 6064 | // | name 1 | name 2 | name 3 | name 4 | ... | |
| 6065 | // |
| 6066 | // Note: More than the minimum amount of space could be reserved to make the "worst case" |
| 6067 | // less likely, but since the worst-case also requires a maximum length component to matter, |
| 6068 | // it's unlikely for it to become a problem in normal scenarios even if all names on the filesystem |
| 6069 | // are made up of non-ASCII characters that have the "one WTF-16 code unit <-> three WTF-8 bytes" |
| 6070 | // property (e.g. code points >= U+0800 and <= U+FFFF), as it's unlikely for a significant |
| 6071 | // number of components to be maximum length. |
| 6072 | |
| 6073 | // We need `3 * NAME_MAX` bytes to store a max-length component as WTF-8 safely. |
| 6074 | // Because needing to store a max-length component depends on a `FileName` *with* the maximum |
| 6075 | // component length, we know that the corresponding populated `FILE_BOTH_DIR_INFORMATION` will |
| 6076 | // be of size `@sizeOf(w.FILE_BOTH_DIR_INFORMATION) + 2 * NAME_MAX` bytes, so we only need to |
| 6077 | // reserve enough to get us to up to having `3 * NAME_MAX` bytes available when taking into account |
| 6078 | // that we have the ability to write over top of the reserved memory + the full footprint of that |
| 6079 | // particular `FILE_BOTH_DIR_INFORMATION`. |
| 6080 | const max_info_len = @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2; |
| 6081 | const info_align = @alignOf(w.FILE_BOTH_DIR_INFORMATION); |
| 6082 | const reserve_needed = std.mem.alignForward(usize, Dir.max_name_bytes, info_align) - max_info_len; |
| 6083 | const unreserved_start = std.mem.alignForward(usize, reserve_needed, info_align); |
| 6084 | const unreserved_buffer = dr.buffer[unreserved_start..]; |
| 6085 | // This is enforced by `Dir.Reader` |
| 6086 | assert(unreserved_buffer.len >= max_info_len); |
| 6087 | |
| 6088 | var name_index: usize = 0; |
| 6089 | var buffer_index: usize = 0; |
| 6090 | while (buffer.len - buffer_index != 0) { |
| 6091 | if (dr.end - dr.index == 0) { |
| 6092 | // Refill the buffer, unless we've already created references to |
| 6093 | // buffered data. |
| 6094 | if (buffer_index != 0) break; |
| 6095 | |
| 6096 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 6097 | const syscall: Syscall = try .start(); |
| 6098 | const rc = while (true) switch (w.ntdll.NtQueryDirectoryFile( |
| 6099 | dr.dir.handle, |
| 6100 | null, |
| 6101 | null, |
| 6102 | null, |
| 6103 | &io_status_block, |
| 6104 | unreserved_buffer.ptr, |
| 6105 | std.math.lossyCast(w.ULONG, unreserved_buffer.len), |
| 6106 | .BothDirectory, |
| 6107 | .FALSE, |
| 6108 | null, |
| 6109 | .fromBool(dr.state == .reset), |
| 6110 | )) { |
| 6111 | .CANCELLED => { |
| 6112 | try syscall.checkCancel(); |
| 6113 | continue; |
| 6114 | }, |
| 6115 | else => |rc| { |
| 6116 | syscall.finish(); |
| 6117 | break rc; |
| 6118 | }, |
| 6119 | }; |
| 6120 | dr.state = .reading; |
| 6121 | if (io_status_block.Information == 0) { |
| 6122 | dr.state = .finished; |
| 6123 | return 0; |
| 6124 | } |
| 6125 | dr.index = 0; |
| 6126 | dr.end = io_status_block.Information; |
| 6127 | switch (rc) { |
| 6128 | .SUCCESS => {}, |
| 6129 | .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability |
| 6130 | else => return w.unexpectedStatus(rc), |
| 6131 | } |
| 6132 | } |
| 6133 | |
| 6134 | // While the official API docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly |
| 6135 | // this may not always be the case (e.g. due to faulty VM/sandboxing tools) |
| 6136 | const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&unreserved_buffer[dr.index])); |
| 6137 | const backtrack_index = dr.index; |
| 6138 | if (dir_info.NextEntryOffset != 0) { |
| 6139 | dr.index += dir_info.NextEntryOffset; |
| 6140 | } else { |
| 6141 | dr.index = dr.end; |
| 6142 | } |
| 6143 | |
| 6144 | const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; |
| 6145 | |
| 6146 | if (std.mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or std.mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) { |
| 6147 | continue; |
| 6148 | } |
| 6149 | |
| 6150 | // Read any relevant information from the `dir_info` now since it's possible the WTF-8 |
| 6151 | // name will overwrite it. |
| 6152 | const kind: File.Kind = blk: { |
| 6153 | const attrs = dir_info.FileAttributes; |
| 6154 | if (attrs.REPARSE_POINT) break :blk .sym_link; |
| 6155 | if (attrs.DIRECTORY) break :blk .directory; |
| 6156 | break :blk .file; |
| 6157 | }; |
| 6158 | const inode: File.INode = dir_info.FileIndex; |
| 6159 | |
| 6160 | // If there's no more space for WTF-8 names without bleeding over into |
| 6161 | // the remaining unprocessed entries, then backtrack and return what we have so far. |
| 6162 | if (name_index + std.unicode.calcWtf8Len(name_wtf16le) > unreserved_start + dr.index) { |
| 6163 | // We should always be able to fit at least one entry into the buffer no matter what |
| 6164 | assert(buffer_index != 0); |
| 6165 | dr.index = backtrack_index; |
| 6166 | break; |
| 6167 | } |
| 6168 | |
| 6169 | const name_buf = dr.buffer[name_index..]; |
| 6170 | const name_wtf8_len = std.unicode.wtf16LeToWtf8(name_buf, name_wtf16le); |
| 6171 | const name_wtf8 = name_buf[0..name_wtf8_len]; |
| 6172 | name_index += name_wtf8_len; |
| 6173 | |
| 6174 | buffer[buffer_index] = .{ |
| 6175 | .name = name_wtf8, |
| 6176 | .kind = kind, |
| 6177 | .inode = inode, |
| 6178 | }; |
| 6179 | buffer_index += 1; |
| 6180 | } |
| 6181 | |
| 6182 | return buffer_index; |
| 6183 | } |
| 6184 | |
| 6185 | fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 6186 | // We intentinally use fd_readdir even when linked with libc, since its |
| 6187 | // implementation is exactly the same as below, and we avoid the code |
| 6188 | // complexity here. |
| 6189 | const wasi = std.os.wasi; |
| 6190 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6191 | _ = t; |
| 6192 | const Header = extern struct { |
| 6193 | cookie: u64, |
| 6194 | }; |
| 6195 | const header: *align(@alignOf(usize)) Header = @ptrCast(dr.buffer.ptr); |
| 6196 | const header_end: usize = @sizeOf(Header); |
| 6197 | if (dr.index < header_end) { |
| 6198 | // Initialize header. |
| 6199 | dr.index = header_end; |
| 6200 | dr.end = header_end; |
| 6201 | header.* = .{ .cookie = wasi.DIRCOOKIE_START }; |
| 6202 | } |
| 6203 | var buffer_index: usize = 0; |
| 6204 | while (buffer.len - buffer_index != 0) { |
| 6205 | // According to the WASI spec, the last entry might be truncated, so we |
| 6206 | // need to check if the remaining buffer contains the whole dirent. |
| 6207 | if (dr.end - dr.index < @sizeOf(wasi.dirent_t)) { |
| 6208 | // Refill the buffer, unless we've already created references to |
| 6209 | // buffered data. |
| 6210 | if (buffer_index != 0) break; |
| 6211 | if (dr.state == .reset) { |
| 6212 | header.* = .{ .cookie = wasi.DIRCOOKIE_START }; |
| 6213 | dr.state = .reading; |
| 6214 | } |
| 6215 | const dents_buffer = dr.buffer[header_end..]; |
| 6216 | var n: usize = undefined; |
| 6217 | const syscall: Syscall = try .start(); |
| 6218 | while (true) { |
| 6219 | switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) { |
| 6220 | .SUCCESS => { |
| 6221 | syscall.finish(); |
| 6222 | break; |
| 6223 | }, |
| 6224 | .INTR => { |
| 6225 | try syscall.checkCancel(); |
| 6226 | continue; |
| 6227 | }, |
| 6228 | else => |e| { |
| 6229 | syscall.finish(); |
| 6230 | switch (e) { |
| 6231 | .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability. |
| 6232 | .FAULT => |err| return errnoBug(err), |
| 6233 | .NOTDIR => |err| return errnoBug(err), |
| 6234 | .INVAL => |err| return errnoBug(err), |
| 6235 | // To be consistent across platforms, iteration |
| 6236 | // ends if the directory being iterated is deleted |
| 6237 | // during iteration. This matches the behavior of |
| 6238 | // non-Linux, non-WASI UNIX platforms. |
| 6239 | .NOENT => { |
| 6240 | dr.state = .finished; |
| 6241 | return 0; |
| 6242 | }, |
| 6243 | .NOTCAPABLE => return error.AccessDenied, |
| 6244 | else => |err| return posix.unexpectedErrno(err), |
| 6245 | } |
| 6246 | }, |
| 6247 | } |
| 6248 | } |
| 6249 | if (n == 0) { |
| 6250 | dr.state = .finished; |
| 6251 | return 0; |
| 6252 | } |
| 6253 | dr.index = header_end; |
| 6254 | dr.end = header_end + n; |
| 6255 | } |
| 6256 | const entry: *align(1) wasi.dirent_t = @ptrCast(&dr.buffer[dr.index]); |
| 6257 | const entry_size = @sizeOf(wasi.dirent_t); |
| 6258 | const name_index = dr.index + entry_size; |
| 6259 | if (name_index + entry.namlen > dr.end) { |
| 6260 | // This case, the name is truncated, so we need to call readdir to store the entire name. |
| 6261 | dr.end = dr.index; // Force fd_readdir in the next loop. |
| 6262 | continue; |
| 6263 | } |
| 6264 | const name = dr.buffer[name_index..][0..entry.namlen]; |
| 6265 | const next_index = name_index + entry.namlen; |
| 6266 | dr.index = next_index; |
| 6267 | header.cookie = entry.next; |
| 6268 | |
| 6269 | if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) |
| 6270 | continue; |
| 6271 | |
| 6272 | const entry_kind: File.Kind = switch (entry.type) { |
| 6273 | .BLOCK_DEVICE => .block_device, |
| 6274 | .CHARACTER_DEVICE => .character_device, |
| 6275 | .DIRECTORY => .directory, |
| 6276 | .SYMBOLIC_LINK => .sym_link, |
| 6277 | .REGULAR_FILE => .file, |
| 6278 | .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, |
| 6279 | else => .unknown, |
| 6280 | }; |
| 6281 | buffer[buffer_index] = .{ |
| 6282 | .name = name, |
| 6283 | .kind = entry_kind, |
| 6284 | .inode = entry.ino, |
| 6285 | }; |
| 6286 | buffer_index += 1; |
| 6287 | } |
| 6288 | return buffer_index; |
| 6289 | } |
| 6290 | |
| 6291 | fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |
| 6292 | _ = userdata; |
| 6293 | _ = dir_reader; |
| 6294 | _ = buffer; |
| 6295 | return error.Unexpected; |
| 6296 | } |
| 6297 | |
| 6298 | const dirRealPathFile = switch (native_os) { |
| 6299 | .windows => dirRealPathFileWindows, |
| 6300 | else => dirRealPathFilePosix, |
| 6301 | }; |
| 6302 | |
| 6303 | fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize { |
| 6304 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6305 | _ = t; |
| 6306 | |
| 6307 | var path_name_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 6308 | |
| 6309 | const h_file = handle: { |
| 6310 | if (OpenFile(path_name_w.span(), .{ |
| 6311 | .dir = dir.handle, |
| 6312 | .access_mask = .{ |
| 6313 | .GENERIC = .{ .READ = true }, |
| 6314 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 6315 | }, |
| 6316 | .creation = .OPEN, |
| 6317 | .filter = .any, |
| 6318 | })) |handle| { |
| 6319 | break :handle handle; |
| 6320 | } else |err| switch (err) { |
| 6321 | error.WouldBlock => unreachable, |
| 6322 | else => |e| return e, |
| 6323 | } |
| 6324 | }; |
| 6325 | defer windows.CloseHandle(h_file); |
| 6326 | |
| 6327 | // We can re-use the path buffer for the WTF-16 representation since |
| 6328 | // we don't need the prefixed path anymore |
| 6329 | return realPathWindowsBuf(h_file, out_buffer, &path_name_w.data); |
| 6330 | } |
| 6331 | |
| 6332 | fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { |
| 6333 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 6334 | return realPathWindowsBuf(h_file, out_buffer, &wide_buf); |
| 6335 | } |
| 6336 | |
| 6337 | fn realPathWindowsBuf(h_file: windows.HANDLE, out_buffer: []u8, wtf16_buffer: []u16) File.RealPathError!usize { |
| 6338 | const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, wtf16_buffer); |
| 6339 | |
| 6340 | const len = std.unicode.calcWtf8Len(wide_slice); |
| 6341 | if (len > out_buffer.len) |
| 6342 | return error.NameTooLong; |
| 6343 | |
| 6344 | return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); |
| 6345 | } |
| 6346 | |
| 6347 | /// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. |
| 6348 | /// Defaults to DOS volume names. |
| 6349 | pub const GetFinalPathNameByHandleFormat = struct { |
| 6350 | volume_name: enum { |
| 6351 | /// Format as DOS volume name |
| 6352 | Dos, |
| 6353 | /// Format as NT volume name |
| 6354 | Nt, |
| 6355 | } = .Dos, |
| 6356 | }; |
| 6357 | |
| 6358 | pub const GetFinalPathNameByHandleError = error{ |
| 6359 | AccessDenied, |
| 6360 | FileNotFound, |
| 6361 | NameTooLong, |
| 6362 | /// The volume does not contain a recognized file system. File system |
| 6363 | /// drivers might not be loaded, or the volume may be corrupt. |
| 6364 | UnrecognizedVolume, |
| 6365 | } || Io.Cancelable || Io.UnexpectedError; |
| 6366 | |
| 6367 | /// Returns canonical (normalized) path of handle. |
| 6368 | /// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include |
| 6369 | /// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). |
| 6370 | /// If DOS volume name format is selected, note that this function does *not* prepend |
| 6371 | /// `\\?\` prefix to the resultant path. |
| 6372 | pub fn GetFinalPathNameByHandle( |
| 6373 | hFile: windows.HANDLE, |
| 6374 | fmt: GetFinalPathNameByHandleFormat, |
| 6375 | out_buffer: []u16, |
| 6376 | ) GetFinalPathNameByHandleError![]u16 { |
| 6377 | const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) { |
| 6378 | // we assume InvalidHandle is close enough to FileNotFound in semantics |
| 6379 | // to not further complicate the error set |
| 6380 | error.InvalidHandle => return error.FileNotFound, |
| 6381 | else => |e| return e, |
| 6382 | }; |
| 6383 | |
| 6384 | switch (fmt.volume_name) { |
| 6385 | .Nt => { |
| 6386 | // the returned path is already in .Nt format |
| 6387 | return final_path; |
| 6388 | }, |
| 6389 | .Dos => { |
| 6390 | // parse the string to separate volume path from file path |
| 6391 | const device_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); |
| 6392 | |
| 6393 | // We aren't entirely sure of the structure of the path returned by |
| 6394 | // QueryObjectName in all contexts/environments. |
| 6395 | // This code is written to cover the various cases that have |
| 6396 | // been encountered and solved appropriately. But note that there's |
| 6397 | // no easy way to verify that they have all been tackled! |
| 6398 | // (Unless you, the reader knows of one then please do action that!) |
| 6399 | if (!std.mem.startsWith(u16, final_path, device_prefix)) { |
| 6400 | // Wine seems to return NT namespaced paths starting with \??\ from QueryObjectName |
| 6401 | // (e.g. `\??\Z:\some\path\to\a\file.txt`), in which case we can just strip the |
| 6402 | // prefix to turn it into an absolute path. |
| 6403 | // https://github.com/ziglang/zig/issues/26029 |
| 6404 | // https://bugs.winehq.org/show_bug.cgi?id=39569 |
| 6405 | return windows.ntToWin32Namespace(final_path, out_buffer) catch |err| switch (err) { |
| 6406 | error.NotNtPath => return error.Unexpected, |
| 6407 | error.NameTooLong => |e| return e, |
| 6408 | }; |
| 6409 | } |
| 6410 | |
| 6411 | const file_path_begin_index = std.mem.findPos(u16, final_path, device_prefix.len, &[_]u16{'\\'}) orelse unreachable; |
| 6412 | const volume_name_u16 = final_path[0..file_path_begin_index]; |
| 6413 | const device_name_u16 = volume_name_u16[device_prefix.len..]; |
| 6414 | const file_name_u16 = final_path[file_path_begin_index..]; |
| 6415 | |
| 6416 | // MUP is Multiple UNC Provider, and indicates that the path is a UNC |
| 6417 | // path. In this case, the canonical UNC path can be gotten by just |
| 6418 | // dropping the \Device\Mup\ and making sure the path begins with \\ |
| 6419 | if (std.mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) { |
| 6420 | out_buffer[0] = '\\'; |
| 6421 | @memmove(out_buffer[1..][0..file_name_u16.len], file_name_u16); |
| 6422 | return out_buffer[0 .. 1 + file_name_u16.len]; |
| 6423 | } |
| 6424 | |
| 6425 | // Get DOS volume name. DOS volume names are actually symbolic link objects to the |
| 6426 | // actual NT volume. For example: |
| 6427 | // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: |
| 6428 | const MIN_SIZE = @sizeOf(windows.MOUNTMGR_MOUNT_POINT) + windows.MAX_PATH; |
| 6429 | // We initialize the input buffer to all zeros for convenience since |
| 6430 | // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this. |
| 6431 | var input_buf: [MIN_SIZE]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINT)) = @splat(0); |
| 6432 | var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINTS)) = undefined; |
| 6433 | |
| 6434 | // This surprising path is a filesystem path to the mount manager on Windows. |
| 6435 | // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points |
| 6436 | // This is the NT namespaced version of \\.\MountPointManager |
| 6437 | const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager"); |
| 6438 | const mgmt_handle = OpenFile(mgmt_path_u16, .{ |
| 6439 | .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } }, |
| 6440 | .creation = .OPEN, |
| 6441 | }) catch |err| switch (err) { |
| 6442 | error.IsDir => return error.Unexpected, |
| 6443 | error.NotDir => return error.Unexpected, |
| 6444 | error.NoDevice => return error.Unexpected, |
| 6445 | error.AccessDenied => return error.Unexpected, |
| 6446 | error.PipeBusy => return error.Unexpected, |
| 6447 | error.FileBusy => return error.Unexpected, |
| 6448 | error.PathAlreadyExists => return error.Unexpected, |
| 6449 | error.WouldBlock => return error.Unexpected, |
| 6450 | error.NetworkNotFound => return error.Unexpected, |
| 6451 | error.AntivirusInterference => return error.Unexpected, |
| 6452 | error.BadPathName => return error.Unexpected, |
| 6453 | else => |e| return e, |
| 6454 | }; |
| 6455 | defer windows.CloseHandle(mgmt_handle); |
| 6456 | |
| 6457 | var input_struct: *windows.MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]); |
| 6458 | input_struct.DeviceNameOffset = @sizeOf(windows.MOUNTMGR_MOUNT_POINT); |
| 6459 | input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2); |
| 6460 | @memcpy(input_buf[@sizeOf(windows.MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr))); |
| 6461 | |
| 6462 | switch ((try deviceIoControl(&.{ |
| 6463 | .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = false } }, |
| 6464 | .code = windows.IOCTL.MOUNTMGR.QUERY_POINTS, |
| 6465 | .in = &input_buf, |
| 6466 | .out = &output_buf, |
| 6467 | })).u.Status) { |
| 6468 | .SUCCESS => {}, |
| 6469 | .CANCELLED => unreachable, |
| 6470 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 6471 | else => |status| return windows.unexpectedStatus(status), |
| 6472 | } |
| 6473 | const mount_points_struct: *const windows.MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]); |
| 6474 | |
| 6475 | const mount_points = @as( |
| 6476 | [*]const windows.MOUNTMGR_MOUNT_POINT, |
| 6477 | @ptrCast(&mount_points_struct.MountPoints[0]), |
| 6478 | )[0..mount_points_struct.NumberOfMountPoints]; |
| 6479 | |
| 6480 | for (mount_points) |mount_point| { |
| 6481 | const symlink = @as( |
| 6482 | [*]const u16, |
| 6483 | @ptrCast(@alignCast(&output_buf[mount_point.SymbolicLinkNameOffset])), |
| 6484 | )[0 .. mount_point.SymbolicLinkNameLength / 2]; |
| 6485 | |
| 6486 | // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks |
| 6487 | // with traditional DOS drive letters, so pick the first one available. |
| 6488 | var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); |
| 6489 | const prefix = prefix_buf[0..prefix_buf.len]; |
| 6490 | |
| 6491 | if (std.mem.startsWith(u16, symlink, prefix)) { |
| 6492 | const drive_letter = symlink[prefix.len..]; |
| 6493 | |
| 6494 | if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; |
| 6495 | |
| 6496 | @memcpy(out_buffer[0..drive_letter.len], drive_letter); |
| 6497 | @memmove(out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16); |
| 6498 | const total_len = drive_letter.len + file_name_u16.len; |
| 6499 | |
| 6500 | // Validate that DOS does not contain any spurious nul bytes. |
| 6501 | assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null); |
| 6502 | |
| 6503 | return out_buffer[0..total_len]; |
| 6504 | } else if (mountmgrIsVolumeName(symlink)) { |
| 6505 | // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d}, |
| 6506 | // then it is a volume mounted as a path rather than a drive letter. We need to |
| 6507 | // query the mount manager again to get the DOS path for the volume. |
| 6508 | |
| 6509 | // 49 is the maximum length accepted by mountmgrIsVolumeName |
| 6510 | const vol_input_size = @sizeOf(windows.MOUNTMGR_TARGET_NAME) + (49 * 2); |
| 6511 | var vol_input_buf: [vol_input_size]u8 align(@alignOf(windows.MOUNTMGR_TARGET_NAME)) = @splat(0); |
| 6512 | // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path, |
| 6513 | // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>). |
| 6514 | // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here. |
| 6515 | const min_output_size = @sizeOf(windows.MOUNTMGR_VOLUME_PATHS) + (windows.PATH_MAX_WIDE * 2); |
| 6516 | var vol_output_buf: [min_output_size]u8 align(@alignOf(windows.MOUNTMGR_VOLUME_PATHS)) = undefined; |
| 6517 | |
| 6518 | var vol_input_struct: *windows.MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]); |
| 6519 | vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); |
| 6520 | @memcpy(@as([*]windows.WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); |
| 6521 | |
| 6522 | switch ((try deviceIoControl(&.{ |
| 6523 | .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = true } }, |
| 6524 | .code = windows.IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, |
| 6525 | .in = &vol_input_buf, |
| 6526 | .out = &vol_output_buf, |
| 6527 | })).u.Status) { |
| 6528 | .SUCCESS => {}, |
| 6529 | .CANCELLED => unreachable, |
| 6530 | .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume, |
| 6531 | else => |status| return windows.unexpectedStatus(status), |
| 6532 | } |
| 6533 | const volume_paths_struct: *const windows.MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); |
| 6534 | const volume_path = std.mem.sliceTo(@as( |
| 6535 | [*]const u16, |
| 6536 | &volume_paths_struct.MultiSz, |
| 6537 | )[0 .. volume_paths_struct.MultiSzLength / 2], 0); |
| 6538 | |
| 6539 | if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong; |
| 6540 | |
| 6541 | // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where |
| 6542 | // we want to place the filename before returning. Here are the possible overlapping cases: |
| 6543 | // |
| 6544 | // out_buffer: [filename] |
| 6545 | // dest: [___(a)___] [___(b)___] |
| 6546 | // |
| 6547 | // In the case of (a), we need to copy forwards, and in the case of (b) we need |
| 6548 | // to copy backwards. We also need to do this before copying the volume path because |
| 6549 | // it could overwrite the file_name_u16 memory. |
| 6550 | const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len]; |
| 6551 | @memmove(file_name_dest, file_name_u16); |
| 6552 | @memcpy(out_buffer[0..volume_path.len], volume_path); |
| 6553 | const total_len = volume_path.len + file_name_u16.len; |
| 6554 | |
| 6555 | // Validate that DOS does not contain any spurious nul bytes. |
| 6556 | assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null); |
| 6557 | |
| 6558 | return out_buffer[0..total_len]; |
| 6559 | } |
| 6560 | } |
| 6561 | |
| 6562 | // If we've ended up here, then something went wrong/is corrupted in the OS, |
| 6563 | // so error out! |
| 6564 | return error.FileNotFound; |
| 6565 | }, |
| 6566 | } |
| 6567 | } |
| 6568 | |
| 6569 | test GetFinalPathNameByHandle { |
| 6570 | if (builtin.os.tag != .windows) |
| 6571 | return; |
| 6572 | |
| 6573 | //any file will do |
| 6574 | var tmp = std.testing.tmpDir(.{}); |
| 6575 | defer tmp.cleanup(); |
| 6576 | const handle = tmp.dir.handle; |
| 6577 | var buffer: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 6578 | |
| 6579 | //check with sufficient size |
| 6580 | const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer); |
| 6581 | _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer); |
| 6582 | |
| 6583 | const required_len_in_u16 = nt_path.len + @divExact(@intFromPtr(nt_path.ptr) - @intFromPtr(&buffer), 2) + 1; |
| 6584 | //check with insufficient size |
| 6585 | try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); |
| 6586 | try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1])); |
| 6587 | |
| 6588 | //check with exactly-sufficient size |
| 6589 | _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]); |
| 6590 | _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); |
| 6591 | } |
| 6592 | |
| 6593 | /// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h |
| 6594 | fn mountmgrIsVolumeName(name: []const u16) bool { |
| 6595 | return (name.len == 48 or (name.len == 49 and name[48] == std.mem.nativeToLittle(u16, '\\'))) and |
| 6596 | name[0] == std.mem.nativeToLittle(u16, '\\') and |
| 6597 | (name[1] == std.mem.nativeToLittle(u16, '?') or name[1] == std.mem.nativeToLittle(u16, '\\')) and |
| 6598 | name[2] == std.mem.nativeToLittle(u16, '?') and |
| 6599 | name[3] == std.mem.nativeToLittle(u16, '\\') and |
| 6600 | std.mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and |
| 6601 | name[19] == std.mem.nativeToLittle(u16, '-') and |
| 6602 | name[24] == std.mem.nativeToLittle(u16, '-') and |
| 6603 | name[29] == std.mem.nativeToLittle(u16, '-') and |
| 6604 | name[34] == std.mem.nativeToLittle(u16, '-') and |
| 6605 | name[47] == std.mem.nativeToLittle(u16, '}'); |
| 6606 | } |
| 6607 | |
| 6608 | test mountmgrIsVolumeName { |
| 6609 | @setEvalBranchQuota(2000); |
| 6610 | const L = std.unicode.utf8ToUtf16LeStringLiteral; |
| 6611 | try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); |
| 6612 | try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); |
| 6613 | try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); |
| 6614 | try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); |
| 6615 | try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); |
| 6616 | try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo"))); |
| 6617 | try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}"))); |
| 6618 | } |
| 6619 | |
| 6620 | pub const QueryObjectNameError = error{ |
| 6621 | AccessDenied, |
| 6622 | InvalidHandle, |
| 6623 | NameTooLong, |
| 6624 | Unexpected, |
| 6625 | }; |
| 6626 | |
| 6627 | pub fn QueryObjectName(handle: windows.HANDLE, out_buffer: []u16) QueryObjectNameError![]u16 { |
| 6628 | const out_buffer_aligned = std.mem.alignInSlice(out_buffer, @alignOf(windows.OBJECT.NAME_INFORMATION)) orelse return error.NameTooLong; |
| 6629 | |
| 6630 | const info: *windows.OBJECT.NAME_INFORMATION = @ptrCast(out_buffer_aligned); |
| 6631 | // buffer size is specified in bytes |
| 6632 | const out_buffer_len = std.math.cast(windows.ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(windows.ULONG); |
| 6633 | // last argument would return the length required for full_buffer, not exposed here |
| 6634 | return switch (windows.ntdll.NtQueryObject(handle, .Name, info, out_buffer_len, null)) { |
| 6635 | .SUCCESS => { |
| 6636 | // info.Name from ObQueryNameString is documented to be empty if the object |
| 6637 | // was "unnamed", not sure if this can happen for file handles |
| 6638 | return if (info.Name.isEmpty()) error.Unexpected else info.Name.slice(); |
| 6639 | }, |
| 6640 | .ACCESS_DENIED => error.AccessDenied, |
| 6641 | .INVALID_HANDLE => error.InvalidHandle, |
| 6642 | // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), |
| 6643 | // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) |
| 6644 | .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, |
| 6645 | else => |e| windows.unexpectedStatus(e), |
| 6646 | }; |
| 6647 | } |
| 6648 | |
| 6649 | test QueryObjectName { |
| 6650 | if (builtin.os.tag != .windows) |
| 6651 | return; |
| 6652 | |
| 6653 | //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. |
| 6654 | var tmp = std.testing.tmpDir(.{}); |
| 6655 | defer tmp.cleanup(); |
| 6656 | const handle = tmp.dir.handle; |
| 6657 | var out_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 6658 | |
| 6659 | const result_path = try QueryObjectName(handle, &out_buffer); |
| 6660 | const required_len_in_u16 = result_path.len + @divExact(@intFromPtr(result_path.ptr) - @intFromPtr(&out_buffer), 2) + 1; |
| 6661 | //insufficient size |
| 6662 | try std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); |
| 6663 | //exactly-sufficient size |
| 6664 | _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); |
| 6665 | } |
| 6666 | |
| 6667 | const Wtf16ToPrefixedFileWError = error{ |
| 6668 | AccessDenied, |
| 6669 | FileNotFound, |
| 6670 | } || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; |
| 6671 | |
| 6672 | const Wtf16ToPrefixedFileWOptions = struct { |
| 6673 | allow_relative: bool = true, |
| 6674 | }; |
| 6675 | |
| 6676 | /// Converts the `path` to WTF16, null-terminated. If the path contains any |
| 6677 | /// namespace prefix, or is anything but a relative path (rooted, drive relative, |
| 6678 | /// etc) the result will have the NT-style prefix `\??\`. |
| 6679 | /// |
| 6680 | /// Similar to RtlDosPathNameToNtPathName_U with a few differences: |
| 6681 | /// - Does not allocate on the heap. |
| 6682 | /// - Relative paths are kept as relative unless they contain too many .. |
| 6683 | /// components, in which case they are resolved against the `dir` if it |
| 6684 | /// is non-null, or the CWD if it is null. |
| 6685 | /// - Special case device names like COM1, NUL, etc are not handled specially (TODO) |
| 6686 | /// - . and space are not stripped from the end of relative paths (potential TODO) |
| 6687 | pub fn wToPrefixedFileW(dir: ?windows.HANDLE, path: [:0]const u16, options: Wtf16ToPrefixedFileWOptions) Wtf16ToPrefixedFileWError!WindowsPathSpace { |
| 6688 | const nt_prefix = [_]u16{ '\\', '?', '?', '\\' }; |
| 6689 | if (windows.hasCommonNtPrefix(u16, path)) { |
| 6690 | // TODO: Figure out a way to design an API that can avoid the copy for NT, |
| 6691 | // since it is always returned fully unmodified. |
| 6692 | var path_space: WindowsPathSpace = undefined; |
| 6693 | path_space.data[0..nt_prefix.len].* = nt_prefix; |
| 6694 | const len_after_prefix = path.len - nt_prefix.len; |
| 6695 | @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); |
| 6696 | path_space.len = path.len; |
| 6697 | path_space.data[path_space.len] = 0; |
| 6698 | return path_space; |
| 6699 | } else { |
| 6700 | const path_type = Dir.path.getWin32PathType(u16, path); |
| 6701 | var path_space: WindowsPathSpace = undefined; |
| 6702 | if (path_type == .local_device) switch (getLocalDevicePathType(u16, path)) { |
| 6703 | .verbatim => { |
| 6704 | path_space.data[0..nt_prefix.len].* = nt_prefix; |
| 6705 | const len_after_prefix = path.len - nt_prefix.len; |
| 6706 | @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); |
| 6707 | path_space.len = path.len; |
| 6708 | path_space.data[path_space.len] = 0; |
| 6709 | return path_space; |
| 6710 | }, |
| 6711 | .local_device, .fake_verbatim => { |
| 6712 | const path_byte_len = windows.ntdll.RtlGetFullPathName_U( |
| 6713 | path.ptr, |
| 6714 | path_space.data.len * 2, |
| 6715 | &path_space.data, |
| 6716 | null, |
| 6717 | ); |
| 6718 | if (path_byte_len == 0) { |
| 6719 | // TODO: This may not be the right error |
| 6720 | return error.BadPathName; |
| 6721 | } else if (path_byte_len / 2 > path_space.data.len) { |
| 6722 | return error.NameTooLong; |
| 6723 | } |
| 6724 | path_space.len = path_byte_len / 2; |
| 6725 | // Both prefixes will be normalized but retained, so all |
| 6726 | // we need to do now is replace them with the NT prefix |
| 6727 | path_space.data[0..nt_prefix.len].* = nt_prefix; |
| 6728 | return path_space; |
| 6729 | }, |
| 6730 | }; |
| 6731 | if (options.allow_relative and path_type == .relative) relative: { |
| 6732 | // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc. |
| 6733 | // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html |
| 6734 | |
| 6735 | // TODO: Potentially strip all trailing . and space characters from the |
| 6736 | // end of the path. This is something that both RtlDosPathNameToNtPathName_U |
| 6737 | // and RtlGetFullPathName_U do. Technically, trailing . and spaces |
| 6738 | // are allowed, but such paths may not interact well with Windows (i.e. |
| 6739 | // files with these paths can't be deleted from explorer.exe, etc). |
| 6740 | // This could be something that normalizePath may want to do. |
| 6741 | |
| 6742 | @memcpy(path_space.data[0..path.len], path); |
| 6743 | // Try to normalize, but if we get too many parent directories, |
| 6744 | // then we need to start over and use RtlGetFullPathName_U instead. |
| 6745 | path_space.len = windows.normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) { |
| 6746 | error.TooManyParentDirs => break :relative, |
| 6747 | }; |
| 6748 | path_space.data[path_space.len] = 0; |
| 6749 | return path_space; |
| 6750 | } |
| 6751 | // We now know we are going to return an absolute NT path, so |
| 6752 | // we can unconditionally prefix it with the NT prefix. |
| 6753 | path_space.data[0..nt_prefix.len].* = nt_prefix; |
| 6754 | if (path_type == .root_local_device) { |
| 6755 | // `\\.` and `\\?` always get converted to `\??\` exactly, so |
| 6756 | // we can just stop here |
| 6757 | path_space.len = nt_prefix.len; |
| 6758 | path_space.data[path_space.len] = 0; |
| 6759 | return path_space; |
| 6760 | } |
| 6761 | const path_buf_offset = switch (path_type) { |
| 6762 | // UNC paths will always start with `\\`. However, we want to |
| 6763 | // end up with something like `\??\UNC\server\share`, so to get |
| 6764 | // RtlGetFullPathName to write into the spot we want the `server` |
| 6765 | // part to end up, we need to provide an offset such that |
| 6766 | // the `\\` part gets written where the `C\` of `UNC\` will be |
| 6767 | // in the final NT path. |
| 6768 | .unc_absolute => nt_prefix.len + 2, |
| 6769 | else => nt_prefix.len, |
| 6770 | }; |
| 6771 | const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset); |
| 6772 | const path_to_get: [:0]const u16 = path_to_get: { |
| 6773 | // If dir is null, then we don't need to bother with GetFinalPathNameByHandle because |
| 6774 | // RtlGetFullPathName_U will resolve relative paths against the CWD for us. |
| 6775 | if (path_type != .relative or dir == null) { |
| 6776 | break :path_to_get path; |
| 6777 | } |
| 6778 | // We can also skip GetFinalPathNameByHandle if the handle matches |
| 6779 | // the handle returned by Io.Dir.cwd() |
| 6780 | if (dir.? == Io.Dir.cwd().handle) { |
| 6781 | break :path_to_get path; |
| 6782 | } |
| 6783 | // At this point, we know we have a relative path that had too many |
| 6784 | // `..` components to be resolved by normalizePath, so we need to |
| 6785 | // convert it into an absolute path and let RtlGetFullPathName_U |
| 6786 | // canonicalize it. We do this by getting the path of the `dir` |
| 6787 | // and appending the relative path to it. |
| 6788 | var dir_path_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; |
| 6789 | const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) { |
| 6790 | // This mapping is not correct; it is actually expected |
| 6791 | // that calling GetFinalPathNameByHandle might return |
| 6792 | // error.UnrecognizedVolume, and in fact has been observed |
| 6793 | // in the wild. The problem is that wToPrefixedFileW was |
| 6794 | // never intended to make *any* OS syscall APIs. It's only |
| 6795 | // supposed to convert a string to one that is eligible to |
| 6796 | // be used in the ntdll syscalls. |
| 6797 | // |
| 6798 | // To solve this, this function needs to no longer call |
| 6799 | // GetFinalPathNameByHandle under any conditions, or the |
| 6800 | // calling function needs to get reworked to not need to |
| 6801 | // call this function. |
| 6802 | // |
| 6803 | // This may involve making breaking API changes. |
| 6804 | error.UnrecognizedVolume => return error.Unexpected, |
| 6805 | else => |e| return e, |
| 6806 | }; |
| 6807 | if (dir_path.len + 1 + path.len > windows.PATH_MAX_WIDE) { |
| 6808 | return error.NameTooLong; |
| 6809 | } |
| 6810 | // We don't have to worry about potentially doubling up path separators |
| 6811 | // here since RtlGetFullPathName_U will handle canonicalizing it. |
| 6812 | dir_path_buf[dir_path.len] = '\\'; |
| 6813 | @memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path); |
| 6814 | const full_len = dir_path.len + 1 + path.len; |
| 6815 | dir_path_buf[full_len] = 0; |
| 6816 | break :path_to_get dir_path_buf[0..full_len :0]; |
| 6817 | }; |
| 6818 | const path_byte_len = windows.ntdll.RtlGetFullPathName_U( |
| 6819 | path_to_get.ptr, |
| 6820 | buf_len * 2, |
| 6821 | path_space.data[path_buf_offset..].ptr, |
| 6822 | null, |
| 6823 | ); |
| 6824 | if (path_byte_len == 0) { |
| 6825 | // TODO: This may not be the right error |
| 6826 | return error.BadPathName; |
| 6827 | } else if (path_byte_len / 2 > buf_len) { |
| 6828 | return error.NameTooLong; |
| 6829 | } |
| 6830 | path_space.len = path_buf_offset + (path_byte_len / 2); |
| 6831 | if (path_type == .unc_absolute) { |
| 6832 | // Now add in the UNC, the `C` should overwrite the first `\` of the |
| 6833 | // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>` |
| 6834 | assert(path_space.data[path_buf_offset] == '\\'); |
| 6835 | assert(path_space.data[path_buf_offset + 1] == '\\'); |
| 6836 | const unc = [_]u16{ 'U', 'N', 'C' }; |
| 6837 | path_space.data[nt_prefix.len..][0..unc.len].* = unc; |
| 6838 | } |
| 6839 | return path_space; |
| 6840 | } |
| 6841 | } |
| 6842 | |
| 6843 | const LocalDevicePathType = enum { |
| 6844 | /// `\\.\` (path separators can be `\` or `/`) |
| 6845 | local_device, |
| 6846 | /// `\\?\` |
| 6847 | /// When converted to an NT path, everything past the prefix is left |
| 6848 | /// untouched and `\\?\` is replaced by `\??\`. |
| 6849 | verbatim, |
| 6850 | /// `\\?\` without all path separators being `\`. |
| 6851 | /// This seems to be recognized as a prefix, but the 'verbatim' aspect |
| 6852 | /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path, |
| 6853 | /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't |
| 6854 | /// be treated as part of the final path]) |
| 6855 | fake_verbatim, |
| 6856 | }; |
| 6857 | |
| 6858 | /// Only relevant for Win32 -> NT path conversion. |
| 6859 | /// Asserts `path` is of type `Dir.path.Win32PathType.local_device`. |
| 6860 | fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType { |
| 6861 | if (std.debug.runtime_safety) { |
| 6862 | assert(Dir.path.getWin32PathType(T, path) == .local_device); |
| 6863 | } |
| 6864 | |
| 6865 | const backslash = std.mem.nativeToLittle(T, '\\'); |
| 6866 | const all_backslash = path[0] == backslash and |
| 6867 | path[1] == backslash and |
| 6868 | path[3] == backslash; |
| 6869 | return switch (path[2]) { |
| 6870 | std.mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim, |
| 6871 | std.mem.nativeToLittle(T, '.') => .local_device, |
| 6872 | else => unreachable, |
| 6873 | }; |
| 6874 | } |
| 6875 | |
| 6876 | pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError; |
| 6877 | |
| 6878 | /// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path. |
| 6879 | /// https://wtf-8.codeberg.page/ |
| 6880 | pub fn sliceToPrefixedFileW(dir: ?windows.HANDLE, path: []const u8, options: Wtf16ToPrefixedFileWOptions) Wtf8ToPrefixedFileWError!WindowsPathSpace { |
| 6881 | var temp_path: WindowsPathSpace = undefined; |
| 6882 | temp_path.len = std.unicode.wtf8ToWtf16Le(&temp_path.data, path) catch |err| switch (err) { |
| 6883 | error.InvalidWtf8 => return error.BadPathName, |
| 6884 | }; |
| 6885 | temp_path.data[temp_path.len] = 0; |
| 6886 | return wToPrefixedFileW(dir, temp_path.span(), options); |
| 6887 | } |
| 6888 | |
| 6889 | pub const WindowsPathSpace = struct { |
| 6890 | data: [windows.PATH_MAX_WIDE:0]u16, |
| 6891 | len: usize, |
| 6892 | |
| 6893 | pub fn span(wps: *const WindowsPathSpace) [:0]const u16 { |
| 6894 | return wps.data[0..wps.len :0]; |
| 6895 | } |
| 6896 | |
| 6897 | pub fn string(wps: *const WindowsPathSpace) windows.UNICODE_STRING { |
| 6898 | return .init(wps.span()); |
| 6899 | } |
| 6900 | }; |
| 6901 | |
| 6902 | fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize { |
| 6903 | if (native_os == .wasi) return error.OperationUnsupported; |
| 6904 | |
| 6905 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6906 | _ = t; |
| 6907 | |
| 6908 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 6909 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 6910 | |
| 6911 | if (builtin.link_libc and dir.handle == posix.AT.FDCWD) { |
| 6912 | if (out_buffer.len < posix.PATH_MAX) return error.NameTooLong; |
| 6913 | const syscall: Syscall = try .start(); |
| 6914 | while (true) { |
| 6915 | if (std.c.realpath(sub_path_posix, out_buffer.ptr)) |redundant_pointer| { |
| 6916 | syscall.finish(); |
| 6917 | assert(redundant_pointer == out_buffer.ptr); |
| 6918 | return std.mem.findScalar(u8, out_buffer, 0) orelse out_buffer.len; |
| 6919 | } |
| 6920 | const err: posix.E = @fromBackingInt(@intCast(std.c._errno().*)); |
| 6921 | if (err == .INTR) { |
| 6922 | try syscall.checkCancel(); |
| 6923 | continue; |
| 6924 | } |
| 6925 | syscall.finish(); |
| 6926 | switch (err) { |
| 6927 | .INVAL => return errnoBug(err), |
| 6928 | .BADF => return errnoBug(err), |
| 6929 | .FAULT => return errnoBug(err), |
| 6930 | .ACCES => return error.AccessDenied, |
| 6931 | .NOENT => return error.FileNotFound, |
| 6932 | .OPNOTSUPP => return error.OperationUnsupported, |
| 6933 | .NOTDIR => return error.NotDir, |
| 6934 | .NAMETOOLONG => return error.NameTooLong, |
| 6935 | .LOOP => return error.SymLinkLoop, |
| 6936 | .IO => return error.InputOutput, |
| 6937 | else => return posix.unexpectedErrno(err), |
| 6938 | } |
| 6939 | } |
| 6940 | } |
| 6941 | |
| 6942 | var flags: posix.O = .{}; |
| 6943 | if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true; |
| 6944 | if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; |
| 6945 | if (@hasField(posix.O, "PATH")) flags.PATH = true; |
| 6946 | |
| 6947 | const mode: posix.mode_t = 0; |
| 6948 | |
| 6949 | const syscall: Syscall = try .start(); |
| 6950 | const fd: posix.fd_t = while (true) { |
| 6951 | const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); |
| 6952 | switch (posix.errno(rc)) { |
| 6953 | .SUCCESS => { |
| 6954 | syscall.finish(); |
| 6955 | break @intCast(rc); |
| 6956 | }, |
| 6957 | .INTR => { |
| 6958 | try syscall.checkCancel(); |
| 6959 | continue; |
| 6960 | }, |
| 6961 | else => |e| { |
| 6962 | syscall.finish(); |
| 6963 | switch (e) { |
| 6964 | .FAULT => |err| return errnoBug(err), |
| 6965 | .INVAL => return error.BadPathName, |
| 6966 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 6967 | .ACCES => return error.AccessDenied, |
| 6968 | .FBIG => return error.FileTooBig, |
| 6969 | .OVERFLOW => return error.FileTooBig, |
| 6970 | .ISDIR => return error.IsDir, |
| 6971 | .LOOP => return error.SymLinkLoop, |
| 6972 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 6973 | .NAMETOOLONG => return error.NameTooLong, |
| 6974 | .NFILE => return error.SystemFdQuotaExceeded, |
| 6975 | .NODEV => return error.NoDevice, |
| 6976 | .NOENT => return error.FileNotFound, |
| 6977 | .SRCH => return error.FileNotFound, // Linux when accessing procfs. |
| 6978 | .NOMEM => return error.SystemResources, |
| 6979 | .NOSPC => return error.NoSpaceLeft, |
| 6980 | .NOTDIR => return error.NotDir, |
| 6981 | .PERM => return error.PermissionDenied, |
| 6982 | .EXIST => return error.PathAlreadyExists, |
| 6983 | .BUSY => return error.DeviceBusy, |
| 6984 | .NXIO => return error.NoDevice, |
| 6985 | .ILSEQ => return error.BadPathName, |
| 6986 | else => |err| return posix.unexpectedErrno(err), |
| 6987 | } |
| 6988 | }, |
| 6989 | } |
| 6990 | }; |
| 6991 | defer closeFd(fd); |
| 6992 | return realPathPosix(fd, out_buffer); |
| 6993 | } |
| 6994 | |
| 6995 | const dirRealPath = switch (native_os) { |
| 6996 | .windows => dirRealPathWindows, |
| 6997 | else => dirRealPathPosix, |
| 6998 | }; |
| 6999 | |
| 7000 | fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize { |
| 7001 | if (native_os == .wasi) return error.OperationUnsupported; |
| 7002 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7003 | _ = t; |
| 7004 | return realPathPosix(dir.handle, out_buffer); |
| 7005 | } |
| 7006 | |
| 7007 | fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize { |
| 7008 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7009 | _ = t; |
| 7010 | return realPathWindows(dir.handle, out_buffer); |
| 7011 | } |
| 7012 | |
| 7013 | const fileRealPath = switch (native_os) { |
| 7014 | .windows => fileRealPathWindows, |
| 7015 | else => fileRealPathPosix, |
| 7016 | }; |
| 7017 | |
| 7018 | fn fileRealPathWindows(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { |
| 7019 | if (native_os == .wasi) return error.OperationUnsupported; |
| 7020 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7021 | _ = t; |
| 7022 | return realPathWindows(file.handle, out_buffer); |
| 7023 | } |
| 7024 | |
| 7025 | fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize { |
| 7026 | if (native_os == .wasi) return error.OperationUnsupported; |
| 7027 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7028 | _ = t; |
| 7029 | return realPathPosix(file.handle, out_buffer); |
| 7030 | } |
| 7031 | |
| 7032 | fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { |
| 7033 | switch (native_os) { |
| 7034 | .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 7035 | var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7036 | @memset(&sufficient_buffer, 0); |
| 7037 | const syscall: Syscall = try .start(); |
| 7038 | while (true) { |
| 7039 | switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) { |
| 7040 | .SUCCESS => { |
| 7041 | syscall.finish(); |
| 7042 | break; |
| 7043 | }, |
| 7044 | .INTR => { |
| 7045 | try syscall.checkCancel(); |
| 7046 | continue; |
| 7047 | }, |
| 7048 | else => |e| { |
| 7049 | syscall.finish(); |
| 7050 | switch (e) { |
| 7051 | .ACCES => return error.AccessDenied, |
| 7052 | .BADF => return error.FileNotFound, |
| 7053 | .NOENT => return error.FileNotFound, |
| 7054 | .NOMEM => return error.SystemResources, |
| 7055 | .NOSPC => return error.NameTooLong, |
| 7056 | .RANGE => return error.NameTooLong, |
| 7057 | else => |err| return posix.unexpectedErrno(err), |
| 7058 | } |
| 7059 | }, |
| 7060 | } |
| 7061 | } |
| 7062 | const n = std.mem.findScalar(u8, &sufficient_buffer, 0) orelse sufficient_buffer.len; |
| 7063 | if (n > out_buffer.len) return error.NameTooLong; |
| 7064 | @memcpy(out_buffer[0..n], sufficient_buffer[0..n]); |
| 7065 | return n; |
| 7066 | }, |
| 7067 | .linux, .serenity, .illumos => { |
| 7068 | var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined; |
| 7069 | const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}"; |
| 7070 | const proc_path = std.mem.printSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable; |
| 7071 | const syscall: Syscall = try .start(); |
| 7072 | while (true) { |
| 7073 | const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len); |
| 7074 | switch (posix.errno(rc)) { |
| 7075 | .SUCCESS => { |
| 7076 | syscall.finish(); |
| 7077 | const len: usize = @bitCast(rc); |
| 7078 | return len; |
| 7079 | }, |
| 7080 | .INTR => { |
| 7081 | try syscall.checkCancel(); |
| 7082 | continue; |
| 7083 | }, |
| 7084 | else => |e| { |
| 7085 | syscall.finish(); |
| 7086 | switch (e) { |
| 7087 | .ACCES => return error.AccessDenied, |
| 7088 | .FAULT => |err| return errnoBug(err), |
| 7089 | .IO => return error.FileSystem, |
| 7090 | .LOOP => return error.SymLinkLoop, |
| 7091 | .NAMETOOLONG => return error.NameTooLong, |
| 7092 | .NOENT => return error.FileNotFound, |
| 7093 | .NOMEM => return error.SystemResources, |
| 7094 | .NOTDIR => return error.NotDir, |
| 7095 | .ILSEQ => |err| return errnoBug(err), |
| 7096 | else => |err| return posix.unexpectedErrno(err), |
| 7097 | } |
| 7098 | }, |
| 7099 | } |
| 7100 | } |
| 7101 | }, |
| 7102 | .freebsd => { |
| 7103 | var k_file: std.c.kinfo_file = undefined; |
| 7104 | k_file.structsize = std.c.KINFO_FILE_SIZE; |
| 7105 | const syscall: Syscall = try .start(); |
| 7106 | while (true) { |
| 7107 | switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&k_file)))) { |
| 7108 | .SUCCESS => { |
| 7109 | syscall.finish(); |
| 7110 | break; |
| 7111 | }, |
| 7112 | .INTR => { |
| 7113 | try syscall.checkCancel(); |
| 7114 | continue; |
| 7115 | }, |
| 7116 | .BADF => { |
| 7117 | syscall.finish(); |
| 7118 | return error.FileNotFound; |
| 7119 | }, |
| 7120 | else => |err| { |
| 7121 | syscall.finish(); |
| 7122 | return posix.unexpectedErrno(err); |
| 7123 | }, |
| 7124 | } |
| 7125 | } |
| 7126 | const len = std.mem.findScalar(u8, &k_file.path, 0) orelse k_file.path.len; |
| 7127 | if (len == 0) return error.NameTooLong; |
| 7128 | @memcpy(out_buffer[0..len], k_file.path[0..len]); |
| 7129 | return len; |
| 7130 | }, |
| 7131 | else => return error.OperationUnsupported, |
| 7132 | } |
| 7133 | comptime unreachable; |
| 7134 | } |
| 7135 | |
| 7136 | fn fileHardLink( |
| 7137 | userdata: ?*anyopaque, |
| 7138 | file: File, |
| 7139 | new_dir: Dir, |
| 7140 | new_sub_path: []const u8, |
| 7141 | options: File.HardLinkOptions, |
| 7142 | ) File.HardLinkError!void { |
| 7143 | _ = userdata; |
| 7144 | if (native_os != .linux) return error.OperationUnsupported; |
| 7145 | |
| 7146 | var new_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7147 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 7148 | |
| 7149 | const flags: u32 = if (options.follow_symlinks) |
| 7150 | posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH |
| 7151 | else |
| 7152 | posix.AT.EMPTY_PATH; |
| 7153 | |
| 7154 | return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) { |
| 7155 | error.FileNotFound => { |
| 7156 | if (options.follow_symlinks) return error.FileNotFound; |
| 7157 | var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; |
| 7158 | const proc_path = std.mem.printSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch |
| 7159 | unreachable; |
| 7160 | return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW); |
| 7161 | }, |
| 7162 | else => |e| return e, |
| 7163 | }; |
| 7164 | } |
| 7165 | |
| 7166 | fn linkat( |
| 7167 | old_dir: posix.fd_t, |
| 7168 | old_path: [*:0]const u8, |
| 7169 | new_dir: posix.fd_t, |
| 7170 | new_path: [*:0]const u8, |
| 7171 | flags: u32, |
| 7172 | ) File.HardLinkError!void { |
| 7173 | const syscall: Syscall = try .start(); |
| 7174 | while (true) { |
| 7175 | switch (posix.errno(posix.system.linkat(old_dir, old_path, new_dir, new_path, flags))) { |
| 7176 | .SUCCESS => return syscall.finish(), |
| 7177 | .INTR => { |
| 7178 | try syscall.checkCancel(); |
| 7179 | continue; |
| 7180 | }, |
| 7181 | .ACCES => return syscall.fail(error.AccessDenied), |
| 7182 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 7183 | .EXIST => return syscall.fail(error.PathAlreadyExists), |
| 7184 | .IO => return syscall.fail(error.HardwareFailure), |
| 7185 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 7186 | .MLINK => return syscall.fail(error.LinkQuotaExceeded), |
| 7187 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 7188 | .NOENT => return syscall.fail(error.FileNotFound), |
| 7189 | .NOMEM => return syscall.fail(error.SystemResources), |
| 7190 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 7191 | .NOTDIR => return syscall.fail(error.NotDir), |
| 7192 | .PERM => return syscall.fail(error.PermissionDenied), |
| 7193 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 7194 | .XDEV => return syscall.fail(error.CrossDevice), |
| 7195 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 7196 | .FAULT => |err| return syscall.errnoBug(err), |
| 7197 | .INVAL => |err| return syscall.errnoBug(err), |
| 7198 | else => |err| return syscall.unexpectedErrno(err), |
| 7199 | } |
| 7200 | } |
| 7201 | } |
| 7202 | |
| 7203 | const dirDeleteFile = switch (native_os) { |
| 7204 | .windows => dirDeleteFileWindows, |
| 7205 | .wasi => dirDeleteFileWasi, |
| 7206 | else => dirDeleteFilePosix, |
| 7207 | }; |
| 7208 | |
| 7209 | fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { |
| 7210 | return dirDeleteWindows(userdata, dir, sub_path, false) catch |err| switch (err) { |
| 7211 | error.DirNotEmpty => unreachable, |
| 7212 | else => |e| return e, |
| 7213 | }; |
| 7214 | } |
| 7215 | |
| 7216 | fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { |
| 7217 | if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path); |
| 7218 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7219 | _ = t; |
| 7220 | const wasi = std.os.wasi; |
| 7221 | const syscall: Syscall = try .start(); |
| 7222 | while (true) { |
| 7223 | const res = wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len); |
| 7224 | switch (res) { |
| 7225 | .SUCCESS => { |
| 7226 | syscall.finish(); |
| 7227 | return; |
| 7228 | }, |
| 7229 | .INTR => { |
| 7230 | try syscall.checkCancel(); |
| 7231 | continue; |
| 7232 | }, |
| 7233 | .ACCES, .PERM => |e| { |
| 7234 | const original_error: Dir.DeleteFileError = switch (e) { |
| 7235 | .ACCES => error.AccessDenied, |
| 7236 | .PERM => error.PermissionDenied, |
| 7237 | else => unreachable, |
| 7238 | }; |
| 7239 | var stat: wasi.filestat_t = undefined; |
| 7240 | while (true) { |
| 7241 | try syscall.checkCancel(); |
| 7242 | switch (wasi.path_filestat_get(dir.handle, .{}, sub_path.ptr, sub_path.len, &stat)) { |
| 7243 | .SUCCESS => { |
| 7244 | syscall.finish(); |
| 7245 | break; |
| 7246 | }, |
| 7247 | .INTR => continue, |
| 7248 | else => { |
| 7249 | syscall.finish(); |
| 7250 | return original_error; |
| 7251 | }, |
| 7252 | } |
| 7253 | } |
| 7254 | if (stat.filetype == .DIRECTORY) |
| 7255 | return error.IsDir |
| 7256 | else |
| 7257 | return original_error; |
| 7258 | }, |
| 7259 | else => |e| { |
| 7260 | syscall.finish(); |
| 7261 | switch (e) { |
| 7262 | .BUSY => return error.FileBusy, |
| 7263 | .FAULT => |err| return errnoBug(err), |
| 7264 | .IO => return error.FileSystem, |
| 7265 | .ISDIR => return error.IsDir, |
| 7266 | .LOOP => return error.SymLinkLoop, |
| 7267 | .NAMETOOLONG => return error.NameTooLong, |
| 7268 | .NOENT => return error.FileNotFound, |
| 7269 | .NOTDIR => return error.NotDir, |
| 7270 | .NOMEM => return error.SystemResources, |
| 7271 | .ROFS => return error.ReadOnlyFileSystem, |
| 7272 | .NOTCAPABLE => return error.AccessDenied, |
| 7273 | .ILSEQ => return error.BadPathName, |
| 7274 | .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component |
| 7275 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 7276 | else => |err| return posix.unexpectedErrno(err), |
| 7277 | } |
| 7278 | }, |
| 7279 | } |
| 7280 | } |
| 7281 | } |
| 7282 | |
| 7283 | fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void { |
| 7284 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7285 | _ = t; |
| 7286 | |
| 7287 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7288 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 7289 | |
| 7290 | const syscall: Syscall = try .start(); |
| 7291 | while (true) { |
| 7292 | switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) { |
| 7293 | .SUCCESS => { |
| 7294 | syscall.finish(); |
| 7295 | return; |
| 7296 | }, |
| 7297 | .INTR => { |
| 7298 | try syscall.checkCancel(); |
| 7299 | continue; |
| 7300 | }, |
| 7301 | // Some systems return permission errors when trying to delete a |
| 7302 | // directory, so we need to handle that case specifically and |
| 7303 | // translate the error. |
| 7304 | .PERM => switch (native_os) { |
| 7305 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => { |
| 7306 | |
| 7307 | // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them). |
| 7308 | var st = std.mem.zeroes(posix.Stat); |
| 7309 | while (true) { |
| 7310 | try syscall.checkCancel(); |
| 7311 | switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) { |
| 7312 | .SUCCESS => { |
| 7313 | syscall.finish(); |
| 7314 | break; |
| 7315 | }, |
| 7316 | .INTR => continue, |
| 7317 | else => { |
| 7318 | syscall.finish(); |
| 7319 | return error.PermissionDenied; |
| 7320 | }, |
| 7321 | } |
| 7322 | } |
| 7323 | const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR; |
| 7324 | if (is_dir) |
| 7325 | return error.IsDir |
| 7326 | else |
| 7327 | return error.PermissionDenied; |
| 7328 | }, |
| 7329 | else => { |
| 7330 | syscall.finish(); |
| 7331 | return error.PermissionDenied; |
| 7332 | }, |
| 7333 | }, |
| 7334 | else => |e| { |
| 7335 | syscall.finish(); |
| 7336 | switch (e) { |
| 7337 | .ACCES => return error.AccessDenied, |
| 7338 | .BUSY => return error.FileBusy, |
| 7339 | .FAULT => |err| return errnoBug(err), |
| 7340 | .IO => return error.FileSystem, |
| 7341 | .ISDIR => return error.IsDir, |
| 7342 | .LOOP => return error.SymLinkLoop, |
| 7343 | .NAMETOOLONG => return error.NameTooLong, |
| 7344 | .NOENT => return error.FileNotFound, |
| 7345 | .NOTDIR => return error.NotDir, |
| 7346 | .NOMEM => return error.SystemResources, |
| 7347 | .ROFS => return error.ReadOnlyFileSystem, |
| 7348 | .EXIST => |err| return errnoBug(err), |
| 7349 | .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR |
| 7350 | .ILSEQ => return error.BadPathName, |
| 7351 | .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component |
| 7352 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 7353 | else => |err| return posix.unexpectedErrno(err), |
| 7354 | } |
| 7355 | }, |
| 7356 | } |
| 7357 | } |
| 7358 | } |
| 7359 | |
| 7360 | const dirDeleteDir = switch (native_os) { |
| 7361 | .windows => dirDeleteDirWindows, |
| 7362 | .wasi => dirDeleteDirWasi, |
| 7363 | else => dirDeleteDirPosix, |
| 7364 | }; |
| 7365 | |
| 7366 | fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { |
| 7367 | return dirDeleteWindows(userdata, dir, sub_path, true) catch |err| switch (err) { |
| 7368 | error.IsDir => unreachable, |
| 7369 | else => |e| return e, |
| 7370 | }; |
| 7371 | } |
| 7372 | |
| 7373 | fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) (Dir.DeleteDirError || Dir.DeleteFileError)!void { |
| 7374 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7375 | _ = t; |
| 7376 | const w = windows; |
| 7377 | |
| 7378 | if (std.mem.eql(u8, sub_path, "..")) { |
| 7379 | // Can't remove the parent directory with an open handle. |
| 7380 | return error.FileBusy; |
| 7381 | } |
| 7382 | var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 7383 | if (std.mem.eql(u8, sub_path, ".")) { |
| 7384 | // Windows does not recognize this, but it does work with empty string. |
| 7385 | sub_path_w.len = 0; |
| 7386 | } |
| 7387 | const attr: w.OBJECT.ATTRIBUTES = .{ |
| 7388 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, |
| 7389 | .ObjectName = @constCast(&sub_path_w.string()), |
| 7390 | }; |
| 7391 | |
| 7392 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 7393 | var tmp_handle: w.HANDLE = undefined; |
| 7394 | { |
| 7395 | const syscall: Syscall = try .start(); |
| 7396 | while (true) switch (w.ntdll.NtCreateFile( |
| 7397 | &tmp_handle, |
| 7398 | .{ .STANDARD = .{ |
| 7399 | .RIGHTS = .{ .DELETE = true }, |
| 7400 | .SYNCHRONIZE = true, |
| 7401 | } }, |
| 7402 | &attr, |
| 7403 | &io_status_block, |
| 7404 | null, |
| 7405 | .{}, |
| 7406 | .VALID_FLAGS, |
| 7407 | .OPEN, |
| 7408 | .{ |
| 7409 | .DIRECTORY_FILE = remove_dir, |
| 7410 | .IO = .SYNCHRONOUS_NONALERT, |
| 7411 | .NON_DIRECTORY_FILE = !remove_dir, |
| 7412 | .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? |
| 7413 | }, |
| 7414 | null, |
| 7415 | 0, |
| 7416 | )) { |
| 7417 | .SUCCESS => break syscall.finish(), |
| 7418 | .OBJECT_NAME_INVALID => |err| return syscall.ntstatusBug(err), |
| 7419 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 7420 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 7421 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 7422 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| 7423 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 7424 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 7425 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 7426 | .SHARING_VIOLATION => return syscall.fail(error.FileBusy), |
| 7427 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 7428 | .DELETE_PENDING => return syscall.finish(), |
| 7429 | else => |rc| return syscall.unexpectedNtstatus(rc), |
| 7430 | }; |
| 7431 | } |
| 7432 | defer w.CloseHandle(tmp_handle); |
| 7433 | |
| 7434 | // FileDispositionInformationEx has varying levels of support: |
| 7435 | // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1 |
| 7436 | // (INVALID_INFO_CLASS is returned if not supported) |
| 7437 | // - Requires the NTFS filesystem |
| 7438 | // (on filesystems like FAT32, INVALID_PARAMETER is returned) |
| 7439 | // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1 |
| 7440 | // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 |
| 7441 | // (NOT_SUPPORTED is returned if a flag is unsupported) |
| 7442 | // |
| 7443 | // The strategy here is just to try using FileDispositionInformationEx and fall back to |
| 7444 | // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. |
| 7445 | const rc = rc: { |
| 7446 | // Deletion with posix semantics if the filesystem supports it. |
| 7447 | var info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{ |
| 7448 | .DELETE = true, |
| 7449 | .POSIX_SEMANTICS = true, |
| 7450 | .IGNORE_READONLY_ATTRIBUTE = true, |
| 7451 | } }; |
| 7452 | |
| 7453 | const syscall: Syscall = try .start(); |
| 7454 | while (true) switch (w.ntdll.NtSetInformationFile( |
| 7455 | tmp_handle, |
| 7456 | &io_status_block, |
| 7457 | &info, |
| 7458 | @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX), |
| 7459 | .DispositionEx, |
| 7460 | )) { |
| 7461 | .CANCELLED => { |
| 7462 | try syscall.checkCancel(); |
| 7463 | continue; |
| 7464 | }, |
| 7465 | // The filesystem does not support FileDispositionInformationEx |
| 7466 | .INVALID_PARAMETER, |
| 7467 | // The operating system does not support FileDispositionInformationEx |
| 7468 | .INVALID_INFO_CLASS, |
| 7469 | // The operating system does not support one of the flags |
| 7470 | .NOT_SUPPORTED, |
| 7471 | => break, // use fallback path below; `syscall` still active |
| 7472 | |
| 7473 | // For all other statuses, fall down to the switch below to handle them. |
| 7474 | else => |rc| { |
| 7475 | syscall.finish(); |
| 7476 | break :rc rc; |
| 7477 | }, |
| 7478 | }; |
| 7479 | |
| 7480 | // Deletion with file pending semantics, which requires waiting or moving |
| 7481 | // files to get them removed (from here). |
| 7482 | var file_dispo: w.FILE.DISPOSITION.INFORMATION = .{ .DeleteFile = .TRUE }; |
| 7483 | |
| 7484 | while (true) switch (w.ntdll.NtSetInformationFile( |
| 7485 | tmp_handle, |
| 7486 | &io_status_block, |
| 7487 | &file_dispo, |
| 7488 | @sizeOf(w.FILE.DISPOSITION.INFORMATION), |
| 7489 | .Disposition, |
| 7490 | )) { |
| 7491 | .CANCELLED => { |
| 7492 | try syscall.checkCancel(); |
| 7493 | continue; |
| 7494 | }, |
| 7495 | else => |rc| { |
| 7496 | syscall.finish(); |
| 7497 | break :rc rc; |
| 7498 | }, |
| 7499 | }; |
| 7500 | }; |
| 7501 | switch (rc) { |
| 7502 | .SUCCESS => {}, |
| 7503 | .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, |
| 7504 | .INVALID_PARAMETER => |err| return w.statusBug(err), |
| 7505 | .CANNOT_DELETE => return error.AccessDenied, |
| 7506 | .MEDIA_WRITE_PROTECTED => return error.AccessDenied, |
| 7507 | .ACCESS_DENIED => return error.AccessDenied, |
| 7508 | else => return w.unexpectedStatus(rc), |
| 7509 | } |
| 7510 | } |
| 7511 | |
| 7512 | fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { |
| 7513 | if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path); |
| 7514 | |
| 7515 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7516 | _ = t; |
| 7517 | |
| 7518 | const syscall: Syscall = try .start(); |
| 7519 | while (true) { |
| 7520 | const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len); |
| 7521 | switch (res) { |
| 7522 | .SUCCESS => { |
| 7523 | syscall.finish(); |
| 7524 | return; |
| 7525 | }, |
| 7526 | .INTR => { |
| 7527 | try syscall.checkCancel(); |
| 7528 | continue; |
| 7529 | }, |
| 7530 | else => |e| { |
| 7531 | syscall.finish(); |
| 7532 | switch (e) { |
| 7533 | .ACCES => return error.AccessDenied, |
| 7534 | .PERM => return error.PermissionDenied, |
| 7535 | .BUSY => return error.FileBusy, |
| 7536 | .FAULT => |err| return errnoBug(err), |
| 7537 | .IO => return error.FileSystem, |
| 7538 | .LOOP => return error.SymLinkLoop, |
| 7539 | .NAMETOOLONG => return error.NameTooLong, |
| 7540 | .NOENT => return error.FileNotFound, |
| 7541 | .NOTDIR => return error.NotDir, |
| 7542 | .NOMEM => return error.SystemResources, |
| 7543 | .ROFS => return error.ReadOnlyFileSystem, |
| 7544 | .NOTEMPTY => return error.DirNotEmpty, |
| 7545 | .NOTCAPABLE => return error.AccessDenied, |
| 7546 | .ILSEQ => return error.BadPathName, |
| 7547 | .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component |
| 7548 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 7549 | else => |err| return posix.unexpectedErrno(err), |
| 7550 | } |
| 7551 | }, |
| 7552 | } |
| 7553 | } |
| 7554 | } |
| 7555 | |
| 7556 | fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void { |
| 7557 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7558 | _ = t; |
| 7559 | |
| 7560 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7561 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 7562 | |
| 7563 | const syscall: Syscall = try .start(); |
| 7564 | while (true) { |
| 7565 | switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) { |
| 7566 | .SUCCESS => { |
| 7567 | syscall.finish(); |
| 7568 | return; |
| 7569 | }, |
| 7570 | .INTR => { |
| 7571 | try syscall.checkCancel(); |
| 7572 | continue; |
| 7573 | }, |
| 7574 | else => |e| { |
| 7575 | syscall.finish(); |
| 7576 | switch (e) { |
| 7577 | .ACCES => return error.AccessDenied, |
| 7578 | .PERM => return error.PermissionDenied, |
| 7579 | .BUSY => return error.FileBusy, |
| 7580 | .FAULT => |err| return errnoBug(err), |
| 7581 | .IO => return error.FileSystem, |
| 7582 | .ISDIR => |err| return errnoBug(err), |
| 7583 | .LOOP => return error.SymLinkLoop, |
| 7584 | .NAMETOOLONG => return error.NameTooLong, |
| 7585 | .NOENT => return error.FileNotFound, |
| 7586 | .NOTDIR => return error.NotDir, |
| 7587 | .NOMEM => return error.SystemResources, |
| 7588 | .ROFS => return error.ReadOnlyFileSystem, |
| 7589 | .EXIST => |err| return errnoBug(err), |
| 7590 | .NOTEMPTY => return error.DirNotEmpty, |
| 7591 | .ILSEQ => return error.BadPathName, |
| 7592 | .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component |
| 7593 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 7594 | else => |err| return posix.unexpectedErrno(err), |
| 7595 | } |
| 7596 | }, |
| 7597 | } |
| 7598 | } |
| 7599 | } |
| 7600 | |
| 7601 | const dirRename = switch (native_os) { |
| 7602 | .windows => dirRenameWindows, |
| 7603 | .wasi => dirRenameWasi, |
| 7604 | else => dirRenamePosix, |
| 7605 | }; |
| 7606 | |
| 7607 | fn dirRenameWindows( |
| 7608 | userdata: ?*anyopaque, |
| 7609 | old_dir: Dir, |
| 7610 | old_sub_path: []const u8, |
| 7611 | new_dir: Dir, |
| 7612 | new_sub_path: []const u8, |
| 7613 | ) Dir.RenameError!void { |
| 7614 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7615 | _ = t; |
| 7616 | return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) { |
| 7617 | error.PathAlreadyExists => return error.Unexpected, |
| 7618 | error.OperationUnsupported => return error.Unexpected, |
| 7619 | else => |e| return e, |
| 7620 | }; |
| 7621 | } |
| 7622 | |
| 7623 | fn dirRenamePreserve( |
| 7624 | userdata: ?*anyopaque, |
| 7625 | old_dir: Dir, |
| 7626 | old_sub_path: []const u8, |
| 7627 | new_dir: Dir, |
| 7628 | new_sub_path: []const u8, |
| 7629 | ) Dir.RenamePreserveError!void { |
| 7630 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7631 | if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false); |
| 7632 | if (is_darwin) return dirRenamePreserveDarwin(old_dir, old_sub_path, new_dir, new_sub_path); |
| 7633 | if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path); |
| 7634 | // Make a hard link then delete the original. |
| 7635 | try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false }); |
| 7636 | const prev = swapCancelProtection(t, .blocked); |
| 7637 | defer _ = swapCancelProtection(t, prev); |
| 7638 | dirDeleteFile(t, old_dir, old_sub_path) catch {}; |
| 7639 | } |
| 7640 | |
| 7641 | fn dirRenameWindowsInner( |
| 7642 | old_dir: Dir, |
| 7643 | old_sub_path: []const u8, |
| 7644 | new_dir: Dir, |
| 7645 | new_sub_path: []const u8, |
| 7646 | replace_if_exists: bool, |
| 7647 | ) Dir.RenamePreserveError!void { |
| 7648 | const w = windows; |
| 7649 | const old_path_w_buf = try sliceToPrefixedFileW(old_dir.handle, old_sub_path, .{}); |
| 7650 | const old_path_w = old_path_w_buf.span(); |
| 7651 | const new_path_w_buf = try sliceToPrefixedFileW(new_dir.handle, new_sub_path, .{}); |
| 7652 | const new_path_w = new_path_w_buf.span(); |
| 7653 | |
| 7654 | const src_fd = src_fd: { |
| 7655 | if (OpenFile(old_path_w, .{ |
| 7656 | .dir = old_dir.handle, |
| 7657 | .access_mask = .{ |
| 7658 | .GENERIC = .{ .WRITE = true }, |
| 7659 | .STANDARD = .{ |
| 7660 | .RIGHTS = .{ .DELETE = true }, |
| 7661 | .SYNCHRONIZE = true, |
| 7662 | }, |
| 7663 | }, |
| 7664 | .creation = .OPEN, |
| 7665 | .filter = .any, // This function is supposed to rename both files and directories. |
| 7666 | .follow_symlinks = false, |
| 7667 | })) |handle| { |
| 7668 | break :src_fd handle; |
| 7669 | } else |err| switch (err) { |
| 7670 | error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. |
| 7671 | else => |e| return e, |
| 7672 | } |
| 7673 | }; |
| 7674 | defer w.CloseHandle(src_fd); |
| 7675 | |
| 7676 | var rc: w.NTSTATUS = undefined; |
| 7677 | // FileRenameInformationEx has varying levels of support: |
| 7678 | // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 |
| 7679 | // (INVALID_INFO_CLASS is returned if not supported) |
| 7680 | // - Requires the NTFS filesystem |
| 7681 | // (on filesystems like FAT32, INVALID_PARAMETER is returned) |
| 7682 | // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 |
| 7683 | // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 |
| 7684 | // (NOT_SUPPORTED is returned if a flag is unsupported) |
| 7685 | // |
| 7686 | // The strategy here is just to try using FileRenameInformationEx and fall back to |
| 7687 | // FileRenameInformation if the return value lets us know that some aspect of it is not supported. |
| 7688 | const need_fallback = need_fallback: { |
| 7689 | var rename_info: w.FILE.RENAME_INFORMATION = .init(.{ |
| 7690 | .Flags = .{ |
| 7691 | .REPLACE_IF_EXISTS = replace_if_exists, |
| 7692 | .POSIX_SEMANTICS = true, |
| 7693 | .IGNORE_READONLY_ATTRIBUTE = true, |
| 7694 | }, |
| 7695 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, |
| 7696 | .FileName = new_path_w, |
| 7697 | }); |
| 7698 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 7699 | const rename_info_buf = rename_info.toBuffer(); |
| 7700 | rc = w.ntdll.NtSetInformationFile( |
| 7701 | src_fd, |
| 7702 | &io_status_block, |
| 7703 | rename_info_buf.ptr, |
| 7704 | @intCast(rename_info_buf.len), |
| 7705 | .RenameEx, |
| 7706 | ); |
| 7707 | switch (rc) { |
| 7708 | .SUCCESS => return, |
| 7709 | // The filesystem does not support FileDispositionInformationEx |
| 7710 | .INVALID_PARAMETER, |
| 7711 | // The operating system does not support FileDispositionInformationEx |
| 7712 | .INVALID_INFO_CLASS, |
| 7713 | // The operating system does not support one of the flags |
| 7714 | .NOT_SUPPORTED, |
| 7715 | => break :need_fallback true, |
| 7716 | // For all other statuses, fall down to the switch below to handle them. |
| 7717 | else => break :need_fallback false, |
| 7718 | } |
| 7719 | }; |
| 7720 | |
| 7721 | if (need_fallback) { |
| 7722 | var rename_info: w.FILE.RENAME_INFORMATION = .init(.{ |
| 7723 | .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists }, |
| 7724 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, |
| 7725 | .FileName = new_path_w, |
| 7726 | }); |
| 7727 | var io_status_block: w.IO_STATUS_BLOCK = undefined; |
| 7728 | const rename_info_buf = rename_info.toBuffer(); |
| 7729 | rc = w.ntdll.NtSetInformationFile( |
| 7730 | src_fd, |
| 7731 | &io_status_block, |
| 7732 | rename_info_buf.ptr, |
| 7733 | @intCast(rename_info_buf.len), |
| 7734 | .Rename, |
| 7735 | ); |
| 7736 | } |
| 7737 | |
| 7738 | switch (rc) { |
| 7739 | .SUCCESS => {}, |
| 7740 | .INVALID_HANDLE => |err| return w.statusBug(err), |
| 7741 | .INVALID_PARAMETER => |err| return w.statusBug(err), |
| 7742 | .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err), |
| 7743 | .ACCESS_DENIED => return error.AccessDenied, |
| 7744 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 7745 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 7746 | .NOT_SAME_DEVICE => return error.CrossDevice, |
| 7747 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, |
| 7748 | .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, |
| 7749 | .FILE_IS_A_DIRECTORY => return error.IsDir, |
| 7750 | .NOT_A_DIRECTORY => return error.NotDir, |
| 7751 | else => return w.unexpectedStatus(rc), |
| 7752 | } |
| 7753 | } |
| 7754 | |
| 7755 | fn dirRenameWasi( |
| 7756 | userdata: ?*anyopaque, |
| 7757 | old_dir: Dir, |
| 7758 | old_sub_path: []const u8, |
| 7759 | new_dir: Dir, |
| 7760 | new_sub_path: []const u8, |
| 7761 | ) Dir.RenameError!void { |
| 7762 | if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path); |
| 7763 | |
| 7764 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7765 | _ = t; |
| 7766 | |
| 7767 | const syscall: Syscall = try .start(); |
| 7768 | while (true) { |
| 7769 | switch (std.os.wasi.path_rename(old_dir.handle, old_sub_path.ptr, old_sub_path.len, new_dir.handle, new_sub_path.ptr, new_sub_path.len)) { |
| 7770 | .SUCCESS => return syscall.finish(), |
| 7771 | .INTR => { |
| 7772 | try syscall.checkCancel(); |
| 7773 | continue; |
| 7774 | }, |
| 7775 | else => |e| { |
| 7776 | syscall.finish(); |
| 7777 | switch (e) { |
| 7778 | .ACCES => return error.AccessDenied, |
| 7779 | .PERM => return error.PermissionDenied, |
| 7780 | .BUSY => return error.FileBusy, |
| 7781 | .DQUOT => return error.DiskQuota, |
| 7782 | .FAULT => |err| return errnoBug(err), |
| 7783 | .INVAL => |err| return errnoBug(err), |
| 7784 | .ISDIR => return error.IsDir, |
| 7785 | .LOOP => return error.SymLinkLoop, |
| 7786 | .MLINK => return error.LinkQuotaExceeded, |
| 7787 | .NAMETOOLONG => return error.NameTooLong, |
| 7788 | .NOENT => return error.FileNotFound, |
| 7789 | .NOTDIR => return error.NotDir, |
| 7790 | .NOMEM => return error.SystemResources, |
| 7791 | .NOSPC => return error.NoSpaceLeft, |
| 7792 | .EXIST => return error.DirNotEmpty, |
| 7793 | .NOTEMPTY => return error.DirNotEmpty, |
| 7794 | .ROFS => return error.ReadOnlyFileSystem, |
| 7795 | .XDEV => return error.CrossDevice, |
| 7796 | .NOTCAPABLE => return error.AccessDenied, |
| 7797 | .ILSEQ => return error.BadPathName, |
| 7798 | else => |err| return posix.unexpectedErrno(err), |
| 7799 | } |
| 7800 | }, |
| 7801 | } |
| 7802 | } |
| 7803 | } |
| 7804 | |
| 7805 | fn dirRenamePosix( |
| 7806 | userdata: ?*anyopaque, |
| 7807 | old_dir: Dir, |
| 7808 | old_sub_path: []const u8, |
| 7809 | new_dir: Dir, |
| 7810 | new_sub_path: []const u8, |
| 7811 | ) Dir.RenameError!void { |
| 7812 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7813 | _ = t; |
| 7814 | |
| 7815 | var old_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7816 | var new_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7817 | |
| 7818 | const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); |
| 7819 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 7820 | |
| 7821 | return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix); |
| 7822 | } |
| 7823 | |
| 7824 | fn dirRenamePreserveDarwin( |
| 7825 | old_dir: Dir, |
| 7826 | old_sub_path: []const u8, |
| 7827 | new_dir: Dir, |
| 7828 | new_sub_path: []const u8, |
| 7829 | ) Dir.RenamePreserveError!void { |
| 7830 | var old_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7831 | var new_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 7832 | const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); |
| 7833 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 7834 | |
| 7835 | const syscall: Syscall = try .start(); |
| 7836 | while (true) { |
| 7837 | switch (posix.errno(std.c.renameatx_np( |
| 7838 | old_dir.handle, |
| 7839 | old_sub_path_posix, |
| 7840 | new_dir.handle, |
| 7841 | new_sub_path_posix, |
| 7842 | .{ .EXCL = true }, |
| 7843 | ))) { |
| 7844 | .SUCCESS => { |
| 7845 | syscall.finish(); |
| 7846 | break; |
| 7847 | }, |
| 7848 | .INTR => { |
| 7849 | try syscall.checkCancel(); |
| 7850 | continue; |
| 7851 | }, |
| 7852 | .INVAL => |err| return syscall.errnoBug(err), |
| 7853 | .FAULT => |err| return syscall.errnoBug(err), |
| 7854 | .BADF => |err| return syscall.errnoBug(err), |
| 7855 | .ISDIR => |err| return syscall.errnoBug(err), |
| 7856 | .NOTEMPTY => |err| return syscall.errnoBug(err), |
| 7857 | .OPNOTSUPP => return syscall.fail(error.OperationUnsupported), |
| 7858 | .IO => return syscall.fail(error.HardwareFailure), |
| 7859 | .DEADLK => return syscall.fail(error.AccessDenied), |
| 7860 | .ACCES => return syscall.fail(error.AccessDenied), |
| 7861 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 7862 | .EXIST => return syscall.fail(error.PathAlreadyExists), |
| 7863 | .LOOP => return syscall.fail(error.LinkQuotaExceeded), |
| 7864 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 7865 | .NOENT => return syscall.fail(error.FileNotFound), |
| 7866 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 7867 | .NOTDIR => return syscall.fail(error.NotDir), |
| 7868 | .PERM => return syscall.fail(error.PermissionDenied), |
| 7869 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 7870 | .XDEV => return syscall.fail(error.CrossDevice), |
| 7871 | else => |err| return syscall.unexpectedErrno(err), |
| 7872 | } |
| 7873 | } |
| 7874 | } |
| 7875 | |
| 7876 | fn dirRenamePreserveLinux( |
| 7877 | old_dir: Dir, |
| 7878 | old_sub_path: []const u8, |
| 7879 | new_dir: Dir, |
| 7880 | new_sub_path: []const u8, |
| 7881 | ) Dir.RenamePreserveError!void { |
| 7882 | const linux = std.os.linux; |
| 7883 | |
| 7884 | var old_path_buffer: [linux.PATH_MAX]u8 = undefined; |
| 7885 | var new_path_buffer: [linux.PATH_MAX]u8 = undefined; |
| 7886 | |
| 7887 | const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); |
| 7888 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 7889 | |
| 7890 | const syscall: Syscall = try .start(); |
| 7891 | while (true) switch (linux.errno(linux.renameat2( |
| 7892 | old_dir.handle, |
| 7893 | old_sub_path_posix, |
| 7894 | new_dir.handle, |
| 7895 | new_sub_path_posix, |
| 7896 | .{ .NOREPLACE = true }, |
| 7897 | ))) { |
| 7898 | .SUCCESS => return syscall.finish(), |
| 7899 | .INTR => { |
| 7900 | try syscall.checkCancel(); |
| 7901 | continue; |
| 7902 | }, |
| 7903 | .ACCES => return syscall.fail(error.AccessDenied), |
| 7904 | .PERM => return syscall.fail(error.PermissionDenied), |
| 7905 | .BUSY => return syscall.fail(error.FileBusy), |
| 7906 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 7907 | .ISDIR => return syscall.fail(error.IsDir), |
| 7908 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 7909 | .MLINK => return syscall.fail(error.LinkQuotaExceeded), |
| 7910 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 7911 | .NOENT => return syscall.fail(error.FileNotFound), |
| 7912 | .NOTDIR => return syscall.fail(error.NotDir), |
| 7913 | .NOMEM => return syscall.fail(error.SystemResources), |
| 7914 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 7915 | .EXIST => return syscall.fail(error.PathAlreadyExists), |
| 7916 | .NOTEMPTY => return syscall.fail(error.DirNotEmpty), |
| 7917 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 7918 | .XDEV => return syscall.fail(error.CrossDevice), |
| 7919 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 7920 | .FAULT => |err| return syscall.errnoBug(err), |
| 7921 | .INVAL => |err| return syscall.errnoBug(err), |
| 7922 | else => |err| return syscall.unexpectedErrno(err), |
| 7923 | }; |
| 7924 | } |
| 7925 | |
| 7926 | fn renameat( |
| 7927 | old_dir: posix.fd_t, |
| 7928 | old_sub_path: [*:0]const u8, |
| 7929 | new_dir: posix.fd_t, |
| 7930 | new_sub_path: [*:0]const u8, |
| 7931 | ) Dir.RenameError!void { |
| 7932 | const syscall: Syscall = try .start(); |
| 7933 | while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) { |
| 7934 | .SUCCESS => return syscall.finish(), |
| 7935 | .INTR => { |
| 7936 | try syscall.checkCancel(); |
| 7937 | continue; |
| 7938 | }, |
| 7939 | .ACCES => return syscall.fail(error.AccessDenied), |
| 7940 | .PERM => return syscall.fail(error.PermissionDenied), |
| 7941 | .BUSY => return syscall.fail(error.FileBusy), |
| 7942 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 7943 | .ISDIR => return syscall.fail(error.IsDir), |
| 7944 | .IO => return syscall.fail(error.HardwareFailure), |
| 7945 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 7946 | .MLINK => return syscall.fail(error.LinkQuotaExceeded), |
| 7947 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 7948 | .NOENT => return syscall.fail(error.FileNotFound), |
| 7949 | .NOTDIR => return syscall.fail(error.NotDir), |
| 7950 | .NOMEM => return syscall.fail(error.SystemResources), |
| 7951 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 7952 | .EXIST => return syscall.fail(error.DirNotEmpty), |
| 7953 | .NOTEMPTY => return syscall.fail(error.DirNotEmpty), |
| 7954 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 7955 | .XDEV => return syscall.fail(error.CrossDevice), |
| 7956 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 7957 | .FAULT => |err| return syscall.errnoBug(err), |
| 7958 | .INVAL => |err| return syscall.errnoBug(err), |
| 7959 | else => |err| return syscall.unexpectedErrno(err), |
| 7960 | }; |
| 7961 | } |
| 7962 | |
| 7963 | const dirSymLink = switch (native_os) { |
| 7964 | .windows => dirSymLinkWindows, |
| 7965 | .wasi => dirSymLinkWasi, |
| 7966 | else => dirSymLinkPosix, |
| 7967 | }; |
| 7968 | |
| 7969 | fn dirSymLinkWindows( |
| 7970 | userdata: ?*anyopaque, |
| 7971 | dir: Dir, |
| 7972 | target_path: []const u8, |
| 7973 | sym_link_path: []const u8, |
| 7974 | flags: Dir.SymLinkFlags, |
| 7975 | ) Dir.SymLinkError!void { |
| 7976 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 7977 | _ = t; |
| 7978 | const w = windows; |
| 7979 | |
| 7980 | // Target path does not use sliceToPrefixedFileW because certain paths |
| 7981 | // are handled differently when creating a symlink than they would be |
| 7982 | // when converting to an NT namespaced path. |
| 7983 | var target_path_w: WindowsPathSpace = undefined; |
| 7984 | target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path); |
| 7985 | target_path_w.data[target_path_w.len] = 0; |
| 7986 | // However, we need to canonicalize any path separators to `\`, since if |
| 7987 | // the target path is relative, then it must use `\` as the path separator. |
| 7988 | std.mem.replaceScalar( |
| 7989 | u16, |
| 7990 | target_path_w.data[0..target_path_w.len], |
| 7991 | std.mem.nativeToLittle(u16, '/'), |
| 7992 | std.mem.nativeToLittle(u16, '\\'), |
| 7993 | ); |
| 7994 | |
| 7995 | const sym_link_path_w = try sliceToPrefixedFileW(dir.handle, sym_link_path, .{}); |
| 7996 | |
| 7997 | const SYMLINK_DATA = extern struct { |
| 7998 | ReparseTag: w.IO_REPARSE_TAG, |
| 7999 | ReparseDataLength: w.USHORT, |
| 8000 | Reserved: w.USHORT, |
| 8001 | SubstituteNameOffset: w.USHORT, |
| 8002 | SubstituteNameLength: w.USHORT, |
| 8003 | PrintNameOffset: w.USHORT, |
| 8004 | PrintNameLength: w.USHORT, |
| 8005 | Flags: w.ULONG, |
| 8006 | }; |
| 8007 | |
| 8008 | const symlink_handle = handle: { |
| 8009 | if (OpenFile(sym_link_path_w.span(), .{ |
| 8010 | .access_mask = .{ |
| 8011 | .GENERIC = .{ .READ = true, .WRITE = true }, |
| 8012 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 8013 | }, |
| 8014 | .dir = dir.handle, |
| 8015 | .creation = .CREATE, |
| 8016 | .filter = if (flags.is_directory) .dir_only else .non_directory_only, |
| 8017 | })) |handle| { |
| 8018 | break :handle handle; |
| 8019 | } else |err| switch (err) { |
| 8020 | error.IsDir => return error.PathAlreadyExists, |
| 8021 | error.NotDir => return error.Unexpected, |
| 8022 | error.WouldBlock => return error.Unexpected, |
| 8023 | error.PipeBusy => return error.Unexpected, |
| 8024 | error.FileBusy => return error.Unexpected, |
| 8025 | error.NoDevice => return error.Unexpected, |
| 8026 | error.AntivirusInterference => return error.Unexpected, |
| 8027 | else => |e| return e, |
| 8028 | } |
| 8029 | }; |
| 8030 | defer w.CloseHandle(symlink_handle); |
| 8031 | |
| 8032 | // Relevant portions of the documentation: |
| 8033 | // > Relative links are specified using the following conventions: |
| 8034 | // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32". |
| 8035 | // > - Current working directory–relative—for example, if the current working directory is |
| 8036 | // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt". |
| 8037 | // > Note: If you specify a current working directory–relative link, it is created as an absolute |
| 8038 | // > link, due to the way the current working directory is processed based on the user and the thread. |
| 8039 | // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw |
| 8040 | var is_target_absolute = false; |
| 8041 | const final_target_path = target_path: { |
| 8042 | if (w.hasCommonNtPrefix(u16, target_path_w.span())) { |
| 8043 | // Already an NT path, no need to do anything to it |
| 8044 | break :target_path target_path_w.span(); |
| 8045 | } else { |
| 8046 | switch (Dir.path.getWin32PathType(u16, target_path_w.span())) { |
| 8047 | // Rooted paths need to avoid getting put through wToPrefixedFileW |
| 8048 | // (and they are treated as relative in this context) |
| 8049 | // Note: It seems that rooted paths in symbolic links are relative to |
| 8050 | // the drive that the symbolic exists on, not to the CWD's drive. |
| 8051 | // So, if the symlink is on C:\ and the CWD is on D:\, |
| 8052 | // it will still resolve the path relative to the root of |
| 8053 | // the C:\ drive. |
| 8054 | .rooted => break :target_path target_path_w.span(), |
| 8055 | // Keep relative paths relative, but anything else needs to get NT-prefixed. |
| 8056 | else => if (!Dir.path.isAbsoluteWindowsWtf16(target_path_w.span())) |
| 8057 | break :target_path target_path_w.span(), |
| 8058 | } |
| 8059 | } |
| 8060 | var prefixed_target_path = try wToPrefixedFileW(dir.handle, target_path_w.span(), .{}); |
| 8061 | // We do this after prefixing to ensure that drive-relative paths are treated as absolute |
| 8062 | is_target_absolute = Dir.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); |
| 8063 | break :target_path prefixed_target_path.span(); |
| 8064 | }; |
| 8065 | |
| 8066 | // prepare reparse data buffer |
| 8067 | var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; |
| 8068 | const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; |
| 8069 | const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2; |
| 8070 | const target_is_absolute = Dir.path.isAbsoluteWindowsWtf16(final_target_path); |
| 8071 | const symlink_data: SYMLINK_DATA = .{ |
| 8072 | .ReparseTag = .SYMLINK, |
| 8073 | .ReparseDataLength = @intCast(buf_len - header_len), |
| 8074 | .Reserved = 0, |
| 8075 | .SubstituteNameOffset = @intCast(final_target_path.len * 2), |
| 8076 | .SubstituteNameLength = @intCast(final_target_path.len * 2), |
| 8077 | .PrintNameOffset = 0, |
| 8078 | .PrintNameLength = @intCast(final_target_path.len * 2), |
| 8079 | .Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0, |
| 8080 | }; |
| 8081 | |
| 8082 | @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data)); |
| 8083 | @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); |
| 8084 | const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; |
| 8085 | @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); |
| 8086 | switch ((try deviceIoControl(&.{ |
| 8087 | .file = .{ .handle = symlink_handle, .flags = .{ .nonblocking = false } }, |
| 8088 | .code = .SET_REPARSE_POINT, |
| 8089 | .in = buffer[0..buf_len], |
| 8090 | })).u.Status) { |
| 8091 | .SUCCESS => {}, |
| 8092 | .CANCELLED => unreachable, |
| 8093 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 8094 | .PRIVILEGE_NOT_HELD => return error.PermissionDenied, |
| 8095 | .ACCESS_DENIED => return error.AccessDenied, |
| 8096 | .INVALID_DEVICE_REQUEST => return error.FileSystem, |
| 8097 | else => |status| return w.unexpectedStatus(status), |
| 8098 | } |
| 8099 | } |
| 8100 | |
| 8101 | fn dirSymLinkWasi( |
| 8102 | userdata: ?*anyopaque, |
| 8103 | dir: Dir, |
| 8104 | target_path: []const u8, |
| 8105 | sym_link_path: []const u8, |
| 8106 | flags: Dir.SymLinkFlags, |
| 8107 | ) Dir.SymLinkError!void { |
| 8108 | if (builtin.link_libc) return dirSymLinkPosix(userdata, dir, target_path, sym_link_path, flags); |
| 8109 | |
| 8110 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8111 | _ = t; |
| 8112 | |
| 8113 | const syscall: Syscall = try .start(); |
| 8114 | while (true) { |
| 8115 | switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) { |
| 8116 | .SUCCESS => return syscall.finish(), |
| 8117 | .INTR => { |
| 8118 | try syscall.checkCancel(); |
| 8119 | continue; |
| 8120 | }, |
| 8121 | else => |e| { |
| 8122 | syscall.finish(); |
| 8123 | switch (e) { |
| 8124 | .FAULT => |err| return errnoBug(err), |
| 8125 | .INVAL => |err| return errnoBug(err), |
| 8126 | .BADF => |err| return errnoBug(err), |
| 8127 | .ACCES => return error.AccessDenied, |
| 8128 | .PERM => return error.PermissionDenied, |
| 8129 | .DQUOT => return error.DiskQuota, |
| 8130 | .EXIST => return error.PathAlreadyExists, |
| 8131 | .IO => return error.FileSystem, |
| 8132 | .LOOP => return error.SymLinkLoop, |
| 8133 | .NAMETOOLONG => return error.NameTooLong, |
| 8134 | .NOENT => return error.FileNotFound, |
| 8135 | .NOTDIR => return error.NotDir, |
| 8136 | .NOMEM => return error.SystemResources, |
| 8137 | .NOSPC => return error.NoSpaceLeft, |
| 8138 | .ROFS => return error.ReadOnlyFileSystem, |
| 8139 | .NOTCAPABLE => return error.AccessDenied, |
| 8140 | .ILSEQ => return error.BadPathName, |
| 8141 | else => |err| return posix.unexpectedErrno(err), |
| 8142 | } |
| 8143 | }, |
| 8144 | } |
| 8145 | } |
| 8146 | } |
| 8147 | |
| 8148 | fn dirSymLinkPosix( |
| 8149 | userdata: ?*anyopaque, |
| 8150 | dir: Dir, |
| 8151 | target_path: []const u8, |
| 8152 | sym_link_path: []const u8, |
| 8153 | flags: Dir.SymLinkFlags, |
| 8154 | ) Dir.SymLinkError!void { |
| 8155 | _ = flags; |
| 8156 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8157 | _ = t; |
| 8158 | |
| 8159 | var target_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 8160 | var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 8161 | |
| 8162 | const target_path_posix = try pathToPosix(target_path, &target_path_buffer); |
| 8163 | const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer); |
| 8164 | |
| 8165 | const syscall: Syscall = try .start(); |
| 8166 | while (true) { |
| 8167 | switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) { |
| 8168 | .SUCCESS => return syscall.finish(), |
| 8169 | .INTR => { |
| 8170 | try syscall.checkCancel(); |
| 8171 | continue; |
| 8172 | }, |
| 8173 | else => |e| { |
| 8174 | syscall.finish(); |
| 8175 | switch (e) { |
| 8176 | .FAULT => |err| return errnoBug(err), |
| 8177 | .INVAL => |err| return errnoBug(err), |
| 8178 | .ACCES => return error.AccessDenied, |
| 8179 | .PERM => return error.PermissionDenied, |
| 8180 | .DQUOT => return error.DiskQuota, |
| 8181 | .EXIST => return error.PathAlreadyExists, |
| 8182 | .IO => return error.FileSystem, |
| 8183 | .LOOP => return error.SymLinkLoop, |
| 8184 | .NAMETOOLONG => return error.NameTooLong, |
| 8185 | .NOENT => return error.FileNotFound, |
| 8186 | .NOTDIR => return error.NotDir, |
| 8187 | .NOMEM => return error.SystemResources, |
| 8188 | .NOSPC => return error.NoSpaceLeft, |
| 8189 | .ROFS => return error.ReadOnlyFileSystem, |
| 8190 | .ILSEQ => return error.BadPathName, |
| 8191 | else => |err| return posix.unexpectedErrno(err), |
| 8192 | } |
| 8193 | }, |
| 8194 | } |
| 8195 | } |
| 8196 | } |
| 8197 | |
| 8198 | fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { |
| 8199 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8200 | _ = t; |
| 8201 | switch (native_os) { |
| 8202 | .windows => return dirReadLinkWindows(dir, sub_path, buffer), |
| 8203 | .wasi => return dirReadLinkWasi(dir, sub_path, buffer), |
| 8204 | else => return dirReadLinkPosix(dir, sub_path, buffer), |
| 8205 | } |
| 8206 | } |
| 8207 | |
| 8208 | fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { |
| 8209 | // This gets used once for `sub_path` and then reused again temporarily |
| 8210 | // before converting back to `buffer`. |
| 8211 | var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path, .{}); |
| 8212 | const attr: windows.OBJECT.ATTRIBUTES = .{ |
| 8213 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, |
| 8214 | .ObjectName = @constCast(&sub_path_w.string()), |
| 8215 | }; |
| 8216 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 8217 | var result_handle: windows.HANDLE = undefined; |
| 8218 | var attempt: u5 = 0; |
| 8219 | var syscall: Syscall = try .start(); |
| 8220 | while (true) switch (windows.ntdll.NtCreateFile( |
| 8221 | &result_handle, |
| 8222 | .{ |
| 8223 | .SPECIFIC = .{ .FILE = .{ |
| 8224 | .READ_ATTRIBUTES = true, |
| 8225 | } }, |
| 8226 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 8227 | }, |
| 8228 | &attr, |
| 8229 | &io_status_block, |
| 8230 | null, |
| 8231 | .{ .NORMAL = true }, |
| 8232 | .VALID_FLAGS, |
| 8233 | .OPEN, |
| 8234 | .{ |
| 8235 | .DIRECTORY_FILE = false, |
| 8236 | .NON_DIRECTORY_FILE = false, |
| 8237 | .IO = .SYNCHRONOUS_NONALERT, |
| 8238 | .OPEN_REPARSE_POINT = true, |
| 8239 | }, |
| 8240 | null, |
| 8241 | 0, |
| 8242 | )) { |
| 8243 | .SUCCESS => { |
| 8244 | syscall.finish(); |
| 8245 | break; |
| 8246 | }, |
| 8247 | .CANCELLED => { |
| 8248 | try syscall.checkCancel(); |
| 8249 | continue; |
| 8250 | }, |
| 8251 | .SHARING_VIOLATION => { |
| 8252 | // This occurs if the file attempting to be opened is a running |
| 8253 | // executable. However, there's a kernel bug: the error may be |
| 8254 | // incorrectly returned for an indeterminate amount of time |
| 8255 | // after an executable file is closed. Here we work around the |
| 8256 | // kernel bug with retry attempts. |
| 8257 | syscall.finish(); |
| 8258 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 8259 | try parking_sleep.sleep(.{ .duration = .{ |
| 8260 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 8261 | .clock = .awake, |
| 8262 | } }); |
| 8263 | attempt += 1; |
| 8264 | syscall = try .start(); |
| 8265 | continue; |
| 8266 | }, |
| 8267 | .DELETE_PENDING => { |
| 8268 | // This error means that there *was* a file in this location on |
| 8269 | // the file system, but it was deleted. However, the OS is not |
| 8270 | // finished with the deletion operation, and so this CreateFile |
| 8271 | // call has failed. Here, we simulate the kernel bug being |
| 8272 | // fixed by sleeping and retrying until the error goes away. |
| 8273 | syscall.finish(); |
| 8274 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 8275 | try parking_sleep.sleep(.{ .duration = .{ |
| 8276 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 8277 | .clock = .awake, |
| 8278 | } }); |
| 8279 | attempt += 1; |
| 8280 | syscall = try .start(); |
| 8281 | continue; |
| 8282 | }, |
| 8283 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 8284 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 8285 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 8286 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 8287 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| 8288 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.FileNotFound), |
| 8289 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 8290 | .PIPE_BUSY => return syscall.fail(error.AccessDenied), |
| 8291 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.FileNotFound), |
| 8292 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 8293 | .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), |
| 8294 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 8295 | .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), |
| 8296 | .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), |
| 8297 | else => |status| return syscall.unexpectedNtstatus(status), |
| 8298 | }; |
| 8299 | defer windows.CloseHandle(result_handle); |
| 8300 | |
| 8301 | var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined; |
| 8302 | switch ((try deviceIoControl(&.{ |
| 8303 | .file = .{ .handle = result_handle, .flags = .{ .nonblocking = false } }, |
| 8304 | .code = .GET_REPARSE_POINT, |
| 8305 | .out = &reparse_buf, |
| 8306 | })).u.Status) { |
| 8307 | .SUCCESS => {}, |
| 8308 | .CANCELLED => unreachable, |
| 8309 | .NOT_A_REPARSE_POINT => return error.NotLink, |
| 8310 | else => |status| return windows.unexpectedStatus(status), |
| 8311 | } |
| 8312 | |
| 8313 | const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf)); |
| 8314 | const IoReparseTagInt = @typeInfo(windows.IO_REPARSE_TAG).@"struct".backing_integer.?; |
| 8315 | const result_w = switch (@as(IoReparseTagInt, @bitCast(reparse_struct.ReparseTag))) { |
| 8316 | @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.SYMLINK)) => r: { |
| 8317 | const buf: *const windows.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); |
| 8318 | const offset = buf.SubstituteNameOffset >> 1; |
| 8319 | const len = buf.SubstituteNameLength >> 1; |
| 8320 | const path_buf = @as([*]const u16, &buf.PathBuffer); |
| 8321 | const is_relative = buf.Flags & windows.SYMLINK_FLAG_RELATIVE != 0; |
| 8322 | break :r try parseReadLinkPath(path_buf[offset..][0..len], is_relative, &sub_path_w.data); |
| 8323 | }, |
| 8324 | @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.MOUNT_POINT)) => r: { |
| 8325 | const buf: *const windows.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); |
| 8326 | const offset = buf.SubstituteNameOffset >> 1; |
| 8327 | const len = buf.SubstituteNameLength >> 1; |
| 8328 | const path_buf = @as([*]const u16, &buf.PathBuffer); |
| 8329 | break :r try parseReadLinkPath(path_buf[offset..][0..len], false, &sub_path_w.data); |
| 8330 | }, |
| 8331 | else => return error.UnsupportedReparsePointType, |
| 8332 | }; |
| 8333 | const len = std.unicode.calcWtf8Len(result_w); |
| 8334 | if (len > buffer.len) return error.NameTooLong; |
| 8335 | |
| 8336 | return std.unicode.wtf16LeToWtf8(buffer, result_w); |
| 8337 | } |
| 8338 | |
| 8339 | fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 { |
| 8340 | path: { |
| 8341 | if (is_relative) break :path; |
| 8342 | return windows.ntToWin32Namespace(path, out_buffer) catch |err| switch (err) { |
| 8343 | error.NameTooLong => |e| return e, |
| 8344 | error.NotNtPath => break :path, |
| 8345 | }; |
| 8346 | } |
| 8347 | if (out_buffer.len < path.len) return error.NameTooLong; |
| 8348 | const dest = out_buffer[0..path.len]; |
| 8349 | @memcpy(dest, path); |
| 8350 | return dest; |
| 8351 | } |
| 8352 | |
| 8353 | fn dirReadLinkWasi(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { |
| 8354 | if (builtin.link_libc) return dirReadLinkPosix(dir, sub_path, buffer); |
| 8355 | |
| 8356 | var n: usize = undefined; |
| 8357 | const syscall: Syscall = try .start(); |
| 8358 | while (true) { |
| 8359 | switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) { |
| 8360 | .SUCCESS => { |
| 8361 | syscall.finish(); |
| 8362 | return n; |
| 8363 | }, |
| 8364 | .INTR => { |
| 8365 | try syscall.checkCancel(); |
| 8366 | continue; |
| 8367 | }, |
| 8368 | else => |e| { |
| 8369 | syscall.finish(); |
| 8370 | switch (e) { |
| 8371 | .ACCES => return error.AccessDenied, |
| 8372 | .FAULT => |err| return errnoBug(err), |
| 8373 | .INVAL => return error.NotLink, |
| 8374 | .IO => return error.FileSystem, |
| 8375 | .LOOP => return error.SymLinkLoop, |
| 8376 | .NAMETOOLONG => return error.NameTooLong, |
| 8377 | .NOENT => return error.FileNotFound, |
| 8378 | .NOMEM => return error.SystemResources, |
| 8379 | .NOTDIR => return error.NotDir, |
| 8380 | .NOTCAPABLE => return error.AccessDenied, |
| 8381 | .ILSEQ => return error.BadPathName, |
| 8382 | else => |err| return posix.unexpectedErrno(err), |
| 8383 | } |
| 8384 | }, |
| 8385 | } |
| 8386 | } |
| 8387 | } |
| 8388 | |
| 8389 | fn dirReadLinkPosix(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { |
| 8390 | var sub_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 8391 | const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer); |
| 8392 | |
| 8393 | const syscall: Syscall = try .start(); |
| 8394 | while (true) { |
| 8395 | const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len); |
| 8396 | switch (posix.errno(rc)) { |
| 8397 | .SUCCESS => { |
| 8398 | syscall.finish(); |
| 8399 | const len: usize = @bitCast(rc); |
| 8400 | return len; |
| 8401 | }, |
| 8402 | .INTR => { |
| 8403 | try syscall.checkCancel(); |
| 8404 | continue; |
| 8405 | }, |
| 8406 | else => |e| { |
| 8407 | syscall.finish(); |
| 8408 | switch (e) { |
| 8409 | .ACCES => return error.AccessDenied, |
| 8410 | .FAULT => |err| return errnoBug(err), |
| 8411 | .INVAL => return error.NotLink, |
| 8412 | .IO => return error.FileSystem, |
| 8413 | .LOOP => return error.SymLinkLoop, |
| 8414 | .NAMETOOLONG => return error.NameTooLong, |
| 8415 | .NOENT => return error.FileNotFound, |
| 8416 | .NOMEM => return error.SystemResources, |
| 8417 | .NOTDIR => return error.NotDir, |
| 8418 | .ILSEQ => return error.BadPathName, |
| 8419 | else => |err| return posix.unexpectedErrno(err), |
| 8420 | } |
| 8421 | }, |
| 8422 | } |
| 8423 | } |
| 8424 | } |
| 8425 | |
| 8426 | const dirSetPermissions = switch (native_os) { |
| 8427 | .windows => dirSetPermissionsWindows, |
| 8428 | else => dirSetPermissionsPosix, |
| 8429 | }; |
| 8430 | |
| 8431 | fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { |
| 8432 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8433 | _ = t; |
| 8434 | _ = dir; |
| 8435 | _ = permissions; |
| 8436 | @panic("TODO implement dirSetPermissionsWindows"); |
| 8437 | } |
| 8438 | |
| 8439 | fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { |
| 8440 | if (@sizeOf(Dir.Permissions) == 0) return; |
| 8441 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8442 | _ = t; |
| 8443 | return setPermissionsPosix(dir.handle, permissions.toMode()); |
| 8444 | } |
| 8445 | |
| 8446 | fn dirSetFilePermissions( |
| 8447 | userdata: ?*anyopaque, |
| 8448 | dir: Dir, |
| 8449 | sub_path: []const u8, |
| 8450 | permissions: Dir.Permissions, |
| 8451 | options: Dir.SetFilePermissionsOptions, |
| 8452 | ) Dir.SetFilePermissionsError!void { |
| 8453 | if (@sizeOf(Dir.Permissions) == 0) return; |
| 8454 | if (is_windows) @panic("TODO implement dirSetFilePermissions windows"); |
| 8455 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8456 | |
| 8457 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 8458 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 8459 | |
| 8460 | const mode = permissions.toMode(); |
| 8461 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| 8462 | |
| 8463 | return posixFchmodat(t, dir.handle, sub_path_posix, mode, flags); |
| 8464 | } |
| 8465 | |
| 8466 | fn posixFchmodat( |
| 8467 | t: *Threaded, |
| 8468 | dir_fd: posix.fd_t, |
| 8469 | path: [*:0]const u8, |
| 8470 | mode: posix.mode_t, |
| 8471 | flags: u32, |
| 8472 | ) Dir.SetFilePermissionsError!void { |
| 8473 | // No special handling for linux is needed if we can use the libc fallback |
| 8474 | // or `flags` is empty. Glibc only added the fallback in 2.32. |
| 8475 | if (have_fchmodat_flags or flags == 0) { |
| 8476 | const syscall: Syscall = try .start(); |
| 8477 | while (true) { |
| 8478 | const rc = if (have_fchmodat_flags or builtin.link_libc) |
| 8479 | posix.system.fchmodat(dir_fd, path, mode, flags) |
| 8480 | else |
| 8481 | posix.system.fchmodat(dir_fd, path, mode); |
| 8482 | switch (posix.errno(rc)) { |
| 8483 | .SUCCESS => return syscall.finish(), |
| 8484 | .INTR => { |
| 8485 | try syscall.checkCancel(); |
| 8486 | continue; |
| 8487 | }, |
| 8488 | else => |e| { |
| 8489 | syscall.finish(); |
| 8490 | switch (e) { |
| 8491 | .BADF => |err| return errnoBug(err), |
| 8492 | .FAULT => |err| return errnoBug(err), |
| 8493 | .INVAL => |err| return errnoBug(err), |
| 8494 | .ACCES => return error.AccessDenied, |
| 8495 | .IO => return error.InputOutput, |
| 8496 | .LOOP => return error.SymLinkLoop, |
| 8497 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 8498 | .NAMETOOLONG => return error.NameTooLong, |
| 8499 | .NFILE => return error.SystemFdQuotaExceeded, |
| 8500 | .NOENT => return error.FileNotFound, |
| 8501 | .NOTDIR => return error.FileNotFound, |
| 8502 | .NOMEM => return error.SystemResources, |
| 8503 | .OPNOTSUPP => return error.OperationUnsupported, |
| 8504 | .PERM => return error.PermissionDenied, |
| 8505 | .ROFS => return error.ReadOnlyFileSystem, |
| 8506 | else => |err| return posix.unexpectedErrno(err), |
| 8507 | } |
| 8508 | }, |
| 8509 | } |
| 8510 | } |
| 8511 | } |
| 8512 | |
| 8513 | if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled) |
| 8514 | return fchmodatFallback(dir_fd, path, mode); |
| 8515 | |
| 8516 | comptime assert(native_os == .linux); |
| 8517 | |
| 8518 | const syscall: Syscall = try .start(); |
| 8519 | while (true) { |
| 8520 | switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) { |
| 8521 | .SUCCESS => return syscall.finish(), |
| 8522 | .INTR => { |
| 8523 | try syscall.checkCancel(); |
| 8524 | continue; |
| 8525 | }, |
| 8526 | else => |e| { |
| 8527 | syscall.finish(); |
| 8528 | switch (e) { |
| 8529 | .BADF => |err| return errnoBug(err), |
| 8530 | .FAULT => |err| return errnoBug(err), |
| 8531 | .INVAL => |err| return errnoBug(err), |
| 8532 | .ACCES => return error.AccessDenied, |
| 8533 | .IO => return error.InputOutput, |
| 8534 | .LOOP => return error.SymLinkLoop, |
| 8535 | .NOENT => return error.FileNotFound, |
| 8536 | .NOMEM => return error.SystemResources, |
| 8537 | .NOTDIR => return error.FileNotFound, |
| 8538 | .OPNOTSUPP => return error.OperationUnsupported, |
| 8539 | .PERM => return error.PermissionDenied, |
| 8540 | .ROFS => return error.ReadOnlyFileSystem, |
| 8541 | .NOSYS => { |
| 8542 | @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic); |
| 8543 | return fchmodatFallback(dir_fd, path, mode); |
| 8544 | }, |
| 8545 | else => |err| return posix.unexpectedErrno(err), |
| 8546 | } |
| 8547 | }, |
| 8548 | } |
| 8549 | } |
| 8550 | } |
| 8551 | |
| 8552 | fn fchmodatFallback( |
| 8553 | dir_fd: posix.fd_t, |
| 8554 | path: [*:0]const u8, |
| 8555 | mode: posix.mode_t, |
| 8556 | ) Dir.SetFilePermissionsError!void { |
| 8557 | comptime assert(native_os == .linux); |
| 8558 | |
| 8559 | // Fallback to changing permissions using procfs: |
| 8560 | // |
| 8561 | // 1. Open `path` as a `PATH` descriptor. |
| 8562 | // 2. Stat the fd and check if it isn't a symbolic link. |
| 8563 | // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. |
| 8564 | // 4. Pass the procfs path to `chmod` with the `mode`. |
| 8565 | const path_fd: posix.fd_t = fd: { |
| 8566 | const syscall: Syscall = try .start(); |
| 8567 | while (true) { |
| 8568 | const rc = posix.system.openat(dir_fd, path, .{ |
| 8569 | .PATH = true, |
| 8570 | .NOFOLLOW = true, |
| 8571 | .CLOEXEC = true, |
| 8572 | }, @as(posix.mode_t, 0)); |
| 8573 | switch (posix.errno(rc)) { |
| 8574 | .SUCCESS => { |
| 8575 | syscall.finish(); |
| 8576 | break :fd @intCast(rc); |
| 8577 | }, |
| 8578 | .INTR => { |
| 8579 | try syscall.checkCancel(); |
| 8580 | continue; |
| 8581 | }, |
| 8582 | else => |e| { |
| 8583 | syscall.finish(); |
| 8584 | switch (e) { |
| 8585 | .FAULT => |err| return errnoBug(err), |
| 8586 | .INVAL => |err| return errnoBug(err), |
| 8587 | .ACCES => return error.AccessDenied, |
| 8588 | .PERM => return error.PermissionDenied, |
| 8589 | .LOOP => return error.SymLinkLoop, |
| 8590 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 8591 | .NAMETOOLONG => return error.NameTooLong, |
| 8592 | .NFILE => return error.SystemFdQuotaExceeded, |
| 8593 | .NOENT => return error.FileNotFound, |
| 8594 | .NOMEM => return error.SystemResources, |
| 8595 | else => |err| return posix.unexpectedErrno(err), |
| 8596 | } |
| 8597 | }, |
| 8598 | } |
| 8599 | } |
| 8600 | }; |
| 8601 | defer closeFd(path_fd); |
| 8602 | |
| 8603 | const path_mode = mode: { |
| 8604 | const sys = if (statx_use_c) std.c else std.os.linux; |
| 8605 | const syscall: Syscall = try .start(); |
| 8606 | while (true) { |
| 8607 | var statx = std.mem.zeroes(std.os.linux.Statx); |
| 8608 | switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { |
| 8609 | .SUCCESS => { |
| 8610 | syscall.finish(); |
| 8611 | if (!statx.mask.TYPE) return error.Unexpected; |
| 8612 | break :mode statx.mode; |
| 8613 | }, |
| 8614 | .INTR => { |
| 8615 | try syscall.checkCancel(); |
| 8616 | continue; |
| 8617 | }, |
| 8618 | else => |e| { |
| 8619 | syscall.finish(); |
| 8620 | switch (e) { |
| 8621 | .ACCES => return error.AccessDenied, |
| 8622 | .LOOP => return error.SymLinkLoop, |
| 8623 | .NOMEM => return error.SystemResources, |
| 8624 | else => |err| return posix.unexpectedErrno(err), |
| 8625 | } |
| 8626 | }, |
| 8627 | } |
| 8628 | } |
| 8629 | }; |
| 8630 | |
| 8631 | // Even though we only wanted TYPE, the kernel can still fill in the additional bits. |
| 8632 | if ((path_mode & posix.S.IFMT) == posix.S.IFLNK) |
| 8633 | return error.OperationUnsupported; |
| 8634 | |
| 8635 | var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; |
| 8636 | const proc_path = std.mem.printSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable; |
| 8637 | const syscall: Syscall = try .start(); |
| 8638 | while (true) { |
| 8639 | switch (posix.errno(posix.system.chmod(proc_path, mode))) { |
| 8640 | .SUCCESS => return syscall.finish(), |
| 8641 | .INTR => { |
| 8642 | try syscall.checkCancel(); |
| 8643 | continue; |
| 8644 | }, |
| 8645 | else => |e| { |
| 8646 | syscall.finish(); |
| 8647 | switch (e) { |
| 8648 | .NOENT => return error.OperationUnsupported, // procfs not mounted. |
| 8649 | .BADF => |err| return errnoBug(err), |
| 8650 | .FAULT => |err| return errnoBug(err), |
| 8651 | .INVAL => |err| return errnoBug(err), |
| 8652 | .ACCES => return error.AccessDenied, |
| 8653 | .IO => return error.InputOutput, |
| 8654 | .LOOP => return error.SymLinkLoop, |
| 8655 | .NOMEM => return error.SystemResources, |
| 8656 | .NOTDIR => return error.FileNotFound, |
| 8657 | .PERM => return error.PermissionDenied, |
| 8658 | .ROFS => return error.ReadOnlyFileSystem, |
| 8659 | else => |err| return posix.unexpectedErrno(err), |
| 8660 | } |
| 8661 | }, |
| 8662 | } |
| 8663 | } |
| 8664 | } |
| 8665 | |
| 8666 | const dirSetOwner = switch (native_os) { |
| 8667 | .windows => dirSetOwnerUnsupported, |
| 8668 | else => dirSetOwnerPosix, |
| 8669 | }; |
| 8670 | |
| 8671 | fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { |
| 8672 | _ = userdata; |
| 8673 | _ = dir; |
| 8674 | _ = owner; |
| 8675 | _ = group; |
| 8676 | return error.Unexpected; |
| 8677 | } |
| 8678 | |
| 8679 | fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void { |
| 8680 | if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. |
| 8681 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8682 | _ = t; |
| 8683 | const uid = owner orelse ~@as(posix.uid_t, 0); |
| 8684 | const gid = group orelse ~@as(posix.gid_t, 0); |
| 8685 | return posixFchown(dir.handle, uid, gid); |
| 8686 | } |
| 8687 | |
| 8688 | fn posixFchown(fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void { |
| 8689 | comptime assert(have_fchown); |
| 8690 | const syscall: Syscall = try .start(); |
| 8691 | while (true) { |
| 8692 | switch (posix.errno(posix.system.fchown(fd, uid, gid))) { |
| 8693 | .SUCCESS => return syscall.finish(), |
| 8694 | .INTR => { |
| 8695 | try syscall.checkCancel(); |
| 8696 | continue; |
| 8697 | }, |
| 8698 | else => |e| { |
| 8699 | syscall.finish(); |
| 8700 | switch (e) { |
| 8701 | .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate` |
| 8702 | .FAULT => |err| return errnoBug(err), |
| 8703 | .INVAL => |err| return errnoBug(err), |
| 8704 | .ACCES => return error.AccessDenied, |
| 8705 | .IO => return error.InputOutput, |
| 8706 | .LOOP => return error.SymLinkLoop, |
| 8707 | .NOENT => return error.FileNotFound, |
| 8708 | .NOMEM => return error.SystemResources, |
| 8709 | .NOTDIR => return error.FileNotFound, |
| 8710 | .PERM => return error.PermissionDenied, |
| 8711 | .ROFS => return error.ReadOnlyFileSystem, |
| 8712 | else => |err| return posix.unexpectedErrno(err), |
| 8713 | } |
| 8714 | }, |
| 8715 | } |
| 8716 | } |
| 8717 | } |
| 8718 | |
| 8719 | fn dirSetFileOwner( |
| 8720 | userdata: ?*anyopaque, |
| 8721 | dir: Dir, |
| 8722 | sub_path: []const u8, |
| 8723 | owner: ?File.Uid, |
| 8724 | group: ?File.Gid, |
| 8725 | options: Dir.SetFileOwnerOptions, |
| 8726 | ) Dir.SetFileOwnerError!void { |
| 8727 | if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. |
| 8728 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8729 | _ = t; |
| 8730 | |
| 8731 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 8732 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 8733 | |
| 8734 | _ = dir; |
| 8735 | _ = sub_path_posix; |
| 8736 | _ = owner; |
| 8737 | _ = group; |
| 8738 | _ = options; |
| 8739 | @panic("TODO implement dirSetFileOwner"); |
| 8740 | } |
| 8741 | |
| 8742 | const fileSync = switch (native_os) { |
| 8743 | .windows => fileSyncWindows, |
| 8744 | .wasi => fileSyncWasi, |
| 8745 | else => fileSyncPosix, |
| 8746 | }; |
| 8747 | |
| 8748 | fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 8749 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8750 | _ = t; |
| 8751 | |
| 8752 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 8753 | const syscall: Syscall = try .start(); |
| 8754 | while (true) { |
| 8755 | switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) { |
| 8756 | .SUCCESS => break syscall.finish(), |
| 8757 | .CANCELLED => { |
| 8758 | try syscall.checkCancel(); |
| 8759 | continue; |
| 8760 | }, |
| 8761 | .INVALID_HANDLE => unreachable, |
| 8762 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time |
| 8763 | .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput), |
| 8764 | else => |status| return syscall.unexpectedNtstatus(status), |
| 8765 | } |
| 8766 | } |
| 8767 | } |
| 8768 | |
| 8769 | fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 8770 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8771 | _ = t; |
| 8772 | const syscall: Syscall = try .start(); |
| 8773 | while (true) { |
| 8774 | switch (posix.errno(posix.system.fsync(file.handle))) { |
| 8775 | .SUCCESS => return syscall.finish(), |
| 8776 | .INTR => { |
| 8777 | try syscall.checkCancel(); |
| 8778 | continue; |
| 8779 | }, |
| 8780 | else => |e| { |
| 8781 | syscall.finish(); |
| 8782 | switch (e) { |
| 8783 | .BADF => |err| return errnoBug(err), |
| 8784 | .INVAL => |err| return errnoBug(err), |
| 8785 | .ROFS => |err| return errnoBug(err), |
| 8786 | .IO => return error.InputOutput, |
| 8787 | .NOSPC => return error.NoSpaceLeft, |
| 8788 | .DQUOT => return error.DiskQuota, |
| 8789 | else => |err| return posix.unexpectedErrno(err), |
| 8790 | } |
| 8791 | }, |
| 8792 | } |
| 8793 | } |
| 8794 | } |
| 8795 | |
| 8796 | fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { |
| 8797 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8798 | _ = t; |
| 8799 | const syscall: Syscall = try .start(); |
| 8800 | while (true) { |
| 8801 | switch (std.os.wasi.fd_sync(file.handle)) { |
| 8802 | .SUCCESS => return syscall.finish(), |
| 8803 | .INTR => { |
| 8804 | try syscall.checkCancel(); |
| 8805 | continue; |
| 8806 | }, |
| 8807 | else => |e| { |
| 8808 | syscall.finish(); |
| 8809 | switch (e) { |
| 8810 | .BADF => |err| return errnoBug(err), |
| 8811 | .INVAL => |err| return errnoBug(err), |
| 8812 | .ROFS => |err| return errnoBug(err), |
| 8813 | .IO => return error.InputOutput, |
| 8814 | .NOSPC => return error.NoSpaceLeft, |
| 8815 | .DQUOT => return error.DiskQuota, |
| 8816 | else => |err| return posix.unexpectedErrno(err), |
| 8817 | } |
| 8818 | }, |
| 8819 | } |
| 8820 | } |
| 8821 | } |
| 8822 | |
| 8823 | fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| 8824 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8825 | _ = t; |
| 8826 | return isTty(file); |
| 8827 | } |
| 8828 | |
| 8829 | fn isTty(file: File) Io.Cancelable!bool { |
| 8830 | if (is_windows) { |
| 8831 | var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; |
| 8832 | switch ((try deviceIoControl(&.{ |
| 8833 | .file = .{ |
| 8834 | .handle = windows.peb().ProcessParameters.ConsoleHandle, |
| 8835 | .flags = .{ .nonblocking = false }, |
| 8836 | }, |
| 8837 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, |
| 8838 | .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), |
| 8839 | })).u.Status) { |
| 8840 | .SUCCESS => return true, |
| 8841 | .CANCELLED => unreachable, |
| 8842 | .INVALID_HANDLE => return isCygwinPty(file), |
| 8843 | else => return false, |
| 8844 | } |
| 8845 | } |
| 8846 | |
| 8847 | if (builtin.link_libc) { |
| 8848 | const syscall: Syscall = try .start(); |
| 8849 | while (true) { |
| 8850 | const rc = posix.system.isatty(file.handle); |
| 8851 | switch (posix.errno(rc - 1)) { |
| 8852 | .SUCCESS => { |
| 8853 | syscall.finish(); |
| 8854 | return true; |
| 8855 | }, |
| 8856 | .INTR => { |
| 8857 | try syscall.checkCancel(); |
| 8858 | continue; |
| 8859 | }, |
| 8860 | else => { |
| 8861 | syscall.finish(); |
| 8862 | return false; |
| 8863 | }, |
| 8864 | } |
| 8865 | } |
| 8866 | } |
| 8867 | |
| 8868 | if (native_os == .wasi) { |
| 8869 | var statbuf: std.os.wasi.fdstat_t = undefined; |
| 8870 | const err = std.os.wasi.fd_fdstat_get(file.handle, &statbuf); |
| 8871 | if (err != .SUCCESS) |
| 8872 | return false; |
| 8873 | |
| 8874 | // A tty is a character device that we can't seek or tell on. |
| 8875 | if (statbuf.fs_filetype != .CHARACTER_DEVICE) |
| 8876 | return false; |
| 8877 | if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL) |
| 8878 | return false; |
| 8879 | |
| 8880 | return true; |
| 8881 | } |
| 8882 | |
| 8883 | if (native_os == .linux) { |
| 8884 | const linux = std.os.linux; |
| 8885 | const syscall: Syscall = try .start(); |
| 8886 | while (true) { |
| 8887 | var wsz: posix.winsize = undefined; |
| 8888 | const fd: usize = @bitCast(@as(isize, file.handle)); |
| 8889 | const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); |
| 8890 | switch (linux.errno(rc)) { |
| 8891 | .SUCCESS => { |
| 8892 | syscall.finish(); |
| 8893 | return true; |
| 8894 | }, |
| 8895 | .INTR => { |
| 8896 | try syscall.checkCancel(); |
| 8897 | continue; |
| 8898 | }, |
| 8899 | else => { |
| 8900 | syscall.finish(); |
| 8901 | return false; |
| 8902 | }, |
| 8903 | } |
| 8904 | } |
| 8905 | } |
| 8906 | |
| 8907 | @compileError("unimplemented"); |
| 8908 | } |
| 8909 | |
| 8910 | fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { |
| 8911 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8912 | |
| 8913 | if (!is_windows) return if (!try supportsAnsiEscapeCodes(t, file)) error.NotTerminalDevice; |
| 8914 | |
| 8915 | // For Windows Terminal, VT Sequences processing is enabled by default. |
| 8916 | const console: File = .{ |
| 8917 | .handle = windows.peb().ProcessParameters.ConsoleHandle, |
| 8918 | .flags = .{ .nonblocking = false }, |
| 8919 | }; |
| 8920 | var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; |
| 8921 | switch ((try deviceIoControl(&.{ |
| 8922 | .file = console, |
| 8923 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, |
| 8924 | .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), |
| 8925 | })).u.Status) { |
| 8926 | .SUCCESS => {}, |
| 8927 | .CANCELLED => unreachable, |
| 8928 | .INVALID_HANDLE => return if (!try isCygwinPty(file)) error.NotTerminalDevice, |
| 8929 | else => return error.NotTerminalDevice, |
| 8930 | } |
| 8931 | |
| 8932 | if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; |
| 8933 | |
| 8934 | // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. |
| 8935 | // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ |
| 8936 | // |
| 8937 | // Note: In Microsoft's example for enabling virtual terminal processing, it |
| 8938 | // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well: |
| 8939 | // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing |
| 8940 | // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n) |
| 8941 | // to behave unexpectedly (the cursor moves down 1 row but remains on the same column). |
| 8942 | // Additionally, the default console mode in Windows Terminal does not have |
| 8943 | // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` |
| 8944 | // we end up matching the mode of Windows Terminal. |
| 8945 | var set_console_mode = windows.CONSOLE.USER_IO.SET_MODE( |
| 8946 | get_console_mode.Data | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING, |
| 8947 | ); |
| 8948 | switch ((try deviceIoControl(&.{ |
| 8949 | .file = console, |
| 8950 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, |
| 8951 | .in = @ptrCast(&set_console_mode.request(file, 0, .{}, 0, .{})), |
| 8952 | })).u.Status) { |
| 8953 | .SUCCESS => {}, |
| 8954 | .CANCELLED => unreachable, |
| 8955 | else => |status| return windows.unexpectedStatus(status), |
| 8956 | } |
| 8957 | } |
| 8958 | |
| 8959 | fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { |
| 8960 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8961 | return supportsAnsiEscapeCodes(t, file); |
| 8962 | } |
| 8963 | |
| 8964 | fn supportsAnsiEscapeCodes(t: *Threaded, file: File) Io.Cancelable!bool { |
| 8965 | if (is_windows) { |
| 8966 | var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; |
| 8967 | return switch ((try deviceIoControl(&.{ |
| 8968 | .file = .{ |
| 8969 | .handle = windows.peb().ProcessParameters.ConsoleHandle, |
| 8970 | .flags = .{ .nonblocking = false }, |
| 8971 | }, |
| 8972 | .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, |
| 8973 | .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), |
| 8974 | })).u.Status) { |
| 8975 | .SUCCESS => get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0, |
| 8976 | .CANCELLED => unreachable, |
| 8977 | .INVALID_HANDLE => isCygwinPty(file), |
| 8978 | else => false, |
| 8979 | }; |
| 8980 | } |
| 8981 | |
| 8982 | if (native_os == .wasi) { |
| 8983 | // WASI sanitizes stdout when fd is a tty so ANSI escape codes |
| 8984 | // will not be interpreted as actual cursor commands, and |
| 8985 | // stderr is always sanitized. |
| 8986 | |
| 8987 | return false; |
| 8988 | } |
| 8989 | |
| 8990 | if (try isTty(file)) { |
| 8991 | if (file.handle == posix.STDOUT_FILENO or file.handle == posix.STDERR_FILENO) { |
| 8992 | t.scanEnviron(); |
| 8993 | if (t.environ.string.TERM) |term| { |
| 8994 | if (std.mem.eql(u8, term, "dumb")) { |
| 8995 | return false; |
| 8996 | } |
| 8997 | } |
| 8998 | } |
| 8999 | |
| 9000 | return true; |
| 9001 | } |
| 9002 | |
| 9003 | return false; |
| 9004 | } |
| 9005 | |
| 9006 | fn isCygwinPty(file: File) Io.Cancelable!bool { |
| 9007 | if (!is_windows) return false; |
| 9008 | |
| 9009 | const handle = file.handle; |
| 9010 | |
| 9011 | // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats: |
| 9012 | // msys-[...]-ptyN-[...] |
| 9013 | // cygwin-[...]-ptyN-[...] |
| 9014 | // |
| 9015 | // Example: msys-1888ae32e00d56aa-pty0-to-master |
| 9016 | |
| 9017 | // First, just check that the handle is a named pipe. |
| 9018 | // This allows us to avoid the more costly NtQueryInformationFile call |
| 9019 | // for handles that aren't named pipes. |
| 9020 | { |
| 9021 | var io_status: windows.IO_STATUS_BLOCK = undefined; |
| 9022 | var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; |
| 9023 | const syscall: Syscall = try .start(); |
| 9024 | while (true) switch (windows.ntdll.NtQueryVolumeInformationFile( |
| 9025 | handle, |
| 9026 | &io_status, |
| 9027 | &device_info, |
| 9028 | @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), |
| 9029 | .Device, |
| 9030 | )) { |
| 9031 | .SUCCESS => break syscall.finish(), |
| 9032 | .CANCELLED => { |
| 9033 | try syscall.checkCancel(); |
| 9034 | continue; |
| 9035 | }, |
| 9036 | else => { |
| 9037 | syscall.finish(); |
| 9038 | return false; |
| 9039 | }, |
| 9040 | }; |
| 9041 | if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false; |
| 9042 | } |
| 9043 | |
| 9044 | const name_bytes_offset = @offsetOf(windows.FILE.NAME_INFORMATION, "FileName"); |
| 9045 | // `NAME_MAX` UTF-16 code units (2 bytes each) |
| 9046 | // This buffer may not be long enough to handle *all* possible paths |
| 9047 | // (PATH_MAX_WIDE would be necessary for that), but because we only care |
| 9048 | // about certain paths and we know they must be within a reasonable length, |
| 9049 | // we can use this smaller buffer and just return false on any error from |
| 9050 | // NtQueryInformationFile. |
| 9051 | const num_name_bytes = windows.MAX_PATH * 2; |
| 9052 | var name_info_bytes: [name_bytes_offset + num_name_bytes]u8 align(@alignOf(windows.FILE.NAME_INFORMATION)) = @splat(0); |
| 9053 | |
| 9054 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9055 | const syscall: Syscall = try .start(); |
| 9056 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 9057 | handle, |
| 9058 | &io_status_block, |
| 9059 | &name_info_bytes, |
| 9060 | @intCast(name_info_bytes.len), |
| 9061 | .Name, |
| 9062 | )) { |
| 9063 | .SUCCESS => break syscall.finish(), |
| 9064 | .CANCELLED => { |
| 9065 | try syscall.checkCancel(); |
| 9066 | continue; |
| 9067 | }, |
| 9068 | .INVALID_PARAMETER => unreachable, |
| 9069 | else => { |
| 9070 | syscall.finish(); |
| 9071 | return false; |
| 9072 | }, |
| 9073 | }; |
| 9074 | |
| 9075 | const name_info: *const windows.FILE.NAME_INFORMATION = @ptrCast(&name_info_bytes); |
| 9076 | const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; |
| 9077 | const name_wide = std.mem.bytesAsSlice(u16, name_bytes); |
| 9078 | // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master |
| 9079 | return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or |
| 9080 | std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and |
| 9081 | std.mem.find(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null; |
| 9082 | } |
| 9083 | |
| 9084 | fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void { |
| 9085 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9086 | _ = t; |
| 9087 | |
| 9088 | const signed_len: i64 = @bitCast(length); |
| 9089 | if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors. |
| 9090 | |
| 9091 | if (is_windows) { |
| 9092 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9093 | var eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{ |
| 9094 | .EndOfFile = signed_len, |
| 9095 | }; |
| 9096 | |
| 9097 | const syscall: Syscall = try .start(); |
| 9098 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 9099 | file.handle, |
| 9100 | &io_status_block, |
| 9101 | &eof_info, |
| 9102 | @sizeOf(windows.FILE.END_OF_FILE_INFORMATION), |
| 9103 | .EndOfFile, |
| 9104 | )) { |
| 9105 | .SUCCESS => return syscall.finish(), |
| 9106 | .CANCELLED => { |
| 9107 | try syscall.checkCancel(); |
| 9108 | continue; |
| 9109 | }, |
| 9110 | .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), // Handle not open for writing. |
| 9111 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 9112 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 9113 | .INVALID_PARAMETER => return syscall.fail(error.FileTooBig), |
| 9114 | else => |status| return syscall.unexpectedNtstatus(status), |
| 9115 | }; |
| 9116 | } |
| 9117 | |
| 9118 | if (native_os == .wasi and !builtin.link_libc) { |
| 9119 | const syscall: Syscall = try .start(); |
| 9120 | while (true) { |
| 9121 | switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) { |
| 9122 | .SUCCESS => return syscall.finish(), |
| 9123 | .INTR => { |
| 9124 | try syscall.checkCancel(); |
| 9125 | continue; |
| 9126 | }, |
| 9127 | else => |e| { |
| 9128 | syscall.finish(); |
| 9129 | switch (e) { |
| 9130 | .FBIG => return error.FileTooBig, |
| 9131 | .IO => return error.InputOutput, |
| 9132 | .PERM => return error.PermissionDenied, |
| 9133 | .TXTBSY => return error.FileBusy, |
| 9134 | .BADF => |err| return errnoBug(err), // Handle not open for writing |
| 9135 | .INVAL => return error.NonResizable, |
| 9136 | .NOTCAPABLE => return error.AccessDenied, |
| 9137 | else => |err| return posix.unexpectedErrno(err), |
| 9138 | } |
| 9139 | }, |
| 9140 | } |
| 9141 | } |
| 9142 | } |
| 9143 | |
| 9144 | const syscall: Syscall = try .start(); |
| 9145 | while (true) { |
| 9146 | switch (posix.errno(ftruncate_sym(file.handle, signed_len))) { |
| 9147 | .SUCCESS => return syscall.finish(), |
| 9148 | .INTR => { |
| 9149 | try syscall.checkCancel(); |
| 9150 | continue; |
| 9151 | }, |
| 9152 | else => |e| { |
| 9153 | syscall.finish(); |
| 9154 | switch (e) { |
| 9155 | .FBIG => return error.FileTooBig, |
| 9156 | .IO => return error.InputOutput, |
| 9157 | .PERM => return error.PermissionDenied, |
| 9158 | .TXTBSY => return error.FileBusy, |
| 9159 | .BADF => |err| return errnoBug(err), // Handle not open for writing. |
| 9160 | .INVAL => return error.NonResizable, // This is returned for /dev/null for example. |
| 9161 | else => |err| return posix.unexpectedErrno(err), |
| 9162 | } |
| 9163 | }, |
| 9164 | } |
| 9165 | } |
| 9166 | } |
| 9167 | |
| 9168 | fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void { |
| 9169 | if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function. |
| 9170 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9171 | _ = t; |
| 9172 | const uid = owner orelse ~@as(posix.uid_t, 0); |
| 9173 | const gid = group orelse ~@as(posix.gid_t, 0); |
| 9174 | return posixFchown(file.handle, uid, gid); |
| 9175 | } |
| 9176 | |
| 9177 | fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void { |
| 9178 | if (@sizeOf(File.Permissions) == 0) return; |
| 9179 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9180 | _ = t; |
| 9181 | switch (native_os) { |
| 9182 | .windows => { |
| 9183 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9184 | var info: windows.FILE.BASIC_INFORMATION = .{ |
| 9185 | .CreationTime = 0, |
| 9186 | .LastAccessTime = 0, |
| 9187 | .LastWriteTime = 0, |
| 9188 | .ChangeTime = 0, |
| 9189 | .FileAttributes = permissions.toAttributes(), |
| 9190 | }; |
| 9191 | const syscall: Syscall = try .start(); |
| 9192 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 9193 | file.handle, |
| 9194 | &io_status_block, |
| 9195 | &info, |
| 9196 | @sizeOf(windows.FILE.BASIC_INFORMATION), |
| 9197 | .Basic, |
| 9198 | )) { |
| 9199 | .SUCCESS => return syscall.finish(), |
| 9200 | .CANCELLED => { |
| 9201 | try syscall.checkCancel(); |
| 9202 | continue; |
| 9203 | }, |
| 9204 | .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), |
| 9205 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 9206 | else => |status| return syscall.unexpectedNtstatus(status), |
| 9207 | }; |
| 9208 | }, |
| 9209 | .wasi => return error.Unexpected, // Unsupported OS. |
| 9210 | else => return setPermissionsPosix(file.handle, permissions.toMode()), |
| 9211 | } |
| 9212 | } |
| 9213 | |
| 9214 | fn setPermissionsPosix(fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void { |
| 9215 | comptime assert(have_fchmod); |
| 9216 | const syscall: Syscall = try .start(); |
| 9217 | while (true) { |
| 9218 | switch (posix.errno(posix.system.fchmod(fd, mode))) { |
| 9219 | .SUCCESS => return syscall.finish(), |
| 9220 | .INTR => { |
| 9221 | try syscall.checkCancel(); |
| 9222 | continue; |
| 9223 | }, |
| 9224 | else => |e| { |
| 9225 | syscall.finish(); |
| 9226 | switch (e) { |
| 9227 | .BADF => |err| return errnoBug(err), |
| 9228 | .FAULT => |err| return errnoBug(err), |
| 9229 | .INVAL => |err| return errnoBug(err), |
| 9230 | .ACCES => return error.AccessDenied, |
| 9231 | .IO => return error.InputOutput, |
| 9232 | .LOOP => return error.SymLinkLoop, |
| 9233 | .NOENT => return error.FileNotFound, |
| 9234 | .NOMEM => return error.SystemResources, |
| 9235 | .NOTDIR => return error.FileNotFound, |
| 9236 | .PERM => return error.PermissionDenied, |
| 9237 | .ROFS => return error.ReadOnlyFileSystem, |
| 9238 | else => |err| return posix.unexpectedErrno(err), |
| 9239 | } |
| 9240 | }, |
| 9241 | } |
| 9242 | } |
| 9243 | } |
| 9244 | |
| 9245 | fn dirSetTimestamps( |
| 9246 | userdata: ?*anyopaque, |
| 9247 | dir: Dir, |
| 9248 | sub_path: []const u8, |
| 9249 | options: Dir.SetTimestampsOptions, |
| 9250 | ) Dir.SetTimestampsError!void { |
| 9251 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9252 | _ = t; |
| 9253 | |
| 9254 | if (is_windows) { |
| 9255 | @panic("TODO implement dirSetTimestamps windows"); |
| 9256 | } |
| 9257 | |
| 9258 | if (native_os == .wasi and !builtin.link_libc) { |
| 9259 | @panic("TODO implement dirSetTimestamps wasi"); |
| 9260 | } |
| 9261 | |
| 9262 | var times_buffer: [2]posix.timespec = undefined; |
| 9263 | const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { |
| 9264 | times_buffer = .{ |
| 9265 | setTimestampToPosix(options.access_timestamp), |
| 9266 | setTimestampToPosix(options.modify_timestamp), |
| 9267 | }; |
| 9268 | break :p &times_buffer; |
| 9269 | }; |
| 9270 | |
| 9271 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| 9272 | |
| 9273 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 9274 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 9275 | |
| 9276 | const syscall: Syscall = try .start(); |
| 9277 | while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) { |
| 9278 | .SUCCESS => return syscall.finish(), |
| 9279 | .INTR => { |
| 9280 | try syscall.checkCancel(); |
| 9281 | continue; |
| 9282 | }, |
| 9283 | .BADF => |err| return syscall.errnoBug(err), // always a race condition |
| 9284 | .FAULT => |err| return syscall.errnoBug(err), |
| 9285 | .INVAL => |err| return syscall.errnoBug(err), |
| 9286 | .ACCES => return syscall.fail(error.AccessDenied), |
| 9287 | .PERM => return syscall.fail(error.PermissionDenied), |
| 9288 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 9289 | else => |err| return syscall.unexpectedErrno(err), |
| 9290 | }; |
| 9291 | } |
| 9292 | |
| 9293 | fn fileSetTimestamps( |
| 9294 | userdata: ?*anyopaque, |
| 9295 | file: File, |
| 9296 | options: File.SetTimestampsOptions, |
| 9297 | ) File.SetTimestampsError!void { |
| 9298 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9299 | _ = t; |
| 9300 | |
| 9301 | if (is_windows) { |
| 9302 | const now_sys = if (options.access_timestamp == .now or options.modify_timestamp == .now) |
| 9303 | windows.ntdll.RtlGetSystemTimePrecise() |
| 9304 | else |
| 9305 | undefined; |
| 9306 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 9307 | var info: windows.FILE.BASIC_INFORMATION = .{ |
| 9308 | .CreationTime = 0, |
| 9309 | .LastAccessTime = switch (options.access_timestamp) { |
| 9310 | .unchanged => 0, |
| 9311 | .now => now_sys, |
| 9312 | .new => |ts| windows.toSysTime(ts), |
| 9313 | }, |
| 9314 | .LastWriteTime = switch (options.modify_timestamp) { |
| 9315 | .unchanged => 0, |
| 9316 | .now => now_sys, |
| 9317 | .new => |ts| windows.toSysTime(ts), |
| 9318 | }, |
| 9319 | .ChangeTime = 0, |
| 9320 | .FileAttributes = .{}, |
| 9321 | }; |
| 9322 | var syscall: Syscall = try .start(); |
| 9323 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 9324 | file.handle, |
| 9325 | &iosb, |
| 9326 | &info, |
| 9327 | @sizeOf(windows.FILE.BASIC_INFORMATION), |
| 9328 | .Basic, |
| 9329 | )) { |
| 9330 | .SUCCESS => return syscall.finish(), |
| 9331 | .CANCELLED => try syscall.checkCancel(), |
| 9332 | else => |status| return syscall.unexpectedNtstatus(status), |
| 9333 | }; |
| 9334 | } |
| 9335 | |
| 9336 | if (native_os == .wasi and !builtin.link_libc) { |
| 9337 | var atime: std.os.wasi.timestamp_t = 0; |
| 9338 | var mtime: std.os.wasi.timestamp_t = 0; |
| 9339 | var flags: std.os.wasi.fstflags_t = .{}; |
| 9340 | |
| 9341 | switch (options.access_timestamp) { |
| 9342 | .unchanged => {}, |
| 9343 | .now => flags.ATIM_NOW = true, |
| 9344 | .new => |ts| { |
| 9345 | atime = timestampToPosix(ts.nanoseconds).toTimestamp(); |
| 9346 | flags.ATIM = true; |
| 9347 | }, |
| 9348 | } |
| 9349 | |
| 9350 | switch (options.modify_timestamp) { |
| 9351 | .unchanged => {}, |
| 9352 | .now => flags.MTIM_NOW = true, |
| 9353 | .new => |ts| { |
| 9354 | mtime = timestampToPosix(ts.nanoseconds).toTimestamp(); |
| 9355 | flags.MTIM = true; |
| 9356 | }, |
| 9357 | } |
| 9358 | |
| 9359 | const syscall: Syscall = try .start(); |
| 9360 | while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) { |
| 9361 | .SUCCESS => return syscall.finish(), |
| 9362 | .INTR => { |
| 9363 | try syscall.checkCancel(); |
| 9364 | continue; |
| 9365 | }, |
| 9366 | .BADF => |err| return syscall.errnoBug(err), // File descriptor use-after-free. |
| 9367 | .FAULT => |err| return syscall.errnoBug(err), |
| 9368 | .INVAL => |err| return syscall.errnoBug(err), |
| 9369 | .ACCES => return syscall.fail(error.AccessDenied), |
| 9370 | .PERM => return syscall.fail(error.PermissionDenied), |
| 9371 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 9372 | else => |err| return syscall.unexpectedErrno(err), |
| 9373 | }; |
| 9374 | } |
| 9375 | |
| 9376 | var times_buffer: [2]posix.timespec = undefined; |
| 9377 | const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { |
| 9378 | times_buffer = .{ |
| 9379 | setTimestampToPosix(options.access_timestamp), |
| 9380 | setTimestampToPosix(options.modify_timestamp), |
| 9381 | }; |
| 9382 | break :p &times_buffer; |
| 9383 | }; |
| 9384 | |
| 9385 | const syscall: Syscall = try .start(); |
| 9386 | while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) { |
| 9387 | .SUCCESS => return syscall.finish(), |
| 9388 | .INTR => { |
| 9389 | try syscall.checkCancel(); |
| 9390 | continue; |
| 9391 | }, |
| 9392 | .BADF => |err| return syscall.errnoBug(err), // always a race condition |
| 9393 | .FAULT => |err| return syscall.errnoBug(err), |
| 9394 | .INVAL => |err| return syscall.errnoBug(err), |
| 9395 | .ACCES => return syscall.fail(error.AccessDenied), |
| 9396 | .PERM => return syscall.fail(error.PermissionDenied), |
| 9397 | .ROFS => return syscall.fail(error.ReadOnlyFileSystem), |
| 9398 | else => |err| return syscall.unexpectedErrno(err), |
| 9399 | }; |
| 9400 | } |
| 9401 | |
| 9402 | const windows_lock_range_off: windows.LARGE_INTEGER = 0; |
| 9403 | const windows_lock_range_len: windows.LARGE_INTEGER = 1; |
| 9404 | |
| 9405 | fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void { |
| 9406 | if (native_os == .wasi) return error.FileLocksUnsupported; |
| 9407 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9408 | _ = t; |
| 9409 | |
| 9410 | if (is_windows) { |
| 9411 | const exclusive = switch (lock) { |
| 9412 | .none => { |
| 9413 | // To match the non-Windows behavior, unlock |
| 9414 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9415 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 9416 | file.handle, |
| 9417 | &io_status_block, |
| 9418 | &windows_lock_range_off, |
| 9419 | &windows_lock_range_len, |
| 9420 | 0, |
| 9421 | )) { |
| 9422 | .SUCCESS => return, |
| 9423 | .CANCELLED => continue, |
| 9424 | .RANGE_NOT_LOCKED => return, |
| 9425 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer |
| 9426 | else => |status| return windows.unexpectedStatus(status), |
| 9427 | }; |
| 9428 | }, |
| 9429 | .shared => false, |
| 9430 | .exclusive => true, |
| 9431 | }; |
| 9432 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9433 | const syscall: Syscall = try .start(); |
| 9434 | while (true) switch (windows.ntdll.NtLockFile( |
| 9435 | file.handle, |
| 9436 | null, |
| 9437 | null, |
| 9438 | null, |
| 9439 | &io_status_block, |
| 9440 | &windows_lock_range_off, |
| 9441 | &windows_lock_range_len, |
| 9442 | null, |
| 9443 | .FALSE, |
| 9444 | .fromBool(exclusive), |
| 9445 | )) { |
| 9446 | .SUCCESS => return syscall.finish(), |
| 9447 | .CANCELLED => { |
| 9448 | try syscall.checkCancel(); |
| 9449 | continue; |
| 9450 | }, |
| 9451 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 9452 | .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // passed FailImmediately=false |
| 9453 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 9454 | else => |status| return syscall.unexpectedNtstatus(status), |
| 9455 | }; |
| 9456 | } |
| 9457 | |
| 9458 | const operation: i32 = switch (lock) { |
| 9459 | .none => posix.LOCK.UN, |
| 9460 | .shared => posix.LOCK.SH, |
| 9461 | .exclusive => posix.LOCK.EX, |
| 9462 | }; |
| 9463 | const syscall: Syscall = try .start(); |
| 9464 | while (true) { |
| 9465 | switch (posix.errno(posix.system.flock(file.handle, operation))) { |
| 9466 | .SUCCESS => return syscall.finish(), |
| 9467 | .INTR => { |
| 9468 | try syscall.checkCancel(); |
| 9469 | continue; |
| 9470 | }, |
| 9471 | else => |e| { |
| 9472 | syscall.finish(); |
| 9473 | switch (e) { |
| 9474 | .BADF => |err| return errnoBug(err), |
| 9475 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 9476 | .NOLCK => return error.SystemResources, |
| 9477 | .AGAIN => |err| return errnoBug(err), |
| 9478 | .OPNOTSUPP => return error.FileLocksUnsupported, |
| 9479 | else => |err| return posix.unexpectedErrno(err), |
| 9480 | } |
| 9481 | }, |
| 9482 | } |
| 9483 | } |
| 9484 | } |
| 9485 | |
| 9486 | fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool { |
| 9487 | if (native_os == .wasi) return error.FileLocksUnsupported; |
| 9488 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9489 | _ = t; |
| 9490 | |
| 9491 | if (is_windows) { |
| 9492 | const exclusive = switch (lock) { |
| 9493 | .none => { |
| 9494 | // To match the non-Windows behavior, unlock |
| 9495 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9496 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 9497 | file.handle, |
| 9498 | &io_status_block, |
| 9499 | &windows_lock_range_off, |
| 9500 | &windows_lock_range_len, |
| 9501 | 0, |
| 9502 | )) { |
| 9503 | .SUCCESS => return true, |
| 9504 | .CANCELLED => continue, |
| 9505 | .RANGE_NOT_LOCKED => return false, |
| 9506 | .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer |
| 9507 | else => |status| return windows.unexpectedStatus(status), |
| 9508 | }; |
| 9509 | }, |
| 9510 | .shared => false, |
| 9511 | .exclusive => true, |
| 9512 | }; |
| 9513 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9514 | const syscall: Syscall = try .start(); |
| 9515 | while (true) switch (windows.ntdll.NtLockFile( |
| 9516 | file.handle, |
| 9517 | null, |
| 9518 | null, |
| 9519 | null, |
| 9520 | &io_status_block, |
| 9521 | &windows_lock_range_off, |
| 9522 | &windows_lock_range_len, |
| 9523 | null, |
| 9524 | .TRUE, |
| 9525 | .fromBool(exclusive), |
| 9526 | )) { |
| 9527 | .SUCCESS => { |
| 9528 | syscall.finish(); |
| 9529 | return true; |
| 9530 | }, |
| 9531 | .LOCK_NOT_GRANTED => { |
| 9532 | syscall.finish(); |
| 9533 | return false; |
| 9534 | }, |
| 9535 | .CANCELLED => { |
| 9536 | try syscall.checkCancel(); |
| 9537 | continue; |
| 9538 | }, |
| 9539 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 9540 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 9541 | else => |status| return syscall.unexpectedNtstatus(status), |
| 9542 | }; |
| 9543 | } |
| 9544 | |
| 9545 | const operation: i32 = switch (lock) { |
| 9546 | .none => posix.LOCK.UN, |
| 9547 | .shared => posix.LOCK.SH | posix.LOCK.NB, |
| 9548 | .exclusive => posix.LOCK.EX | posix.LOCK.NB, |
| 9549 | }; |
| 9550 | const syscall: Syscall = try .start(); |
| 9551 | while (true) { |
| 9552 | switch (posix.errno(posix.system.flock(file.handle, operation))) { |
| 9553 | .SUCCESS => { |
| 9554 | syscall.finish(); |
| 9555 | return true; |
| 9556 | }, |
| 9557 | .INTR => { |
| 9558 | try syscall.checkCancel(); |
| 9559 | continue; |
| 9560 | }, |
| 9561 | .AGAIN => { |
| 9562 | syscall.finish(); |
| 9563 | return false; |
| 9564 | }, |
| 9565 | else => |e| { |
| 9566 | syscall.finish(); |
| 9567 | switch (e) { |
| 9568 | .BADF => |err| return errnoBug(err), |
| 9569 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 9570 | .NOLCK => return error.SystemResources, |
| 9571 | .OPNOTSUPP => return error.FileLocksUnsupported, |
| 9572 | else => |err| return posix.unexpectedErrno(err), |
| 9573 | } |
| 9574 | }, |
| 9575 | } |
| 9576 | } |
| 9577 | } |
| 9578 | |
| 9579 | fn fileUnlock(userdata: ?*anyopaque, file: File) void { |
| 9580 | if (native_os == .wasi) return; |
| 9581 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9582 | _ = t; |
| 9583 | |
| 9584 | if (is_windows) { |
| 9585 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9586 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 9587 | file.handle, |
| 9588 | &io_status_block, |
| 9589 | &windows_lock_range_off, |
| 9590 | &windows_lock_range_len, |
| 9591 | 0, |
| 9592 | )) { |
| 9593 | .SUCCESS => return, |
| 9594 | .CANCELLED => continue, |
| 9595 | .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // Function asserts unlocked. |
| 9596 | .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer |
| 9597 | else => if (is_debug) unreachable else return, // Resource deallocation must succeed. |
| 9598 | }; |
| 9599 | } |
| 9600 | |
| 9601 | while (true) { |
| 9602 | switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) { |
| 9603 | .SUCCESS => return, |
| 9604 | .CANCELED, .INTR => continue, |
| 9605 | .AGAIN => return assert(!is_debug), // unlocking can't block |
| 9606 | .BADF => return assert(!is_debug), // File descriptor used after closed. |
| 9607 | .INVAL => return assert(!is_debug), // invalid parameters |
| 9608 | .NOLCK => return assert(!is_debug), // Resource deallocation. |
| 9609 | .OPNOTSUPP => return assert(!is_debug), // We already got the lock. |
| 9610 | else => return assert(!is_debug), // Resource deallocation must succeed. |
| 9611 | } |
| 9612 | } |
| 9613 | } |
| 9614 | |
| 9615 | fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void { |
| 9616 | if (native_os == .wasi) return; |
| 9617 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9618 | _ = t; |
| 9619 | |
| 9620 | if (is_windows) { |
| 9621 | // On Windows it works like a semaphore + exclusivity flag. To |
| 9622 | // implement this function, we first obtain another lock in shared |
| 9623 | // mode. This changes the exclusivity flag, but increments the |
| 9624 | // semaphore to 2. So we follow up with an NtUnlockFile which |
| 9625 | // decrements the semaphore but does not modify the exclusivity flag. |
| 9626 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 9627 | const syscall: Syscall = try .start(); |
| 9628 | while (true) switch (windows.ntdll.NtLockFile( |
| 9629 | file.handle, |
| 9630 | null, |
| 9631 | null, |
| 9632 | null, |
| 9633 | &io_status_block, |
| 9634 | &windows_lock_range_off, |
| 9635 | &windows_lock_range_len, |
| 9636 | null, |
| 9637 | .TRUE, |
| 9638 | .FALSE, |
| 9639 | )) { |
| 9640 | .SUCCESS => break syscall.finish(), |
| 9641 | .CANCELLED => { |
| 9642 | try syscall.checkCancel(); |
| 9643 | continue; |
| 9644 | }, |
| 9645 | .INSUFFICIENT_RESOURCES => |err| return syscall.ntstatusBug(err), |
| 9646 | .LOCK_NOT_GRANTED => |err| return syscall.ntstatusBug(err), // File was not locked in exclusive mode. |
| 9647 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 9648 | else => |status| return syscall.unexpectedNtstatus(status), |
| 9649 | }; |
| 9650 | while (true) switch (windows.ntdll.NtUnlockFile( |
| 9651 | file.handle, |
| 9652 | &io_status_block, |
| 9653 | &windows_lock_range_off, |
| 9654 | &windows_lock_range_len, |
| 9655 | 0, |
| 9656 | )) { |
| 9657 | .SUCCESS => return, |
| 9658 | .CANCELLED => continue, |
| 9659 | .RANGE_NOT_LOCKED => if (is_debug) unreachable else return, // File was not locked. |
| 9660 | .ACCESS_VIOLATION => if (is_debug) unreachable else return, // bad io_status_block pointer |
| 9661 | else => if (is_debug) unreachable else return, // Resource deallocation must succeed. |
| 9662 | }; |
| 9663 | } |
| 9664 | |
| 9665 | const operation = posix.LOCK.SH | posix.LOCK.NB; |
| 9666 | |
| 9667 | const syscall: Syscall = try .start(); |
| 9668 | while (true) { |
| 9669 | switch (posix.errno(posix.system.flock(file.handle, operation))) { |
| 9670 | .SUCCESS => { |
| 9671 | syscall.finish(); |
| 9672 | return; |
| 9673 | }, |
| 9674 | .INTR => { |
| 9675 | try syscall.checkCancel(); |
| 9676 | continue; |
| 9677 | }, |
| 9678 | else => |e| { |
| 9679 | syscall.finish(); |
| 9680 | switch (e) { |
| 9681 | .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode. |
| 9682 | .BADF => |err| return errnoBug(err), |
| 9683 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 9684 | .NOLCK => |err| return errnoBug(err), // Lock already obtained. |
| 9685 | .OPNOTSUPP => |err| return errnoBug(err), // Lock already obtained. |
| 9686 | else => |err| return posix.unexpectedErrno(err), |
| 9687 | } |
| 9688 | }, |
| 9689 | } |
| 9690 | } |
| 9691 | } |
| 9692 | |
| 9693 | fn dirOpenDirWasi( |
| 9694 | userdata: ?*anyopaque, |
| 9695 | dir: Dir, |
| 9696 | sub_path: []const u8, |
| 9697 | options: Dir.OpenOptions, |
| 9698 | ) Dir.OpenError!Dir { |
| 9699 | if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); |
| 9700 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9701 | _ = t; |
| 9702 | const wasi = std.os.wasi; |
| 9703 | |
| 9704 | var base: std.os.wasi.rights_t = .{ |
| 9705 | .FD_FILESTAT_GET = true, |
| 9706 | .FD_FDSTAT_SET_FLAGS = true, |
| 9707 | .FD_FILESTAT_SET_TIMES = true, |
| 9708 | }; |
| 9709 | if (options.access_sub_paths) { |
| 9710 | base.FD_READDIR = true; |
| 9711 | base.PATH_CREATE_DIRECTORY = true; |
| 9712 | base.PATH_CREATE_FILE = true; |
| 9713 | base.PATH_LINK_SOURCE = true; |
| 9714 | base.PATH_LINK_TARGET = true; |
| 9715 | base.PATH_OPEN = true; |
| 9716 | base.PATH_READLINK = true; |
| 9717 | base.PATH_RENAME_SOURCE = true; |
| 9718 | base.PATH_RENAME_TARGET = true; |
| 9719 | base.PATH_FILESTAT_GET = true; |
| 9720 | base.PATH_FILESTAT_SET_SIZE = true; |
| 9721 | base.PATH_FILESTAT_SET_TIMES = true; |
| 9722 | base.PATH_SYMLINK = true; |
| 9723 | base.PATH_REMOVE_DIRECTORY = true; |
| 9724 | base.PATH_UNLINK_FILE = true; |
| 9725 | } |
| 9726 | |
| 9727 | const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks }; |
| 9728 | const oflags: wasi.oflags_t = .{ .DIRECTORY = true }; |
| 9729 | const fdflags: wasi.fdflags_t = .{}; |
| 9730 | var fd: posix.fd_t = undefined; |
| 9731 | const syscall: Syscall = try .start(); |
| 9732 | while (true) { |
| 9733 | switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) { |
| 9734 | .SUCCESS => { |
| 9735 | syscall.finish(); |
| 9736 | return .{ .handle = fd }; |
| 9737 | }, |
| 9738 | .INTR => { |
| 9739 | try syscall.checkCancel(); |
| 9740 | continue; |
| 9741 | }, |
| 9742 | else => |e| { |
| 9743 | syscall.finish(); |
| 9744 | switch (e) { |
| 9745 | .FAULT => |err| return errnoBug(err), |
| 9746 | .INVAL => return error.BadPathName, |
| 9747 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 9748 | .ACCES => return error.AccessDenied, |
| 9749 | .LOOP => return error.SymLinkLoop, |
| 9750 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 9751 | .NAMETOOLONG => return error.NameTooLong, |
| 9752 | .NFILE => return error.SystemFdQuotaExceeded, |
| 9753 | .NODEV => return error.NoDevice, |
| 9754 | .NOENT => return error.FileNotFound, |
| 9755 | .NOMEM => return error.SystemResources, |
| 9756 | .NOTDIR => return error.NotDir, |
| 9757 | .PERM => return error.PermissionDenied, |
| 9758 | .NOTCAPABLE => return error.AccessDenied, |
| 9759 | .ILSEQ => return error.BadPathName, |
| 9760 | else => |err| return posix.unexpectedErrno(err), |
| 9761 | } |
| 9762 | }, |
| 9763 | } |
| 9764 | } |
| 9765 | } |
| 9766 | |
| 9767 | fn dirHardLink( |
| 9768 | userdata: ?*anyopaque, |
| 9769 | old_dir: Dir, |
| 9770 | old_sub_path: []const u8, |
| 9771 | new_dir: Dir, |
| 9772 | new_sub_path: []const u8, |
| 9773 | options: Dir.HardLinkOptions, |
| 9774 | ) Dir.HardLinkError!void { |
| 9775 | if (is_windows) return error.OperationUnsupported; |
| 9776 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9777 | _ = t; |
| 9778 | |
| 9779 | if (native_os == .wasi and !builtin.link_libc) { |
| 9780 | const flags: std.os.wasi.lookupflags_t = .{ |
| 9781 | .SYMLINK_FOLLOW = options.follow_symlinks, |
| 9782 | }; |
| 9783 | const syscall: Syscall = try .start(); |
| 9784 | while (true) { |
| 9785 | switch (std.os.wasi.path_link( |
| 9786 | old_dir.handle, |
| 9787 | flags, |
| 9788 | old_sub_path.ptr, |
| 9789 | old_sub_path.len, |
| 9790 | new_dir.handle, |
| 9791 | new_sub_path.ptr, |
| 9792 | new_sub_path.len, |
| 9793 | )) { |
| 9794 | .SUCCESS => return syscall.finish(), |
| 9795 | .INTR => { |
| 9796 | try syscall.checkCancel(); |
| 9797 | continue; |
| 9798 | }, |
| 9799 | else => |e| { |
| 9800 | syscall.finish(); |
| 9801 | switch (e) { |
| 9802 | .ACCES => return error.AccessDenied, |
| 9803 | .DQUOT => return error.DiskQuota, |
| 9804 | .EXIST => return error.PathAlreadyExists, |
| 9805 | .FAULT => |err| return errnoBug(err), |
| 9806 | .IO => return error.HardwareFailure, |
| 9807 | .LOOP => return error.SymLinkLoop, |
| 9808 | .MLINK => return error.LinkQuotaExceeded, |
| 9809 | .NAMETOOLONG => return error.NameTooLong, |
| 9810 | .NOENT => return error.FileNotFound, |
| 9811 | .NOMEM => return error.SystemResources, |
| 9812 | .NOSPC => return error.NoSpaceLeft, |
| 9813 | .NOTDIR => return error.NotDir, |
| 9814 | .PERM => return error.PermissionDenied, |
| 9815 | .ROFS => return error.ReadOnlyFileSystem, |
| 9816 | .XDEV => return error.CrossDevice, |
| 9817 | .INVAL => |err| return errnoBug(err), |
| 9818 | .ILSEQ => return error.BadPathName, |
| 9819 | else => |err| return posix.unexpectedErrno(err), |
| 9820 | } |
| 9821 | }, |
| 9822 | } |
| 9823 | } |
| 9824 | } |
| 9825 | |
| 9826 | var old_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 9827 | var new_path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 9828 | |
| 9829 | const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); |
| 9830 | const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); |
| 9831 | |
| 9832 | const flags: u32 = if (options.follow_symlinks) posix.AT.SYMLINK_FOLLOW else 0; |
| 9833 | return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags); |
| 9834 | } |
| 9835 | |
| 9836 | fn fileClose(userdata: ?*anyopaque, files: []const File) void { |
| 9837 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9838 | _ = t; |
| 9839 | for (files) |file| { |
| 9840 | if (is_windows) { |
| 9841 | windows.CloseHandle(file.handle); |
| 9842 | } else { |
| 9843 | closeFd(file.handle); |
| 9844 | } |
| 9845 | } |
| 9846 | } |
| 9847 | |
| 9848 | fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize { |
| 9849 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 9850 | _ = t; |
| 9851 | if (is_windows) return fileReadStreamingWindows(file, data); |
| 9852 | return fileReadStreamingPosix(file, data); |
| 9853 | } |
| 9854 | |
| 9855 | fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize { |
| 9856 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; |
| 9857 | var i: usize = 0; |
| 9858 | for (data) |buf| { |
| 9859 | if (iovecs_buffer.len - i == 0) break; |
| 9860 | if (buf.len != 0) { |
| 9861 | iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; |
| 9862 | i += 1; |
| 9863 | } |
| 9864 | } |
| 9865 | if (i == 0) return 0; |
| 9866 | const dest = iovecs_buffer[0..i]; |
| 9867 | assert(dest[0].len > 0); |
| 9868 | |
| 9869 | if (native_os == .wasi and !builtin.link_libc) { |
| 9870 | const syscall: Syscall = try .start(); |
| 9871 | while (true) { |
| 9872 | var nread: usize = undefined; |
| 9873 | switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { |
| 9874 | .SUCCESS => { |
| 9875 | syscall.finish(); |
| 9876 | if (nread == 0) return error.EndOfStream; |
| 9877 | return nread; |
| 9878 | }, |
| 9879 | .INTR, .TIMEDOUT => { |
| 9880 | try syscall.checkCancel(); |
| 9881 | continue; |
| 9882 | }, |
| 9883 | .BADF => return syscall.fail(error.IsDir), // File operation on directory. |
| 9884 | .IO => return syscall.fail(error.InputOutput), |
| 9885 | .ISDIR => return syscall.fail(error.IsDir), |
| 9886 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 9887 | .NOMEM => return syscall.fail(error.SystemResources), |
| 9888 | .NOTCONN => return syscall.fail(error.SocketUnconnected), |
| 9889 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 9890 | .NOTCAPABLE => return syscall.fail(error.AccessDenied), |
| 9891 | .INVAL => |err| return syscall.errnoBug(err), |
| 9892 | .FAULT => |err| return syscall.errnoBug(err), |
| 9893 | else => |err| return syscall.unexpectedErrno(err), |
| 9894 | } |
| 9895 | } |
| 9896 | } |
| 9897 | |
| 9898 | const syscall: Syscall = try .start(); |
| 9899 | while (true) { |
| 9900 | const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len)); |
| 9901 | switch (posix.errno(rc)) { |
| 9902 | .SUCCESS => { |
| 9903 | syscall.finish(); |
| 9904 | if (rc == 0) return error.EndOfStream; |
| 9905 | return @intCast(rc); |
| 9906 | }, |
| 9907 | .INTR, .TIMEDOUT => { |
| 9908 | try syscall.checkCancel(); |
| 9909 | continue; |
| 9910 | }, |
| 9911 | .BADF => { |
| 9912 | syscall.finish(); |
| 9913 | if (native_os == .wasi) return error.IsDir; // File operation on directory. |
| 9914 | return error.NotOpenForReading; |
| 9915 | }, |
| 9916 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 9917 | .IO => return syscall.fail(error.InputOutput), |
| 9918 | .ISDIR => return syscall.fail(error.IsDir), |
| 9919 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 9920 | .NOMEM => return syscall.fail(error.SystemResources), |
| 9921 | .NOTCONN => return syscall.fail(error.SocketUnconnected), |
| 9922 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 9923 | .INVAL => |err| return syscall.errnoBug(err), |
| 9924 | .FAULT => |err| return syscall.errnoBug(err), |
| 9925 | else => |err| return syscall.unexpectedErrno(err), |
| 9926 | } |
| 9927 | } |
| 9928 | } |
| 9929 | |
| 9930 | fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { |
| 9931 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 9932 | var index: usize = 0; |
| 9933 | while (data.len - index != 0 and data[index].len == 0) index += 1; |
| 9934 | if (data.len - index == 0) return 0; |
| 9935 | const buffer = data[index]; |
| 9936 | const short_buffer_len = std.math.lossyCast(u32, buffer.len); |
| 9937 | if (file.flags.nonblocking) { |
| 9938 | var done: bool = false; |
| 9939 | switch (windows.ntdll.NtReadFile( |
| 9940 | file.handle, |
| 9941 | null, // event |
| 9942 | flagApc, |
| 9943 | &done, // APC context |
| 9944 | &iosb, |
| 9945 | buffer.ptr, |
| 9946 | short_buffer_len, |
| 9947 | null, // byte offset |
| 9948 | null, // key |
| 9949 | )) { |
| 9950 | // We must wait for the APC routine. |
| 9951 | .PENDING, .SUCCESS => while (!done) { |
| 9952 | // Once we get here we must not return from the function until the |
| 9953 | // operation completes, thereby releasing reference to the iosb. |
| 9954 | const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { |
| 9955 | error.Canceled => |e| { |
| 9956 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; |
| 9957 | _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); |
| 9958 | while (!done) waitForApcOrAlert(); |
| 9959 | return e; |
| 9960 | }, |
| 9961 | }; |
| 9962 | waitForApcOrAlert(); |
| 9963 | alertable_syscall.finish(); |
| 9964 | }, |
| 9965 | else => |status| iosb.u.Status = status, |
| 9966 | } |
| 9967 | } else { |
| 9968 | const syscall: Syscall = try .start(); |
| 9969 | while (true) switch (windows.ntdll.NtReadFile( |
| 9970 | file.handle, |
| 9971 | null, // event |
| 9972 | null, // APC routine |
| 9973 | null, // APC context |
| 9974 | &iosb, |
| 9975 | buffer.ptr, |
| 9976 | short_buffer_len, |
| 9977 | null, // byte offset |
| 9978 | null, // key |
| 9979 | )) { |
| 9980 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 9981 | .CANCELLED => { |
| 9982 | try syscall.checkCancel(); |
| 9983 | continue; |
| 9984 | }, |
| 9985 | else => |status| { |
| 9986 | syscall.finish(); |
| 9987 | iosb.u.Status = status; |
| 9988 | break; |
| 9989 | }, |
| 9990 | }; |
| 9991 | } |
| 9992 | return ntReadFileResult(&iosb); |
| 9993 | } |
| 9994 | |
| 9995 | fn flagApc(userdata: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(apc_align) callconv(.winapi) void { |
| 9996 | const flag: *bool = @ptrCast(userdata); |
| 9997 | flag.* = true; |
| 9998 | } |
| 9999 | |
| 10000 | fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { |
| 10001 | switch (io_status_block.u.Status) { |
| 10002 | .PENDING => unreachable, |
| 10003 | .CANCELLED => unreachable, |
| 10004 | .SUCCESS => return io_status_block.Information, |
| 10005 | .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream, |
| 10006 | .INVALID_HANDLE => return error.NotOpenForReading, |
| 10007 | .INVALID_DEVICE_REQUEST => return error.IsDir, |
| 10008 | .FILE_LOCK_CONFLICT => return error.LockViolation, |
| 10009 | .ACCESS_DENIED => return error.AccessDenied, |
| 10010 | else => |status| return windows.unexpectedStatus(status), |
| 10011 | } |
| 10012 | } |
| 10013 | |
| 10014 | fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { |
| 10015 | switch (io_status_block.u.Status) { |
| 10016 | .PENDING => unreachable, |
| 10017 | .CANCELLED => unreachable, |
| 10018 | .SUCCESS => return io_status_block.Information, |
| 10019 | .INVALID_USER_BUFFER => return error.SystemResources, |
| 10020 | .NO_MEMORY => return error.SystemResources, |
| 10021 | .QUOTA_EXCEEDED => return error.SystemResources, |
| 10022 | .PIPE_BROKEN => return error.BrokenPipe, |
| 10023 | .INVALID_HANDLE => return error.NotOpenForWriting, |
| 10024 | .FILE_LOCK_CONFLICT => return error.LockViolation, |
| 10025 | .ACCESS_DENIED => return error.AccessDenied, |
| 10026 | .WORKING_SET_QUOTA => return error.SystemResources, |
| 10027 | .DISK_FULL => return error.NoSpaceLeft, |
| 10028 | else => |status| return windows.unexpectedStatus(status), |
| 10029 | } |
| 10030 | } |
| 10031 | |
| 10032 | fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { |
| 10033 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; |
| 10034 | var i: usize = 0; |
| 10035 | for (data) |buf| { |
| 10036 | if (iovecs_buffer.len - i == 0) break; |
| 10037 | if (buf.len != 0) { |
| 10038 | iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; |
| 10039 | i += 1; |
| 10040 | } |
| 10041 | } |
| 10042 | if (i == 0) return 0; |
| 10043 | const dest = iovecs_buffer[0..i]; |
| 10044 | assert(dest[0].len > 0); |
| 10045 | |
| 10046 | if (native_os == .wasi and !builtin.link_libc) { |
| 10047 | const syscall: Syscall = try .start(); |
| 10048 | while (true) { |
| 10049 | var nread: usize = undefined; |
| 10050 | switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { |
| 10051 | .SUCCESS => { |
| 10052 | syscall.finish(); |
| 10053 | return nread; |
| 10054 | }, |
| 10055 | .INTR, .TIMEDOUT => { |
| 10056 | try syscall.checkCancel(); |
| 10057 | continue; |
| 10058 | }, |
| 10059 | .NOTCONN => |err| return syscall.errnoBug(err), // not a socket |
| 10060 | .CONNRESET => |err| return syscall.errnoBug(err), // not a socket |
| 10061 | .INVAL => |err| return syscall.errnoBug(err), |
| 10062 | .FAULT => |err| return syscall.errnoBug(err), // segmentation fault |
| 10063 | .AGAIN => |err| return syscall.errnoBug(err), |
| 10064 | .IO => return syscall.fail(error.InputOutput), |
| 10065 | .ISDIR => return syscall.fail(error.IsDir), |
| 10066 | .BADF => return syscall.fail(error.IsDir), |
| 10067 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 10068 | .NOMEM => return syscall.fail(error.SystemResources), |
| 10069 | .NXIO => return syscall.fail(error.Unseekable), |
| 10070 | .SPIPE => return syscall.fail(error.Unseekable), |
| 10071 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 10072 | .NOTCAPABLE => return syscall.fail(error.AccessDenied), |
| 10073 | else => |err| return syscall.unexpectedErrno(err), |
| 10074 | } |
| 10075 | } |
| 10076 | } |
| 10077 | |
| 10078 | if (have_preadv) { |
| 10079 | const syscall: Syscall = try .start(); |
| 10080 | while (true) { |
| 10081 | const rc = if (native_os == .haiku) |
| 10082 | posix.system.readv_pos(file.handle, @bitCast(offset), dest.ptr, @intCast(dest.len)) |
| 10083 | else |
| 10084 | preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); |
| 10085 | switch (posix.errno(rc)) { |
| 10086 | .SUCCESS => { |
| 10087 | syscall.finish(); |
| 10088 | return @bitCast(rc); |
| 10089 | }, |
| 10090 | .INTR, .TIMEDOUT => { |
| 10091 | try syscall.checkCancel(); |
| 10092 | continue; |
| 10093 | }, |
| 10094 | .NXIO => return syscall.fail(error.Unseekable), |
| 10095 | .SPIPE => return syscall.fail(error.Unseekable), |
| 10096 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 10097 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 10098 | .NOMEM => return syscall.fail(error.SystemResources), |
| 10099 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 10100 | .IO => return syscall.fail(error.InputOutput), |
| 10101 | .ISDIR => return syscall.fail(error.IsDir), |
| 10102 | .NOTCONN => |err| return syscall.errnoBug(err), // not a socket |
| 10103 | .CONNRESET => |err| return syscall.errnoBug(err), // not a socket |
| 10104 | .INVAL => |err| return syscall.errnoBug(err), |
| 10105 | .FAULT => |err| return syscall.errnoBug(err), |
| 10106 | .BADF => { |
| 10107 | syscall.finish(); |
| 10108 | if (native_os == .wasi) return error.IsDir; // File operation on directory. |
| 10109 | return error.NotOpenForReading; |
| 10110 | }, |
| 10111 | else => |err| return syscall.unexpectedErrno(err), |
| 10112 | } |
| 10113 | } |
| 10114 | } |
| 10115 | |
| 10116 | const syscall: Syscall = try .start(); |
| 10117 | while (true) { |
| 10118 | const rc = pread_sym(file.handle, dest[0].base, @intCast(dest[0].len), @bitCast(offset)); |
| 10119 | switch (posix.errno(rc)) { |
| 10120 | .SUCCESS => { |
| 10121 | syscall.finish(); |
| 10122 | return @bitCast(rc); |
| 10123 | }, |
| 10124 | .INTR, .TIMEDOUT => { |
| 10125 | try syscall.checkCancel(); |
| 10126 | continue; |
| 10127 | }, |
| 10128 | .NXIO => return syscall.fail(error.Unseekable), |
| 10129 | .SPIPE => return syscall.fail(error.Unseekable), |
| 10130 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 10131 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 10132 | .NOMEM => return syscall.fail(error.SystemResources), |
| 10133 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 10134 | .IO => return syscall.fail(error.InputOutput), |
| 10135 | .ISDIR => return syscall.fail(error.IsDir), |
| 10136 | .NOTCONN => |err| return syscall.errnoBug(err), // not a socket |
| 10137 | .CONNRESET => |err| return syscall.errnoBug(err), // not a socket |
| 10138 | .INVAL => |err| return syscall.errnoBug(err), |
| 10139 | .FAULT => |err| return syscall.errnoBug(err), |
| 10140 | .BADF => { |
| 10141 | syscall.finish(); |
| 10142 | if (native_os == .wasi) return error.IsDir; // File operation on directory. |
| 10143 | return error.NotOpenForReading; |
| 10144 | }, |
| 10145 | else => |err| return syscall.unexpectedErrno(err), |
| 10146 | } |
| 10147 | } |
| 10148 | } |
| 10149 | |
| 10150 | fn fileReadPositional(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { |
| 10151 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10152 | _ = t; |
| 10153 | if (is_windows) return fileReadPositionalWindows(file, data, offset); |
| 10154 | return fileReadPositionalPosix(file, data, offset); |
| 10155 | } |
| 10156 | |
| 10157 | fn fileReadPositionalWindows(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { |
| 10158 | var index: usize = 0; |
| 10159 | while (index < data.len and data[index].len == 0) index += 1; |
| 10160 | if (index == data.len) return 0; |
| 10161 | const buffer = data[index]; |
| 10162 | |
| 10163 | return readFilePositionalWindows(file, buffer, offset); |
| 10164 | } |
| 10165 | |
| 10166 | fn readFilePositionalWindows(file: File, buffer: []u8, offset: u64) File.ReadPositionalError!usize { |
| 10167 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 10168 | const short_buffer_len = std.math.lossyCast(u32, buffer.len); |
| 10169 | const signed_offset: windows.LARGE_INTEGER = @intCast(offset); |
| 10170 | if (file.flags.nonblocking) { |
| 10171 | var done: bool = false; |
| 10172 | switch (windows.ntdll.NtReadFile( |
| 10173 | file.handle, |
| 10174 | null, // event |
| 10175 | flagApc, |
| 10176 | &done, // APC context |
| 10177 | &iosb, |
| 10178 | buffer.ptr, |
| 10179 | short_buffer_len, |
| 10180 | &signed_offset, |
| 10181 | null, // key |
| 10182 | )) { |
| 10183 | // We must wait for the APC routine. |
| 10184 | .PENDING, .SUCCESS => while (!done) { |
| 10185 | // Once we get here we must not return from the function until the |
| 10186 | // operation completes, thereby releasing reference to the iosb. |
| 10187 | const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { |
| 10188 | error.Canceled => |e| { |
| 10189 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; |
| 10190 | _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); |
| 10191 | while (!done) waitForApcOrAlert(); |
| 10192 | return e; |
| 10193 | }, |
| 10194 | }; |
| 10195 | waitForApcOrAlert(); |
| 10196 | alertable_syscall.finish(); |
| 10197 | }, |
| 10198 | else => |status| iosb.u.Status = status, |
| 10199 | } |
| 10200 | } else { |
| 10201 | const syscall: Syscall = try .start(); |
| 10202 | while (true) switch (windows.ntdll.NtReadFile( |
| 10203 | file.handle, |
| 10204 | null, // event |
| 10205 | null, // APC routine |
| 10206 | null, // APC context |
| 10207 | &iosb, |
| 10208 | buffer.ptr, |
| 10209 | short_buffer_len, |
| 10210 | &signed_offset, |
| 10211 | null, // key |
| 10212 | )) { |
| 10213 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 10214 | .CANCELLED => try syscall.checkCancel(), |
| 10215 | else => |status| { |
| 10216 | syscall.finish(); |
| 10217 | iosb.u.Status = status; |
| 10218 | break; |
| 10219 | }, |
| 10220 | }; |
| 10221 | } |
| 10222 | return ntReadFileResult(&iosb) catch |err| switch (err) { |
| 10223 | error.EndOfStream => 0, |
| 10224 | else => |e| e, |
| 10225 | }; |
| 10226 | } |
| 10227 | |
| 10228 | fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void { |
| 10229 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10230 | _ = t; |
| 10231 | |
| 10232 | if (is_windows) { |
| 10233 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 10234 | var info: windows.FILE.POSITION_INFORMATION = undefined; |
| 10235 | const syscall: Syscall = try .start(); |
| 10236 | while (true) switch (windows.ntdll.NtQueryInformationFile( |
| 10237 | file.handle, |
| 10238 | &iosb, |
| 10239 | &info, |
| 10240 | @sizeOf(windows.FILE.POSITION_INFORMATION), |
| 10241 | .Position, |
| 10242 | )) { |
| 10243 | .SUCCESS => break, |
| 10244 | .CANCELLED => try syscall.checkCancel(), |
| 10245 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 10246 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), |
| 10247 | else => |status| return syscall.unexpectedNtstatus(status), |
| 10248 | }; |
| 10249 | info.CurrentByteOffset = @bitCast((if (offset >= 0) std.math.add( |
| 10250 | u64, |
| 10251 | @bitCast(info.CurrentByteOffset), |
| 10252 | @intCast(offset), |
| 10253 | ) else std.math.sub( |
| 10254 | u64, |
| 10255 | @bitCast(info.CurrentByteOffset), |
| 10256 | @intCast(-offset), |
| 10257 | )) catch |err| switch (err) { |
| 10258 | error.Overflow => return syscall.fail(error.Unseekable), |
| 10259 | }); |
| 10260 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 10261 | file.handle, |
| 10262 | &iosb, |
| 10263 | &info, |
| 10264 | @sizeOf(windows.FILE.POSITION_INFORMATION), |
| 10265 | .Position, |
| 10266 | )) { |
| 10267 | .SUCCESS => return syscall.finish(), |
| 10268 | .CANCELLED => try syscall.checkCancel(), |
| 10269 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 10270 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), |
| 10271 | else => |status| return syscall.unexpectedNtstatus(status), |
| 10272 | }; |
| 10273 | } |
| 10274 | |
| 10275 | if (native_os == .wasi and !builtin.link_libc) { |
| 10276 | var new_offset: std.os.wasi.filesize_t = undefined; |
| 10277 | const syscall: Syscall = try .start(); |
| 10278 | while (true) { |
| 10279 | switch (std.os.wasi.fd_seek(file.handle, offset, .CUR, &new_offset)) { |
| 10280 | .SUCCESS => { |
| 10281 | syscall.finish(); |
| 10282 | return; |
| 10283 | }, |
| 10284 | .INTR => { |
| 10285 | try syscall.checkCancel(); |
| 10286 | continue; |
| 10287 | }, |
| 10288 | else => |e| { |
| 10289 | syscall.finish(); |
| 10290 | switch (e) { |
| 10291 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 10292 | .INVAL => return error.Unseekable, |
| 10293 | .OVERFLOW => return error.Unseekable, |
| 10294 | .SPIPE => return error.Unseekable, |
| 10295 | .NXIO => return error.Unseekable, |
| 10296 | .NOTCAPABLE => return error.AccessDenied, |
| 10297 | else => |err| return posix.unexpectedErrno(err), |
| 10298 | } |
| 10299 | }, |
| 10300 | } |
| 10301 | } |
| 10302 | } |
| 10303 | |
| 10304 | if (posix.SEEK == void) return error.Unseekable; |
| 10305 | |
| 10306 | if (native_os == .linux and !builtin.link_libc and @sizeOf(posix.system.syscall_arg_t) == 4) { |
| 10307 | var result: i64 = undefined; |
| 10308 | const syscall: Syscall = try .start(); |
| 10309 | while (true) { |
| 10310 | switch (posix.errno(posix.system.llseek(file.handle, offset, &result, posix.SEEK.CUR))) { |
| 10311 | .SUCCESS => { |
| 10312 | syscall.finish(); |
| 10313 | return; |
| 10314 | }, |
| 10315 | .INTR => { |
| 10316 | try syscall.checkCancel(); |
| 10317 | continue; |
| 10318 | }, |
| 10319 | else => |e| { |
| 10320 | syscall.finish(); |
| 10321 | switch (e) { |
| 10322 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 10323 | .INVAL => return error.Unseekable, |
| 10324 | .OVERFLOW => return error.Unseekable, |
| 10325 | .SPIPE => return error.Unseekable, |
| 10326 | .NXIO => return error.Unseekable, |
| 10327 | else => |err| return posix.unexpectedErrno(err), |
| 10328 | } |
| 10329 | }, |
| 10330 | } |
| 10331 | } |
| 10332 | } |
| 10333 | |
| 10334 | const syscall: Syscall = try .start(); |
| 10335 | while (true) { |
| 10336 | switch (posix.errno(lseek_sym(file.handle, offset, posix.SEEK.CUR))) { |
| 10337 | .SUCCESS => { |
| 10338 | syscall.finish(); |
| 10339 | return; |
| 10340 | }, |
| 10341 | .INTR => { |
| 10342 | try syscall.checkCancel(); |
| 10343 | continue; |
| 10344 | }, |
| 10345 | else => |e| { |
| 10346 | syscall.finish(); |
| 10347 | switch (e) { |
| 10348 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 10349 | .INVAL => return error.Unseekable, |
| 10350 | .OVERFLOW => return error.Unseekable, |
| 10351 | .SPIPE => return error.Unseekable, |
| 10352 | .NXIO => return error.Unseekable, |
| 10353 | else => |err| return posix.unexpectedErrno(err), |
| 10354 | } |
| 10355 | }, |
| 10356 | } |
| 10357 | } |
| 10358 | } |
| 10359 | |
| 10360 | fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void { |
| 10361 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10362 | _ = t; |
| 10363 | |
| 10364 | if (is_windows) { |
| 10365 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 10366 | var info: windows.FILE.POSITION_INFORMATION = .{ .CurrentByteOffset = @bitCast(offset) }; |
| 10367 | const syscall: Syscall = try .start(); |
| 10368 | while (true) switch (windows.ntdll.NtSetInformationFile( |
| 10369 | file.handle, |
| 10370 | &iosb, |
| 10371 | &info, |
| 10372 | @sizeOf(windows.FILE.POSITION_INFORMATION), |
| 10373 | .Position, |
| 10374 | )) { |
| 10375 | .SUCCESS => return syscall.finish(), |
| 10376 | .CANCELLED => try syscall.checkCancel(), |
| 10377 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 10378 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), |
| 10379 | else => |status| return syscall.unexpectedNtstatus(status), |
| 10380 | }; |
| 10381 | } |
| 10382 | |
| 10383 | if (native_os == .wasi and !builtin.link_libc) { |
| 10384 | const syscall: Syscall = try .start(); |
| 10385 | while (true) { |
| 10386 | var new_offset: std.os.wasi.filesize_t = undefined; |
| 10387 | switch (std.os.wasi.fd_seek(file.handle, @bitCast(offset), .SET, &new_offset)) { |
| 10388 | .SUCCESS => { |
| 10389 | syscall.finish(); |
| 10390 | return; |
| 10391 | }, |
| 10392 | .INTR => { |
| 10393 | try syscall.checkCancel(); |
| 10394 | continue; |
| 10395 | }, |
| 10396 | else => |e| { |
| 10397 | syscall.finish(); |
| 10398 | switch (e) { |
| 10399 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 10400 | .INVAL => return error.Unseekable, |
| 10401 | .OVERFLOW => return error.Unseekable, |
| 10402 | .SPIPE => return error.Unseekable, |
| 10403 | .NXIO => return error.Unseekable, |
| 10404 | .NOTCAPABLE => return error.AccessDenied, |
| 10405 | else => |err| return posix.unexpectedErrno(err), |
| 10406 | } |
| 10407 | }, |
| 10408 | } |
| 10409 | } |
| 10410 | } |
| 10411 | |
| 10412 | if (posix.SEEK == void) return error.Unseekable; |
| 10413 | |
| 10414 | return posixSeekTo(file.handle, offset); |
| 10415 | } |
| 10416 | |
| 10417 | fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void { |
| 10418 | if (native_os == .linux and !builtin.link_libc and @sizeOf(posix.system.syscall_arg_t) == 4) { |
| 10419 | const syscall: Syscall = try .start(); |
| 10420 | while (true) { |
| 10421 | var result: i64 = undefined; |
| 10422 | switch (posix.errno(posix.system.llseek(fd, @bitCast(offset), &result, posix.SEEK.SET))) { |
| 10423 | .SUCCESS => { |
| 10424 | syscall.finish(); |
| 10425 | return; |
| 10426 | }, |
| 10427 | .INTR => { |
| 10428 | try syscall.checkCancel(); |
| 10429 | continue; |
| 10430 | }, |
| 10431 | else => |e| { |
| 10432 | syscall.finish(); |
| 10433 | switch (e) { |
| 10434 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 10435 | .INVAL => return error.Unseekable, |
| 10436 | .OVERFLOW => return error.Unseekable, |
| 10437 | .SPIPE => return error.Unseekable, |
| 10438 | .NXIO => return error.Unseekable, |
| 10439 | else => |err| return posix.unexpectedErrno(err), |
| 10440 | } |
| 10441 | }, |
| 10442 | } |
| 10443 | } |
| 10444 | } |
| 10445 | |
| 10446 | const syscall: Syscall = try .start(); |
| 10447 | while (true) { |
| 10448 | switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) { |
| 10449 | .SUCCESS => { |
| 10450 | syscall.finish(); |
| 10451 | return; |
| 10452 | }, |
| 10453 | .INTR => { |
| 10454 | try syscall.checkCancel(); |
| 10455 | continue; |
| 10456 | }, |
| 10457 | else => |e| { |
| 10458 | syscall.finish(); |
| 10459 | switch (e) { |
| 10460 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 10461 | .INVAL => return error.Unseekable, |
| 10462 | .OVERFLOW => return error.Unseekable, |
| 10463 | .SPIPE => return error.Unseekable, |
| 10464 | .NXIO => return error.Unseekable, |
| 10465 | else => |err| return posix.unexpectedErrno(err), |
| 10466 | } |
| 10467 | }, |
| 10468 | } |
| 10469 | } |
| 10470 | } |
| 10471 | |
| 10472 | fn processExecutableOpen(userdata: ?*anyopaque, flags: Dir.OpenFileOptions) process.OpenExecutableError!File { |
| 10473 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10474 | switch (native_os) { |
| 10475 | .wasi => return error.OperationUnsupported, |
| 10476 | .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), |
| 10477 | .windows => { |
| 10478 | // If ImagePathName is a symlink, then it will contain the path of the symlink, |
| 10479 | // not the path that the symlink points to. However, because we are opening |
| 10480 | // the file, we can let the openFileW call follow the symlink for us. |
| 10481 | const image_path_name = windows.peb().ProcessParameters.ImagePathName.sliceZ(); |
| 10482 | const prefixed_path_w = try wToPrefixedFileW(null, image_path_name, .{}); |
| 10483 | return dirOpenFileWtf16(null, prefixed_path_w.span(), flags); |
| 10484 | }, |
| 10485 | .driverkit, |
| 10486 | .ios, |
| 10487 | .maccatalyst, |
| 10488 | .macos, |
| 10489 | .tvos, |
| 10490 | .visionos, |
| 10491 | .watchos, |
| 10492 | => { |
| 10493 | // _NSGetExecutablePath() returns a path that might be a symlink to |
| 10494 | // the executable. Here it does not matter since we open it. |
| 10495 | var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined; |
| 10496 | var n: u32 = symlink_path_buf.len; |
| 10497 | const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n); |
| 10498 | if (rc != 0) return error.NameTooLong; |
| 10499 | const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); |
| 10500 | return dirOpenFilePosix(t, .cwd(), symlink_path, flags); |
| 10501 | }, |
| 10502 | else => { |
| 10503 | var buffer: [Dir.max_path_bytes]u8 = undefined; |
| 10504 | const n = try processExecutablePath(t, &buffer); |
| 10505 | buffer[n] = 0; |
| 10506 | const executable_path = buffer[0..n :0]; |
| 10507 | return dirOpenFilePosix(t, .cwd(), executable_path, flags); |
| 10508 | }, |
| 10509 | } |
| 10510 | } |
| 10511 | |
| 10512 | fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.ExecutablePathError!usize { |
| 10513 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10514 | |
| 10515 | switch (native_os) { |
| 10516 | .driverkit, |
| 10517 | .ios, |
| 10518 | .maccatalyst, |
| 10519 | .macos, |
| 10520 | .tvos, |
| 10521 | .visionos, |
| 10522 | .watchos, |
| 10523 | => { |
| 10524 | // _NSGetExecutablePath() returns a path that might be a symlink to |
| 10525 | // the executable. |
| 10526 | var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined; |
| 10527 | var n: u32 = symlink_path_buf.len; |
| 10528 | const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n); |
| 10529 | if (rc != 0) return error.NameTooLong; |
| 10530 | const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); |
| 10531 | return Io.Dir.realPathFileAbsolute(io(t), symlink_path, out_buffer) catch |err| switch (err) { |
| 10532 | error.NetworkNotFound => unreachable, // Windows-only |
| 10533 | error.FileBusy => unreachable, // Windows-only |
| 10534 | else => |e| return e, |
| 10535 | }; |
| 10536 | }, |
| 10537 | .linux, .serenity => return Io.Dir.readLinkAbsolute(io(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { |
| 10538 | error.UnsupportedReparsePointType => unreachable, // Windows-only |
| 10539 | error.NetworkNotFound => unreachable, // Windows-only |
| 10540 | error.FileBusy => unreachable, // Windows-only |
| 10541 | else => |e| return e, |
| 10542 | }, |
| 10543 | .illumos => return Io.Dir.readLinkAbsolute(io(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) { |
| 10544 | error.UnsupportedReparsePointType => unreachable, // Windows-only |
| 10545 | error.NetworkNotFound => unreachable, // Windows-only |
| 10546 | error.FileBusy => unreachable, // Windows-only |
| 10547 | else => |e| return e, |
| 10548 | }, |
| 10549 | .freebsd, .dragonfly => { |
| 10550 | var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; |
| 10551 | var out_len: usize = out_buffer.len; |
| 10552 | const syscall: Syscall = try .start(); |
| 10553 | while (true) switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { |
| 10554 | .SUCCESS => { |
| 10555 | syscall.finish(); |
| 10556 | return out_len - 1; // discard terminating NUL |
| 10557 | }, |
| 10558 | .INTR => { |
| 10559 | try syscall.checkCancel(); |
| 10560 | continue; |
| 10561 | }, |
| 10562 | .PERM => return syscall.fail(error.PermissionDenied), |
| 10563 | .NOMEM => return syscall.fail(error.SystemResources), |
| 10564 | .FAULT => |err| return syscall.errnoBug(err), |
| 10565 | .NOENT => |err| return syscall.errnoBug(err), |
| 10566 | else => |err| return syscall.unexpectedErrno(err), |
| 10567 | }; |
| 10568 | }, |
| 10569 | .netbsd => { |
| 10570 | var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; |
| 10571 | var out_len: usize = out_buffer.len; |
| 10572 | const syscall: Syscall = try .start(); |
| 10573 | while (true) { |
| 10574 | switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { |
| 10575 | .SUCCESS => { |
| 10576 | syscall.finish(); |
| 10577 | return out_len - 1; // discard terminating NUL |
| 10578 | }, |
| 10579 | .INTR => { |
| 10580 | try syscall.checkCancel(); |
| 10581 | continue; |
| 10582 | }, |
| 10583 | .PERM => return syscall.fail(error.PermissionDenied), |
| 10584 | .NOMEM => return syscall.fail(error.SystemResources), |
| 10585 | .FAULT => |err| return syscall.errnoBug(err), |
| 10586 | .NOENT => |err| return syscall.errnoBug(err), |
| 10587 | else => |err| return syscall.unexpectedErrno(err), |
| 10588 | } |
| 10589 | } |
| 10590 | }, |
| 10591 | .openbsd, .haiku => { |
| 10592 | // The best we can do on these operating systems is check based on |
| 10593 | // the first process argument. |
| 10594 | const argv0 = std.mem.span(t.argv0.value orelse return error.OperationUnsupported); |
| 10595 | if (std.mem.findScalar(u8, argv0, '/') != null) { |
| 10596 | // argv[0] is a path (relative or absolute): use realpath(3) directly |
| 10597 | var resolved_buf: [std.c.PATH_MAX]u8 = undefined; |
| 10598 | const syscall: Syscall = try .start(); |
| 10599 | while (true) { |
| 10600 | if (std.c.realpath(argv0, &resolved_buf)) |p| { |
| 10601 | assert(p == &resolved_buf); |
| 10602 | break syscall.finish(); |
| 10603 | } else switch (@as(std.c.E, @fromBackingInt(@intCast(std.c._errno().*)))) { |
| 10604 | .INTR => { |
| 10605 | try syscall.checkCancel(); |
| 10606 | continue; |
| 10607 | }, |
| 10608 | else => |e| { |
| 10609 | syscall.finish(); |
| 10610 | switch (e) { |
| 10611 | .ACCES => return error.AccessDenied, |
| 10612 | .INVAL => |err| return errnoBug(err), // the pathname argument is a null pointer |
| 10613 | .IO => return error.InputOutput, |
| 10614 | .LOOP => return error.SymLinkLoop, |
| 10615 | .NAMETOOLONG => return error.NameTooLong, |
| 10616 | .NOENT => return error.FileNotFound, |
| 10617 | .NOTDIR => return error.NotDir, |
| 10618 | .NOMEM => |err| return errnoBug(err), // sufficient storage space is unavailable for allocation |
| 10619 | else => |err| return posix.unexpectedErrno(err), |
| 10620 | } |
| 10621 | }, |
| 10622 | } |
| 10623 | } |
| 10624 | const resolved = std.mem.sliceTo(&resolved_buf, 0); |
| 10625 | if (resolved.len > out_buffer.len) |
| 10626 | return error.NameTooLong; |
| 10627 | @memcpy(out_buffer[0..resolved.len], resolved); |
| 10628 | return resolved.len; |
| 10629 | } else if (argv0.len != 0) { |
| 10630 | // argv[0] is not empty (and not a path): search PATH |
| 10631 | t.scanEnviron(); |
| 10632 | const PATH = t.environ.string.PATH orelse return error.FileNotFound; |
| 10633 | var it = std.mem.tokenizeScalar(u8, PATH, ':'); |
| 10634 | it: while (it.next()) |dir| { |
| 10635 | var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined; |
| 10636 | const resolved_path = std.mem.printSentinel(&resolved_path_buf, "{s}/{s}", .{ |
| 10637 | dir, argv0, |
| 10638 | }, 0) catch continue; |
| 10639 | |
| 10640 | var resolved_buf: [std.c.PATH_MAX]u8 = undefined; |
| 10641 | const syscall: Syscall = try .start(); |
| 10642 | while (true) { |
| 10643 | if (std.c.realpath(resolved_path, &resolved_buf)) |p| { |
| 10644 | assert(p == &resolved_buf); |
| 10645 | break syscall.finish(); |
| 10646 | } else switch (@as(std.c.E, @fromBackingInt(@intCast(std.c._errno().*)))) { |
| 10647 | .INTR => { |
| 10648 | try syscall.checkCancel(); |
| 10649 | continue; |
| 10650 | }, |
| 10651 | .NAMETOOLONG => { |
| 10652 | syscall.finish(); |
| 10653 | return error.NameTooLong; |
| 10654 | }, |
| 10655 | .NOMEM => { |
| 10656 | syscall.finish(); |
| 10657 | return error.SystemResources; |
| 10658 | }, |
| 10659 | .IO => { |
| 10660 | syscall.finish(); |
| 10661 | return error.InputOutput; |
| 10662 | }, |
| 10663 | .ACCES, .LOOP, .NOENT, .NOTDIR => { |
| 10664 | syscall.finish(); |
| 10665 | continue :it; |
| 10666 | }, |
| 10667 | else => |err| { |
| 10668 | syscall.finish(); |
| 10669 | return posix.unexpectedErrno(err); |
| 10670 | }, |
| 10671 | } |
| 10672 | } |
| 10673 | const resolved = std.mem.sliceTo(&resolved_buf, 0); |
| 10674 | if (resolved.len > out_buffer.len) |
| 10675 | return error.NameTooLong; |
| 10676 | @memcpy(out_buffer[0..resolved.len], resolved); |
| 10677 | return resolved.len; |
| 10678 | } |
| 10679 | } |
| 10680 | return error.FileNotFound; |
| 10681 | }, |
| 10682 | .windows => { |
| 10683 | // If ImagePathName is a symlink, then it will contain the path of the |
| 10684 | // symlink, not the path that the symlink points to. We want the path |
| 10685 | // that the symlink points to, though, so we need to get the realpath. |
| 10686 | var path_name_w_buf = try wToPrefixedFileW( |
| 10687 | null, |
| 10688 | windows.peb().ProcessParameters.ImagePathName.sliceZ(), |
| 10689 | .{}, |
| 10690 | ); |
| 10691 | |
| 10692 | const h_file = handle: { |
| 10693 | if (OpenFile(path_name_w_buf.span(), .{ |
| 10694 | .dir = null, |
| 10695 | .access_mask = .{ |
| 10696 | .GENERIC = .{ .READ = true }, |
| 10697 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 10698 | }, |
| 10699 | .creation = .OPEN, |
| 10700 | .filter = .any, |
| 10701 | })) |handle| { |
| 10702 | break :handle handle; |
| 10703 | } else |err| switch (err) { |
| 10704 | error.WouldBlock => unreachable, |
| 10705 | error.FileBusy => unreachable, |
| 10706 | else => |e| return e, |
| 10707 | } |
| 10708 | }; |
| 10709 | defer windows.CloseHandle(h_file); |
| 10710 | |
| 10711 | const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); |
| 10712 | |
| 10713 | const len = std.unicode.calcWtf8Len(wide_slice); |
| 10714 | if (len > out_buffer.len) |
| 10715 | return error.NameTooLong; |
| 10716 | |
| 10717 | const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); |
| 10718 | return end_index; |
| 10719 | }, |
| 10720 | else => return error.OperationUnsupported, |
| 10721 | } |
| 10722 | } |
| 10723 | |
| 10724 | fn fileWritePositional( |
| 10725 | userdata: ?*anyopaque, |
| 10726 | file: File, |
| 10727 | header: []const u8, |
| 10728 | data: []const []const u8, |
| 10729 | splat: usize, |
| 10730 | offset: u64, |
| 10731 | ) File.WritePositionalError!usize { |
| 10732 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10733 | _ = t; |
| 10734 | |
| 10735 | if (is_windows) { |
| 10736 | if (header.len != 0) { |
| 10737 | return writeFilePositionalWindows(file, header, offset); |
| 10738 | } |
| 10739 | for (data[0 .. data.len - 1]) |buf| { |
| 10740 | if (buf.len == 0) continue; |
| 10741 | return writeFilePositionalWindows(file, buf, offset); |
| 10742 | } |
| 10743 | const pattern = data[data.len - 1]; |
| 10744 | if (pattern.len == 0 or splat == 0) return 0; |
| 10745 | return writeFilePositionalWindows(file, pattern, offset); |
| 10746 | } |
| 10747 | |
| 10748 | var iovecs: [max_iovecs_len]posix.iovec_const = undefined; |
| 10749 | var iovlen: iovlen_t = 0; |
| 10750 | addBuf(&iovecs, &iovlen, header); |
| 10751 | for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); |
| 10752 | const pattern = data[data.len - 1]; |
| 10753 | |
| 10754 | var splat_backup_buffer: [splat_buffer_size]u8 = undefined; |
| 10755 | if (iovecs.len - iovlen != 0) switch (splat) { |
| 10756 | 0 => {}, |
| 10757 | 1 => addBuf(&iovecs, &iovlen, pattern), |
| 10758 | else => switch (pattern.len) { |
| 10759 | 0 => {}, |
| 10760 | 1 => { |
| 10761 | const splat_buffer = &splat_backup_buffer; |
| 10762 | const memset_len = @min(splat_buffer.len, splat); |
| 10763 | const buf = splat_buffer[0..memset_len]; |
| 10764 | @memset(buf, pattern[0]); |
| 10765 | addBuf(&iovecs, &iovlen, buf); |
| 10766 | var remaining_splat = splat - buf.len; |
| 10767 | while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { |
| 10768 | assert(buf.len == splat_buffer.len); |
| 10769 | addBuf(&iovecs, &iovlen, splat_buffer); |
| 10770 | remaining_splat -= splat_buffer.len; |
| 10771 | } |
| 10772 | addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); |
| 10773 | }, |
| 10774 | else => for (0..@min(splat, iovecs.len - iovlen)) |_| { |
| 10775 | addBuf(&iovecs, &iovlen, pattern); |
| 10776 | }, |
| 10777 | }, |
| 10778 | }; |
| 10779 | |
| 10780 | if (iovlen == 0) return 0; |
| 10781 | |
| 10782 | if (native_os == .wasi and !builtin.link_libc) { |
| 10783 | var n_written: usize = undefined; |
| 10784 | const syscall: Syscall = try .start(); |
| 10785 | while (true) { |
| 10786 | switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) { |
| 10787 | .SUCCESS => { |
| 10788 | syscall.finish(); |
| 10789 | return n_written; |
| 10790 | }, |
| 10791 | .INTR => { |
| 10792 | try syscall.checkCancel(); |
| 10793 | continue; |
| 10794 | }, |
| 10795 | else => |e| { |
| 10796 | syscall.finish(); |
| 10797 | switch (e) { |
| 10798 | .INVAL => |err| return errnoBug(err), |
| 10799 | .FAULT => |err| return errnoBug(err), |
| 10800 | .AGAIN => |err| return errnoBug(err), |
| 10801 | .BADF => return error.NotOpenForWriting, |
| 10802 | .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. |
| 10803 | .DQUOT => return error.DiskQuota, |
| 10804 | .FBIG => return error.FileTooBig, |
| 10805 | .IO => return error.InputOutput, |
| 10806 | .NOSPC => return error.NoSpaceLeft, |
| 10807 | .PERM => return error.PermissionDenied, |
| 10808 | .PIPE => return error.BrokenPipe, |
| 10809 | .NOTCAPABLE => return error.AccessDenied, |
| 10810 | .NXIO => return error.Unseekable, |
| 10811 | .SPIPE => return error.Unseekable, |
| 10812 | .OVERFLOW => return error.Unseekable, |
| 10813 | else => |err| return posix.unexpectedErrno(err), |
| 10814 | } |
| 10815 | }, |
| 10816 | } |
| 10817 | } |
| 10818 | } |
| 10819 | |
| 10820 | const syscall: Syscall = try .start(); |
| 10821 | while (true) { |
| 10822 | const rc = if (native_os == .haiku) |
| 10823 | posix.system.writev_pos(file.handle, @bitCast(offset), &iovecs, @intCast(iovlen)) |
| 10824 | else |
| 10825 | pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset)); |
| 10826 | switch (posix.errno(rc)) { |
| 10827 | .SUCCESS => { |
| 10828 | syscall.finish(); |
| 10829 | return @intCast(rc); |
| 10830 | }, |
| 10831 | .INTR => { |
| 10832 | try syscall.checkCancel(); |
| 10833 | continue; |
| 10834 | }, |
| 10835 | .INVAL => |err| return syscall.errnoBug(err), |
| 10836 | .FAULT => |err| return syscall.errnoBug(err), |
| 10837 | .DESTADDRREQ => |err| return syscall.errnoBug(err), // `connect` was never called. |
| 10838 | .CONNRESET => |err| return syscall.errnoBug(err), // Not a socket handle. |
| 10839 | .BADF => return syscall.fail(error.NotOpenForWriting), |
| 10840 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 10841 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 10842 | .FBIG => return syscall.fail(error.FileTooBig), |
| 10843 | .IO => return syscall.fail(error.InputOutput), |
| 10844 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 10845 | .PERM => return syscall.fail(error.PermissionDenied), |
| 10846 | .PIPE => return syscall.fail(error.BrokenPipe), |
| 10847 | .BUSY => return syscall.fail(error.DeviceBusy), |
| 10848 | .TXTBSY => return syscall.fail(error.FileBusy), |
| 10849 | .NXIO => return syscall.fail(error.Unseekable), |
| 10850 | .SPIPE => return syscall.fail(error.Unseekable), |
| 10851 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 10852 | else => |err| return syscall.unexpectedErrno(err), |
| 10853 | } |
| 10854 | } |
| 10855 | } |
| 10856 | |
| 10857 | fn writeFilePositionalWindows(file: File, buffer: []const u8, offset: u64) File.WritePositionalError!usize { |
| 10858 | assert(buffer.len != 0); |
| 10859 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 10860 | const short_buffer_len = std.math.lossyCast(u32, buffer.len); |
| 10861 | const signed_offset: windows.LARGE_INTEGER = @intCast(offset); |
| 10862 | if (file.flags.nonblocking) { |
| 10863 | var done: bool = false; |
| 10864 | switch (windows.ntdll.NtWriteFile( |
| 10865 | file.handle, |
| 10866 | null, // event |
| 10867 | flagApc, |
| 10868 | &done, // APC context |
| 10869 | &iosb, |
| 10870 | buffer.ptr, |
| 10871 | short_buffer_len, |
| 10872 | &signed_offset, |
| 10873 | null, // key |
| 10874 | )) { |
| 10875 | // We must wait for the APC routine. |
| 10876 | .PENDING, .SUCCESS => while (!done) { |
| 10877 | // Once we get here we must not return from the function until the |
| 10878 | // operation completes, thereby releasing reference to the iosb. |
| 10879 | const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { |
| 10880 | error.Canceled => |e| { |
| 10881 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; |
| 10882 | _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); |
| 10883 | while (!done) waitForApcOrAlert(); |
| 10884 | return e; |
| 10885 | }, |
| 10886 | }; |
| 10887 | waitForApcOrAlert(); |
| 10888 | alertable_syscall.finish(); |
| 10889 | }, |
| 10890 | else => |status| iosb.u.Status = status, |
| 10891 | } |
| 10892 | } else { |
| 10893 | const syscall: Syscall = try .start(); |
| 10894 | while (true) switch (windows.ntdll.NtWriteFile( |
| 10895 | file.handle, |
| 10896 | null, // event |
| 10897 | null, // APC routine |
| 10898 | null, // APC context |
| 10899 | &iosb, |
| 10900 | buffer.ptr, |
| 10901 | short_buffer_len, |
| 10902 | &signed_offset, |
| 10903 | null, // key |
| 10904 | )) { |
| 10905 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 10906 | .CANCELLED => try syscall.checkCancel(), |
| 10907 | else => |status| { |
| 10908 | syscall.finish(); |
| 10909 | iosb.u.Status = status; |
| 10910 | return ntWriteFileResult(&iosb); |
| 10911 | }, |
| 10912 | }; |
| 10913 | } |
| 10914 | return ntWriteFileResult(&iosb); |
| 10915 | } |
| 10916 | |
| 10917 | fn fileWriteStreaming( |
| 10918 | userdata: ?*anyopaque, |
| 10919 | file: File, |
| 10920 | header: []const u8, |
| 10921 | data: []const []const u8, |
| 10922 | splat: usize, |
| 10923 | ) File.Writer.Error!usize { |
| 10924 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10925 | _ = t; |
| 10926 | |
| 10927 | if (is_windows) { |
| 10928 | const buffer = windowsWriteBuffer(header, data, splat); |
| 10929 | if (buffer.len == 0) return 0; |
| 10930 | return fileWriteStreamingWindows(file, buffer); |
| 10931 | } |
| 10932 | |
| 10933 | var iovecs: [max_iovecs_len]posix.iovec_const = undefined; |
| 10934 | var iovlen: iovlen_t = 0; |
| 10935 | addBuf(&iovecs, &iovlen, header); |
| 10936 | for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes); |
| 10937 | const pattern = data[data.len - 1]; |
| 10938 | |
| 10939 | var splat_backup_buffer: [splat_buffer_size]u8 = undefined; |
| 10940 | if (iovecs.len - iovlen != 0) switch (splat) { |
| 10941 | 0 => {}, |
| 10942 | 1 => addBuf(&iovecs, &iovlen, pattern), |
| 10943 | else => switch (pattern.len) { |
| 10944 | 0 => {}, |
| 10945 | 1 => { |
| 10946 | const splat_buffer = &splat_backup_buffer; |
| 10947 | const memset_len = @min(splat_buffer.len, splat); |
| 10948 | const buf = splat_buffer[0..memset_len]; |
| 10949 | @memset(buf, pattern[0]); |
| 10950 | addBuf(&iovecs, &iovlen, buf); |
| 10951 | var remaining_splat = splat - buf.len; |
| 10952 | while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) { |
| 10953 | assert(buf.len == splat_buffer.len); |
| 10954 | addBuf(&iovecs, &iovlen, splat_buffer); |
| 10955 | remaining_splat -= splat_buffer.len; |
| 10956 | } |
| 10957 | addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); |
| 10958 | }, |
| 10959 | else => for (0..@min(splat, iovecs.len - iovlen)) |_| { |
| 10960 | addBuf(&iovecs, &iovlen, pattern); |
| 10961 | }, |
| 10962 | }, |
| 10963 | }; |
| 10964 | |
| 10965 | if (iovlen == 0) return 0; |
| 10966 | |
| 10967 | if (native_os == .wasi and !builtin.link_libc) { |
| 10968 | var n_written: usize = undefined; |
| 10969 | const syscall: Syscall = try .start(); |
| 10970 | while (true) { |
| 10971 | switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) { |
| 10972 | .SUCCESS => { |
| 10973 | syscall.finish(); |
| 10974 | return n_written; |
| 10975 | }, |
| 10976 | .INTR => { |
| 10977 | try syscall.checkCancel(); |
| 10978 | continue; |
| 10979 | }, |
| 10980 | else => |e| { |
| 10981 | syscall.finish(); |
| 10982 | switch (e) { |
| 10983 | .INVAL => |err| return errnoBug(err), |
| 10984 | .FAULT => |err| return errnoBug(err), |
| 10985 | .AGAIN => |err| return errnoBug(err), |
| 10986 | .BADF => return error.NotOpenForWriting, // can be a race condition. |
| 10987 | .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. |
| 10988 | .DQUOT => return error.DiskQuota, |
| 10989 | .FBIG => return error.FileTooBig, |
| 10990 | .IO => return error.InputOutput, |
| 10991 | .NOSPC => return error.NoSpaceLeft, |
| 10992 | .PERM => return error.PermissionDenied, |
| 10993 | .PIPE => return error.BrokenPipe, |
| 10994 | .NOTCAPABLE => return error.AccessDenied, |
| 10995 | else => |err| return posix.unexpectedErrno(err), |
| 10996 | } |
| 10997 | }, |
| 10998 | } |
| 10999 | } |
| 11000 | } |
| 11001 | |
| 11002 | const syscall: Syscall = try .start(); |
| 11003 | while (true) { |
| 11004 | const rc = posix.system.writev(file.handle, &iovecs, @intCast(iovlen)); |
| 11005 | switch (posix.errno(rc)) { |
| 11006 | .SUCCESS => { |
| 11007 | syscall.finish(); |
| 11008 | return @intCast(rc); |
| 11009 | }, |
| 11010 | .INTR => { |
| 11011 | try syscall.checkCancel(); |
| 11012 | continue; |
| 11013 | }, |
| 11014 | else => |e| { |
| 11015 | syscall.finish(); |
| 11016 | switch (e) { |
| 11017 | .INVAL => |err| return errnoBug(err), |
| 11018 | .FAULT => |err| return errnoBug(err), |
| 11019 | .AGAIN => return error.WouldBlock, |
| 11020 | .BADF => return error.NotOpenForWriting, // Can be a race condition. |
| 11021 | .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called. |
| 11022 | .DQUOT => return error.DiskQuota, |
| 11023 | .FBIG => return error.FileTooBig, |
| 11024 | .IO => return error.InputOutput, |
| 11025 | .NOSPC => return error.NoSpaceLeft, |
| 11026 | .PERM => return error.PermissionDenied, |
| 11027 | .PIPE => return error.BrokenPipe, |
| 11028 | .CONNRESET => |err| return errnoBug(err), // Not a socket handle. |
| 11029 | .BUSY => return error.DeviceBusy, |
| 11030 | .ACCES => return error.AccessDenied, |
| 11031 | else => |err| return posix.unexpectedErrno(err), |
| 11032 | } |
| 11033 | }, |
| 11034 | } |
| 11035 | } |
| 11036 | } |
| 11037 | |
| 11038 | fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!usize { |
| 11039 | assert(buffer.len != 0); |
| 11040 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 11041 | if (file.flags.nonblocking) { |
| 11042 | var done: bool = false; |
| 11043 | switch (windows.ntdll.NtWriteFile( |
| 11044 | file.handle, |
| 11045 | null, // event |
| 11046 | flagApc, |
| 11047 | &done, // APC context |
| 11048 | &iosb, |
| 11049 | buffer.ptr, |
| 11050 | @intCast(buffer.len), |
| 11051 | null, // byte offset |
| 11052 | null, // key |
| 11053 | )) { |
| 11054 | // We must wait for the APC routine. |
| 11055 | .PENDING, .SUCCESS => while (!done) { |
| 11056 | // Once we get here we must not return from the function until the |
| 11057 | // operation completes, thereby releasing reference to io_status_block. |
| 11058 | const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { |
| 11059 | error.Canceled => |e| { |
| 11060 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; |
| 11061 | _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); |
| 11062 | while (!done) waitForApcOrAlert(); |
| 11063 | return e; |
| 11064 | }, |
| 11065 | }; |
| 11066 | waitForApcOrAlert(); |
| 11067 | alertable_syscall.finish(); |
| 11068 | }, |
| 11069 | else => |status| iosb.u.Status = status, |
| 11070 | } |
| 11071 | } else { |
| 11072 | const syscall: Syscall = try .start(); |
| 11073 | while (true) switch (windows.ntdll.NtWriteFile( |
| 11074 | file.handle, |
| 11075 | null, // event |
| 11076 | null, // APC routine |
| 11077 | null, // APC context |
| 11078 | &iosb, |
| 11079 | buffer.ptr, |
| 11080 | @intCast(buffer.len), |
| 11081 | null, // byte offset |
| 11082 | null, // key |
| 11083 | )) { |
| 11084 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag |
| 11085 | .CANCELLED => try syscall.checkCancel(), |
| 11086 | else => |status| { |
| 11087 | syscall.finish(); |
| 11088 | iosb.u.Status = status; |
| 11089 | break; |
| 11090 | }, |
| 11091 | }; |
| 11092 | } |
| 11093 | return ntWriteFileResult(&iosb); |
| 11094 | } |
| 11095 | |
| 11096 | fn fileWriteFileStreaming( |
| 11097 | userdata: ?*anyopaque, |
| 11098 | file: File, |
| 11099 | header: []const u8, |
| 11100 | file_reader: *File.Reader, |
| 11101 | limit: Io.Limit, |
| 11102 | ) File.Writer.WriteFileError!usize { |
| 11103 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11104 | const reader_buffered = file_reader.interface.buffered(); |
| 11105 | if (reader_buffered.len >= @backingInt(limit)) { |
| 11106 | const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); |
| 11107 | file_reader.interface.toss(n -| header.len); |
| 11108 | return n; |
| 11109 | } |
| 11110 | const file_limit = @backingInt(limit) - reader_buffered.len; |
| 11111 | const out_fd = file.handle; |
| 11112 | const in_fd = file_reader.file.handle; |
| 11113 | |
| 11114 | if (file_reader.size) |size| { |
| 11115 | if (size - file_reader.pos == 0) { |
| 11116 | if (reader_buffered.len != 0) { |
| 11117 | const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); |
| 11118 | file_reader.interface.toss(n -| header.len); |
| 11119 | return n; |
| 11120 | } else { |
| 11121 | return error.EndOfStream; |
| 11122 | } |
| 11123 | } |
| 11124 | } |
| 11125 | |
| 11126 | if (native_os == .freebsd) sf: { |
| 11127 | // Try using sendfile on FreeBSD. |
| 11128 | if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; |
| 11129 | const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; |
| 11130 | var hdtr_data: std.c.sf_hdtr = undefined; |
| 11131 | var headers: [2]posix.iovec_const = undefined; |
| 11132 | var headers_i: u8 = 0; |
| 11133 | if (header.len != 0) { |
| 11134 | headers[headers_i] = .{ .base = header.ptr, .len = header.len }; |
| 11135 | headers_i += 1; |
| 11136 | } |
| 11137 | if (reader_buffered.len != 0) { |
| 11138 | headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; |
| 11139 | headers_i += 1; |
| 11140 | } |
| 11141 | const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { |
| 11142 | hdtr_data = .{ |
| 11143 | .headers = &headers, |
| 11144 | .hdr_cnt = headers_i, |
| 11145 | .trailers = null, |
| 11146 | .trl_cnt = 0, |
| 11147 | }; |
| 11148 | break :b &hdtr_data; |
| 11149 | }; |
| 11150 | var sbytes: std.c.off_t = 0; |
| 11151 | const nbytes: usize = @min(file_limit, std.math.maxInt(usize)); |
| 11152 | const flags = 0; |
| 11153 | |
| 11154 | const syscall: Syscall = try .start(); |
| 11155 | while (true) { |
| 11156 | switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) { |
| 11157 | .SUCCESS => { |
| 11158 | syscall.finish(); |
| 11159 | break; |
| 11160 | }, |
| 11161 | .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { |
| 11162 | // Give calling code chance to observe before trying |
| 11163 | // something else. |
| 11164 | syscall.finish(); |
| 11165 | @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); |
| 11166 | return 0; |
| 11167 | }, |
| 11168 | .INTR, .BUSY => { |
| 11169 | if (sbytes == 0) { |
| 11170 | try syscall.checkCancel(); |
| 11171 | continue; |
| 11172 | } else { |
| 11173 | // Even if we are being canceled, there have been side |
| 11174 | // effects, so it is better to report those side |
| 11175 | // effects to the caller. |
| 11176 | syscall.finish(); |
| 11177 | break; |
| 11178 | } |
| 11179 | }, |
| 11180 | .AGAIN => { |
| 11181 | syscall.finish(); |
| 11182 | if (sbytes == 0) return error.WouldBlock; |
| 11183 | break; |
| 11184 | }, |
| 11185 | else => |e| { |
| 11186 | syscall.finish(); |
| 11187 | assert(error.Unexpected == switch (e) { |
| 11188 | .NOTCONN => return error.BrokenPipe, |
| 11189 | .IO => return error.InputOutput, |
| 11190 | .PIPE => return error.BrokenPipe, |
| 11191 | .NOBUFS => return error.SystemResources, |
| 11192 | .BADF => |err| errnoBug(err), |
| 11193 | .FAULT => |err| errnoBug(err), |
| 11194 | else => |err| posix.unexpectedErrno(err), |
| 11195 | }); |
| 11196 | // Give calling code chance to observe the error before trying |
| 11197 | // something else. |
| 11198 | @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); |
| 11199 | return 0; |
| 11200 | }, |
| 11201 | } |
| 11202 | } |
| 11203 | if (sbytes == 0) { |
| 11204 | file_reader.size = file_reader.pos; |
| 11205 | return error.EndOfStream; |
| 11206 | } |
| 11207 | const ubytes: usize = @intCast(sbytes); |
| 11208 | file_reader.interface.toss(ubytes -| header.len); |
| 11209 | return ubytes; |
| 11210 | } |
| 11211 | |
| 11212 | if (is_darwin) sf: { |
| 11213 | // Try using sendfile on macOS. |
| 11214 | if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; |
| 11215 | const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf; |
| 11216 | var hdtr_data: std.c.sf_hdtr = undefined; |
| 11217 | var headers: [2]posix.iovec_const = undefined; |
| 11218 | var headers_i: u8 = 0; |
| 11219 | if (header.len != 0) { |
| 11220 | headers[headers_i] = .{ .base = header.ptr, .len = header.len }; |
| 11221 | headers_i += 1; |
| 11222 | } |
| 11223 | if (reader_buffered.len != 0) { |
| 11224 | headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len }; |
| 11225 | headers_i += 1; |
| 11226 | } |
| 11227 | const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: { |
| 11228 | hdtr_data = .{ |
| 11229 | .headers = &headers, |
| 11230 | .hdr_cnt = headers_i, |
| 11231 | .trailers = null, |
| 11232 | .trl_cnt = 0, |
| 11233 | }; |
| 11234 | break :b &hdtr_data; |
| 11235 | }; |
| 11236 | const max_count = std.math.maxInt(i32); // Avoid EINVAL. |
| 11237 | var len: std.c.off_t = @min(file_limit, max_count); |
| 11238 | const flags = 0; |
| 11239 | const syscall: Syscall = try .start(); |
| 11240 | while (true) { |
| 11241 | switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) { |
| 11242 | .SUCCESS => { |
| 11243 | syscall.finish(); |
| 11244 | break; |
| 11245 | }, |
| 11246 | .OPNOTSUPP, .NOTSOCK, .NOSYS => { |
| 11247 | // Give calling code chance to observe before trying |
| 11248 | // something else. |
| 11249 | syscall.finish(); |
| 11250 | @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); |
| 11251 | return 0; |
| 11252 | }, |
| 11253 | .INTR => { |
| 11254 | if (len == 0) { |
| 11255 | try syscall.checkCancel(); |
| 11256 | continue; |
| 11257 | } else { |
| 11258 | // Even if we are being canceled, there have been side |
| 11259 | // effects, so it is better to report those side |
| 11260 | // effects to the caller. |
| 11261 | syscall.finish(); |
| 11262 | break; |
| 11263 | } |
| 11264 | }, |
| 11265 | .AGAIN => { |
| 11266 | syscall.finish(); |
| 11267 | if (len == 0) return error.WouldBlock; |
| 11268 | break; |
| 11269 | }, |
| 11270 | else => |e| { |
| 11271 | syscall.finish(); |
| 11272 | assert(error.Unexpected == switch (e) { |
| 11273 | .NOTCONN => return error.BrokenPipe, |
| 11274 | .IO => return error.InputOutput, |
| 11275 | .PIPE => return error.BrokenPipe, |
| 11276 | .BADF => |err| errnoBug(err), |
| 11277 | .FAULT => |err| errnoBug(err), |
| 11278 | .INVAL => |err| errnoBug(err), |
| 11279 | else => |err| posix.unexpectedErrno(err), |
| 11280 | }); |
| 11281 | // Give calling code chance to observe the error before trying |
| 11282 | // something else. |
| 11283 | @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); |
| 11284 | return 0; |
| 11285 | }, |
| 11286 | } |
| 11287 | } |
| 11288 | if (len == 0) { |
| 11289 | file_reader.size = file_reader.pos; |
| 11290 | return error.EndOfStream; |
| 11291 | } |
| 11292 | const u_len: usize = @bitCast(len); |
| 11293 | file_reader.interface.toss(u_len -| header.len); |
| 11294 | return u_len; |
| 11295 | } |
| 11296 | |
| 11297 | if (native_os == .linux) sf: { |
| 11298 | // Try using sendfile on Linux. |
| 11299 | if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf; |
| 11300 | // Linux sendfile does not support headers. |
| 11301 | if (header.len != 0 or reader_buffered.len != 0) { |
| 11302 | const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); |
| 11303 | file_reader.interface.toss(n -| header.len); |
| 11304 | return n; |
| 11305 | } |
| 11306 | const max_count = 0x7ffff000; // Avoid EINVAL. |
| 11307 | var off: std.os.linux.off_t = undefined; |
| 11308 | const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { |
| 11309 | .positional => o: { |
| 11310 | const size = file_reader.getSize() catch |err| switch (err) { |
| 11311 | error.Canceled => |e| return e, |
| 11312 | else => break :sf, |
| 11313 | }; |
| 11314 | off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed; |
| 11315 | break :o .{ &off, @min(@backingInt(limit), size - file_reader.pos, max_count) }; |
| 11316 | }, |
| 11317 | .streaming => .{ null, limit.minInt(max_count) }, |
| 11318 | .streaming_simple, .positional_simple => break :sf, |
| 11319 | .failure => return error.ReadFailed, |
| 11320 | }; |
| 11321 | const syscall: Syscall = try .start(); |
| 11322 | const n: usize = while (true) { |
| 11323 | const rc = sendfile_sym(out_fd, in_fd, off_ptr, count); |
| 11324 | switch (posix.errno(rc)) { |
| 11325 | .SUCCESS => { |
| 11326 | syscall.finish(); |
| 11327 | break @intCast(rc); |
| 11328 | }, |
| 11329 | .NOSYS, .INVAL => { |
| 11330 | // Give calling code chance to observe before trying |
| 11331 | // something else. |
| 11332 | syscall.finish(); |
| 11333 | @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); |
| 11334 | return 0; |
| 11335 | }, |
| 11336 | .INTR => { |
| 11337 | try syscall.checkCancel(); |
| 11338 | continue; |
| 11339 | }, |
| 11340 | else => |e| { |
| 11341 | syscall.finish(); |
| 11342 | assert(error.Unexpected == switch (e) { |
| 11343 | .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket |
| 11344 | .AGAIN => return error.WouldBlock, |
| 11345 | .IO => return error.InputOutput, |
| 11346 | .PIPE => return error.BrokenPipe, |
| 11347 | .NOMEM => return error.SystemResources, |
| 11348 | .NXIO, .SPIPE => { |
| 11349 | file_reader.mode = file_reader.mode.toStreaming(); |
| 11350 | const pos = file_reader.pos; |
| 11351 | if (pos != 0) { |
| 11352 | file_reader.pos = 0; |
| 11353 | file_reader.seekBy(@intCast(pos)) catch { |
| 11354 | file_reader.mode = .failure; |
| 11355 | return error.ReadFailed; |
| 11356 | }; |
| 11357 | } |
| 11358 | return 0; |
| 11359 | }, |
| 11360 | .BADF => |err| errnoBug(err), // Always a race condition. |
| 11361 | .FAULT => |err| errnoBug(err), // Segmentation fault. |
| 11362 | .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`. |
| 11363 | else => |err| posix.unexpectedErrno(err), |
| 11364 | }); |
| 11365 | // Give calling code chance to observe the error before trying |
| 11366 | // something else. |
| 11367 | @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic); |
| 11368 | return 0; |
| 11369 | }, |
| 11370 | } |
| 11371 | }; |
| 11372 | if (n == 0) { |
| 11373 | file_reader.size = file_reader.pos; |
| 11374 | return error.EndOfStream; |
| 11375 | } |
| 11376 | file_reader.pos += n; |
| 11377 | return n; |
| 11378 | } |
| 11379 | |
| 11380 | if (have_copy_file_range) cfr: { |
| 11381 | if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; |
| 11382 | if (header.len != 0 or reader_buffered.len != 0) { |
| 11383 | const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1); |
| 11384 | file_reader.interface.toss(n -| header.len); |
| 11385 | return n; |
| 11386 | } |
| 11387 | var len: usize = @backingInt(limit); |
| 11388 | var off_in: i64 = undefined; |
| 11389 | const off_in_ptr: ?*i64 = switch (file_reader.mode) { |
| 11390 | .positional_simple, .streaming_simple => return error.Unimplemented, |
| 11391 | .positional => p: { |
| 11392 | len = @min(len, std.math.maxInt(usize) - file_reader.pos); |
| 11393 | off_in = @intCast(file_reader.pos); |
| 11394 | break :p &off_in; |
| 11395 | }, |
| 11396 | .streaming => null, |
| 11397 | .failure => return error.ReadFailed, |
| 11398 | }; |
| 11399 | const n: usize = switch (native_os) { |
| 11400 | .linux => n: { |
| 11401 | const syscall: Syscall = try .start(); |
| 11402 | while (true) { |
| 11403 | const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, len, 0); |
| 11404 | switch (linux_copy_file_range_sys.errno(rc)) { |
| 11405 | .SUCCESS => { |
| 11406 | syscall.finish(); |
| 11407 | break :n @intCast(rc); |
| 11408 | }, |
| 11409 | .INTR => { |
| 11410 | try syscall.checkCancel(); |
| 11411 | continue; |
| 11412 | }, |
| 11413 | .OPNOTSUPP, .INVAL, .NOSYS => { |
| 11414 | // Give calling code chance to observe before trying |
| 11415 | // something else. |
| 11416 | syscall.finish(); |
| 11417 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11418 | return 0; |
| 11419 | }, |
| 11420 | else => |e| { |
| 11421 | syscall.finish(); |
| 11422 | assert(error.Unexpected == switch (e) { |
| 11423 | .FBIG => return error.FileTooBig, |
| 11424 | .IO => return error.InputOutput, |
| 11425 | .NOMEM => return error.SystemResources, |
| 11426 | .NOSPC => return error.NoSpaceLeft, |
| 11427 | .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. |
| 11428 | .PERM => return error.PermissionDenied, |
| 11429 | .BUSY => return error.DeviceBusy, |
| 11430 | .TXTBSY => return error.FileBusy, |
| 11431 | // copy_file_range can still work but not on |
| 11432 | // this pair of file descriptors. |
| 11433 | .XDEV => return error.Unimplemented, |
| 11434 | .ISDIR => |err| errnoBug(err), |
| 11435 | .BADF => |err| errnoBug(err), |
| 11436 | else => |err| posix.unexpectedErrno(err), |
| 11437 | }); |
| 11438 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11439 | return 0; |
| 11440 | }, |
| 11441 | } |
| 11442 | } |
| 11443 | }, |
| 11444 | .freebsd => n: { |
| 11445 | const syscall: Syscall = try .start(); |
| 11446 | while (true) { |
| 11447 | const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @backingInt(limit), 0); |
| 11448 | switch (std.c.errno(rc)) { |
| 11449 | .SUCCESS => { |
| 11450 | syscall.finish(); |
| 11451 | break :n @intCast(rc); |
| 11452 | }, |
| 11453 | .INTR => { |
| 11454 | try syscall.checkCancel(); |
| 11455 | continue; |
| 11456 | }, |
| 11457 | .OPNOTSUPP, .INVAL, .NOSYS => { |
| 11458 | // Give calling code chance to observe before trying |
| 11459 | // something else. |
| 11460 | syscall.finish(); |
| 11461 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11462 | return 0; |
| 11463 | }, |
| 11464 | else => |e| { |
| 11465 | syscall.finish(); |
| 11466 | assert(error.Unexpected == switch (e) { |
| 11467 | .FBIG => return error.FileTooBig, |
| 11468 | .IO => return error.InputOutput, |
| 11469 | .INTEGRITY => return error.CorruptedData, |
| 11470 | .NOSPC => return error.NoSpaceLeft, |
| 11471 | .ISDIR => |err| errnoBug(err), |
| 11472 | .BADF => |err| errnoBug(err), |
| 11473 | else => |err| posix.unexpectedErrno(err), |
| 11474 | }); |
| 11475 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11476 | return 0; |
| 11477 | }, |
| 11478 | } |
| 11479 | } |
| 11480 | }, |
| 11481 | else => comptime unreachable, |
| 11482 | }; |
| 11483 | if (n == 0) { |
| 11484 | file_reader.size = file_reader.pos; |
| 11485 | return error.EndOfStream; |
| 11486 | } |
| 11487 | file_reader.pos += n; |
| 11488 | return n; |
| 11489 | } |
| 11490 | |
| 11491 | return error.Unimplemented; |
| 11492 | } |
| 11493 | |
| 11494 | fn netWriteFile( |
| 11495 | userdata: ?*anyopaque, |
| 11496 | socket_handle: net.Socket.Handle, |
| 11497 | header: []const u8, |
| 11498 | file_reader: *File.Reader, |
| 11499 | limit: Io.Limit, |
| 11500 | ) net.Stream.Writer.WriteFileError!usize { |
| 11501 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11502 | _ = t; |
| 11503 | _ = socket_handle; |
| 11504 | _ = header; |
| 11505 | _ = file_reader; |
| 11506 | _ = limit; |
| 11507 | // TODO implement netWriteFile |
| 11508 | return error.Unimplemented; |
| 11509 | } |
| 11510 | |
| 11511 | fn fileWriteFilePositional( |
| 11512 | userdata: ?*anyopaque, |
| 11513 | file: File, |
| 11514 | header: []const u8, |
| 11515 | file_reader: *File.Reader, |
| 11516 | limit: Io.Limit, |
| 11517 | offset: u64, |
| 11518 | ) File.WriteFilePositionalError!usize { |
| 11519 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11520 | const reader_buffered = file_reader.interface.buffered(); |
| 11521 | if (reader_buffered.len >= @backingInt(limit)) { |
| 11522 | const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); |
| 11523 | file_reader.interface.toss(n -| header.len); |
| 11524 | return n; |
| 11525 | } |
| 11526 | const out_fd = file.handle; |
| 11527 | const in_fd = file_reader.file.handle; |
| 11528 | |
| 11529 | if (file_reader.size) |size| { |
| 11530 | if (size - file_reader.pos == 0) { |
| 11531 | if (reader_buffered.len != 0) { |
| 11532 | const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); |
| 11533 | file_reader.interface.toss(n -| header.len); |
| 11534 | return n; |
| 11535 | } else { |
| 11536 | return error.EndOfStream; |
| 11537 | } |
| 11538 | } |
| 11539 | } |
| 11540 | |
| 11541 | if (have_copy_file_range) cfr: { |
| 11542 | if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr; |
| 11543 | if (header.len != 0 or reader_buffered.len != 0) { |
| 11544 | const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); |
| 11545 | file_reader.interface.toss(n -| header.len); |
| 11546 | return n; |
| 11547 | } |
| 11548 | var len: usize = @min(@backingInt(limit), std.math.maxInt(usize) - offset); |
| 11549 | var off_in: i64 = undefined; |
| 11550 | const off_in_ptr: ?*i64 = switch (file_reader.mode) { |
| 11551 | .positional_simple, .streaming_simple => return error.Unimplemented, |
| 11552 | .positional => p: { |
| 11553 | len = @min(len, std.math.maxInt(usize) - file_reader.pos); |
| 11554 | off_in = @intCast(file_reader.pos); |
| 11555 | break :p &off_in; |
| 11556 | }, |
| 11557 | .streaming => null, |
| 11558 | .failure => return error.ReadFailed, |
| 11559 | }; |
| 11560 | var off_out: i64 = @intCast(offset); |
| 11561 | const n: usize = switch (native_os) { |
| 11562 | .linux => n: { |
| 11563 | const syscall: Syscall = try .start(); |
| 11564 | while (true) { |
| 11565 | const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, len, 0); |
| 11566 | switch (linux_copy_file_range_sys.errno(rc)) { |
| 11567 | .SUCCESS => { |
| 11568 | syscall.finish(); |
| 11569 | break :n @intCast(rc); |
| 11570 | }, |
| 11571 | .INTR => { |
| 11572 | try syscall.checkCancel(); |
| 11573 | continue; |
| 11574 | }, |
| 11575 | .OPNOTSUPP, .INVAL, .NOSYS => { |
| 11576 | // Give calling code chance to observe before trying |
| 11577 | // something else. |
| 11578 | syscall.finish(); |
| 11579 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11580 | return 0; |
| 11581 | }, |
| 11582 | else => |e| { |
| 11583 | syscall.finish(); |
| 11584 | assert(error.Unexpected == switch (e) { |
| 11585 | .FBIG => return error.FileTooBig, |
| 11586 | .IO => return error.InputOutput, |
| 11587 | .NOMEM => return error.SystemResources, |
| 11588 | .NOSPC => return error.NoSpaceLeft, |
| 11589 | .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. |
| 11590 | .NXIO => return error.Unseekable, |
| 11591 | .SPIPE => return error.Unseekable, |
| 11592 | .PERM => return error.PermissionDenied, |
| 11593 | .TXTBSY => return error.FileBusy, |
| 11594 | // copy_file_range can still work but not on |
| 11595 | // this pair of file descriptors. |
| 11596 | .XDEV => return error.Unimplemented, |
| 11597 | .ISDIR => |err| errnoBug(err), |
| 11598 | .BADF => |err| errnoBug(err), |
| 11599 | else => |err| posix.unexpectedErrno(err), |
| 11600 | }); |
| 11601 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11602 | return 0; |
| 11603 | }, |
| 11604 | } |
| 11605 | } |
| 11606 | }, |
| 11607 | .freebsd => n: { |
| 11608 | const syscall: Syscall = try .start(); |
| 11609 | while (true) { |
| 11610 | const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @backingInt(limit), 0); |
| 11611 | switch (std.c.errno(rc)) { |
| 11612 | .SUCCESS => { |
| 11613 | syscall.finish(); |
| 11614 | break :n @intCast(rc); |
| 11615 | }, |
| 11616 | .INTR => { |
| 11617 | try syscall.checkCancel(); |
| 11618 | continue; |
| 11619 | }, |
| 11620 | .OPNOTSUPP, .INVAL, .NOSYS => { |
| 11621 | // Give calling code chance to observe before trying |
| 11622 | // something else. |
| 11623 | syscall.finish(); |
| 11624 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11625 | return 0; |
| 11626 | }, |
| 11627 | else => |e| { |
| 11628 | syscall.finish(); |
| 11629 | assert(error.Unexpected == switch (e) { |
| 11630 | .FBIG => return error.FileTooBig, |
| 11631 | .IO => return error.InputOutput, |
| 11632 | .INTEGRITY => return error.CorruptedData, |
| 11633 | .NOSPC => return error.NoSpaceLeft, |
| 11634 | .OVERFLOW => return error.Unseekable, |
| 11635 | .NXIO => return error.Unseekable, |
| 11636 | .SPIPE => return error.Unseekable, |
| 11637 | .ISDIR => |err| errnoBug(err), |
| 11638 | .BADF => |err| errnoBug(err), |
| 11639 | else => |err| posix.unexpectedErrno(err), |
| 11640 | }); |
| 11641 | @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic); |
| 11642 | return 0; |
| 11643 | }, |
| 11644 | } |
| 11645 | } |
| 11646 | }, |
| 11647 | else => comptime unreachable, |
| 11648 | }; |
| 11649 | if (n == 0) { |
| 11650 | file_reader.size = file_reader.pos; |
| 11651 | return error.EndOfStream; |
| 11652 | } |
| 11653 | file_reader.pos += n; |
| 11654 | return n; |
| 11655 | } |
| 11656 | |
| 11657 | if (is_darwin) fcf: { |
| 11658 | if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf; |
| 11659 | if (file_reader.pos != 0) break :fcf; |
| 11660 | if (offset != 0) break :fcf; |
| 11661 | if (limit != .unlimited) break :fcf; |
| 11662 | const size = file_reader.getSize() catch |err| switch (err) { |
| 11663 | error.Canceled => |e| return e, |
| 11664 | else => break :fcf, |
| 11665 | }; |
| 11666 | if (header.len != 0 or reader_buffered.len != 0) { |
| 11667 | const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset); |
| 11668 | file_reader.interface.toss(n -| header.len); |
| 11669 | return n; |
| 11670 | } |
| 11671 | const syscall: Syscall = try .start(); |
| 11672 | while (true) { |
| 11673 | const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); |
| 11674 | switch (posix.errno(rc)) { |
| 11675 | .SUCCESS => { |
| 11676 | syscall.finish(); |
| 11677 | break; |
| 11678 | }, |
| 11679 | .INTR => { |
| 11680 | try syscall.checkCancel(); |
| 11681 | continue; |
| 11682 | }, |
| 11683 | .OPNOTSUPP => { |
| 11684 | // Give calling code chance to observe before trying |
| 11685 | // something else. |
| 11686 | syscall.finish(); |
| 11687 | @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic); |
| 11688 | return 0; |
| 11689 | }, |
| 11690 | else => |e| { |
| 11691 | syscall.finish(); |
| 11692 | assert(error.Unexpected == switch (e) { |
| 11693 | .NOMEM => return error.SystemResources, |
| 11694 | .INVAL => |err| errnoBug(err), |
| 11695 | else => |err| posix.unexpectedErrno(err), |
| 11696 | }); |
| 11697 | return 0; |
| 11698 | }, |
| 11699 | } |
| 11700 | } |
| 11701 | file_reader.pos = size; |
| 11702 | return size; |
| 11703 | } |
| 11704 | |
| 11705 | return error.Unimplemented; |
| 11706 | } |
| 11707 | |
| 11708 | fn nowPosix(clock: Io.Clock) Io.Timestamp { |
| 11709 | const clock_id: posix.clockid_t = clockToPosix(clock); |
| 11710 | var timespec: posix.timespec = undefined; |
| 11711 | switch (posix.errno(posix.system.clock_gettime(clock_id, &timespec))) { |
| 11712 | .SUCCESS => return timestampFromPosix(&timespec), |
| 11713 | else => return .zero, |
| 11714 | } |
| 11715 | } |
| 11716 | |
| 11717 | fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp { |
| 11718 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11719 | _ = t; |
| 11720 | return switch (native_os) { |
| 11721 | .windows => nowWindows(clock), |
| 11722 | .wasi => nowWasi(clock), |
| 11723 | else => nowPosix(clock), |
| 11724 | }; |
| 11725 | } |
| 11726 | |
| 11727 | fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { |
| 11728 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11729 | _ = t; |
| 11730 | return switch (native_os) { |
| 11731 | .windows => switch (clock) { |
| 11732 | .awake, .boot, .real => { |
| 11733 | // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA |
| 11734 | // (a read-only page of info updated and mapped by the kernel to all processes): |
| 11735 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data |
| 11736 | // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm |
| 11737 | var qpf: windows.LARGE_INTEGER = undefined; |
| 11738 | if (!windows.ntdll.RtlQueryPerformanceFrequency(&qpf).toBool()) { |
| 11739 | recoverableOsBugDetected(); |
| 11740 | return .zero; |
| 11741 | } |
| 11742 | // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. |
| 11743 | // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 |
| 11744 | const common_qpf = 10_000_000; |
| 11745 | if (qpf == common_qpf) return .fromNanoseconds(std.time.ns_per_s / common_qpf); |
| 11746 | |
| 11747 | // Convert to ns using fixed point. |
| 11748 | const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); |
| 11749 | const result = scale >> 32; |
| 11750 | return .fromNanoseconds(result); |
| 11751 | }, |
| 11752 | .cpu_process, .cpu_thread => return error.ClockUnavailable, |
| 11753 | }, |
| 11754 | .wasi => { |
| 11755 | if (builtin.link_libc) return clockResolutionPosix(clock); |
| 11756 | var ns: std.os.wasi.timestamp_t = undefined; |
| 11757 | return switch (std.os.wasi.clock_res_get(clockToWasi(clock), &ns)) { |
| 11758 | .SUCCESS => .fromNanoseconds(ns), |
| 11759 | .INVAL => return error.ClockUnavailable, |
| 11760 | else => |err| return posix.unexpectedErrno(err), |
| 11761 | }; |
| 11762 | }, |
| 11763 | else => return clockResolutionPosix(clock), |
| 11764 | }; |
| 11765 | } |
| 11766 | |
| 11767 | fn clockResolutionPosix(clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { |
| 11768 | const clock_id: posix.clockid_t = clockToPosix(clock); |
| 11769 | var timespec: posix.timespec = undefined; |
| 11770 | return switch (posix.errno(posix.system.clock_getres(clock_id, &timespec))) { |
| 11771 | .SUCCESS => .fromNanoseconds(nanosecondsFromPosix(&timespec)), |
| 11772 | .INVAL => return error.ClockUnavailable, |
| 11773 | else => |err| return posix.unexpectedErrno(err), |
| 11774 | }; |
| 11775 | } |
| 11776 | |
| 11777 | fn nowWindows(clock: Io.Clock) Io.Timestamp { |
| 11778 | switch (clock) { |
| 11779 | .real => { |
| 11780 | // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds |
| 11781 | // and uses the NTFS/Windows epoch, which is 1601-01-01. |
| 11782 | const epoch_ns = std.time.epoch.windows * std.time.ns_per_s; |
| 11783 | return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 + epoch_ns }; |
| 11784 | }, |
| 11785 | .awake, .boot => { |
| 11786 | // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA |
| 11787 | // (a read-only page of info updated and mapped by the kernel to all processes): |
| 11788 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data |
| 11789 | // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm |
| 11790 | const qpf: u64 = qpf: { |
| 11791 | var qpf: windows.LARGE_INTEGER = undefined; |
| 11792 | assert(windows.ntdll.RtlQueryPerformanceFrequency(&qpf).toBool()); |
| 11793 | break :qpf @bitCast(qpf); |
| 11794 | }; |
| 11795 | |
| 11796 | // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. |
| 11797 | const qpc: u64 = qpc: { |
| 11798 | var qpc: windows.LARGE_INTEGER = undefined; |
| 11799 | assert(windows.ntdll.RtlQueryPerformanceCounter(&qpc).toBool()); |
| 11800 | break :qpc @bitCast(qpc); |
| 11801 | }; |
| 11802 | |
| 11803 | // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. |
| 11804 | // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 |
| 11805 | const common_qpf = 10_000_000; |
| 11806 | if (qpf == common_qpf) return .{ .nanoseconds = qpc * (std.time.ns_per_s / common_qpf) }; |
| 11807 | |
| 11808 | // Convert to ns using fixed point. |
| 11809 | const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); |
| 11810 | const result = (@as(u96, qpc) * scale) >> 32; |
| 11811 | return .{ .nanoseconds = @intCast(result) }; |
| 11812 | }, |
| 11813 | .cpu_process => { |
| 11814 | const handle = windows.GetCurrentProcess(); |
| 11815 | var times: windows.KERNEL_USER_TIMES = undefined; |
| 11816 | |
| 11817 | // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L442-L485 |
| 11818 | if (windows.ntdll.NtQueryInformationProcess( |
| 11819 | handle, |
| 11820 | .Times, |
| 11821 | &times, |
| 11822 | @sizeOf(windows.KERNEL_USER_TIMES), |
| 11823 | null, |
| 11824 | ) != .SUCCESS) return .zero; |
| 11825 | |
| 11826 | const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); |
| 11827 | return .{ .nanoseconds = sum * 100 }; |
| 11828 | }, |
| 11829 | .cpu_thread => { |
| 11830 | const handle = windows.GetCurrentThread(); |
| 11831 | var times: windows.KERNEL_USER_TIMES = undefined; |
| 11832 | |
| 11833 | // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L2971-L3019 |
| 11834 | if (windows.ntdll.NtQueryInformationThread( |
| 11835 | handle, |
| 11836 | .Times, |
| 11837 | &times, |
| 11838 | @sizeOf(windows.KERNEL_USER_TIMES), |
| 11839 | null, |
| 11840 | ) != .SUCCESS) return .zero; |
| 11841 | |
| 11842 | const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); |
| 11843 | return .{ .nanoseconds = sum * 100 }; |
| 11844 | }, |
| 11845 | } |
| 11846 | } |
| 11847 | |
| 11848 | fn nowWasi(clock: Io.Clock) Io.Timestamp { |
| 11849 | var ns: std.os.wasi.timestamp_t = undefined; |
| 11850 | const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); |
| 11851 | if (err != .SUCCESS) return .zero; |
| 11852 | return .fromNanoseconds(ns); |
| 11853 | } |
| 11854 | |
| 11855 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void { |
| 11856 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11857 | if (use_parking_sleep) return parking_sleep.sleep(timeout); |
| 11858 | if (native_os == .wasi) return sleepWasi(t, timeout); |
| 11859 | if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); |
| 11860 | return sleepNanosleep(t, timeout); |
| 11861 | } |
| 11862 | |
| 11863 | fn sleepPosix(timeout: Io.Timeout) Io.Cancelable!void { |
| 11864 | const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { |
| 11865 | .none => .awake, |
| 11866 | .duration => |d| d.clock, |
| 11867 | .deadline => |d| d.clock, |
| 11868 | }); |
| 11869 | const deadline_nanoseconds: i96 = switch (timeout) { |
| 11870 | .none => std.math.maxInt(i96), |
| 11871 | .duration => |duration| duration.raw.nanoseconds, |
| 11872 | .deadline => |deadline| deadline.raw.nanoseconds, |
| 11873 | }; |
| 11874 | var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds); |
| 11875 | const syscall: Syscall = try .start(); |
| 11876 | while (true) { |
| 11877 | const rc = posix.system.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { |
| 11878 | .none, .duration => false, |
| 11879 | .deadline => true, |
| 11880 | } }, &timespec, &timespec); |
| 11881 | // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly |
| 11882 | switch (if (builtin.link_libc) @as(posix.E, @fromBackingInt(@intCast(rc))) else posix.errno(rc)) { |
| 11883 | .INTR => { |
| 11884 | try syscall.checkCancel(); |
| 11885 | continue; |
| 11886 | }, |
| 11887 | // Handles SUCCESS as well as clock not available and unexpected |
| 11888 | // errors. The user had a chance to check clock resolution before |
| 11889 | // getting here, which would have reported 0, making this a legal |
| 11890 | // amount of time to sleep. |
| 11891 | else => { |
| 11892 | syscall.finish(); |
| 11893 | return; |
| 11894 | }, |
| 11895 | } |
| 11896 | } |
| 11897 | } |
| 11898 | |
| 11899 | fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { |
| 11900 | const t_io = io(t); |
| 11901 | const w = std.os.wasi; |
| 11902 | |
| 11903 | const clock: w.subscription_clock_t = if (timeout.toDurationFromNow(t_io)) |d| .{ |
| 11904 | .id = clockToWasi(d.clock), |
| 11905 | .timeout = std.math.lossyCast(u64, d.raw.nanoseconds), |
| 11906 | .precision = 0, |
| 11907 | .flags = 0, |
| 11908 | } else .{ |
| 11909 | .id = .MONOTONIC, |
| 11910 | .timeout = std.math.maxInt(u64), |
| 11911 | .precision = 0, |
| 11912 | .flags = 0, |
| 11913 | }; |
| 11914 | const in: w.subscription_t = .{ |
| 11915 | .userdata = 0, |
| 11916 | .u = .{ |
| 11917 | .tag = .CLOCK, |
| 11918 | .u = .{ .clock = clock }, |
| 11919 | }, |
| 11920 | }; |
| 11921 | var event: w.event_t = undefined; |
| 11922 | var nevents: usize = undefined; |
| 11923 | const syscall: Syscall = try .start(); |
| 11924 | _ = w.poll_oneoff(&in, &event, 1, &nevents); |
| 11925 | syscall.finish(); |
| 11926 | } |
| 11927 | |
| 11928 | fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { |
| 11929 | const t_io = io(t); |
| 11930 | const sec_type = @typeInfo(posix.timespec).@"struct".field_types[0]; |
| 11931 | const nsec_type = @typeInfo(posix.timespec).@"struct".field_types[1]; |
| 11932 | |
| 11933 | var timespec: posix.timespec = t: { |
| 11934 | const d = timeout.toDurationFromNow(t_io) orelse break :t .{ |
| 11935 | .sec = std.math.maxInt(sec_type), |
| 11936 | .nsec = std.math.maxInt(nsec_type), |
| 11937 | }; |
| 11938 | break :t timestampToPosix(d.raw.toNanoseconds()); |
| 11939 | }; |
| 11940 | const syscall: Syscall = try .start(); |
| 11941 | while (true) { |
| 11942 | switch (posix.errno(posix.system.nanosleep(&timespec, &timespec))) { |
| 11943 | .INTR => { |
| 11944 | try syscall.checkCancel(); |
| 11945 | continue; |
| 11946 | }, |
| 11947 | // This prong handles success as well as unexpected errors. |
| 11948 | else => return syscall.finish(), |
| 11949 | } |
| 11950 | } |
| 11951 | } |
| 11952 | |
| 11953 | fn netListenIpPosix( |
| 11954 | userdata: ?*anyopaque, |
| 11955 | address: *const IpAddress, |
| 11956 | options: IpAddress.ListenOptions, |
| 11957 | ) IpAddress.ListenError!net.Socket { |
| 11958 | if (!have_networking) return error.NetworkDown; |
| 11959 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 11960 | _ = t; |
| 11961 | const family = posixAddressFamily(address); |
| 11962 | const socket_fd = try openSocketPosix(family, .{ .mode = options.mode, .protocol = options.protocol }); |
| 11963 | errdefer closeFd(socket_fd); |
| 11964 | |
| 11965 | if (options.reuse_address) { |
| 11966 | try setSocketOptionPosix(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); |
| 11967 | if (@hasDecl(posix.SO, "REUSEPORT")) |
| 11968 | try setSocketOptionPosix(socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); |
| 11969 | } |
| 11970 | |
| 11971 | var storage: PosixAddress = undefined; |
| 11972 | var addr_len = addressToPosix(address, &storage); |
| 11973 | try posixBind(socket_fd, &storage.any, addr_len); |
| 11974 | |
| 11975 | const syscall: Syscall = try .start(); |
| 11976 | while (true) { |
| 11977 | switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { |
| 11978 | .SUCCESS => { |
| 11979 | syscall.finish(); |
| 11980 | break; |
| 11981 | }, |
| 11982 | .INTR => { |
| 11983 | try syscall.checkCancel(); |
| 11984 | continue; |
| 11985 | }, |
| 11986 | .ADDRINUSE => return syscall.fail(error.AddressInUse), |
| 11987 | .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. |
| 11988 | else => |err| return syscall.unexpectedErrno(err), |
| 11989 | } |
| 11990 | } |
| 11991 | |
| 11992 | try posixGetSockName(socket_fd, &storage.any, &addr_len); |
| 11993 | return .{ .handle = socket_fd, .address = addressFromPosix(&storage) }; |
| 11994 | } |
| 11995 | |
| 11996 | fn netListenIpWindows( |
| 11997 | userdata: ?*anyopaque, |
| 11998 | address: *const IpAddress, |
| 11999 | options: IpAddress.ListenOptions, |
| 12000 | ) IpAddress.ListenError!net.Socket { |
| 12001 | if (!have_networking) return error.NetworkDown; |
| 12002 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12003 | _ = t; |
| 12004 | const family = posixAddressFamily(address); |
| 12005 | const socket_handle = try openSocketAfd(family, .{ .mode = options.mode, .protocol = options.protocol }); |
| 12006 | errdefer windows.CloseHandle(socket_handle); |
| 12007 | if (options.reuse_address) try setSocketOptionAfd(socket_handle, ws2_32.SOL.SOCKET, ws2_32.SO.REUSEADDR, true); |
| 12008 | const bound_address = try bindSocketIpAfd(socket_handle, address, .Passive); |
| 12009 | switch ((try deviceIoControl(&.{ |
| 12010 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 12011 | .code = windows.IOCTL.AFD.START_LISTEN, |
| 12012 | .in = @ptrCast(&windows.AFD.LISTEN_INFO{ |
| 12013 | .UseSAN = .FALSE, |
| 12014 | .MaximumConnectionQueue = options.kernel_backlog, |
| 12015 | .UseDelayedAcceptance = .FALSE, |
| 12016 | }), |
| 12017 | })).u.Status) { |
| 12018 | .SUCCESS => {}, |
| 12019 | .CANCELLED => unreachable, |
| 12020 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12021 | else => |status| return windows.unexpectedStatus(status), |
| 12022 | } |
| 12023 | return .{ .handle = socket_handle, .address = bound_address }; |
| 12024 | } |
| 12025 | |
| 12026 | fn netListenUnixPosix( |
| 12027 | userdata: ?*anyopaque, |
| 12028 | address: *const net.UnixAddress, |
| 12029 | options: net.UnixAddress.ListenOptions, |
| 12030 | ) net.UnixAddress.ListenError!net.Socket.Handle { |
| 12031 | if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; |
| 12032 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12033 | _ = t; |
| 12034 | const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { |
| 12035 | error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported, |
| 12036 | error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, |
| 12037 | error.SocketModeUnsupported => return error.AddressFamilyUnsupported, |
| 12038 | error.OptionUnsupported => return error.Unexpected, |
| 12039 | else => |e| return e, |
| 12040 | }; |
| 12041 | errdefer closeFd(socket_fd); |
| 12042 | |
| 12043 | var storage: UnixAddress = undefined; |
| 12044 | const addr_len = addressUnixToPosix(address, &storage); |
| 12045 | try posixBindUnix(socket_fd, &storage.any, addr_len); |
| 12046 | |
| 12047 | const syscall: Syscall = try .start(); |
| 12048 | while (true) { |
| 12049 | switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { |
| 12050 | .SUCCESS => { |
| 12051 | syscall.finish(); |
| 12052 | break; |
| 12053 | }, |
| 12054 | .INTR => { |
| 12055 | try syscall.checkCancel(); |
| 12056 | continue; |
| 12057 | }, |
| 12058 | .ADDRINUSE => return syscall.fail(error.AddressInUse), |
| 12059 | .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. |
| 12060 | else => |err| return syscall.unexpectedErrno(err), |
| 12061 | } |
| 12062 | } |
| 12063 | |
| 12064 | return socket_fd; |
| 12065 | } |
| 12066 | |
| 12067 | fn netListenUnixWindows( |
| 12068 | userdata: ?*anyopaque, |
| 12069 | address: *const net.UnixAddress, |
| 12070 | options: net.UnixAddress.ListenOptions, |
| 12071 | ) net.UnixAddress.ListenError!net.Socket.Handle { |
| 12072 | if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; |
| 12073 | if (!have_networking) return error.NetworkDown; |
| 12074 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12075 | _ = t; |
| 12076 | const is_abstract = address.isAbstract(); |
| 12077 | const wps = if (!is_abstract) sliceToPrefixedFileW(null, address.path, .{ |
| 12078 | .allow_relative = false, |
| 12079 | }) catch |err| switch (err) { |
| 12080 | error.NameTooLong, error.BadPathName => return error.AddressUnavailable, |
| 12081 | else => |e| return e, |
| 12082 | } else undefined; |
| 12083 | const socket_handle = openSocketAfd(ws2_32.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { |
| 12084 | error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, |
| 12085 | else => |e| return e, |
| 12086 | }; |
| 12087 | errdefer windows.CloseHandle(socket_handle); |
| 12088 | if (!is_abstract) try socketOptionAfd(socket_handle, .special, 0, ws2_32.SO.UNIX_PATH, @constCast( |
| 12089 | @as([]const u8, @ptrCast(&windows.AFD.SOCKOPT_INFO.UNIX_PATH{ |
| 12090 | .Path = wps.data, |
| 12091 | }))[0 .. @offsetOf(windows.AFD.SOCKOPT_INFO.UNIX_PATH, "Path") + @sizeOf(windows.WCHAR) * wps.len], |
| 12092 | )); |
| 12093 | try bindSocketUnixAfd(socket_handle, address); |
| 12094 | switch ((try deviceIoControl(&.{ |
| 12095 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 12096 | .code = windows.IOCTL.AFD.START_LISTEN, |
| 12097 | .in = @ptrCast(&windows.AFD.LISTEN_INFO{ |
| 12098 | .UseSAN = .FALSE, |
| 12099 | .MaximumConnectionQueue = options.kernel_backlog, |
| 12100 | .UseDelayedAcceptance = .FALSE, |
| 12101 | }), |
| 12102 | })).u.Status) { |
| 12103 | .SUCCESS => {}, |
| 12104 | .CANCELLED => unreachable, |
| 12105 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12106 | else => |status| return windows.unexpectedStatus(status), |
| 12107 | } |
| 12108 | return socket_handle; |
| 12109 | } |
| 12110 | |
| 12111 | fn posixBindUnix( |
| 12112 | fd: posix.socket_t, |
| 12113 | addr: *const posix.sockaddr, |
| 12114 | addr_len: posix.socklen_t, |
| 12115 | ) !void { |
| 12116 | const syscall: Syscall = try .start(); |
| 12117 | while (true) { |
| 12118 | switch (posix.errno(posix.system.bind(fd, addr, addr_len))) { |
| 12119 | .SUCCESS => { |
| 12120 | syscall.finish(); |
| 12121 | break; |
| 12122 | }, |
| 12123 | .INTR => { |
| 12124 | try syscall.checkCancel(); |
| 12125 | continue; |
| 12126 | }, |
| 12127 | else => |e| { |
| 12128 | syscall.finish(); |
| 12129 | switch (e) { |
| 12130 | .ACCES => return error.AccessDenied, |
| 12131 | .ADDRINUSE => return error.AddressInUse, |
| 12132 | .AFNOSUPPORT => return error.AddressFamilyUnsupported, |
| 12133 | .ADDRNOTAVAIL => return error.AddressUnavailable, |
| 12134 | .NOMEM => return error.SystemResources, |
| 12135 | |
| 12136 | .LOOP => return error.SymLinkLoop, |
| 12137 | .NOENT => return error.FileNotFound, |
| 12138 | .NOTDIR => return error.NotDir, |
| 12139 | .ROFS => return error.ReadOnlyFileSystem, |
| 12140 | .PERM => return error.PermissionDenied, |
| 12141 | |
| 12142 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12143 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 12144 | .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` |
| 12145 | .FAULT => |err| return errnoBug(err), // invalid `addr` pointer |
| 12146 | .NAMETOOLONG => |err| return errnoBug(err), |
| 12147 | else => |err| return posix.unexpectedErrno(err), |
| 12148 | } |
| 12149 | }, |
| 12150 | } |
| 12151 | } |
| 12152 | } |
| 12153 | |
| 12154 | fn posixBind( |
| 12155 | socket_fd: posix.socket_t, |
| 12156 | addr: *const posix.sockaddr, |
| 12157 | addr_len: posix.socklen_t, |
| 12158 | ) !void { |
| 12159 | const syscall: Syscall = try .start(); |
| 12160 | while (true) { |
| 12161 | switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) { |
| 12162 | .SUCCESS => { |
| 12163 | syscall.finish(); |
| 12164 | break; |
| 12165 | }, |
| 12166 | .INTR => { |
| 12167 | try syscall.checkCancel(); |
| 12168 | continue; |
| 12169 | }, |
| 12170 | else => |e| { |
| 12171 | syscall.finish(); |
| 12172 | switch (e) { |
| 12173 | .ACCES => return error.AccessDenied, |
| 12174 | .ADDRINUSE => return error.AddressInUse, |
| 12175 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12176 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 12177 | .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` |
| 12178 | .AFNOSUPPORT => return error.AddressFamilyUnsupported, |
| 12179 | .ADDRNOTAVAIL => return error.AddressUnavailable, |
| 12180 | .FAULT => |err| return errnoBug(err), // invalid `addr` pointer |
| 12181 | .NOMEM => return error.SystemResources, |
| 12182 | else => |err| return posix.unexpectedErrno(err), |
| 12183 | } |
| 12184 | }, |
| 12185 | } |
| 12186 | } |
| 12187 | } |
| 12188 | |
| 12189 | fn posixConnect( |
| 12190 | socket_fd: posix.socket_t, |
| 12191 | addr: *const posix.sockaddr, |
| 12192 | addr_len: posix.socklen_t, |
| 12193 | ) !void { |
| 12194 | const syscall: Syscall = try .start(); |
| 12195 | while (true) switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { |
| 12196 | .SUCCESS => { |
| 12197 | syscall.finish(); |
| 12198 | return; |
| 12199 | }, |
| 12200 | .INTR => { |
| 12201 | try syscall.checkCancel(); |
| 12202 | continue; |
| 12203 | }, |
| 12204 | .ADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), |
| 12205 | .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), |
| 12206 | .AGAIN, .INPROGRESS => return syscall.fail(error.WouldBlock), |
| 12207 | .ALREADY => return syscall.fail(error.ConnectionPending), |
| 12208 | .CONNREFUSED => return syscall.fail(error.ConnectionRefused), |
| 12209 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 12210 | .HOSTUNREACH => return syscall.fail(error.HostUnreachable), |
| 12211 | .NETUNREACH => return syscall.fail(error.NetworkUnreachable), |
| 12212 | .TIMEDOUT => return syscall.fail(error.Timeout), |
| 12213 | .ACCES => return syscall.fail(error.AccessDenied), |
| 12214 | .NETDOWN => return syscall.fail(error.NetworkDown), |
| 12215 | .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. |
| 12216 | .CONNABORTED => |err| return syscall.errnoBug(err), |
| 12217 | .FAULT => |err| return syscall.errnoBug(err), |
| 12218 | .ISCONN => |err| return syscall.errnoBug(err), |
| 12219 | .NOENT => |err| return syscall.errnoBug(err), |
| 12220 | .NOTSOCK => |err| return syscall.errnoBug(err), |
| 12221 | .PERM => |err| return syscall.errnoBug(err), |
| 12222 | .PROTOTYPE => |err| return syscall.errnoBug(err), |
| 12223 | else => |err| return syscall.unexpectedErrno(err), |
| 12224 | }; |
| 12225 | } |
| 12226 | |
| 12227 | fn posixConnectUnix( |
| 12228 | fd: posix.socket_t, |
| 12229 | addr: *const posix.sockaddr, |
| 12230 | addr_len: posix.socklen_t, |
| 12231 | ) !void { |
| 12232 | const syscall: Syscall = try .start(); |
| 12233 | while (true) { |
| 12234 | switch (posix.errno(posix.system.connect(fd, addr, addr_len))) { |
| 12235 | .SUCCESS => { |
| 12236 | syscall.finish(); |
| 12237 | return; |
| 12238 | }, |
| 12239 | .INTR => { |
| 12240 | try syscall.checkCancel(); |
| 12241 | continue; |
| 12242 | }, |
| 12243 | else => |e| { |
| 12244 | syscall.finish(); |
| 12245 | switch (e) { |
| 12246 | .AFNOSUPPORT => return error.AddressFamilyUnsupported, |
| 12247 | .AGAIN => return error.WouldBlock, |
| 12248 | .INPROGRESS => return error.WouldBlock, |
| 12249 | .ACCES => return error.AccessDenied, |
| 12250 | |
| 12251 | .LOOP => return error.SymLinkLoop, |
| 12252 | .NOENT => return error.FileNotFound, |
| 12253 | .NOTDIR => return error.NotDir, |
| 12254 | .ROFS => return error.ReadOnlyFileSystem, |
| 12255 | .PERM => return error.PermissionDenied, |
| 12256 | .CONNREFUSED => return error.ConnectionRefused, |
| 12257 | |
| 12258 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12259 | .CONNABORTED => |err| return errnoBug(err), |
| 12260 | .FAULT => |err| return errnoBug(err), |
| 12261 | .ISCONN => |err| return errnoBug(err), |
| 12262 | .NOTSOCK => |err| return errnoBug(err), |
| 12263 | .PROTOTYPE => |err| return errnoBug(err), |
| 12264 | else => |err| return posix.unexpectedErrno(err), |
| 12265 | } |
| 12266 | }, |
| 12267 | } |
| 12268 | } |
| 12269 | } |
| 12270 | |
| 12271 | fn posixGetSockName( |
| 12272 | socket_fd: posix.fd_t, |
| 12273 | addr: *posix.sockaddr, |
| 12274 | addr_len: *posix.socklen_t, |
| 12275 | ) !void { |
| 12276 | const syscall: Syscall = try .start(); |
| 12277 | while (true) { |
| 12278 | switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) { |
| 12279 | .SUCCESS => { |
| 12280 | syscall.finish(); |
| 12281 | break; |
| 12282 | }, |
| 12283 | .INTR => { |
| 12284 | try syscall.checkCancel(); |
| 12285 | continue; |
| 12286 | }, |
| 12287 | else => |e| { |
| 12288 | syscall.finish(); |
| 12289 | switch (e) { |
| 12290 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12291 | .FAULT => |err| return errnoBug(err), |
| 12292 | .INVAL => |err| return errnoBug(err), // invalid parameters |
| 12293 | .NOTSOCK => |err| return errnoBug(err), // always a race condition |
| 12294 | .NOBUFS => return error.SystemResources, |
| 12295 | else => |err| return posix.unexpectedErrno(err), |
| 12296 | } |
| 12297 | }, |
| 12298 | } |
| 12299 | } |
| 12300 | } |
| 12301 | |
| 12302 | fn setSocketOptionPosix(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { |
| 12303 | const o: []const u8 = @ptrCast(&option); |
| 12304 | const syscall: Syscall = try .start(); |
| 12305 | while (true) { |
| 12306 | switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) { |
| 12307 | .SUCCESS => { |
| 12308 | syscall.finish(); |
| 12309 | return; |
| 12310 | }, |
| 12311 | .INTR => { |
| 12312 | try syscall.checkCancel(); |
| 12313 | continue; |
| 12314 | }, |
| 12315 | else => |e| { |
| 12316 | syscall.finish(); |
| 12317 | switch (e) { |
| 12318 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12319 | .NOTSOCK => |err| return errnoBug(err), |
| 12320 | .INVAL => |err| return errnoBug(err), |
| 12321 | .FAULT => |err| return errnoBug(err), |
| 12322 | else => |err| return posix.unexpectedErrno(err), |
| 12323 | } |
| 12324 | }, |
| 12325 | } |
| 12326 | } |
| 12327 | } |
| 12328 | |
| 12329 | fn setSocketOptionAfd(socket: net.Socket.Handle, level: i32, opt_name: u32, opt_val: anytype) !void { |
| 12330 | try socketOptionAfd(socket, .set, level, opt_name, @ptrCast(@constCast(&opt_val))); |
| 12331 | } |
| 12332 | |
| 12333 | fn socketOptionAfd(socket: net.Socket.Handle, mode: windows.AFD.SOCKOPT_INFO.Mode, level: i32, opt_name: u32, opt_val: []u8) !void { |
| 12334 | switch ((try deviceIoControl(&.{ |
| 12335 | .file = .{ .handle = socket, .flags = .{ .nonblocking = true } }, |
| 12336 | .code = windows.IOCTL.AFD.SOCKOPT, |
| 12337 | .in = @ptrCast(&windows.AFD.SOCKOPT_INFO{ |
| 12338 | .mode = mode, |
| 12339 | .level = level, |
| 12340 | .optname = opt_name, |
| 12341 | .optval = opt_val.ptr, |
| 12342 | .optlen = opt_val.len, |
| 12343 | }), |
| 12344 | })).u.Status) { |
| 12345 | .SUCCESS => return, |
| 12346 | .CANCELLED => unreachable, |
| 12347 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12348 | else => |status| return windows.unexpectedStatus(status), |
| 12349 | } |
| 12350 | } |
| 12351 | |
| 12352 | fn netConnectIpPosix( |
| 12353 | userdata: ?*anyopaque, |
| 12354 | address: *const IpAddress, |
| 12355 | options: IpAddress.ConnectOptions, |
| 12356 | ) IpAddress.ConnectError!net.Socket { |
| 12357 | if (!have_networking) return error.NetworkDown; |
| 12358 | if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout"); |
| 12359 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12360 | _ = t; |
| 12361 | const family = posixAddressFamily(address); |
| 12362 | const socket_fd = try openSocketPosix(family, .{ .mode = options.mode, .protocol = options.protocol }); |
| 12363 | errdefer closeFd(socket_fd); |
| 12364 | var storage: PosixAddress = undefined; |
| 12365 | var addr_len = addressToPosix(address, &storage); |
| 12366 | try posixConnect(socket_fd, &storage.any, addr_len); |
| 12367 | try posixGetSockName(socket_fd, &storage.any, &addr_len); |
| 12368 | return .{ .handle = socket_fd, .address = addressFromPosix(&storage) }; |
| 12369 | } |
| 12370 | |
| 12371 | fn netConnectIpWindows( |
| 12372 | userdata: ?*anyopaque, |
| 12373 | address: *const IpAddress, |
| 12374 | options: IpAddress.ConnectOptions, |
| 12375 | ) IpAddress.ConnectError!net.Socket { |
| 12376 | if (!have_networking) return error.NetworkDown; |
| 12377 | if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout"); |
| 12378 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12379 | _ = t; |
| 12380 | const family = posixAddressFamily(address); |
| 12381 | const socket_handle = try openSocketAfd(family, .{ .mode = options.mode, .protocol = options.protocol }); |
| 12382 | errdefer windows.CloseHandle(socket_handle); |
| 12383 | try setSocketOptionAfd(socket_handle, ws2_32.SOL.SOCKET, ws2_32.SO.REUSE_UNICASTPORT, true); |
| 12384 | const bound_address = bindSocketIpAfd(socket_handle, &switch (address.*) { |
| 12385 | .ip4 => .{ .ip4 = .unspecified(0) }, |
| 12386 | .ip6 => .{ .ip6 = .unspecified(0) }, |
| 12387 | }, .Active) catch |err| switch (err) { |
| 12388 | error.AddressInUse => return error.Unexpected, |
| 12389 | else => |e| return e, |
| 12390 | }; |
| 12391 | const Storage = extern struct { Reserved0: [3]usize = @splat(0), Address: PosixAddress }; |
| 12392 | var storage: Storage = .{ .Address = undefined }; |
| 12393 | const addr_len = addressToPosix(address, &storage.Address); |
| 12394 | switch ((try deviceIoControl(&.{ |
| 12395 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 12396 | .code = windows.IOCTL.AFD.CONNECT, |
| 12397 | .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len], |
| 12398 | })).u.Status) { |
| 12399 | .SUCCESS => {}, |
| 12400 | .CANCELLED => unreachable, |
| 12401 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12402 | .CONNECTION_REFUSED => return error.ConnectionRefused, |
| 12403 | else => |status| return windows.unexpectedStatus(status), |
| 12404 | } |
| 12405 | return .{ .handle = socket_handle, .address = bound_address }; |
| 12406 | } |
| 12407 | |
| 12408 | fn netConnectUnixPosix( |
| 12409 | userdata: ?*anyopaque, |
| 12410 | address: *const net.UnixAddress, |
| 12411 | ) net.UnixAddress.ConnectError!net.Socket.Handle { |
| 12412 | if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; |
| 12413 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12414 | _ = t; |
| 12415 | const socket_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { |
| 12416 | error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, |
| 12417 | error.OptionUnsupported => return error.Unexpected, |
| 12418 | else => |e| return e, |
| 12419 | }; |
| 12420 | errdefer closeFd(socket_fd); |
| 12421 | var storage: UnixAddress = undefined; |
| 12422 | const addr_len = addressUnixToPosix(address, &storage); |
| 12423 | try posixConnectUnix(socket_fd, &storage.any, addr_len); |
| 12424 | return socket_fd; |
| 12425 | } |
| 12426 | |
| 12427 | fn netConnectUnixWindows( |
| 12428 | userdata: ?*anyopaque, |
| 12429 | address: *const net.UnixAddress, |
| 12430 | ) net.UnixAddress.ConnectError!net.Socket.Handle { |
| 12431 | if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; |
| 12432 | if (!have_networking) return error.NetworkDown; |
| 12433 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12434 | _ = t; |
| 12435 | const is_abstract = address.isAbstract(); |
| 12436 | const wps = if (!is_abstract) sliceToPrefixedFileW(null, address.path, .{ |
| 12437 | .allow_relative = false, |
| 12438 | }) catch |err| switch (err) { |
| 12439 | error.NameTooLong, error.BadPathName => return error.FileNotFound, |
| 12440 | else => |e| return e, |
| 12441 | } else undefined; |
| 12442 | const socket_handle = openSocketAfd(ws2_32.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { |
| 12443 | error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, |
| 12444 | else => |e| return e, |
| 12445 | }; |
| 12446 | errdefer windows.CloseHandle(socket_handle); |
| 12447 | if (!is_abstract) try socketOptionAfd(socket_handle, .special, 0, ws2_32.SO.UNIX_PATH, @constCast( |
| 12448 | @as([]const u8, @ptrCast(&windows.AFD.SOCKOPT_INFO.UNIX_PATH{ |
| 12449 | .Path = wps.data, |
| 12450 | }))[0 .. @offsetOf(windows.AFD.SOCKOPT_INFO.UNIX_PATH, "Path") + @sizeOf(windows.WCHAR) * wps.len], |
| 12451 | )); |
| 12452 | bindSocketUnixAfd(socket_handle, &(net.UnixAddress.init("") catch |err| switch (err) { |
| 12453 | error.NameTooLong => unreachable, |
| 12454 | })) catch |err| switch (err) { |
| 12455 | error.AddressInUse => return error.Unexpected, |
| 12456 | else => |e| return e, |
| 12457 | }; |
| 12458 | const Storage = extern struct { Reserved0: [3]usize = @splat(0), Address: UnixAddress }; |
| 12459 | var storage: Storage = .{ .Address = undefined }; |
| 12460 | const addr_len = addressUnixToPosix(address, &storage.Address); |
| 12461 | switch ((try deviceIoControl(&.{ |
| 12462 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 12463 | .code = windows.IOCTL.AFD.CONNECT, |
| 12464 | .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len], |
| 12465 | })).u.Status) { |
| 12466 | .SUCCESS => {}, |
| 12467 | .CANCELLED => unreachable, |
| 12468 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12469 | else => |status| return windows.unexpectedStatus(status), |
| 12470 | } |
| 12471 | return socket_handle; |
| 12472 | } |
| 12473 | |
| 12474 | fn netBindIpPosix( |
| 12475 | userdata: ?*anyopaque, |
| 12476 | address: *const IpAddress, |
| 12477 | options: IpAddress.BindOptions, |
| 12478 | ) IpAddress.BindError!net.Socket { |
| 12479 | if (!have_networking) return error.NetworkDown; |
| 12480 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12481 | _ = t; |
| 12482 | const family = posixAddressFamily(address); |
| 12483 | const socket_fd = try openSocketPosix(family, options); |
| 12484 | errdefer closeFd(socket_fd); |
| 12485 | var storage: PosixAddress = undefined; |
| 12486 | var addr_len = addressToPosix(address, &storage); |
| 12487 | try posixBind(socket_fd, &storage.any, addr_len); |
| 12488 | if (options.allow_broadcast) try setSocketOptionPosix(socket_fd, std.posix.SOL.SOCKET, std.posix.SO.BROADCAST, 1); |
| 12489 | try posixGetSockName(socket_fd, &storage.any, &addr_len); |
| 12490 | return .{ .handle = socket_fd, .address = addressFromPosix(&storage) }; |
| 12491 | } |
| 12492 | |
| 12493 | fn netBindIpWindows( |
| 12494 | userdata: ?*anyopaque, |
| 12495 | address: *const IpAddress, |
| 12496 | options: IpAddress.BindOptions, |
| 12497 | ) IpAddress.BindError!net.Socket { |
| 12498 | if (!have_networking) return error.NetworkDown; |
| 12499 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12500 | _ = t; |
| 12501 | const family = posixAddressFamily(address); |
| 12502 | const socket_handle = try openSocketAfd(family, options); |
| 12503 | errdefer windows.CloseHandle(socket_handle); |
| 12504 | const bound_address = try bindSocketIpAfd(socket_handle, address, .Active); |
| 12505 | if (options.allow_broadcast) try setSocketOptionAfd(socket_handle, ws2_32.SOL.SOCKET, ws2_32.SO.BROADCAST, true); |
| 12506 | return .{ .handle = socket_handle, .address = bound_address }; |
| 12507 | } |
| 12508 | |
| 12509 | fn openSocketPosix( |
| 12510 | family: posix.sa_family_t, |
| 12511 | options: IpAddress.BindOptions, |
| 12512 | ) error{ |
| 12513 | AddressFamilyUnsupported, |
| 12514 | ProtocolUnsupportedBySystem, |
| 12515 | ProcessFdQuotaExceeded, |
| 12516 | SystemFdQuotaExceeded, |
| 12517 | SystemResources, |
| 12518 | ProtocolUnsupportedByAddressFamily, |
| 12519 | SocketModeUnsupported, |
| 12520 | OptionUnsupported, |
| 12521 | Unexpected, |
| 12522 | Canceled, |
| 12523 | }!posix.socket_t { |
| 12524 | const mode, const protocol = try posixSocketModeProtocol(family, options.mode, options.protocol); |
| 12525 | const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; |
| 12526 | const syscall: Syscall = try .start(); |
| 12527 | const socket_fd = while (true) { |
| 12528 | const rc = posix.system.socket(family, flags, protocol); |
| 12529 | switch (posix.errno(rc)) { |
| 12530 | .SUCCESS => { |
| 12531 | syscall.finish(); |
| 12532 | const fd: posix.fd_t = @intCast(rc); |
| 12533 | errdefer closeFd(fd); |
| 12534 | if (socket_flags_unsupported) try setCloexec(fd); |
| 12535 | break fd; |
| 12536 | }, |
| 12537 | .INTR => { |
| 12538 | try syscall.checkCancel(); |
| 12539 | continue; |
| 12540 | }, |
| 12541 | .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), |
| 12542 | .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), |
| 12543 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| 12544 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), |
| 12545 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 12546 | .NOMEM => return syscall.fail(error.SystemResources), |
| 12547 | .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), |
| 12548 | .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), |
| 12549 | else => |err| return syscall.unexpectedErrno(err), |
| 12550 | } |
| 12551 | }; |
| 12552 | errdefer closeFd(socket_fd); |
| 12553 | |
| 12554 | if (options.ip6_only) |ip6_only| { |
| 12555 | if (posix.IPV6 == void) return error.OptionUnsupported; |
| 12556 | try setSocketOptionPosix(socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, @intFromBool(ip6_only)); |
| 12557 | } |
| 12558 | |
| 12559 | return socket_fd; |
| 12560 | } |
| 12561 | |
| 12562 | fn setCloexec(fd: posix.fd_t) error{ Canceled, Unexpected }!void { |
| 12563 | const syscall: Syscall = try .start(); |
| 12564 | while (true) switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { |
| 12565 | .SUCCESS => return syscall.finish(), |
| 12566 | .INTR => { |
| 12567 | try syscall.checkCancel(); |
| 12568 | continue; |
| 12569 | }, |
| 12570 | else => |err| return syscall.unexpectedErrno(err), |
| 12571 | }; |
| 12572 | } |
| 12573 | |
| 12574 | fn netSocketCreatePair( |
| 12575 | userdata: ?*anyopaque, |
| 12576 | options: net.Socket.CreatePairOptions, |
| 12577 | ) net.Socket.CreatePairError![2]net.Socket { |
| 12578 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12579 | _ = t; |
| 12580 | if (!have_networking) return error.OperationUnsupported; |
| 12581 | if (@TypeOf(posix.system.socketpair) == void) return error.OperationUnsupported; |
| 12582 | if (native_os == .haiku) @panic("TODO"); |
| 12583 | |
| 12584 | const family: posix.sa_family_t = switch (options.family) { |
| 12585 | .ip4 => posix.AF.INET, |
| 12586 | .ip6 => posix.AF.INET6, |
| 12587 | }; |
| 12588 | const mode, const protocol = try posixSocketModeProtocol(family, options.mode, options.protocol); |
| 12589 | const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; |
| 12590 | |
| 12591 | var sockets: [2]posix.socket_t = undefined; |
| 12592 | const syscall: Syscall = try .start(); |
| 12593 | while (true) switch (posix.errno(posix.system.socketpair(family, flags, protocol, &sockets))) { |
| 12594 | .SUCCESS => { |
| 12595 | syscall.finish(); |
| 12596 | errdefer { |
| 12597 | closeFd(sockets[0]); |
| 12598 | closeFd(sockets[1]); |
| 12599 | } |
| 12600 | if (socket_flags_unsupported) { |
| 12601 | try setCloexec(sockets[0]); |
| 12602 | try setCloexec(sockets[1]); |
| 12603 | } |
| 12604 | var storages: [2]PosixAddress = undefined; |
| 12605 | var addr_lens: [2]posix.socklen_t = .{ @sizeOf(PosixAddress), @sizeOf(PosixAddress) }; |
| 12606 | try posixGetSockName(sockets[0], &storages[0].any, &addr_lens[0]); |
| 12607 | try posixGetSockName(sockets[1], &storages[1].any, &addr_lens[1]); |
| 12608 | return .{ |
| 12609 | .{ .handle = sockets[0], .address = addressFromPosix(&storages[0]) }, |
| 12610 | .{ .handle = sockets[1], .address = addressFromPosix(&storages[1]) }, |
| 12611 | }; |
| 12612 | }, |
| 12613 | .INTR => { |
| 12614 | try syscall.checkCancel(); |
| 12615 | continue; |
| 12616 | }, |
| 12617 | .ACCES => return syscall.fail(error.AccessDenied), |
| 12618 | .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), |
| 12619 | .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), |
| 12620 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| 12621 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), |
| 12622 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 12623 | .NOMEM => return syscall.fail(error.SystemResources), |
| 12624 | .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), |
| 12625 | .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), |
| 12626 | else => |err| return syscall.unexpectedErrno(err), |
| 12627 | }; |
| 12628 | } |
| 12629 | |
| 12630 | fn openSocketAfd(family: ws2_32.ADDRESS_FAMILY, options: IpAddress.BindOptions) !net.Socket.Handle { |
| 12631 | const mode, const protocol = try posixSocketModeProtocol(family, options.mode, options.protocol); |
| 12632 | var handle: windows.HANDLE = undefined; |
| 12633 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 12634 | var syscall: Syscall = try .start(); |
| 12635 | while (true) switch (windows.ntdll.NtCreateFile( |
| 12636 | &handle, |
| 12637 | .{ |
| 12638 | .STANDARD = .{ .RIGHTS = .{ .WRITE_DAC = true }, .SYNCHRONIZE = true }, |
| 12639 | .GENERIC = .{ .WRITE = true, .READ = true }, |
| 12640 | }, |
| 12641 | &.{ |
| 12642 | .ObjectName = @constCast(&windows.UNICODE_STRING.init( |
| 12643 | windows.AFD.DEVICE_NAME ++ .{ '\\', 'E', 'n', 'd', 'p', 'o', 'i', 'n', 't' }, |
| 12644 | )), |
| 12645 | }, |
| 12646 | &iosb, |
| 12647 | null, |
| 12648 | .{}, |
| 12649 | .{ .READ = true, .WRITE = true }, |
| 12650 | .OPEN_IF, |
| 12651 | .{ .IO = .ASYNCHRONOUS }, |
| 12652 | &windows.AFD.OPEN_PACKET.FULL_EA_INFORMATION{ .Value = .{ |
| 12653 | .EndpointType = .{ |
| 12654 | .CONNECTIONLESS = switch (options.mode) { |
| 12655 | .stream, .seqpacket, .rdm => false, |
| 12656 | .dgram, .raw => true, |
| 12657 | }, |
| 12658 | .MESSAGEMODE = options.mode != .stream, |
| 12659 | .RAW = options.mode == .raw, |
| 12660 | }, |
| 12661 | .GroupID = 0, |
| 12662 | .AddressFamily = family, |
| 12663 | .SocketType = @bitCast(mode), |
| 12664 | .Protocol = @bitCast(protocol), |
| 12665 | .TransportDeviceNameLength = 0, |
| 12666 | .TransportDeviceName = undefined, |
| 12667 | } }, |
| 12668 | @sizeOf(windows.AFD.OPEN_PACKET.FULL_EA_INFORMATION), |
| 12669 | )) { |
| 12670 | .SUCCESS => { |
| 12671 | syscall.finish(); |
| 12672 | return handle; |
| 12673 | }, |
| 12674 | .CANCELLED => { |
| 12675 | try syscall.checkCancel(); |
| 12676 | continue; |
| 12677 | }, |
| 12678 | .PROTOCOL_NOT_SUPPORTED => return syscall.fail(error.AddressFamilyUnsupported), |
| 12679 | .NO_SUCH_FILE => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), |
| 12680 | else => |status| return syscall.unexpectedNtstatus(status), |
| 12681 | }; |
| 12682 | } |
| 12683 | |
| 12684 | fn bindSocketIpAfd(socket_handle: net.Socket.Handle, address: *const IpAddress, mode: windows.AFD.BIND_INFO.MODE) !IpAddress { |
| 12685 | const Storage = extern struct { Info: windows.AFD.BIND_INFO, Address: PosixAddress }; |
| 12686 | var storage: Storage = .{ .Info = .{ .Mode = mode }, .Address = undefined }; |
| 12687 | const addr_len = addressToPosix(address, &storage.Address); |
| 12688 | switch ((try deviceIoControl(&.{ |
| 12689 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 12690 | .code = windows.IOCTL.AFD.BIND, |
| 12691 | .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len], |
| 12692 | .out = @as([]u8, @ptrCast(&storage.Address))[0..addr_len], |
| 12693 | })).u.Status) { |
| 12694 | .SUCCESS => {}, |
| 12695 | .CANCELLED => unreachable, |
| 12696 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12697 | .SHARING_VIOLATION => return error.AddressInUse, |
| 12698 | else => |status| return windows.unexpectedStatus(status), |
| 12699 | } |
| 12700 | return addressFromPosix(&storage.Address); |
| 12701 | } |
| 12702 | |
| 12703 | fn bindSocketUnixAfd(socket_handle: net.Socket.Handle, address: *const net.UnixAddress) !void { |
| 12704 | const Storage = extern struct { Info: windows.AFD.BIND_INFO, Address: UnixAddress }; |
| 12705 | var storage: Storage = .{ .Info = .{ .Mode = .Unix }, .Address = undefined }; |
| 12706 | const addr_len = addressUnixToPosix(address, &storage.Address); |
| 12707 | switch ((try deviceIoControl(&.{ |
| 12708 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 12709 | .code = windows.IOCTL.AFD.BIND, |
| 12710 | .in = @as([]const u8, @ptrCast(&storage))[0 .. @offsetOf(Storage, "Address") + addr_len], |
| 12711 | .out = @ptrCast(&storage.Address), |
| 12712 | })).u.Status) { |
| 12713 | .SUCCESS => {}, |
| 12714 | .CANCELLED => unreachable, |
| 12715 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12716 | .ADDRESS_ALREADY_EXISTS => return error.AddressInUse, |
| 12717 | else => |status| return windows.unexpectedStatus(status), |
| 12718 | } |
| 12719 | } |
| 12720 | |
| 12721 | fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle, options: net.Server.AcceptOptions) net.Server.AcceptError!net.Socket { |
| 12722 | if (!have_networking) return error.NetworkDown; |
| 12723 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12724 | _ = t; |
| 12725 | options; |
| 12726 | var storage: PosixAddress = undefined; |
| 12727 | var addr_len: posix.socklen_t = @sizeOf(PosixAddress); |
| 12728 | const syscall: Syscall = try .start(); |
| 12729 | const fd = while (true) { |
| 12730 | const rc = if (have_accept4) |
| 12731 | posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC) |
| 12732 | else |
| 12733 | posix.system.accept(listen_fd, &storage.any, &addr_len); |
| 12734 | switch (posix.errno(rc)) { |
| 12735 | .SUCCESS => { |
| 12736 | syscall.finish(); |
| 12737 | const fd: posix.fd_t = @intCast(rc); |
| 12738 | errdefer closeFd(fd); |
| 12739 | if (!have_accept4) try setCloexec(fd); |
| 12740 | break fd; |
| 12741 | }, |
| 12742 | .INTR => { |
| 12743 | try syscall.checkCancel(); |
| 12744 | continue; |
| 12745 | }, |
| 12746 | else => |e| { |
| 12747 | syscall.finish(); |
| 12748 | switch (e) { |
| 12749 | .AGAIN => |err| return errnoBug(err), |
| 12750 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12751 | .CONNABORTED => return error.ConnectionAborted, |
| 12752 | .FAULT => |err| return errnoBug(err), |
| 12753 | .INVAL => return error.SocketNotListening, |
| 12754 | .NOTSOCK => |err| return errnoBug(err), |
| 12755 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 12756 | .NFILE => return error.SystemFdQuotaExceeded, |
| 12757 | .NOBUFS => return error.SystemResources, |
| 12758 | .NOMEM => return error.SystemResources, |
| 12759 | .OPNOTSUPP => |err| return errnoBug(err), |
| 12760 | .PROTO => return error.ProtocolFailure, |
| 12761 | .PERM => return error.BlockedByFirewall, |
| 12762 | else => |err| return posix.unexpectedErrno(err), |
| 12763 | } |
| 12764 | }, |
| 12765 | } |
| 12766 | }; |
| 12767 | return .{ .handle = fd, .address = addressFromPosix(&storage) }; |
| 12768 | } |
| 12769 | |
| 12770 | fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle, options: net.Server.AcceptOptions) net.Server.AcceptError!net.Socket { |
| 12771 | if (!have_networking) return error.NetworkDown; |
| 12772 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 12773 | const Storage = extern struct { |
| 12774 | Info: windows.AFD.LISTEN_RESPONSE_INFO, |
| 12775 | RemoteAddress: extern union { posix: PosixAddress, unix: UnixAddress }, |
| 12776 | }; |
| 12777 | var storage: Storage = undefined; |
| 12778 | switch ((try deviceIoControl(&.{ |
| 12779 | .file = .{ .handle = listen_handle, .flags = .{ .nonblocking = true } }, |
| 12780 | .code = windows.IOCTL.AFD.WAIT_FOR_LISTEN, |
| 12781 | .out = @ptrCast(&storage), |
| 12782 | })).u.Status) { |
| 12783 | .SUCCESS => {}, |
| 12784 | .CANCELLED => unreachable, |
| 12785 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12786 | else => |status| return windows.unexpectedStatus(status), |
| 12787 | } |
| 12788 | errdefer t.deferAcceptAfd(listen_handle, storage.Info); |
| 12789 | const accept_handle = openSocketAfd( |
| 12790 | storage.RemoteAddress.posix.any.family, |
| 12791 | .{ .mode = options.mode, .protocol = options.protocol }, |
| 12792 | ) catch |err| switch (err) { |
| 12793 | error.AddressFamilyUnsupported => return error.Unexpected, |
| 12794 | error.ProtocolUnsupportedByAddressFamily => return error.Unexpected, |
| 12795 | else => |e| return e, |
| 12796 | }; |
| 12797 | errdefer windows.CloseHandle(accept_handle); |
| 12798 | switch ((try deviceIoControl(&.{ |
| 12799 | .file = .{ .handle = listen_handle, .flags = .{ .nonblocking = true } }, |
| 12800 | .code = windows.IOCTL.AFD.ACCEPT, |
| 12801 | .in = @ptrCast(&windows.AFD.ACCEPT_INFO{ |
| 12802 | .UseSAN = .FALSE, |
| 12803 | .Sequence = storage.Info.Sequence, |
| 12804 | .AcceptHandle = accept_handle, |
| 12805 | }), |
| 12806 | })).u.Status) { |
| 12807 | .SUCCESS => {}, |
| 12808 | .CANCELLED => unreachable, |
| 12809 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12810 | else => |status| return windows.unexpectedStatus(status), |
| 12811 | } |
| 12812 | return .{ .handle = accept_handle, .address = addressFromPosix(&storage.RemoteAddress.posix) }; |
| 12813 | } |
| 12814 | |
| 12815 | fn deferAcceptAfd(t: *Threaded, listen_handle: net.Socket.Handle, info: windows.AFD.LISTEN_RESPONSE_INFO) void { |
| 12816 | const cancel_protection = swapCancelProtection(t, .blocked); |
| 12817 | defer _ = swapCancelProtection(t, cancel_protection); |
| 12818 | switch ((deviceIoControl(&.{ |
| 12819 | .file = .{ .handle = listen_handle, .flags = .{ .nonblocking = true } }, |
| 12820 | .code = windows.IOCTL.AFD.DEFER_ACCEPT, |
| 12821 | .in = @ptrCast(&windows.AFD.DEFER_ACCEPT_INFO{ |
| 12822 | .Sequence = info.Sequence, |
| 12823 | .Reject = .FALSE, |
| 12824 | }), |
| 12825 | }) catch |err| switch (err) { |
| 12826 | error.Canceled => unreachable, // blocked |
| 12827 | }).u.Status) { |
| 12828 | .SUCCESS => {}, |
| 12829 | .CANCELLED => unreachable, |
| 12830 | else => |status| windows.unexpectedStatus(status) catch {}, |
| 12831 | } |
| 12832 | } |
| 12833 | |
| 12834 | fn netRead(socket_handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { |
| 12835 | if (!have_networking) return error.NetworkDown; |
| 12836 | |
| 12837 | if (is_windows) return netReadWindows(socket_handle, data); |
| 12838 | return netReadPosix(socket_handle, data); |
| 12839 | } |
| 12840 | |
| 12841 | fn netReadPosix(fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { |
| 12842 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; |
| 12843 | var i: usize = 0; |
| 12844 | for (data) |buf| { |
| 12845 | if (iovecs_buffer.len - i == 0) break; |
| 12846 | if (buf.len != 0) { |
| 12847 | iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len }; |
| 12848 | i += 1; |
| 12849 | } |
| 12850 | } |
| 12851 | const dest = iovecs_buffer[0..i]; |
| 12852 | assert(dest[0].len > 0); |
| 12853 | |
| 12854 | if (native_os == .wasi and !builtin.link_libc) { |
| 12855 | const syscall: Syscall = try .start(); |
| 12856 | while (true) { |
| 12857 | var n: usize = undefined; |
| 12858 | switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) { |
| 12859 | .SUCCESS => { |
| 12860 | syscall.finish(); |
| 12861 | return n; |
| 12862 | }, |
| 12863 | .INTR => { |
| 12864 | try syscall.checkCancel(); |
| 12865 | continue; |
| 12866 | }, |
| 12867 | else => |e| { |
| 12868 | syscall.finish(); |
| 12869 | switch (e) { |
| 12870 | .INVAL => |err| return errnoBug(err), |
| 12871 | .FAULT => |err| return errnoBug(err), |
| 12872 | .AGAIN => |err| return errnoBug(err), |
| 12873 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12874 | .NOBUFS => return error.SystemResources, |
| 12875 | .NOMEM => return error.SystemResources, |
| 12876 | .NOTCONN => return error.SocketUnconnected, |
| 12877 | .CONNRESET => return error.ConnectionResetByPeer, |
| 12878 | .TIMEDOUT => return error.ConnectionTimedOut, |
| 12879 | .NOTCAPABLE => return error.AccessDenied, |
| 12880 | else => |err| return posix.unexpectedErrno(err), |
| 12881 | } |
| 12882 | }, |
| 12883 | } |
| 12884 | } |
| 12885 | } |
| 12886 | |
| 12887 | const syscall: Syscall = try .start(); |
| 12888 | while (true) { |
| 12889 | const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len)); |
| 12890 | switch (posix.errno(rc)) { |
| 12891 | .SUCCESS => { |
| 12892 | syscall.finish(); |
| 12893 | return @intCast(rc); |
| 12894 | }, |
| 12895 | .INTR => { |
| 12896 | try syscall.checkCancel(); |
| 12897 | continue; |
| 12898 | }, |
| 12899 | else => |e| { |
| 12900 | syscall.finish(); |
| 12901 | switch (e) { |
| 12902 | .INVAL => |err| return errnoBug(err), |
| 12903 | .FAULT => |err| return errnoBug(err), |
| 12904 | .AGAIN => |err| return errnoBug(err), |
| 12905 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 12906 | .NOBUFS => return error.SystemResources, |
| 12907 | .NOMEM => return error.SystemResources, |
| 12908 | .NOTCONN => return error.SocketUnconnected, |
| 12909 | .CONNRESET => return error.ConnectionResetByPeer, |
| 12910 | .TIMEDOUT => return error.ConnectionTimedOut, |
| 12911 | .PIPE => return error.SocketUnconnected, |
| 12912 | .NETDOWN => return error.NetworkDown, |
| 12913 | else => |err| return posix.unexpectedErrno(err), |
| 12914 | } |
| 12915 | }, |
| 12916 | } |
| 12917 | } |
| 12918 | } |
| 12919 | |
| 12920 | fn netReadWindows(socket_handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { |
| 12921 | var iovecs: [max_iovecs_len]windows.AFD.WSABUF(.@"var") = undefined; |
| 12922 | var len: u32 = 0; |
| 12923 | for (data) |buf| { |
| 12924 | if (iovecs.len - len == 0) break; |
| 12925 | addAfdBuf(.@"var", &iovecs, &len, buf); |
| 12926 | } |
| 12927 | |
| 12928 | const iosb = try deviceIoControl(&.{ |
| 12929 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 12930 | .code = windows.IOCTL.AFD.RECEIVE, |
| 12931 | .in = @ptrCast(&windows.AFD.RECV_INFO{ |
| 12932 | .BufferArray = &iovecs, |
| 12933 | .BufferCount = len, |
| 12934 | .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true }, |
| 12935 | .TdiFlags = .{ .NORMAL = true }, |
| 12936 | }), |
| 12937 | }); |
| 12938 | switch (iosb.u.Status) { |
| 12939 | .SUCCESS => return iosb.Information, |
| 12940 | .CANCELLED => unreachable, |
| 12941 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 12942 | .CONNECTION_RESET, .REMOTE_DISCONNECT => return error.ConnectionResetByPeer, |
| 12943 | .IO_TIMEOUT => return error.ConnectionTimedOut, |
| 12944 | else => |status| return windows.unexpectedStatus(status), |
| 12945 | } |
| 12946 | } |
| 12947 | |
| 12948 | fn netSendPosix( |
| 12949 | t: *Threaded, |
| 12950 | socket_handle: net.Socket.Handle, |
| 12951 | messages: []net.OutgoingMessage, |
| 12952 | flags: net.SendFlags, |
| 12953 | nonblocking: bool, |
| 12954 | ) struct { ?(net.Socket.SendError || error{WouldBlock}), usize } { |
| 12955 | if (!have_networking) return .{ error.NetworkDown, 0 }; |
| 12956 | |
| 12957 | const posix_flags: u32 = |
| 12958 | @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) | |
| 12959 | @as(u32, if (@hasDecl(posix.MSG, "DONTROUTE") and flags.dont_route) posix.MSG.DONTROUTE else 0) | |
| 12960 | @as(u32, if (@hasDecl(posix.MSG, "EOR") and flags.eor) posix.MSG.EOR else 0) | |
| 12961 | @as(u32, if (@hasDecl(posix.MSG, "OOB") and flags.oob) posix.MSG.OOB else 0) | |
| 12962 | @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) | |
| 12963 | @as(u32, if (@hasDecl(posix.MSG, "DONTWAIT") and nonblocking) posix.MSG.DONTWAIT else 0) | |
| 12964 | posix.MSG.NOSIGNAL; |
| 12965 | |
| 12966 | var i: usize = 0; |
| 12967 | while (messages.len - i != 0) { |
| 12968 | if (have_sendmmsg) { |
| 12969 | i += netSendManyPosix(socket_handle, messages[i..], posix_flags) catch |err| return .{ err, i }; |
| 12970 | continue; |
| 12971 | } |
| 12972 | t.netSendOnePosix(socket_handle, &messages[i], posix_flags) catch |err| return .{ err, i }; |
| 12973 | i += 1; |
| 12974 | } |
| 12975 | return .{ null, i }; |
| 12976 | } |
| 12977 | |
| 12978 | fn netSendWindows( |
| 12979 | t: *Threaded, |
| 12980 | socket_handle: net.Socket.Handle, |
| 12981 | messages: []net.OutgoingMessage, |
| 12982 | flags: net.SendFlags, |
| 12983 | ) struct { ?net.Socket.SendError, usize } { |
| 12984 | if (!have_networking) return .{ error.NetworkDown, 0 }; |
| 12985 | for (messages, 0..) |*m, i| { |
| 12986 | t.netSendOneWindows(socket_handle, m, flags) catch |err| return .{ err, i }; |
| 12987 | } |
| 12988 | return .{ null, messages.len }; |
| 12989 | } |
| 12990 | |
| 12991 | fn netSendOneWindows( |
| 12992 | t: *Threaded, |
| 12993 | socket_handle: net.Socket.Handle, |
| 12994 | message: *net.OutgoingMessage, |
| 12995 | flags: net.SendFlags, |
| 12996 | ) net.Socket.SendError!void { |
| 12997 | _ = t; |
| 12998 | _ = flags; |
| 12999 | const iovecs: [1]windows.AFD.WSABUF(.@"const") = .{.{ |
| 13000 | .buf = message.data_ptr, |
| 13001 | .len = std.math.cast(std.os.windows.ULONG, message.data_len) orelse |
| 13002 | return error.MessageOversize, |
| 13003 | }}; |
| 13004 | var storage: PosixAddress = undefined; |
| 13005 | const addr_len = addressToPosix(message.address, &storage); |
| 13006 | switch ((try deviceIoControl(&.{ |
| 13007 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 13008 | .code = windows.IOCTL.AFD.SEND_DATAGRAM, |
| 13009 | .in = @ptrCast(&windows.AFD.SEND_DATAGRAM_INFO{ |
| 13010 | .BufferArray = &iovecs, |
| 13011 | .BufferCount = iovecs.len, |
| 13012 | .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true }, |
| 13013 | .TdiRequest = undefined, |
| 13014 | .TdiConnInfo = .{ |
| 13015 | .UserDataLength = undefined, |
| 13016 | .UserData = undefined, |
| 13017 | .OptionsLength = undefined, |
| 13018 | .Options = undefined, |
| 13019 | .RemoteAddressLength = @bitCast(addr_len), |
| 13020 | .RemoteAddress = &storage, |
| 13021 | }, |
| 13022 | }), |
| 13023 | })).u.Status) { |
| 13024 | .SUCCESS => return, |
| 13025 | .CANCELLED => unreachable, |
| 13026 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 13027 | else => |status| return windows.unexpectedStatus(status), |
| 13028 | } |
| 13029 | } |
| 13030 | |
| 13031 | fn netSendOnePosix( |
| 13032 | t: *Threaded, |
| 13033 | socket_handle: net.Socket.Handle, |
| 13034 | message: *net.OutgoingMessage, |
| 13035 | flags: u32, |
| 13036 | ) (net.Socket.SendError || error{WouldBlock})!void { |
| 13037 | _ = t; |
| 13038 | var addr: PosixAddress = undefined; |
| 13039 | var iovec: posix.iovec_const = .{ .base = @constCast(message.data_ptr), .len = message.data_len }; |
| 13040 | const msg: posix.msghdr_const = .{ |
| 13041 | .name = &addr.any, |
| 13042 | .namelen = addressToPosix(message.address, &addr), |
| 13043 | .iov = (&iovec)[0..1], |
| 13044 | .iovlen = 1, |
| 13045 | // OS returns EINVAL if this pointer is invalid even if controllen is zero. |
| 13046 | .control = if (message.control.len == 0) null else @constCast(message.control.ptr), |
| 13047 | .controllen = @intCast(message.control.len), |
| 13048 | .flags = 0, |
| 13049 | }; |
| 13050 | var syscall: if (is_windows) AlertableSyscall else Syscall = try .start(); |
| 13051 | while (true) { |
| 13052 | const rc = posix.system.sendmsg(socket_handle, &msg, flags); |
| 13053 | switch (posix.errno(rc)) { |
| 13054 | .SUCCESS => { |
| 13055 | syscall.finish(); |
| 13056 | message.data_len = @intCast(rc); |
| 13057 | return; |
| 13058 | }, |
| 13059 | .INTR => { |
| 13060 | try syscall.checkCancel(); |
| 13061 | continue; |
| 13062 | }, |
| 13063 | .ACCES => return syscall.fail(error.AccessDenied), |
| 13064 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 13065 | .ALREADY => return syscall.fail(error.FastOpenAlreadyInProgress), |
| 13066 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 13067 | .MSGSIZE => return syscall.fail(error.MessageOversize), |
| 13068 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 13069 | .NOMEM => return syscall.fail(error.SystemResources), |
| 13070 | .PIPE => return syscall.fail(error.SocketUnconnected), |
| 13071 | .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), |
| 13072 | .HOSTUNREACH => return syscall.fail(error.HostUnreachable), |
| 13073 | .NETUNREACH => return syscall.fail(error.NetworkUnreachable), |
| 13074 | .NOTCONN => return syscall.fail(error.SocketUnconnected), |
| 13075 | .TIMEDOUT => return syscall.fail(error.ConnectionTimedOut), |
| 13076 | .NETDOWN => return syscall.fail(error.NetworkDown), |
| 13077 | .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. |
| 13078 | .DESTADDRREQ => |err| return syscall.errnoBug(err), |
| 13079 | .FAULT => |err| return syscall.errnoBug(err), |
| 13080 | .INVAL => |err| return syscall.errnoBug(err), |
| 13081 | .ISCONN => |err| return syscall.errnoBug(err), |
| 13082 | .NOTSOCK => |err| return syscall.errnoBug(err), |
| 13083 | .OPNOTSUPP => |err| return syscall.errnoBug(err), |
| 13084 | else => |err| return syscall.unexpectedErrno(err), |
| 13085 | } |
| 13086 | } |
| 13087 | } |
| 13088 | |
| 13089 | fn netSendManyPosix( |
| 13090 | socket_handle: net.Socket.Handle, |
| 13091 | messages: []net.OutgoingMessage, |
| 13092 | flags: u32, |
| 13093 | ) (net.Socket.SendError || error{WouldBlock})!usize { |
| 13094 | var msg_buffer: [64]posix.system.mmsghdr = undefined; |
| 13095 | var addr_buffer: [msg_buffer.len]PosixAddress = undefined; |
| 13096 | var iovecs_buffer: [msg_buffer.len]posix.iovec = undefined; |
| 13097 | const min_len: usize = @min(messages.len, msg_buffer.len); |
| 13098 | const clamped_messages = messages[0..min_len]; |
| 13099 | const clamped_msgs = (&msg_buffer)[0..min_len]; |
| 13100 | const clamped_addrs = (&addr_buffer)[0..min_len]; |
| 13101 | const clamped_iovecs = (&iovecs_buffer)[0..min_len]; |
| 13102 | |
| 13103 | for (clamped_messages, clamped_msgs, clamped_addrs, clamped_iovecs) |*message, *msg, *addr, *iovec| { |
| 13104 | iovec.* = .{ .base = @constCast(message.data_ptr), .len = message.data_len }; |
| 13105 | msg.* = .{ |
| 13106 | .hdr = .{ |
| 13107 | .name = &addr.any, |
| 13108 | .namelen = addressToPosix(message.address, addr), |
| 13109 | .iov = iovec[0..1], |
| 13110 | .iovlen = 1, |
| 13111 | .control = @constCast(message.control.ptr), |
| 13112 | .controllen = message.control.len, |
| 13113 | .flags = 0, |
| 13114 | }, |
| 13115 | .len = undefined, // Populated by calling sendmmsg below. |
| 13116 | }; |
| 13117 | } |
| 13118 | |
| 13119 | const syscall: Syscall = try .start(); |
| 13120 | while (true) { |
| 13121 | const rc = posix.system.sendmmsg(socket_handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags); |
| 13122 | switch (posix.errno(rc)) { |
| 13123 | .SUCCESS => { |
| 13124 | syscall.finish(); |
| 13125 | const n: usize = @intCast(rc); |
| 13126 | for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| { |
| 13127 | message.data_len = msg.len; |
| 13128 | } |
| 13129 | return n; |
| 13130 | }, |
| 13131 | .INTR => { |
| 13132 | try syscall.checkCancel(); |
| 13133 | continue; |
| 13134 | }, |
| 13135 | .ACCES => return syscall.fail(error.AccessDenied), |
| 13136 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 13137 | .ALREADY => return syscall.fail(error.FastOpenAlreadyInProgress), |
| 13138 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 13139 | .MSGSIZE => return syscall.fail(error.MessageOversize), |
| 13140 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 13141 | .NOMEM => return syscall.fail(error.SystemResources), |
| 13142 | .PIPE => return syscall.fail(error.SocketUnconnected), |
| 13143 | .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), |
| 13144 | .HOSTUNREACH => return syscall.fail(error.HostUnreachable), |
| 13145 | .NETUNREACH => return syscall.fail(error.NetworkUnreachable), |
| 13146 | .NOTCONN => return syscall.fail(error.SocketUnconnected), |
| 13147 | .TIMEDOUT => return syscall.fail(error.ConnectionTimedOut), |
| 13148 | .NETDOWN => return syscall.fail(error.NetworkDown), |
| 13149 | |
| 13150 | .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. |
| 13151 | .DESTADDRREQ => |err| return syscall.errnoBug(err), // The socket is not connection-mode, and no peer address is set. |
| 13152 | .FAULT => |err| return syscall.errnoBug(err), // An invalid user space address was specified for an argument. |
| 13153 | .INVAL => |err| return syscall.errnoBug(err), // Invalid argument passed. |
| 13154 | .ISCONN => |err| return syscall.errnoBug(err), // connection-mode socket was connected already but a recipient was specified |
| 13155 | .NOTSOCK => |err| return syscall.errnoBug(err), // The file descriptor sockfd does not refer to a socket. |
| 13156 | .OPNOTSUPP => |err| return syscall.errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. |
| 13157 | |
| 13158 | else => |err| return syscall.unexpectedErrno(err), |
| 13159 | } |
| 13160 | } |
| 13161 | } |
| 13162 | |
| 13163 | fn netReceivePosix( |
| 13164 | socket_handle: net.Socket.Handle, |
| 13165 | message: *net.IncomingMessage, |
| 13166 | data_buffer: []u8, |
| 13167 | flags: net.ReceiveFlags, |
| 13168 | nonblocking: bool, |
| 13169 | ) (net.Socket.ReceiveError || error{WouldBlock})!void { |
| 13170 | // recvmmsg is useless, here's why: |
| 13171 | // * [timeout bug](https://bugzilla.kernel.org/show_bug.cgi?id=75371) |
| 13172 | // * it wants iovecs for each message but we have a better API: one data |
| 13173 | // buffer to handle all the messages. The better API cannot be lowered to |
| 13174 | // the split vectors though because reducing the buffer size might make |
| 13175 | // some messages unreceivable. |
| 13176 | const posix_flags: u32 = |
| 13177 | @as(u32, if (flags.oob) posix.MSG.OOB else 0) | |
| 13178 | @as(u32, if (flags.peek) posix.MSG.PEEK else 0) | |
| 13179 | @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) | |
| 13180 | posix.MSG.NOSIGNAL | |
| 13181 | @as(u32, if (nonblocking) posix.MSG.DONTWAIT else 0); |
| 13182 | |
| 13183 | var storage: PosixAddress = undefined; |
| 13184 | var iov: posix.iovec = .{ .base = data_buffer.ptr, .len = data_buffer.len }; |
| 13185 | var msg: posix.msghdr = .{ |
| 13186 | .name = &storage.any, |
| 13187 | .namelen = @sizeOf(PosixAddress), |
| 13188 | .iov = (&iov)[0..1], |
| 13189 | .iovlen = 1, |
| 13190 | .control = message.control.ptr, |
| 13191 | .controllen = @intCast(message.control.len), |
| 13192 | .flags = undefined, |
| 13193 | }; |
| 13194 | |
| 13195 | const syscall = try Syscall.start(); |
| 13196 | while (true) { |
| 13197 | const rc = posix.system.recvmsg(socket_handle, &msg, posix_flags); |
| 13198 | switch (posix.errno(rc)) { |
| 13199 | .SUCCESS => { |
| 13200 | syscall.finish(); |
| 13201 | const data = data_buffer[0..@intCast(rc)]; |
| 13202 | message.* = .{ |
| 13203 | .from = addressFromPosix(&storage), |
| 13204 | .data = data, |
| 13205 | .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control, |
| 13206 | .flags = .{ |
| 13207 | .eor = (msg.flags & posix.MSG.EOR) != 0, |
| 13208 | .trunc = (msg.flags & posix.MSG.TRUNC) != 0, |
| 13209 | .ctrunc = (msg.flags & posix.MSG.CTRUNC) != 0, |
| 13210 | .oob = (msg.flags & posix.MSG.OOB) != 0, |
| 13211 | .errqueue = if (@hasDecl(posix.MSG, "ERRQUEUE")) (msg.flags & posix.MSG.ERRQUEUE) != 0 else false, |
| 13212 | }, |
| 13213 | }; |
| 13214 | return; |
| 13215 | }, |
| 13216 | .INTR => { |
| 13217 | try syscall.checkCancel(); |
| 13218 | continue; |
| 13219 | }, |
| 13220 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), |
| 13221 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| 13222 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 13223 | .NOMEM => return syscall.fail(error.SystemResources), |
| 13224 | .NOTCONN => return syscall.fail(error.SocketUnconnected), |
| 13225 | .MSGSIZE => return syscall.fail(error.MessageOversize), |
| 13226 | .PIPE => return syscall.fail(error.SocketUnconnected), |
| 13227 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), |
| 13228 | .TIMEDOUT => return syscall.fail(error.ConnectionTimedOut), |
| 13229 | .NETDOWN => return syscall.fail(error.NetworkDown), |
| 13230 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 13231 | .BADF => |err| return syscall.errnoBug(err), |
| 13232 | .FAULT => |err| return syscall.errnoBug(err), |
| 13233 | .INVAL => |err| return syscall.errnoBug(err), |
| 13234 | .NOTSOCK => |err| return syscall.errnoBug(err), |
| 13235 | .OPNOTSUPP => |err| return syscall.errnoBug(err), |
| 13236 | else => |err| return syscall.unexpectedErrno(err), |
| 13237 | } |
| 13238 | } |
| 13239 | } |
| 13240 | |
| 13241 | fn netReceiveWindows( |
| 13242 | t: *Threaded, |
| 13243 | socket_handle: net.Socket.Handle, |
| 13244 | message_buffer: []net.IncomingMessage, |
| 13245 | data_buffer: []u8, |
| 13246 | flags: net.ReceiveFlags, |
| 13247 | ) struct { ?net.Socket.ReceiveError, usize } { |
| 13248 | t.netReceiveOneWindows(socket_handle, &message_buffer[0], data_buffer, flags) catch |err| |
| 13249 | return .{ err, 0 }; |
| 13250 | return .{ null, 1 }; |
| 13251 | } |
| 13252 | |
| 13253 | fn netReceiveOneWindows( |
| 13254 | t: *Threaded, |
| 13255 | socket_handle: net.Socket.Handle, |
| 13256 | message: *net.IncomingMessage, |
| 13257 | data_buffer: []u8, |
| 13258 | flags: net.ReceiveFlags, |
| 13259 | ) net.Socket.ReceiveError!void { |
| 13260 | if (!have_networking) return error.NetworkDown; |
| 13261 | _ = t; |
| 13262 | const iovecs: [1]windows.AFD.WSABUF(.@"var") = .{.{ |
| 13263 | .buf = data_buffer.ptr, |
| 13264 | .len = std.math.cast(std.os.windows.ULONG, data_buffer.len) orelse return error.MessageOversize, |
| 13265 | }}; |
| 13266 | var storage: PosixAddress = undefined; |
| 13267 | var addr_len: windows.ULONG = @sizeOf(PosixAddress); |
| 13268 | const iosb = try deviceIoControl(&.{ |
| 13269 | .file = .{ .handle = socket_handle, .flags = .{ .nonblocking = true } }, |
| 13270 | .code = windows.IOCTL.AFD.RECEIVE_DATAGRAM, |
| 13271 | .in = @ptrCast(&windows.AFD.RECV_DATAGRAM_INFO{ |
| 13272 | .BufferArray = &iovecs, |
| 13273 | .BufferCount = iovecs.len, |
| 13274 | .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true }, |
| 13275 | .TdiFlags = .{ .NORMAL = !flags.oob, .EXPEDITED = flags.oob, .PEEK = flags.peek }, |
| 13276 | .Address = &storage, |
| 13277 | .AddressLength = &addr_len, |
| 13278 | }), |
| 13279 | }); |
| 13280 | switch (iosb.u.Status) { |
| 13281 | .SUCCESS, .RECEIVE_EXPEDITED => |status| message.* = .{ |
| 13282 | .from = addressFromPosix(&storage), |
| 13283 | .data = data_buffer[0..iosb.Information], |
| 13284 | .control = &.{}, |
| 13285 | .flags = .{ |
| 13286 | .eor = false, |
| 13287 | .trunc = false, |
| 13288 | .ctrunc = false, |
| 13289 | .oob = switch (status) { |
| 13290 | else => unreachable, |
| 13291 | .SUCCESS, .RECEIVE_PARTIAL, .BUFFER_OVERFLOW => false, |
| 13292 | .RECEIVE_EXPEDITED, .RECEIVE_PARTIAL_EXPEDITED => true, |
| 13293 | }, |
| 13294 | .errqueue = false, |
| 13295 | }, |
| 13296 | }, |
| 13297 | .RECEIVE_PARTIAL, |
| 13298 | .RECEIVE_PARTIAL_EXPEDITED, |
| 13299 | => |status| return windows.unexpectedStatus(status), // TdiFlags.PARTIAL = false |
| 13300 | .CANCELLED => unreachable, |
| 13301 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 13302 | .BUFFER_OVERFLOW => return error.MessageOversize, |
| 13303 | .PORT_UNREACHABLE => return error.PortUnreachable, |
| 13304 | else => |status| return windows.unexpectedStatus(status), |
| 13305 | } |
| 13306 | } |
| 13307 | |
| 13308 | fn netWritePosix( |
| 13309 | fd: net.Socket.Handle, |
| 13310 | header: []const u8, |
| 13311 | data: []const []const u8, |
| 13312 | splat: usize, |
| 13313 | ) net.Stream.Writer.Error!usize { |
| 13314 | if (!have_networking) return error.NetworkDown; |
| 13315 | |
| 13316 | var iovecs: [max_iovecs_len]posix.iovec_const = undefined; |
| 13317 | var msg: posix.msghdr_const = .{ |
| 13318 | .name = null, |
| 13319 | .namelen = 0, |
| 13320 | .iov = &iovecs, |
| 13321 | .iovlen = 0, |
| 13322 | .control = null, |
| 13323 | .controllen = 0, |
| 13324 | .flags = 0, |
| 13325 | }; |
| 13326 | addBuf(&iovecs, &msg.iovlen, header); |
| 13327 | for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes); |
| 13328 | const pattern = data[data.len - 1]; |
| 13329 | |
| 13330 | var splat_backup_buffer: [splat_buffer_size]u8 = undefined; |
| 13331 | if (iovecs.len - msg.iovlen != 0) switch (splat) { |
| 13332 | 0 => {}, |
| 13333 | 1 => addBuf(&iovecs, &msg.iovlen, pattern), |
| 13334 | else => switch (pattern.len) { |
| 13335 | 0 => {}, |
| 13336 | 1 => { |
| 13337 | const splat_buffer = &splat_backup_buffer; |
| 13338 | const memset_len = @min(splat_buffer.len, splat); |
| 13339 | const buf = splat_buffer[0..memset_len]; |
| 13340 | @memset(buf, pattern[0]); |
| 13341 | addBuf(&iovecs, &msg.iovlen, buf); |
| 13342 | var remaining_splat = splat - buf.len; |
| 13343 | while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) { |
| 13344 | assert(buf.len == splat_buffer.len); |
| 13345 | addBuf(&iovecs, &msg.iovlen, splat_buffer); |
| 13346 | remaining_splat -= splat_buffer.len; |
| 13347 | } |
| 13348 | addBuf(&iovecs, &msg.iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); |
| 13349 | }, |
| 13350 | else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| { |
| 13351 | addBuf(&iovecs, &msg.iovlen, pattern); |
| 13352 | }, |
| 13353 | }, |
| 13354 | }; |
| 13355 | const flags = posix.MSG.NOSIGNAL; |
| 13356 | |
| 13357 | const syscall: Syscall = try .start(); |
| 13358 | while (true) { |
| 13359 | const rc = posix.system.sendmsg(fd, &msg, flags); |
| 13360 | switch (posix.errno(rc)) { |
| 13361 | .SUCCESS => { |
| 13362 | syscall.finish(); |
| 13363 | return @intCast(rc); |
| 13364 | }, |
| 13365 | .INTR => { |
| 13366 | try syscall.checkCancel(); |
| 13367 | continue; |
| 13368 | }, |
| 13369 | else => |e| { |
| 13370 | syscall.finish(); |
| 13371 | switch (e) { |
| 13372 | .ACCES => |err| return errnoBug(err), |
| 13373 | .AGAIN => |err| return errnoBug(err), |
| 13374 | .ALREADY => return error.FastOpenAlreadyInProgress, |
| 13375 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 13376 | .CONNRESET => return error.ConnectionResetByPeer, |
| 13377 | .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. |
| 13378 | .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. |
| 13379 | .INVAL => |err| return errnoBug(err), // Invalid argument passed. |
| 13380 | .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified |
| 13381 | .MSGSIZE => |err| return errnoBug(err), |
| 13382 | .NOBUFS => return error.SystemResources, |
| 13383 | .NOMEM => return error.SystemResources, |
| 13384 | .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. |
| 13385 | .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. |
| 13386 | .PIPE => return error.SocketUnconnected, |
| 13387 | .AFNOSUPPORT => return error.AddressFamilyUnsupported, |
| 13388 | .HOSTUNREACH => return error.HostUnreachable, |
| 13389 | .NETUNREACH => return error.NetworkUnreachable, |
| 13390 | .NOTCONN => return error.SocketUnconnected, |
| 13391 | .TIMEDOUT => return error.ConnectionTimedOut, |
| 13392 | .NETDOWN => return error.NetworkDown, |
| 13393 | else => |err| return posix.unexpectedErrno(err), |
| 13394 | } |
| 13395 | }, |
| 13396 | } |
| 13397 | } |
| 13398 | } |
| 13399 | |
| 13400 | fn netWriteWindows( |
| 13401 | handle: net.Socket.Handle, |
| 13402 | header: []const u8, |
| 13403 | data: []const []const u8, |
| 13404 | splat: usize, |
| 13405 | ) net.Stream.Writer.Error!usize { |
| 13406 | if (!have_networking) return error.NetworkDown; |
| 13407 | |
| 13408 | var iovecs: [max_iovecs_len]windows.AFD.WSABUF(.@"const") = undefined; |
| 13409 | var len: u32 = 0; |
| 13410 | addAfdBuf(.@"const", &iovecs, &len, header); |
| 13411 | for (data[0 .. data.len - 1]) |bytes| addAfdBuf(.@"const", &iovecs, &len, bytes); |
| 13412 | const pattern = data[data.len - 1]; |
| 13413 | var backup_buffer: [64]u8 = undefined; |
| 13414 | if (iovecs.len - len != 0) switch (splat) { |
| 13415 | 0 => {}, |
| 13416 | 1 => addAfdBuf(.@"const", &iovecs, &len, pattern), |
| 13417 | else => switch (pattern.len) { |
| 13418 | 0 => {}, |
| 13419 | 1 => { |
| 13420 | const splat_buffer = &backup_buffer; |
| 13421 | const memset_len = @min(splat_buffer.len, splat); |
| 13422 | const buf = splat_buffer[0..memset_len]; |
| 13423 | @memset(buf, pattern[0]); |
| 13424 | addAfdBuf(.@"const", &iovecs, &len, buf); |
| 13425 | var remaining_splat = splat - buf.len; |
| 13426 | while (remaining_splat > splat_buffer.len and len < iovecs.len) { |
| 13427 | addAfdBuf(.@"const", &iovecs, &len, splat_buffer); |
| 13428 | remaining_splat -= splat_buffer.len; |
| 13429 | } |
| 13430 | addAfdBuf(.@"const", &iovecs, &len, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]); |
| 13431 | }, |
| 13432 | else => for (0..@min(splat, iovecs.len - len)) |_| { |
| 13433 | addAfdBuf(.@"const", &iovecs, &len, pattern); |
| 13434 | }, |
| 13435 | }, |
| 13436 | }; |
| 13437 | |
| 13438 | const iosb = try deviceIoControl(&.{ |
| 13439 | .file = .{ .handle = handle, .flags = .{ .nonblocking = true } }, |
| 13440 | .code = windows.IOCTL.AFD.SEND, |
| 13441 | .in = @ptrCast(&windows.AFD.SEND_INFO{ |
| 13442 | .BufferArray = &iovecs, |
| 13443 | .BufferCount = len, |
| 13444 | .AfdFlags = .{ .NO_FAST_IO = true, .OVERLAPPED = true }, |
| 13445 | .TdiFlags = .{}, |
| 13446 | }), |
| 13447 | }); |
| 13448 | switch (iosb.u.Status) { |
| 13449 | .SUCCESS => return iosb.Information, |
| 13450 | .CANCELLED => unreachable, |
| 13451 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 13452 | .CONNECTION_RESET, .REMOTE_DISCONNECT => return error.ConnectionResetByPeer, |
| 13453 | .IO_TIMEOUT => return error.ConnectionTimedOut, |
| 13454 | else => |status| return windows.unexpectedStatus(status), |
| 13455 | } |
| 13456 | } |
| 13457 | |
| 13458 | fn addAfdBuf( |
| 13459 | comptime mutability: windows.AFD.Mutability, |
| 13460 | iovecs: []windows.AFD.WSABUF(mutability), |
| 13461 | len: *u32, |
| 13462 | bytes: switch (mutability) { |
| 13463 | .@"const" => []const u8, |
| 13464 | .@"var" => []u8, |
| 13465 | }, |
| 13466 | ) void { |
| 13467 | if (bytes.len == 0) return; |
| 13468 | const cap = std.math.maxInt(u32); |
| 13469 | var remaining = bytes; |
| 13470 | while (remaining.len > cap) { |
| 13471 | if (iovecs.len - len.* == 0) return; |
| 13472 | iovecs[len.*] = .{ .buf = remaining.ptr, .len = cap }; |
| 13473 | len.* += 1; |
| 13474 | remaining = remaining[cap..]; |
| 13475 | } else { |
| 13476 | @branchHint(.likely); |
| 13477 | if (iovecs.len - len.* == 0) return; |
| 13478 | iovecs[len.*] = .{ .buf = remaining.ptr, .len = @intCast(remaining.len) }; |
| 13479 | len.* += 1; |
| 13480 | } |
| 13481 | } |
| 13482 | |
| 13483 | /// This is either usize or u32. Since, either is fine, let's use the same |
| 13484 | /// `addBuf` function for both writing to a file and sending network messages. |
| 13485 | const iovlen_t = switch (native_os) { |
| 13486 | .wasi => u32, |
| 13487 | else => @FieldType(posix.msghdr_const, "iovlen"), |
| 13488 | }; |
| 13489 | |
| 13490 | fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void { |
| 13491 | // OS checks ptr addr before length so zero length vectors must be omitted. |
| 13492 | if (bytes.len == 0) return; |
| 13493 | if (v.len - i.* == 0) return; |
| 13494 | v[i.*] = .{ .base = bytes.ptr, .len = bytes.len }; |
| 13495 | i.* += 1; |
| 13496 | } |
| 13497 | |
| 13498 | fn netClose(userdata: ?*anyopaque, sockets: []const net.Socket) void { |
| 13499 | if (!have_networking) unreachable; |
| 13500 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13501 | _ = t; |
| 13502 | for (sockets) |socket| switch (native_os) { |
| 13503 | .windows => windows.CloseHandle(socket.handle), |
| 13504 | else => closeFd(socket.handle), |
| 13505 | }; |
| 13506 | } |
| 13507 | |
| 13508 | fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { |
| 13509 | if (!have_networking) return error.NetworkDown; |
| 13510 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13511 | _ = t; |
| 13512 | |
| 13513 | const posix_how: i32 = switch (how) { |
| 13514 | .recv => posix.SHUT.RD, |
| 13515 | .send => posix.SHUT.WR, |
| 13516 | .both => posix.SHUT.RDWR, |
| 13517 | }; |
| 13518 | |
| 13519 | const syscall: Syscall = try .start(); |
| 13520 | while (true) { |
| 13521 | switch (posix.errno(posix.system.shutdown(handle, posix_how))) { |
| 13522 | .SUCCESS => return syscall.finish(), |
| 13523 | .INTR => { |
| 13524 | try syscall.checkCancel(); |
| 13525 | continue; |
| 13526 | }, |
| 13527 | else => |e| { |
| 13528 | syscall.finish(); |
| 13529 | switch (e) { |
| 13530 | .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err), |
| 13531 | .NOTCONN => return error.SocketUnconnected, |
| 13532 | .NOBUFS => return error.SystemResources, |
| 13533 | else => |err| return posix.unexpectedErrno(err), |
| 13534 | } |
| 13535 | }, |
| 13536 | } |
| 13537 | } |
| 13538 | } |
| 13539 | |
| 13540 | fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { |
| 13541 | if (!have_networking) return error.NetworkDown; |
| 13542 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13543 | _ = t; |
| 13544 | |
| 13545 | // shutdown does not support apcs at all |
| 13546 | switch ((try deviceIoControl(&.{ |
| 13547 | .file = .{ .handle = handle, .flags = .{ .nonblocking = false } }, |
| 13548 | .code = windows.IOCTL.AFD.PARTIAL_DISCONNECT, |
| 13549 | .in = @ptrCast(&windows.AFD.PARTIAL_DISCONNECT_INFO{ |
| 13550 | .DisconnectMode = .{ .SEND = how != .recv, .RECEIVE = how != .send }, |
| 13551 | .Timeout = -1, |
| 13552 | }), |
| 13553 | })).u.Status) { |
| 13554 | .SUCCESS => {}, |
| 13555 | .CANCELLED => unreachable, |
| 13556 | .INSUFFICIENT_RESOURCES => return error.SystemResources, |
| 13557 | else => |status| return windows.unexpectedStatus(status), |
| 13558 | } |
| 13559 | } |
| 13560 | |
| 13561 | fn netInterfaceNameResolve( |
| 13562 | userdata: ?*anyopaque, |
| 13563 | name: *const net.Interface.Name, |
| 13564 | ) net.Interface.Name.ResolveError!net.Interface { |
| 13565 | if (!have_networking) return error.InterfaceNotFound; |
| 13566 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13567 | |
| 13568 | if (native_os == .linux) { |
| 13569 | const sock_fd = openSocketPosix(posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) { |
| 13570 | error.ProcessFdQuotaExceeded => return error.SystemResources, |
| 13571 | error.SystemFdQuotaExceeded => return error.SystemResources, |
| 13572 | error.AddressFamilyUnsupported => return error.Unexpected, |
| 13573 | error.ProtocolUnsupportedBySystem => return error.Unexpected, |
| 13574 | error.ProtocolUnsupportedByAddressFamily => return error.Unexpected, |
| 13575 | error.SocketModeUnsupported => return error.Unexpected, |
| 13576 | error.OptionUnsupported => return error.Unexpected, |
| 13577 | else => |e| return e, |
| 13578 | }; |
| 13579 | defer closeFd(sock_fd); |
| 13580 | |
| 13581 | var ifr: posix.ifreq = .{ |
| 13582 | .ifrn = .{ .name = @bitCast(name.bytes) }, |
| 13583 | .ifru = undefined, |
| 13584 | }; |
| 13585 | |
| 13586 | const syscall: Syscall = try .start(); |
| 13587 | while (true) switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { |
| 13588 | .SUCCESS => { |
| 13589 | syscall.finish(); |
| 13590 | return .{ .index = @bitCast(ifr.ifru.ivalue) }; |
| 13591 | }, |
| 13592 | .INTR => { |
| 13593 | try syscall.checkCancel(); |
| 13594 | continue; |
| 13595 | }, |
| 13596 | .NODEV => return syscall.fail(error.InterfaceNotFound), |
| 13597 | else => |err| return syscall.unexpectedErrno(err), |
| 13598 | }; |
| 13599 | } |
| 13600 | |
| 13601 | if (is_windows) { |
| 13602 | var ConvertInterfaceNameToLuidW = t.dl.ConvertInterfaceNameToLuidW.load(.acquire); |
| 13603 | var ConvertInterfaceLuidToIndex = t.dl.ConvertInterfaceLuidToIndex.load(.acquire); |
| 13604 | if (ConvertInterfaceNameToLuidW == null or ConvertInterfaceLuidToIndex == null) { |
| 13605 | const iphlpapi_dll = t.dl.iphlpapi_dll.load(.acquire) orelse iphlpapi_dll: { |
| 13606 | try Thread.checkCancel(); |
| 13607 | var iphlpapi_dll: *anyopaque = undefined; |
| 13608 | switch (windows.ntdll.LdrLoadDll(null, null, &.init( |
| 13609 | &.{ 'I', 'P', 'H', 'L', 'P', 'A', 'P', 'I', '.', 'D', 'L', 'L' }, |
| 13610 | ), &iphlpapi_dll)) { |
| 13611 | .SUCCESS => {}, |
| 13612 | .DLL_NOT_FOUND => return error.Unexpected, |
| 13613 | else => |status| return windows.unexpectedStatus(status), |
| 13614 | } |
| 13615 | const handle = t.dl.iphlpapi_dll.cmpxchgStrong(null, iphlpapi_dll, .release, .monotonic) orelse |
| 13616 | break :iphlpapi_dll iphlpapi_dll; |
| 13617 | switch (windows.ntdll.LdrUnloadDll(iphlpapi_dll)) { |
| 13618 | .SUCCESS => break :iphlpapi_dll handle.?, |
| 13619 | else => |status| return windows.unexpectedStatus(status), |
| 13620 | } |
| 13621 | }; |
| 13622 | switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init( |
| 13623 | &.{ |
| 13624 | 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e', |
| 13625 | 'N', 'a', 'm', 'e', 'T', 'o', 'L', 'u', 'i', 'd', 'W', |
| 13626 | }, |
| 13627 | ), 0, @ptrCast(&ConvertInterfaceNameToLuidW))) { |
| 13628 | .SUCCESS => t.dl.ConvertInterfaceNameToLuidW.store(ConvertInterfaceNameToLuidW, .release), |
| 13629 | else => |status| return windows.unexpectedStatus(status), |
| 13630 | } |
| 13631 | switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init( |
| 13632 | &.{ |
| 13633 | 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e', |
| 13634 | 'L', 'u', 'i', 'd', 'T', 'o', 'I', 'n', 'd', 'e', 'x', |
| 13635 | }, |
| 13636 | ), 0, @ptrCast(&ConvertInterfaceLuidToIndex))) { |
| 13637 | .SUCCESS => t.dl.ConvertInterfaceLuidToIndex.store(ConvertInterfaceLuidToIndex, .release), |
| 13638 | else => |status| return windows.unexpectedStatus(status), |
| 13639 | } |
| 13640 | } |
| 13641 | try Thread.checkCancel(); |
| 13642 | var name_w: [net.Interface.Name.max_len:0]windows.WCHAR = undefined; |
| 13643 | name_w[ |
| 13644 | std.unicode.wtf8ToWtf16Le(&name_w, name.toSlice()) catch |err| switch (err) { |
| 13645 | error.InvalidWtf8 => return error.InterfaceNotFound, |
| 13646 | } |
| 13647 | ] = 0; |
| 13648 | var luid: windows.NET.LUID = undefined; |
| 13649 | switch (ConvertInterfaceNameToLuidW.?(&name_w, &luid)) { |
| 13650 | .SUCCESS => {}, |
| 13651 | .INVALID_NAME => return error.InterfaceNotFound, |
| 13652 | .INVALID_PARAMETER => unreachable, |
| 13653 | else => |err| return windows.unexpectedError(err), |
| 13654 | } |
| 13655 | var index: windows.NET.IFINDEX = undefined; |
| 13656 | switch (ConvertInterfaceLuidToIndex.?(&luid, &index)) { |
| 13657 | .SUCCESS => {}, |
| 13658 | .INVALID_PARAMETER => unreachable, |
| 13659 | else => |err| return windows.unexpectedError(err), |
| 13660 | } |
| 13661 | return .{ .index = @backingInt(index) }; |
| 13662 | } |
| 13663 | |
| 13664 | if (builtin.link_libc) { |
| 13665 | try Thread.checkCancel(); |
| 13666 | const index = std.c.if_nametoindex(&name.bytes); |
| 13667 | if (index == 0) return error.InterfaceNotFound; |
| 13668 | return .{ .index = @bitCast(index) }; |
| 13669 | } |
| 13670 | |
| 13671 | @panic("unimplemented"); |
| 13672 | } |
| 13673 | |
| 13674 | fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { |
| 13675 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13676 | |
| 13677 | if (native_os == .linux) { |
| 13678 | try Thread.checkCancel(); |
| 13679 | @panic("TODO implement netInterfaceName for linux"); |
| 13680 | } |
| 13681 | |
| 13682 | if (is_windows) { |
| 13683 | var ConvertInterfaceIndexToLuid = t.dl.ConvertInterfaceIndexToLuid.load(.acquire); |
| 13684 | var ConvertInterfaceLuidToNameW = t.dl.ConvertInterfaceLuidToNameW.load(.acquire); |
| 13685 | if (ConvertInterfaceIndexToLuid == null or ConvertInterfaceLuidToNameW == null) { |
| 13686 | const iphlpapi_dll = t.dl.iphlpapi_dll.load(.acquire) orelse iphlpapi_dll: { |
| 13687 | try Thread.checkCancel(); |
| 13688 | var iphlpapi_dll: *anyopaque = undefined; |
| 13689 | switch (windows.ntdll.LdrLoadDll(null, null, &.init( |
| 13690 | &.{ 'I', 'P', 'H', 'L', 'P', 'A', 'P', 'I', '.', 'D', 'L', 'L' }, |
| 13691 | ), &iphlpapi_dll)) { |
| 13692 | .SUCCESS => {}, |
| 13693 | .DLL_NOT_FOUND => return error.Unexpected, |
| 13694 | else => |status| return windows.unexpectedStatus(status), |
| 13695 | } |
| 13696 | const handle = t.dl.iphlpapi_dll.cmpxchgStrong(null, iphlpapi_dll, .release, .monotonic) orelse |
| 13697 | break :iphlpapi_dll iphlpapi_dll; |
| 13698 | switch (windows.ntdll.LdrUnloadDll(iphlpapi_dll)) { |
| 13699 | .SUCCESS => break :iphlpapi_dll handle.?, |
| 13700 | else => |status| return windows.unexpectedStatus(status), |
| 13701 | } |
| 13702 | }; |
| 13703 | switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init( |
| 13704 | &.{ |
| 13705 | 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e', |
| 13706 | 'I', 'n', 'd', 'e', 'x', 'T', 'o', 'L', 'u', 'i', 'd', |
| 13707 | }, |
| 13708 | ), 0, @ptrCast(&ConvertInterfaceIndexToLuid))) { |
| 13709 | .SUCCESS => t.dl.ConvertInterfaceIndexToLuid.store(ConvertInterfaceIndexToLuid, .release), |
| 13710 | else => |status| return windows.unexpectedStatus(status), |
| 13711 | } |
| 13712 | switch (windows.ntdll.LdrGetProcedureAddress(iphlpapi_dll, &.init( |
| 13713 | &.{ |
| 13714 | 'C', 'o', 'n', 'v', 'e', 'r', 't', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e', |
| 13715 | 'L', 'u', 'i', 'd', 'T', 'o', 'N', 'a', 'm', 'e', 'W', |
| 13716 | }, |
| 13717 | ), 0, @ptrCast(&ConvertInterfaceLuidToNameW))) { |
| 13718 | .SUCCESS => t.dl.ConvertInterfaceLuidToNameW.store(ConvertInterfaceLuidToNameW, .release), |
| 13719 | else => |status| return windows.unexpectedStatus(status), |
| 13720 | } |
| 13721 | } |
| 13722 | try Thread.checkCancel(); |
| 13723 | var luid: windows.NET.LUID = undefined; |
| 13724 | switch (ConvertInterfaceIndexToLuid.?(@fromBackingInt(@intCast(interface.index)), &luid)) { |
| 13725 | .SUCCESS => {}, |
| 13726 | .FILE_NOT_FOUND => return error.InterfaceNotFound, |
| 13727 | .INVALID_PARAMETER => unreachable, |
| 13728 | else => |err| return windows.unexpectedError(err), |
| 13729 | } |
| 13730 | var name_w: [net.Interface.Name.max_len:0]windows.WCHAR = undefined; |
| 13731 | switch (ConvertInterfaceLuidToNameW.?(&luid, &name_w, name_w.len)) { |
| 13732 | .SUCCESS => {}, |
| 13733 | .INVALID_PARAMETER => unreachable, |
| 13734 | .NOT_ENOUGH_MEMORY => return error.NameTooLong, |
| 13735 | else => |err| return windows.unexpectedError(err), |
| 13736 | } |
| 13737 | var name: [3 * net.Interface.Name.max_len]u8 = undefined; |
| 13738 | return .fromSlice(name[0..std.unicode.wtf16LeToWtf8(&name, std.mem.sliceTo(&name_w, 0))]); |
| 13739 | } |
| 13740 | |
| 13741 | if (builtin.link_libc) { |
| 13742 | try Thread.checkCancel(); |
| 13743 | @panic("TODO implement netInterfaceName for libc"); |
| 13744 | } |
| 13745 | |
| 13746 | @panic("unimplemented"); |
| 13747 | } |
| 13748 | |
| 13749 | fn netLookup( |
| 13750 | userdata: ?*anyopaque, |
| 13751 | host_name: HostName, |
| 13752 | resolved: *Io.Queue(HostName.LookupResult), |
| 13753 | options: HostName.LookupOptions, |
| 13754 | ) net.HostName.LookupError!void { |
| 13755 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13756 | defer resolved.close(io(t)); |
| 13757 | t.netLookupFallible(host_name, resolved, options) catch |err| switch (err) { |
| 13758 | error.Closed => unreachable, // `resolved` must not be closed until `netLookup` returns |
| 13759 | else => |e| return e, |
| 13760 | }; |
| 13761 | } |
| 13762 | |
| 13763 | fn netLookupFallible( |
| 13764 | t: *Threaded, |
| 13765 | host_name: HostName, |
| 13766 | resolved: *Io.Queue(HostName.LookupResult), |
| 13767 | options: HostName.LookupOptions, |
| 13768 | ) (net.HostName.LookupError || Io.QueueClosedError)!void { |
| 13769 | if (!have_networking) return error.NetworkDown; |
| 13770 | |
| 13771 | const t_io = t.io(); |
| 13772 | const name = host_name.bytes; |
| 13773 | assert(name.len <= HostName.max_len); |
| 13774 | |
| 13775 | // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics. |
| 13776 | // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it. |
| 13777 | const is_glibc = builtin.link_libc and builtin.target.isGnuLibC(); |
| 13778 | |
| 13779 | if (is_glibc) { |
| 13780 | // TODO use getaddrinfo_a / gai_cancel |
| 13781 | } |
| 13782 | |
| 13783 | // On Linux, we have to go through glibc because of the Name Service Switch feature. |
| 13784 | const non_glibc_linux = native_os == .linux and !is_glibc; |
| 13785 | if (non_glibc_linux or is_windows) { |
| 13786 | if (IpAddress.parseIp6(name, options.port)) |addr| { |
| 13787 | if (options.family == .ip4) return error.UnknownHostName; |
| 13788 | if (copyCanon(options.canonical_name_buffer, name)) |canon| { |
| 13789 | try resolved.putAll(t_io, &.{ |
| 13790 | .{ .address = addr }, |
| 13791 | .{ .canonical_name = canon }, |
| 13792 | }); |
| 13793 | } else { |
| 13794 | try resolved.putOne(t_io, .{ .address = addr }); |
| 13795 | } |
| 13796 | return; |
| 13797 | } else |_| {} |
| 13798 | |
| 13799 | if (IpAddress.parseIp4(name, options.port)) |addr| { |
| 13800 | if (options.family == .ip6) return error.UnknownHostName; |
| 13801 | if (copyCanon(options.canonical_name_buffer, name)) |canon| { |
| 13802 | try resolved.putAll(t_io, &.{ |
| 13803 | .{ .address = addr }, |
| 13804 | .{ .canonical_name = canon }, |
| 13805 | }); |
| 13806 | } else { |
| 13807 | try resolved.putOne(t_io, .{ .address = addr }); |
| 13808 | } |
| 13809 | return; |
| 13810 | } else |_| {} |
| 13811 | |
| 13812 | if (t.lookupHosts(host_name, resolved, options)) return else |err| switch (err) { |
| 13813 | error.UnknownHostName => {}, |
| 13814 | else => |e| return e, |
| 13815 | } |
| 13816 | |
| 13817 | // RFC 6761 Section 6.3.3 |
| 13818 | // Name resolution APIs and libraries SHOULD recognize |
| 13819 | // localhost names as special and SHOULD always return the IP |
| 13820 | // loopback address for address queries and negative responses |
| 13821 | // for all other query types. |
| 13822 | |
| 13823 | // Check for equal to "localhost(.)" or ends in ".localhost(.)" |
| 13824 | const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; |
| 13825 | if (std.mem.endsWith(u8, name, localhost) and |
| 13826 | (name.len == localhost.len or name[name.len - localhost.len - 1] == '.')) |
| 13827 | { |
| 13828 | var results_buffer: [3]HostName.LookupResult = undefined; |
| 13829 | var results_index: usize = 0; |
| 13830 | if (options.family != .ip4) { |
| 13831 | results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } }; |
| 13832 | results_index += 1; |
| 13833 | } |
| 13834 | if (options.family != .ip6) { |
| 13835 | results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } }; |
| 13836 | results_index += 1; |
| 13837 | } |
| 13838 | if (options.canonical_name_buffer) |buf| { |
| 13839 | const canon_name = "localhost"; |
| 13840 | const canon_name_dest = buf[0..canon_name.len]; |
| 13841 | canon_name_dest.* = canon_name.*; |
| 13842 | results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } }; |
| 13843 | results_index += 1; |
| 13844 | } |
| 13845 | try resolved.putAll(t_io, results_buffer[0..results_index]); |
| 13846 | return; |
| 13847 | } |
| 13848 | |
| 13849 | if (native_os == .linux) return t.lookupDnsSearch(host_name, resolved, options); |
| 13850 | |
| 13851 | comptime assert(is_windows); |
| 13852 | var DnsQueryEx = t.dl.DnsQueryEx.load(.acquire); |
| 13853 | //var DnsCancelQuery = t.dl.DnsCancelQuery.load(.acquire); |
| 13854 | var DnsFree = t.dl.DnsFree.load(.acquire); |
| 13855 | if (DnsQueryEx == null or |
| 13856 | //DnsCancelQuery == null or |
| 13857 | DnsFree == null) |
| 13858 | { |
| 13859 | const dnsapi_dll = t.dl.dnsapi_dll.load(.acquire) orelse dnsapi_dll: { |
| 13860 | try Thread.checkCancel(); |
| 13861 | var dnsapi_dll: *anyopaque = undefined; |
| 13862 | switch (windows.ntdll.LdrLoadDll(null, null, &.init( |
| 13863 | &.{ 'd', 'n', 's', 'a', 'p', 'i', '.', 'd', 'l', 'l' }, |
| 13864 | ), &dnsapi_dll)) { |
| 13865 | .SUCCESS => {}, |
| 13866 | .DLL_NOT_FOUND => return error.Unexpected, |
| 13867 | else => |status| return windows.unexpectedStatus(status), |
| 13868 | } |
| 13869 | const handle = t.dl.dnsapi_dll.cmpxchgStrong(null, dnsapi_dll, .release, .monotonic) orelse |
| 13870 | break :dnsapi_dll dnsapi_dll; |
| 13871 | switch (windows.ntdll.LdrUnloadDll(dnsapi_dll)) { |
| 13872 | .SUCCESS => break :dnsapi_dll handle.?, |
| 13873 | else => |status| return windows.unexpectedStatus(status), |
| 13874 | } |
| 13875 | }; |
| 13876 | switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init( |
| 13877 | &.{ 'D', 'n', 's', 'Q', 'u', 'e', 'r', 'y', 'E', 'x' }, |
| 13878 | ), 0, @ptrCast(&DnsQueryEx))) { |
| 13879 | .SUCCESS => t.dl.DnsQueryEx.store(DnsQueryEx, .release), |
| 13880 | else => |status| return windows.unexpectedStatus(status), |
| 13881 | } |
| 13882 | //switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init( |
| 13883 | // &.{ 'D', 'n', 's', 'C', 'a', 'n', 'c', 'e', 'l', 'Q', 'u', 'e', 'r', 'y' }, |
| 13884 | //), 0, @ptrCast(&DnsCancelQuery))) { |
| 13885 | // .SUCCESS => t.dl.DnsCancelQuery.store(DnsCancelQuery, .release), |
| 13886 | // else => |status| return windows.unexpectedStatus(status), |
| 13887 | //} |
| 13888 | switch (windows.ntdll.LdrGetProcedureAddress(dnsapi_dll, &.init( |
| 13889 | &.{ 'D', 'n', 's', 'F', 'r', 'e', 'e' }, |
| 13890 | ), 0, @ptrCast(&DnsFree))) { |
| 13891 | .SUCCESS => t.dl.DnsFree.store(DnsFree, .release), |
| 13892 | else => |status| return windows.unexpectedStatus(status), |
| 13893 | } |
| 13894 | } |
| 13895 | try Thread.checkCancel(); |
| 13896 | const current_thread = Thread.current; |
| 13897 | var lookup_dns: LookupDnsWindows = .{ |
| 13898 | .threaded = t, |
| 13899 | .thread = if (current_thread) |thread| thread.handle else undefined, |
| 13900 | .resolved = resolved, |
| 13901 | .options = options, |
| 13902 | .results = .{ |
| 13903 | .Version = 1, |
| 13904 | .QueryStatus = undefined, |
| 13905 | .QueryOptions = undefined, |
| 13906 | .pQueryRecords = undefined, |
| 13907 | .Reserved = undefined, |
| 13908 | }, |
| 13909 | .done = false, |
| 13910 | }; |
| 13911 | var host_name_w: [HostName.max_len:0]windows.WCHAR = undefined; |
| 13912 | host_name_w[ |
| 13913 | std.unicode.wtf8ToWtf16Le(&host_name_w, name) catch |err| switch (err) { |
| 13914 | error.InvalidWtf8 => return error.UnknownHostName, |
| 13915 | } |
| 13916 | ] = 0; |
| 13917 | //var cancel_token: windows.DNS.QUERY.CANCEL = undefined; |
| 13918 | // Workaround various bugs by attempting a synchronous non-wire query first |
| 13919 | switch (DnsQueryEx.?(&.{ |
| 13920 | .Version = 1, |
| 13921 | .QueryName = &host_name_w, |
| 13922 | .QueryType = if (options.family == .ip4) .A else .AAAA, |
| 13923 | .QueryOptions = .{ |
| 13924 | .NO_WIRE_QUERY = true, |
| 13925 | .NO_HOSTS_FILE = true, // handled above |
| 13926 | .ADDRCONFIG = true, |
| 13927 | .DUAL_ADDR = options.family == null, |
| 13928 | }, |
| 13929 | }, &lookup_dns.results, null)) { |
| 13930 | .SUCCESS => try lookup_dns.completedFallible(), |
| 13931 | // We must wait for the APC routine. |
| 13932 | .DNS_REQUEST_PENDING => unreachable, // `pQueryCompletionCallback` was `null` |
| 13933 | .DNS_ERROR_RECORD_DOES_NOT_EXIST => switch (DnsQueryEx.?(&.{ |
| 13934 | .Version = 1, |
| 13935 | .QueryName = &host_name_w, |
| 13936 | .QueryType = if (options.family == .ip4) .A else .AAAA, |
| 13937 | .QueryOptions = .{ |
| 13938 | .NO_HOSTS_FILE = true, // handled above |
| 13939 | .ADDRCONFIG = true, |
| 13940 | .DUAL_ADDR = options.family == null, |
| 13941 | .MULTICAST_WAIT = true, |
| 13942 | }, |
| 13943 | .pQueryCompletionCallback = if (current_thread) |_| &LookupDnsWindows.completed else null, |
| 13944 | }, &lookup_dns.results, |
| 13945 | //&cancel_token, |
| 13946 | null)) { |
| 13947 | .SUCCESS => try lookup_dns.completedFallible(), |
| 13948 | // We must wait for the APC routine. |
| 13949 | .DNS_REQUEST_PENDING => { |
| 13950 | assert(current_thread != null); // `pQueryCompletionCallback` was `null` |
| 13951 | while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) { |
| 13952 | // Once we get here we must not return from the function until the |
| 13953 | // operation completes, thereby releasing references to `host_name_w`, |
| 13954 | // `lookup_dns.results`, and `cancel_token`. |
| 13955 | const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { |
| 13956 | error.Canceled => |e| { |
| 13957 | //_ = DnsCancelQuery.?(&cancel_token); |
| 13958 | while (!@atomicLoad(bool, &lookup_dns.done, .acquire)) waitForApcOrAlert(); |
| 13959 | return e; |
| 13960 | }, |
| 13961 | }; |
| 13962 | waitForApcOrAlert(); |
| 13963 | alertable_syscall.finish(); |
| 13964 | } |
| 13965 | }, |
| 13966 | else => |status| lookup_dns.results.QueryStatus = status, |
| 13967 | }, |
| 13968 | else => |status| lookup_dns.results.QueryStatus = status, |
| 13969 | } |
| 13970 | switch (lookup_dns.results.QueryStatus) { |
| 13971 | .SUCCESS => return, |
| 13972 | .DNS_REQUEST_PENDING => unreachable, // already handled |
| 13973 | .INVALID_NAME, |
| 13974 | .DNS_ERROR_RCODE_NAME_ERROR, |
| 13975 | .DNS_INFO_NO_RECORDS, |
| 13976 | .DNS_ERROR_INVALID_NAME_CHAR, |
| 13977 | .DNS_ERROR_RECORD_DOES_NOT_EXIST, |
| 13978 | => return error.UnknownHostName, |
| 13979 | .TIMEOUT => return error.NameServerFailure, |
| 13980 | else => |err| return windows.unexpectedError(err), |
| 13981 | } |
| 13982 | } |
| 13983 | |
| 13984 | if (native_os == .openbsd) { |
| 13985 | // TODO use getaddrinfo_async / asr_abort |
| 13986 | } |
| 13987 | |
| 13988 | if (native_os == .freebsd) { |
| 13989 | // TODO use dnsres_getaddrinfo |
| 13990 | } |
| 13991 | |
| 13992 | if (is_darwin) { |
| 13993 | // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution |
| 13994 | } |
| 13995 | |
| 13996 | if (builtin.link_libc) { |
| 13997 | // This operating system lacks a way to resolve asynchronously. We are |
| 13998 | // stuck with getaddrinfo. |
| 13999 | var name_buffer: [HostName.max_len:0]u8 = undefined; |
| 14000 | @memcpy(name_buffer[0..name.len], name); |
| 14001 | name_buffer[name.len] = 0; |
| 14002 | const name_c = name_buffer[0..name.len :0]; |
| 14003 | |
| 14004 | var port_buffer: [8]u8 = undefined; |
| 14005 | const port_c = std.mem.printSentinel(&port_buffer, "{d}", .{options.port}, 0) catch unreachable; |
| 14006 | |
| 14007 | const family: i32 = if (options.family) |f| switch (f) { |
| 14008 | .ip4 => posix.AF.INET, |
| 14009 | .ip6 => posix.AF.INET6, |
| 14010 | } else posix.AF.UNSPEC; |
| 14011 | |
| 14012 | const hints: posix.addrinfo = .{ |
| 14013 | .flags = .{ .CANONNAME = options.canonical_name_buffer != null, .NUMERICSERV = true }, |
| 14014 | .family = family, |
| 14015 | .socktype = posix.SOCK.STREAM, |
| 14016 | .protocol = posix.IPPROTO.TCP, |
| 14017 | .canonname = null, |
| 14018 | .addr = null, |
| 14019 | .addrlen = 0, |
| 14020 | .next = null, |
| 14021 | }; |
| 14022 | var res: ?*posix.addrinfo = null; |
| 14023 | const syscall: Syscall = try .start(); |
| 14024 | while (true) { |
| 14025 | switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) { |
| 14026 | @as(posix.system.EAI, @fromBackingInt(@intCast(0))) => { |
| 14027 | syscall.finish(); |
| 14028 | break; |
| 14029 | }, |
| 14030 | .SYSTEM => switch (posix.errno(-1)) { |
| 14031 | .INTR => { |
| 14032 | try syscall.checkCancel(); |
| 14033 | continue; |
| 14034 | }, |
| 14035 | else => |e| { |
| 14036 | syscall.finish(); |
| 14037 | return posix.unexpectedErrno(e); |
| 14038 | }, |
| 14039 | }, |
| 14040 | else => |e| { |
| 14041 | syscall.finish(); |
| 14042 | switch (e) { |
| 14043 | .ADDRFAMILY => return error.AddressFamilyUnsupported, |
| 14044 | .AGAIN => return error.NameServerFailure, |
| 14045 | .FAIL => return error.NameServerFailure, |
| 14046 | .FAMILY => return error.AddressFamilyUnsupported, |
| 14047 | .MEMORY => return error.SystemResources, |
| 14048 | .NODATA => return error.UnknownHostName, |
| 14049 | .NONAME => return error.UnknownHostName, |
| 14050 | else => return error.Unexpected, |
| 14051 | } |
| 14052 | }, |
| 14053 | } |
| 14054 | } |
| 14055 | defer if (res) |some| posix.system.freeaddrinfo(some); |
| 14056 | |
| 14057 | var it = res; |
| 14058 | var canon_name: ?[*:0]const u8 = null; |
| 14059 | while (it) |info| : (it = info.next) { |
| 14060 | const addr = info.addr orelse continue; |
| 14061 | try resolved.putOne(t_io, .{ .address = addressFromPosix(@alignCast(@fieldParentPtr("any", addr))) }); |
| 14062 | |
| 14063 | if (info.canonname) |n| { |
| 14064 | if (canon_name == null) { |
| 14065 | canon_name = n; |
| 14066 | } |
| 14067 | } |
| 14068 | } |
| 14069 | if (canon_name) |n| { |
| 14070 | if (copyCanon(options.canonical_name_buffer, std.mem.sliceTo(n, 0))) |canon| { |
| 14071 | try resolved.putOne(t_io, .{ .canonical_name = canon }); |
| 14072 | } |
| 14073 | } |
| 14074 | return; |
| 14075 | } |
| 14076 | |
| 14077 | return error.OptionUnsupported; |
| 14078 | } |
| 14079 | |
| 14080 | fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr { |
| 14081 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 14082 | const current_thread_id = Thread.currentId(); |
| 14083 | |
| 14084 | if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) { |
| 14085 | mutexLock(&t.stderr_mutex); |
| 14086 | assert(t.stderr_mutex_lock_count == 0); |
| 14087 | @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered); |
| 14088 | } |
| 14089 | t.stderr_mutex_lock_count += 1; |
| 14090 | |
| 14091 | return initLockedStderr(t, terminal_mode); |
| 14092 | } |
| 14093 | |
| 14094 | fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr { |
| 14095 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 14096 | const current_thread_id = Thread.currentId(); |
| 14097 | |
| 14098 | if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) { |
| 14099 | if (!t.stderr_mutex.tryLock()) return null; |
| 14100 | assert(t.stderr_mutex_lock_count == 0); |
| 14101 | @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered); |
| 14102 | } |
| 14103 | t.stderr_mutex_lock_count += 1; |
| 14104 | |
| 14105 | return try initLockedStderr(t, terminal_mode); |
| 14106 | } |
| 14107 | |
| 14108 | fn initLockedStderr(t: *Threaded, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr { |
| 14109 | if (!t.stderr_writer_initialized) { |
| 14110 | const io_t = io(t); |
| 14111 | if (is_windows) t.stderr_writer.file = .stderr(); |
| 14112 | t.stderr_writer.io = io_t; |
| 14113 | t.stderr_writer_initialized = true; |
| 14114 | t.scanEnviron(); |
| 14115 | const NO_COLOR = t.environ.exist.NO_COLOR; |
| 14116 | const CLICOLOR_FORCE = t.environ.exist.CLICOLOR_FORCE; |
| 14117 | t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file, NO_COLOR, CLICOLOR_FORCE); |
| 14118 | } |
| 14119 | return .{ |
| 14120 | .file_writer = &t.stderr_writer, |
| 14121 | .terminal_mode = terminal_mode orelse t.stderr_mode, |
| 14122 | }; |
| 14123 | } |
| 14124 | |
| 14125 | fn unlockStderr(userdata: ?*anyopaque) void { |
| 14126 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 14127 | if (t.stderr_writer.err == null) t.stderr_writer.interface.flush() catch {}; |
| 14128 | if (t.stderr_writer.err) |err| { |
| 14129 | switch (err) { |
| 14130 | error.Canceled => recancelInner(), |
| 14131 | else => {}, |
| 14132 | } |
| 14133 | t.stderr_writer.err = null; |
| 14134 | } |
| 14135 | t.stderr_writer.interface.end = 0; |
| 14136 | t.stderr_writer.interface.buffer = &.{}; |
| 14137 | |
| 14138 | t.stderr_mutex_lock_count -= 1; |
| 14139 | if (t.stderr_mutex_lock_count == 0) { |
| 14140 | @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, Thread.invalid_id, .unordered); |
| 14141 | mutexUnlock(&t.stderr_mutex); |
| 14142 | } |
| 14143 | } |
| 14144 | |
| 14145 | fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize { |
| 14146 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 14147 | _ = t; |
| 14148 | if (is_windows) { |
| 14149 | var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; |
| 14150 | const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len * 2 + 2, &wtf16le_buf) / 2; |
| 14151 | if (n == 0) return error.Unexpected; |
| 14152 | assert(n <= wtf16le_buf.len); |
| 14153 | const wtf16le_slice = wtf16le_buf[0..n]; |
| 14154 | var end_index: usize = 0; |
| 14155 | var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice); |
| 14156 | while (it.nextCodepoint()) |codepoint| { |
| 14157 | const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable; |
| 14158 | if (end_index + seq_len >= buffer.len) |
| 14159 | return error.NameTooLong; |
| 14160 | end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable; |
| 14161 | } |
| 14162 | return end_index; |
| 14163 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 14164 | if (buffer.len == 0) return error.NameTooLong; |
| 14165 | buffer[0] = '.'; |
| 14166 | return 1; |
| 14167 | } |
| 14168 | |
| 14169 | const err: posix.E = if (builtin.link_libc) err: { |
| 14170 | const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*; |
| 14171 | break :err @fromBackingInt(@intCast(c_err)); |
| 14172 | } else err: { |
| 14173 | break :err posix.errno(posix.system.getcwd(buffer.ptr, buffer.len)); |
| 14174 | }; |
| 14175 | switch (err) { |
| 14176 | .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?, |
| 14177 | .NOENT => return error.CurrentDirUnlinked, |
| 14178 | .RANGE => return error.NameTooLong, |
| 14179 | .FAULT => |e| return errnoBug(e), |
| 14180 | .INVAL => |e| return errnoBug(e), |
| 14181 | else => return posix.unexpectedErrno(err), |
| 14182 | } |
| 14183 | } |
| 14184 | |
| 14185 | fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void { |
| 14186 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 14187 | _ = t; |
| 14188 | |
| 14189 | if (native_os == .wasi) return error.OperationUnsupported; |
| 14190 | |
| 14191 | if (is_windows) { |
| 14192 | var dir_path_buf: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 14193 | const dir_path = try GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buf); |
| 14194 | const syscall: Syscall = try .start(); |
| 14195 | while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(dir_path))) { |
| 14196 | .SUCCESS => return syscall.finish(), |
| 14197 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 14198 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 14199 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 14200 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 14201 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 14202 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 14203 | .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), |
| 14204 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 14205 | .CANCELLED => { |
| 14206 | try syscall.checkCancel(); |
| 14207 | continue; |
| 14208 | }, |
| 14209 | else => |status| return syscall.unexpectedNtstatus(status), |
| 14210 | }; |
| 14211 | } |
| 14212 | |
| 14213 | return fchdir(dir.handle); |
| 14214 | } |
| 14215 | |
| 14216 | fn processSetCurrentPath(userdata: ?*anyopaque, path: []const u8) process.SetCurrentPathError!void { |
| 14217 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 14218 | _ = t; |
| 14219 | |
| 14220 | if (native_os == .wasi) return error.OperationUnsupported; |
| 14221 | |
| 14222 | if (is_windows) { |
| 14223 | var path_w_buf: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 14224 | const len = std.unicode.calcWtf16LeLen(path) catch return error.InvalidWtf8; |
| 14225 | if (len > path_w_buf.len) return error.NameTooLong; |
| 14226 | const path_w_len = std.unicode.wtf8ToWtf16Le(&path_w_buf, path) catch |err| switch (err) { |
| 14227 | error.InvalidWtf8 => unreachable, // already validated |
| 14228 | }; |
| 14229 | const path_w = path_w_buf[0..path_w_len]; |
| 14230 | |
| 14231 | const syscall: Syscall = try .start(); |
| 14232 | while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(path_w))) { |
| 14233 | .SUCCESS => return syscall.finish(), |
| 14234 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 14235 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 14236 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 14237 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 14238 | .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), |
| 14239 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 14240 | .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), |
| 14241 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 14242 | .CANCELLED => { |
| 14243 | try syscall.checkCancel(); |
| 14244 | continue; |
| 14245 | }, |
| 14246 | else => |status| return syscall.unexpectedNtstatus(status), |
| 14247 | }; |
| 14248 | } |
| 14249 | |
| 14250 | return chdir(path); |
| 14251 | } |
| 14252 | |
| 14253 | pub const PosixAddress = extern union { |
| 14254 | any: posix.sockaddr, |
| 14255 | in: posix.sockaddr.in, |
| 14256 | in6: posix.sockaddr.in6, |
| 14257 | }; |
| 14258 | |
| 14259 | const UnixAddress = extern union { |
| 14260 | any: posix.sockaddr, |
| 14261 | un: posix.sockaddr.un, |
| 14262 | }; |
| 14263 | |
| 14264 | pub fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t { |
| 14265 | return switch (a.*) { |
| 14266 | .ip4 => posix.AF.INET, |
| 14267 | .ip6 => posix.AF.INET6, |
| 14268 | }; |
| 14269 | } |
| 14270 | |
| 14271 | pub fn addressFromPosix(posix_address: *const PosixAddress) IpAddress { |
| 14272 | return switch (posix_address.any.family) { |
| 14273 | posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) }, |
| 14274 | posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) }, |
| 14275 | else => .{ .ip4 = .loopback(0) }, |
| 14276 | }; |
| 14277 | } |
| 14278 | |
| 14279 | pub fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t { |
| 14280 | return switch (a.*) { |
| 14281 | .ip4 => |ip4| { |
| 14282 | storage.in = address4ToPosix(ip4); |
| 14283 | return @sizeOf(posix.sockaddr.in); |
| 14284 | }, |
| 14285 | .ip6 => |*ip6| { |
| 14286 | storage.in6 = address6ToPosix(ip6); |
| 14287 | return @sizeOf(posix.sockaddr.in6); |
| 14288 | }, |
| 14289 | }; |
| 14290 | } |
| 14291 | |
| 14292 | fn addressUnixToPosix(a: *const net.UnixAddress, storage: *UnixAddress) posix.socklen_t { |
| 14293 | storage.un.family = posix.AF.UNIX; |
| 14294 | var path_len = switch (native_os) { |
| 14295 | .windows => @min(a.path.len, storage.un.path.len), |
| 14296 | else => a.path.len, |
| 14297 | }; |
| 14298 | // With the AFD API, `sockaddr.un` is purely informational, so |
| 14299 | // use a suffix which is usually the most relevant part of a path. |
| 14300 | @memcpy(storage.un.path[0..path_len], a.path[a.path.len - path_len ..]); |
| 14301 | if (storage.un.path.len - path_len > 0) { |
| 14302 | @branchHint(.likely); |
| 14303 | storage.un.path[path_len] = 0; |
| 14304 | path_len += 1; |
| 14305 | } |
| 14306 | switch (native_os) { |
| 14307 | .windows => { |
| 14308 | if (storage.un.path[0] == 0) @memset(storage.un.path[path_len..], 0); |
| 14309 | return @sizeOf(posix.sockaddr.un); |
| 14310 | }, |
| 14311 | else => return @intCast(@offsetOf(posix.sockaddr.un, "path") + path_len), |
| 14312 | } |
| 14313 | } |
| 14314 | |
| 14315 | fn address4FromPosix(in: *const posix.sockaddr.in) net.Ip4Address { |
| 14316 | // The network byte order address in `in.addr` is already the byte order we want. |
| 14317 | const addr_bytes: *const [4]u8 = @ptrCast(&in.addr); |
| 14318 | return .{ |
| 14319 | .port = std.mem.bigToNative(u16, in.port), |
| 14320 | .bytes = addr_bytes.*, |
| 14321 | }; |
| 14322 | } |
| 14323 | |
| 14324 | fn address6FromPosix(in6: *const posix.sockaddr.in6) net.Ip6Address { |
| 14325 | return .{ |
| 14326 | .port = std.mem.bigToNative(u16, in6.port), |
| 14327 | .bytes = in6.addr, |
| 14328 | .flow = in6.flowinfo, |
| 14329 | .interface = .{ .index = in6.scope_id }, |
| 14330 | }; |
| 14331 | } |
| 14332 | |
| 14333 | fn address4ToPosix(a: net.Ip4Address) posix.sockaddr.in { |
| 14334 | // The byte order of `a.bytes` is already equivalent to a network byte order address. |
| 14335 | const addr_raw: *align(1) const u32 = @ptrCast(&a.bytes); |
| 14336 | return .{ |
| 14337 | .port = std.mem.nativeToBig(u16, a.port), |
| 14338 | .addr = addr_raw.*, |
| 14339 | }; |
| 14340 | } |
| 14341 | |
| 14342 | fn address6ToPosix(a: *const net.Ip6Address) posix.sockaddr.in6 { |
| 14343 | return .{ |
| 14344 | .port = std.mem.nativeToBig(u16, a.port), |
| 14345 | .flowinfo = a.flow, |
| 14346 | .addr = a.bytes, |
| 14347 | .scope_id = a.interface.index, |
| 14348 | }; |
| 14349 | } |
| 14350 | |
| 14351 | pub fn errnoBug(err: posix.E) Io.UnexpectedError { |
| 14352 | if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err}); |
| 14353 | return error.Unexpected; |
| 14354 | } |
| 14355 | |
| 14356 | pub fn posixSocketModeProtocol(family: posix.sa_family_t, mode: net.Socket.Mode, protocol: ?net.Protocol) !struct { u32, u32 } { |
| 14357 | return .{ |
| 14358 | switch (mode) { |
| 14359 | .stream => posix.SOCK.STREAM, |
| 14360 | .dgram => posix.SOCK.DGRAM, |
| 14361 | .seqpacket => posix.SOCK.SEQPACKET, |
| 14362 | .raw => posix.SOCK.RAW, |
| 14363 | .rdm => if (@hasDecl(posix.SOCK, "RDM")) posix.SOCK.RDM else return error.OptionUnsupported, |
| 14364 | }, |
| 14365 | if (protocol) |p| @backingInt(p) else if (is_windows) switch (family) { |
| 14366 | posix.AF.UNIX => switch (mode) { |
| 14367 | .stream => 0, |
| 14368 | else => return error.ProtocolUnsupportedByAddressFamily, |
| 14369 | }, |
| 14370 | posix.AF.INET, posix.AF.INET6 => @backingInt(@as(net.Protocol, switch (mode) { |
| 14371 | .stream => .tcp, |
| 14372 | .dgram => .udp, |
| 14373 | else => return error.ProtocolUnsupportedByAddressFamily, |
| 14374 | })), |
| 14375 | else => return error.ProtocolUnsupportedByAddressFamily, |
| 14376 | } else 0, |
| 14377 | }; |
| 14378 | } |
| 14379 | |
| 14380 | pub fn recoverableOsBugDetected() void { |
| 14381 | if (is_debug) unreachable; |
| 14382 | } |
| 14383 | |
| 14384 | pub fn clockToPosix(clock: Io.Clock) posix.clockid_t { |
| 14385 | return switch (clock) { |
| 14386 | .real => posix.CLOCK.REALTIME, |
| 14387 | .awake => switch (native_os) { |
| 14388 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW, |
| 14389 | else => posix.CLOCK.MONOTONIC, |
| 14390 | }, |
| 14391 | .boot => switch (native_os) { |
| 14392 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.MONOTONIC_RAW, |
| 14393 | // On freebsd derivatives, use MONOTONIC_FAST as currently there's |
| 14394 | // no precision tradeoff. |
| 14395 | .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST, |
| 14396 | // On linux, use BOOTTIME instead of MONOTONIC as it ticks while |
| 14397 | // suspended. |
| 14398 | .linux => posix.CLOCK.BOOTTIME, |
| 14399 | // On other posix systems, MONOTONIC is generally the fastest and |
| 14400 | // ticks while suspended. |
| 14401 | else => posix.CLOCK.MONOTONIC, |
| 14402 | }, |
| 14403 | .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID, |
| 14404 | .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID, |
| 14405 | }; |
| 14406 | } |
| 14407 | |
| 14408 | fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { |
| 14409 | return switch (clock) { |
| 14410 | .real => .REALTIME, |
| 14411 | .awake => .MONOTONIC, |
| 14412 | .boot => .MONOTONIC, |
| 14413 | .cpu_process => .PROCESS_CPUTIME_ID, |
| 14414 | .cpu_thread => .THREAD_CPUTIME_ID, |
| 14415 | }; |
| 14416 | } |
| 14417 | |
| 14418 | pub const linux_statx_request: std.os.linux.STATX = .{ |
| 14419 | .TYPE = true, |
| 14420 | .MODE = true, |
| 14421 | .ATIME = true, |
| 14422 | .MTIME = true, |
| 14423 | .CTIME = true, |
| 14424 | .INO = true, |
| 14425 | .SIZE = true, |
| 14426 | .NLINK = true, |
| 14427 | .BLOCKS = true, |
| 14428 | }; |
| 14429 | |
| 14430 | pub const linux_statx_check: std.os.linux.STATX = .{ |
| 14431 | .TYPE = true, |
| 14432 | .MODE = true, |
| 14433 | .ATIME = false, |
| 14434 | .MTIME = true, |
| 14435 | .CTIME = true, |
| 14436 | .INO = true, |
| 14437 | .SIZE = true, |
| 14438 | .NLINK = true, |
| 14439 | .BLOCKS = false, |
| 14440 | }; |
| 14441 | |
| 14442 | pub fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { |
| 14443 | const actual_mask_int: u32 = @bitCast(stx.mask); |
| 14444 | const wanted_mask_int: u32 = @bitCast(linux_statx_check); |
| 14445 | if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected; |
| 14446 | |
| 14447 | return .{ |
| 14448 | .inode = stx.ino, |
| 14449 | .nlink = stx.nlink, |
| 14450 | .size = stx.size, |
| 14451 | .permissions = .fromMode(stx.mode), |
| 14452 | .kind = statxKind(stx.mode), |
| 14453 | .atime = if (!stx.mask.ATIME) null else .{ |
| 14454 | .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec), |
| 14455 | }, |
| 14456 | .mtime = .{ .nanoseconds = @intCast(@as(i128, stx.mtime.sec) * std.time.ns_per_s + stx.mtime.nsec) }, |
| 14457 | .ctime = .{ .nanoseconds = @intCast(@as(i128, stx.ctime.sec) * std.time.ns_per_s + stx.ctime.nsec) }, |
| 14458 | .block_size = if (stx.mask.BLOCKS) stx.blksize else 1, |
| 14459 | }; |
| 14460 | } |
| 14461 | |
| 14462 | pub fn statxKind(stx_mode: u16) File.Kind { |
| 14463 | return switch (stx_mode & std.os.linux.S.IFMT) { |
| 14464 | std.os.linux.S.IFDIR => .directory, |
| 14465 | std.os.linux.S.IFCHR => .character_device, |
| 14466 | std.os.linux.S.IFBLK => .block_device, |
| 14467 | std.os.linux.S.IFREG => .file, |
| 14468 | std.os.linux.S.IFIFO => .named_pipe, |
| 14469 | std.os.linux.S.IFLNK => .sym_link, |
| 14470 | std.os.linux.S.IFSOCK => .unix_domain_socket, |
| 14471 | else => .unknown, |
| 14472 | }; |
| 14473 | } |
| 14474 | |
| 14475 | pub fn statFromPosix(st: *const posix.Stat) File.Stat { |
| 14476 | const atime = st.atime(); |
| 14477 | const mtime = st.mtime(); |
| 14478 | const ctime = st.ctime(); |
| 14479 | return .{ |
| 14480 | .inode = st.ino, |
| 14481 | .nlink = st.nlink, |
| 14482 | .size = @bitCast(st.size), |
| 14483 | .permissions = .fromMode(st.mode), |
| 14484 | .kind = k: { |
| 14485 | const m = st.mode & posix.S.IFMT; |
| 14486 | switch (m) { |
| 14487 | posix.S.IFBLK => break :k .block_device, |
| 14488 | posix.S.IFCHR => break :k .character_device, |
| 14489 | posix.S.IFDIR => break :k .directory, |
| 14490 | posix.S.IFIFO => break :k .named_pipe, |
| 14491 | posix.S.IFLNK => break :k .sym_link, |
| 14492 | posix.S.IFREG => break :k .file, |
| 14493 | posix.S.IFSOCK => break :k .unix_domain_socket, |
| 14494 | else => {}, |
| 14495 | } |
| 14496 | if (native_os == .illumos) switch (m) { |
| 14497 | posix.S.IFDOOR => break :k .door, |
| 14498 | posix.S.IFPORT => break :k .event_port, |
| 14499 | else => {}, |
| 14500 | }; |
| 14501 | |
| 14502 | break :k .unknown; |
| 14503 | }, |
| 14504 | .atime = timestampFromPosix(&atime), |
| 14505 | .mtime = timestampFromPosix(&mtime), |
| 14506 | .ctime = timestampFromPosix(&ctime), |
| 14507 | .block_size = @intCast(st.blksize), |
| 14508 | }; |
| 14509 | } |
| 14510 | |
| 14511 | fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { |
| 14512 | return .{ |
| 14513 | .inode = st.ino, |
| 14514 | .nlink = st.nlink, |
| 14515 | .size = @bitCast(st.size), |
| 14516 | .permissions = .default_file, |
| 14517 | .kind = switch (st.filetype) { |
| 14518 | .BLOCK_DEVICE => .block_device, |
| 14519 | .CHARACTER_DEVICE => .character_device, |
| 14520 | .DIRECTORY => .directory, |
| 14521 | .SYMBOLIC_LINK => .sym_link, |
| 14522 | .REGULAR_FILE => .file, |
| 14523 | .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, |
| 14524 | else => .unknown, |
| 14525 | }, |
| 14526 | .atime = .fromNanoseconds(st.atim), |
| 14527 | .mtime = .fromNanoseconds(st.mtim), |
| 14528 | .ctime = .fromNanoseconds(st.ctim), |
| 14529 | .block_size = 1, |
| 14530 | }; |
| 14531 | } |
| 14532 | |
| 14533 | pub fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp { |
| 14534 | return .{ .nanoseconds = nanosecondsFromPosix(timespec) }; |
| 14535 | } |
| 14536 | |
| 14537 | pub fn nanosecondsFromPosix(timespec: *const posix.timespec) i96 { |
| 14538 | return @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec); |
| 14539 | } |
| 14540 | |
| 14541 | fn timestampToPosix(nanoseconds: i96) posix.timespec { |
| 14542 | return .{ |
| 14543 | .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)), |
| 14544 | .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)), |
| 14545 | }; |
| 14546 | } |
| 14547 | |
| 14548 | pub fn setTimestampToPosix(set_ts: File.SetTimestamp) posix.timespec { |
| 14549 | return switch (set_ts) { |
| 14550 | .unchanged => posix.UTIME.OMIT, |
| 14551 | .now => posix.UTIME.NOW, |
| 14552 | .new => |t| timestampToPosix(t.nanoseconds), |
| 14553 | }; |
| 14554 | } |
| 14555 | |
| 14556 | pub fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { |
| 14557 | if (std.mem.containsAtLeastScalar(u8, file_path, 0, 1)) return error.BadPathName; |
| 14558 | // >= rather than > to make room for the null byte |
| 14559 | if (file_path.len >= buffer.len) return error.NameTooLong; |
| 14560 | @memcpy(buffer[0..file_path.len], file_path); |
| 14561 | buffer[file_path.len] = 0; |
| 14562 | return buffer[0..file_path.len :0]; |
| 14563 | } |
| 14564 | |
| 14565 | fn lookupDnsSearch( |
| 14566 | t: *Threaded, |
| 14567 | host_name: HostName, |
| 14568 | resolved: *Io.Queue(HostName.LookupResult), |
| 14569 | options: HostName.LookupOptions, |
| 14570 | ) (HostName.LookupError || Io.QueueClosedError)!void { |
| 14571 | const t_io = io(t); |
| 14572 | const rc = HostName.ResolvConf.init(t_io) catch return error.ResolvConfParseFailed; |
| 14573 | |
| 14574 | // Count dots, suppress search when >=ndots or name ends in |
| 14575 | // a dot, which is an explicit request for global scope. |
| 14576 | const dots = std.mem.countScalar(u8, host_name.bytes, '.'); |
| 14577 | const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len; |
| 14578 | const search = rc.search_buffer[0..search_len]; |
| 14579 | |
| 14580 | var canon_name = host_name.bytes; |
| 14581 | |
| 14582 | // Strip final dot for canon, fail if multiple trailing dots. |
| 14583 | if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; |
| 14584 | if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName; |
| 14585 | |
| 14586 | // Name with search domain appended is set up in `canon_name`. This |
| 14587 | // both provides the desired default canonical name (if the requested |
| 14588 | // name is not a CNAME record) and serves as a buffer for passing the |
| 14589 | // full requested name to `lookupDns`. |
| 14590 | var local_buf: [HostName.max_len]u8 = undefined; |
| 14591 | const canon_buf = options.canonical_name_buffer orelse &local_buf; |
| 14592 | @memcpy(canon_buf[0..canon_name.len], canon_name); |
| 14593 | canon_buf[canon_name.len] = '.'; |
| 14594 | var it = std.mem.tokenizeAny(u8, search, " \t"); |
| 14595 | while (it.next()) |token| { |
| 14596 | @memcpy(canon_buf[canon_name.len + 1 ..][0..token.len], token); |
| 14597 | const lookup_canon_name = canon_buf[0 .. canon_name.len + 1 + token.len]; |
| 14598 | if (t.lookupDns(lookup_canon_name, &rc, resolved, options)) |result| { |
| 14599 | return result; |
| 14600 | } else |err| switch (err) { |
| 14601 | error.UnknownHostName, error.NoAddressReturned => continue, |
| 14602 | else => |e| return e, |
| 14603 | } |
| 14604 | } |
| 14605 | |
| 14606 | const lookup_canon_name = canon_buf[0..canon_name.len]; |
| 14607 | return t.lookupDns(lookup_canon_name, &rc, resolved, options); |
| 14608 | } |
| 14609 | |
| 14610 | fn lookupDns( |
| 14611 | t: *Threaded, |
| 14612 | lookup_canon_name: []const u8, |
| 14613 | rc: *const HostName.ResolvConf, |
| 14614 | resolved: *Io.Queue(HostName.LookupResult), |
| 14615 | options: HostName.LookupOptions, |
| 14616 | ) (HostName.LookupError || Io.QueueClosedError)!void { |
| 14617 | const t_io = io(t); |
| 14618 | const family_records: [2]struct { af: IpAddress.Family, rr: HostName.DnsRecord } = .{ |
| 14619 | .{ .af = .ip6, .rr = .A }, |
| 14620 | .{ .af = .ip4, .rr = .AAAA }, |
| 14621 | }; |
| 14622 | var query_buffers: [2][280]u8 = undefined; |
| 14623 | var answer_buffer: [2 * 512]u8 = undefined; |
| 14624 | var queries_buffer: [2][]const u8 = undefined; |
| 14625 | var answers_buffer: [2][]const u8 = undefined; |
| 14626 | var nq: usize = 0; |
| 14627 | var answer_buffer_i: usize = 0; |
| 14628 | |
| 14629 | for (family_records) |fr| { |
| 14630 | if (options.family != fr.af) { |
| 14631 | var entropy: [2]u8 = undefined; |
| 14632 | random(t, &entropy); |
| 14633 | const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy); |
| 14634 | queries_buffer[nq] = query_buffers[nq][0..len]; |
| 14635 | nq += 1; |
| 14636 | } |
| 14637 | } |
| 14638 | |
| 14639 | var ip4_mapped_buffer: [HostName.ResolvConf.max_nameservers]IpAddress = undefined; |
| 14640 | const ip4_mapped = ip4_mapped_buffer[0..rc.nameservers_len]; |
| 14641 | var any_ip6 = false; |
| 14642 | for (rc.nameservers(), ip4_mapped) |*ns, *m| { |
| 14643 | m.* = .{ .ip6 = .fromAny(ns.*) }; |
| 14644 | any_ip6 = any_ip6 or ns.* == .ip6; |
| 14645 | } |
| 14646 | var socket = s: { |
| 14647 | if (any_ip6) ip6: { |
| 14648 | const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; |
| 14649 | const socket = ip6_addr.bind(t_io, .{ .ip6_only = false, .mode = .dgram }) catch |err| switch (err) { |
| 14650 | error.AddressFamilyUnsupported => break :ip6, |
| 14651 | else => |e| return e, |
| 14652 | }; |
| 14653 | break :s socket; |
| 14654 | } |
| 14655 | any_ip6 = false; |
| 14656 | const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; |
| 14657 | const socket = try ip4_addr.bind(t_io, .{ .mode = .dgram }); |
| 14658 | break :s socket; |
| 14659 | }; |
| 14660 | defer socket.close(t_io); |
| 14661 | |
| 14662 | const mapped_nameservers = if (any_ip6) ip4_mapped else rc.nameservers(); |
| 14663 | const queries = queries_buffer[0..nq]; |
| 14664 | const answers = answers_buffer[0..queries.len]; |
| 14665 | var answers_remaining = answers.len; |
| 14666 | for (answers) |*answer| answer.len = 0; |
| 14667 | |
| 14668 | // boot clock is chosen because time the computer is suspended should count |
| 14669 | // against time spent waiting for external messages to arrive. |
| 14670 | const clock: Io.Clock = .boot; |
| 14671 | var now_ts = clock.now(t_io); |
| 14672 | const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); |
| 14673 | const attempt_duration: Io.Duration = .{ |
| 14674 | .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds), |
| 14675 | }; |
| 14676 | |
| 14677 | send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = clock.now(t_io)) { |
| 14678 | const timeout: Io.Timeout = .{ .deadline = .{ |
| 14679 | .raw = now_ts.addDuration(attempt_duration), |
| 14680 | .clock = clock, |
| 14681 | } }; |
| 14682 | |
| 14683 | const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers; |
| 14684 | { |
| 14685 | var message_buffer: [max_messages]net.OutgoingMessage = undefined; |
| 14686 | var message_i: usize = 0; |
| 14687 | for (queries, answers) |query, *answer| { |
| 14688 | if (answer.len != 0) continue; |
| 14689 | for (mapped_nameservers) |*ns| { |
| 14690 | message_buffer[message_i] = .{ |
| 14691 | .address = ns, |
| 14692 | .data_ptr = query.ptr, |
| 14693 | .data_len = query.len, |
| 14694 | }; |
| 14695 | message_i += 1; |
| 14696 | } |
| 14697 | } |
| 14698 | const send_err, _ = socket.sendManyTimeout(t_io, message_buffer[0..message_i], .{}, timeout); |
| 14699 | if (send_err) |err| switch (err) { |
| 14700 | error.Canceled => |e| return e, |
| 14701 | error.Timeout => continue :send, |
| 14702 | else => {}, |
| 14703 | }; |
| 14704 | } |
| 14705 | |
| 14706 | while (true) { |
| 14707 | var message_buffer: [max_messages]net.IncomingMessage = @splat(.init); |
| 14708 | const buf = answer_buffer[answer_buffer_i..]; |
| 14709 | const recv_err, const recv_n = socket.receiveManyTimeout(t_io, &message_buffer, buf, .{}, timeout); |
| 14710 | for (message_buffer[0..recv_n]) |*received_message| { |
| 14711 | const reply = received_message.data; |
| 14712 | // Ignore non-identifiable packets. |
| 14713 | if (reply.len < 4) continue; |
| 14714 | |
| 14715 | // Ignore replies from addresses we didn't send to. |
| 14716 | const ns = for (mapped_nameservers) |*ns| { |
| 14717 | if (received_message.from.eql(ns)) break ns; |
| 14718 | } else { |
| 14719 | continue; |
| 14720 | }; |
| 14721 | |
| 14722 | // Find which query this answer goes with, if any. |
| 14723 | const query, const answer = for (queries, answers) |query, *answer| { |
| 14724 | if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer }; |
| 14725 | } else { |
| 14726 | continue; |
| 14727 | }; |
| 14728 | if (answer.len != 0) continue; |
| 14729 | |
| 14730 | // Only accept positive or negative responses; retry immediately on |
| 14731 | // server failure, and ignore all other codes such as refusal. |
| 14732 | switch (reply[3] & 15) { |
| 14733 | 0, 3 => { |
| 14734 | answer.* = reply; |
| 14735 | answer_buffer_i += reply.len; |
| 14736 | answers_remaining -= 1; |
| 14737 | if (answer_buffer.len - answer_buffer_i == 0) break :send; |
| 14738 | if (answers_remaining == 0) break :send; |
| 14739 | }, |
| 14740 | 2 => { |
| 14741 | socket.sendTimeout(t_io, ns, query, timeout) catch |err| switch (err) { |
| 14742 | error.Canceled => |e| return e, |
| 14743 | error.Timeout => continue :send, |
| 14744 | else => {}, |
| 14745 | }; |
| 14746 | continue; |
| 14747 | }, |
| 14748 | else => continue, |
| 14749 | } |
| 14750 | } |
| 14751 | if (recv_err) |err| switch (err) { |
| 14752 | error.Canceled => |e| return e, |
| 14753 | error.Timeout => continue :send, |
| 14754 | else => continue, |
| 14755 | }; |
| 14756 | } |
| 14757 | } else { |
| 14758 | return error.NameServerFailure; |
| 14759 | } |
| 14760 | |
| 14761 | var addresses_len: usize = 0; |
| 14762 | var canonical_name: ?HostName = null; |
| 14763 | |
| 14764 | for (answers) |answer| { |
| 14765 | var it = HostName.DnsResponse.init(answer) catch { |
| 14766 | // Here we could potentially add diagnostics to the results queue. |
| 14767 | continue; |
| 14768 | }; |
| 14769 | while (it.next() catch { |
| 14770 | // Here we could potentially add diagnostics to the results queue. |
| 14771 | continue; |
| 14772 | }) |record| switch (record.rr) { |
| 14773 | .A => { |
| 14774 | const data = record.packet[record.data_off..][0..record.data_len]; |
| 14775 | if (data.len != 4) return error.InvalidDnsARecord; |
| 14776 | try resolved.putOne(t_io, .{ .address = .{ .ip4 = .{ |
| 14777 | .bytes = data[0..4].*, |
| 14778 | .port = options.port, |
| 14779 | } } }); |
| 14780 | addresses_len += 1; |
| 14781 | }, |
| 14782 | .AAAA => { |
| 14783 | const data = record.packet[record.data_off..][0..record.data_len]; |
| 14784 | if (data.len != 16) return error.InvalidDnsAAAARecord; |
| 14785 | try resolved.putOne(t_io, .{ .address = .{ .ip6 = .{ |
| 14786 | .bytes = data[0..16].*, |
| 14787 | .port = options.port, |
| 14788 | } } }); |
| 14789 | addresses_len += 1; |
| 14790 | }, |
| 14791 | .CNAME => { |
| 14792 | if (options.canonical_name_buffer) |buf| { |
| 14793 | _, canonical_name = HostName.expand( |
| 14794 | record.packet, |
| 14795 | record.data_off, |
| 14796 | buf, |
| 14797 | ) catch return error.InvalidDnsCnameRecord; |
| 14798 | } |
| 14799 | }, |
| 14800 | _ => continue, |
| 14801 | }; |
| 14802 | } |
| 14803 | |
| 14804 | if (options.canonical_name_buffer != null) { |
| 14805 | try resolved.putOne(t_io, .{ |
| 14806 | .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name }, |
| 14807 | }); |
| 14808 | } |
| 14809 | if (addresses_len == 0) return error.NoAddressReturned; |
| 14810 | } |
| 14811 | |
| 14812 | fn lookupHosts( |
| 14813 | t: *Threaded, |
| 14814 | host_name: HostName, |
| 14815 | resolved: *Io.Queue(HostName.LookupResult), |
| 14816 | options: HostName.LookupOptions, |
| 14817 | ) !void { |
| 14818 | const path_w = if (is_windows) path_w: { |
| 14819 | var path_w_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; |
| 14820 | const system_dir = windows.getSystemDirectoryWtf16Le(); |
| 14821 | const suffix = [_]u16{ |
| 14822 | '\\', 'd', 'r', 'i', 'v', 'e', 'r', 's', '\\', 'e', 't', 'c', '\\', 'h', 'o', 's', 't', 's', |
| 14823 | }; |
| 14824 | @memcpy(path_w_buf[0..system_dir.len], system_dir); |
| 14825 | @memcpy(path_w_buf[system_dir.len..][0..suffix.len], &suffix); |
| 14826 | path_w_buf[system_dir.len + suffix.len] = 0; |
| 14827 | break :path_w wToPrefixedFileW(null, &path_w_buf, .{}) catch |err| switch (err) { |
| 14828 | error.FileNotFound, |
| 14829 | error.AccessDenied, |
| 14830 | => return error.UnknownHostName, |
| 14831 | |
| 14832 | error.Canceled => |e| return e, |
| 14833 | |
| 14834 | else => { |
| 14835 | // Here we could add more detailed diagnostics to the results queue. |
| 14836 | return error.DetectingNetworkConfigurationFailed; |
| 14837 | }, |
| 14838 | }; |
| 14839 | }; |
| 14840 | const file = (if (is_windows) |
| 14841 | dirOpenFileWtf16(null, path_w.span(), .{}) |
| 14842 | else |
| 14843 | dirOpenFile(t, .cwd(), "/etc/hosts", .{})) catch |err| switch (err) { |
| 14844 | error.FileNotFound, |
| 14845 | error.NotDir, |
| 14846 | error.AccessDenied, |
| 14847 | => return error.UnknownHostName, |
| 14848 | |
| 14849 | error.Canceled => |e| return e, |
| 14850 | |
| 14851 | else => { |
| 14852 | // Here we could add more detailed diagnostics to the results queue. |
| 14853 | return error.DetectingNetworkConfigurationFailed; |
| 14854 | }, |
| 14855 | }; |
| 14856 | defer fileClose(t, &.{file}); |
| 14857 | |
| 14858 | var line_buf: [512]u8 = undefined; |
| 14859 | var file_reader = file.reader(t.io(), &line_buf); |
| 14860 | return t.lookupHostsReader(host_name, resolved, options, &file_reader.interface) catch |err| switch (err) { |
| 14861 | error.ReadFailed => switch (file_reader.err.?) { |
| 14862 | error.Canceled => |e| return e, |
| 14863 | else => { |
| 14864 | // Here we could add more detailed diagnostics to the results queue. |
| 14865 | return error.DetectingNetworkConfigurationFailed; |
| 14866 | }, |
| 14867 | }, |
| 14868 | error.Canceled, |
| 14869 | error.Closed, |
| 14870 | error.UnknownHostName, |
| 14871 | => |e| return e, |
| 14872 | }; |
| 14873 | } |
| 14874 | |
| 14875 | fn lookupHostsReader( |
| 14876 | t: *Threaded, |
| 14877 | host_name: HostName, |
| 14878 | resolved: *Io.Queue(HostName.LookupResult), |
| 14879 | options: HostName.LookupOptions, |
| 14880 | reader: *Io.Reader, |
| 14881 | ) error{ ReadFailed, Canceled, UnknownHostName, Closed }!void { |
| 14882 | const t_io = io(t); |
| 14883 | var addresses_len: usize = 0; |
| 14884 | var canonical_name: ?HostName = null; |
| 14885 | while (true) { |
| 14886 | const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { |
| 14887 | error.StreamTooLong => { |
| 14888 | // Skip lines that are too long. |
| 14889 | _ = reader.discardDelimiterInclusive('\n') catch |er| switch (er) { |
| 14890 | error.EndOfStream => break, |
| 14891 | error.ReadFailed => |e| return e, |
| 14892 | }; |
| 14893 | continue; |
| 14894 | }, |
| 14895 | error.ReadFailed => |e| return e, |
| 14896 | error.EndOfStream => break, |
| 14897 | }; |
| 14898 | reader.toss(@min(1, reader.bufferedLen())); |
| 14899 | var split_it = std.mem.splitScalar(u8, if (is_windows and std.mem.endsWith(u8, line, "\r")) |
| 14900 | line[0 .. line.len - 1] |
| 14901 | else |
| 14902 | line, '#'); |
| 14903 | const no_comment_line = split_it.first(); |
| 14904 | |
| 14905 | var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); |
| 14906 | const ip_text = line_it.next() orelse continue; |
| 14907 | var first_name_text: ?[]const u8 = null; |
| 14908 | while (line_it.next()) |name_text| { |
| 14909 | if (std.ascii.eqlIgnoreCase(name_text, host_name.bytes)) { |
| 14910 | if (first_name_text == null) first_name_text = name_text; |
| 14911 | break; |
| 14912 | } |
| 14913 | } else continue; |
| 14914 | |
| 14915 | if (canonical_name == null) { |
| 14916 | if (options.canonical_name_buffer) |buf| { |
| 14917 | if (HostName.init(first_name_text.?)) |name_text| { |
| 14918 | if (name_text.bytes.len <= buf.len) { |
| 14919 | const canonical_name_dest = buf[0..name_text.bytes.len]; |
| 14920 | @memcpy(canonical_name_dest, name_text.bytes); |
| 14921 | canonical_name = .{ .bytes = canonical_name_dest }; |
| 14922 | } |
| 14923 | } else |_| {} |
| 14924 | } |
| 14925 | } |
| 14926 | |
| 14927 | if (options.family != .ip6) { |
| 14928 | if (IpAddress.parseIp4(ip_text, options.port)) |addr| { |
| 14929 | try resolved.putOne(t_io, .{ .address = addr }); |
| 14930 | addresses_len += 1; |
| 14931 | } else |_| {} |
| 14932 | } |
| 14933 | if (options.family != .ip4) { |
| 14934 | if (IpAddress.parseIp6(ip_text, options.port)) |addr| { |
| 14935 | try resolved.putOne(t_io, .{ .address = addr }); |
| 14936 | addresses_len += 1; |
| 14937 | } else |_| {} |
| 14938 | } |
| 14939 | } |
| 14940 | |
| 14941 | if (canonical_name) |canon_name| try resolved.putOne(t_io, .{ .canonical_name = canon_name }); |
| 14942 | if (addresses_len == 0) return error.UnknownHostName; |
| 14943 | } |
| 14944 | |
| 14945 | /// Writes DNS resolution query packet data to `w`; at most 280 bytes. |
| 14946 | fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: HostName.DnsRecord, entropy: [2]u8) usize { |
| 14947 | // This implementation is ported from musl libc. |
| 14948 | // A more idiomatic "ziggy" implementation would be welcome. |
| 14949 | var name = dname; |
| 14950 | if (std.mem.endsWith(u8, name, ".")) name.len -= 1; |
| 14951 | assert(name.len <= 253); |
| 14952 | const n = 17 + name.len + @intFromBool(name.len != 0); |
| 14953 | |
| 14954 | // Construct query template - ID will be filled later |
| 14955 | q[0..2].* = entropy; |
| 14956 | @memset(q[2..n], 0); |
| 14957 | q[2] = @as(u8, op) * 8 + 1; |
| 14958 | q[5] = 1; |
| 14959 | @memcpy(q[13..][0..name.len], name); |
| 14960 | var i: usize = 13; |
| 14961 | var j: usize = undefined; |
| 14962 | while (q[i] != 0) : (i = j + 1) { |
| 14963 | j = i; |
| 14964 | while (q[j] != 0 and q[j] != '.') : (j += 1) {} |
| 14965 | // TODO determine the circumstances for this and whether or |
| 14966 | // not this should be an error. |
| 14967 | if (j - i - 1 > 62) unreachable; |
| 14968 | q[i - 1] = @intCast(j - i); |
| 14969 | } |
| 14970 | q[i + 1] = @backingInt(ty); |
| 14971 | q[i + 3] = class; |
| 14972 | return n; |
| 14973 | } |
| 14974 | |
| 14975 | const LookupDnsWindows = struct { |
| 14976 | threaded: *Threaded, |
| 14977 | thread: Thread.Handle, |
| 14978 | resolved: *Io.Queue(HostName.LookupResult), |
| 14979 | options: HostName.LookupOptions, |
| 14980 | results: windows.DNS.QUERY.RESULT, |
| 14981 | done: bool, |
| 14982 | |
| 14983 | fn completed( |
| 14984 | pQueryContext: ?*anyopaque, |
| 14985 | pQueryResults: *windows.DNS.QUERY.RESULT, |
| 14986 | ) callconv(.winapi) void { |
| 14987 | _ = pQueryContext; |
| 14988 | const lookup_dns: *LookupDnsWindows = @fieldParentPtr("results", pQueryResults); |
| 14989 | lookup_dns.completedFallible() catch |err| switch (err) { |
| 14990 | error.Closed => unreachable, // `resolved` must not be closed until `netLookup` returns |
| 14991 | error.Canceled => unreachable, // called from an uncancelable thread |
| 14992 | }; |
| 14993 | @atomicStore(bool, &lookup_dns.done, true, .release); |
| 14994 | _ = windows.ntdll.NtAlertThread(lookup_dns.thread); |
| 14995 | } |
| 14996 | fn completedFallible(lookup_dns: *LookupDnsWindows) (Io.QueueClosedError || Io.Cancelable)!void { |
| 14997 | assert(!lookup_dns.done); |
| 14998 | const t = lookup_dns.threaded; |
| 14999 | defer t.dl.DnsFree.raw.?(lookup_dns.results.pQueryRecords, .RecordList); |
| 15000 | if (lookup_dns.results.QueryStatus != .SUCCESS) return; |
| 15001 | const t_io = t.io(); |
| 15002 | var record_it = lookup_dns.results.pQueryRecords; |
| 15003 | while (record_it) |record| : (record_it = record.pNext) switch (record.wType) { |
| 15004 | else => {}, |
| 15005 | .A => try lookup_dns.resolved.putOne(t_io, .{ |
| 15006 | .address = .{ .ip4 = .{ .bytes = record.Data.A, .port = lookup_dns.options.port } }, |
| 15007 | }), |
| 15008 | .AAAA => { |
| 15009 | const ip6: net.Ip6Address = .{ |
| 15010 | .bytes = record.Data.AAAA, |
| 15011 | .port = lookup_dns.options.port, |
| 15012 | }; |
| 15013 | try lookup_dns.resolved.putOne(t_io, .{ |
| 15014 | .address = if (lookup_dns.options.family) |_| .{ .ip6 = ip6 } else .fromIp6(ip6), |
| 15015 | }); |
| 15016 | }, |
| 15017 | }; |
| 15018 | if (lookup_dns.results.pQueryRecords) |record| { |
| 15019 | if (lookup_dns.options.canonical_name_buffer) |buf| { |
| 15020 | const name_wtf16 = std.mem.span( |
| 15021 | @as([*:0]const windows.WCHAR, @ptrCast(@alignCast(record.pName))), |
| 15022 | ); |
| 15023 | const len = std.unicode.wtf16LeToWtf8(buf, name_wtf16); |
| 15024 | try lookup_dns.resolved.putOne(t_io, .{ |
| 15025 | .canonical_name = .{ .bytes = buf[0..len] }, |
| 15026 | }); |
| 15027 | } |
| 15028 | } |
| 15029 | } |
| 15030 | }; |
| 15031 | |
| 15032 | fn copyCanon(canonical_name_buffer: ?*[HostName.max_len]u8, name: []const u8) ?HostName { |
| 15033 | const buf = canonical_name_buffer orelse return null; |
| 15034 | const dest = buf[0..name.len]; |
| 15035 | @memcpy(dest, name); |
| 15036 | return .{ .bytes = dest }; |
| 15037 | } |
| 15038 | |
| 15039 | /// Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it: |
| 15040 | /// https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6 |
| 15041 | /// |
| 15042 | /// This XNU version appears to correspond to 11.0.1: |
| 15043 | /// https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html |
| 15044 | /// |
| 15045 | /// ulock_wait() uses 32-bit micro-second timeouts where 0 = INFINITE or no-timeout |
| 15046 | /// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) |
| 15047 | const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11; |
| 15048 | |
| 15049 | fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} |
| 15050 | |
| 15051 | const WindowsEnvironStrings = struct { |
| 15052 | PATH: ?[:0]const u16 = null, |
| 15053 | PATHEXT: ?[:0]const u16 = null, |
| 15054 | |
| 15055 | fn scan() WindowsEnvironStrings { |
| 15056 | const peb = windows.peb(); |
| 15057 | assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); |
| 15058 | defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); |
| 15059 | const ptr = peb.ProcessParameters.Environment; |
| 15060 | |
| 15061 | var result: WindowsEnvironStrings = .{}; |
| 15062 | var i: usize = 0; |
| 15063 | while (ptr[i] != 0) { |
| 15064 | const key_start = i; |
| 15065 | |
| 15066 | // There are some special environment variables that start with =, |
| 15067 | // so we need a special case to not treat = as a key/value separator |
| 15068 | // if it's the first character. |
| 15069 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 15070 | if (ptr[key_start] == '=') i += 1; |
| 15071 | |
| 15072 | while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} |
| 15073 | const key_w = ptr[key_start..i]; |
| 15074 | |
| 15075 | if (ptr[i] == '=') i += 1; |
| 15076 | |
| 15077 | const value_start = i; |
| 15078 | while (ptr[i] != 0) : (i += 1) {} |
| 15079 | const value_w = ptr[value_start..i :0]; |
| 15080 | |
| 15081 | i += 1; // skip over null byte |
| 15082 | |
| 15083 | inline for (@typeInfo(WindowsEnvironStrings).@"struct".field_names) |field_name| { |
| 15084 | const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field_name); |
| 15085 | if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field_name) = value_w; |
| 15086 | } |
| 15087 | } |
| 15088 | |
| 15089 | return result; |
| 15090 | } |
| 15091 | }; |
| 15092 | |
| 15093 | fn scanEnviron(t: *Threaded) void { |
| 15094 | mutexLock(&t.mutex); |
| 15095 | defer mutexUnlock(&t.mutex); |
| 15096 | if (t.environ_initialized) return; |
| 15097 | t.environ.scan(t.allocator); |
| 15098 | t.environ_initialized = true; |
| 15099 | } |
| 15100 | |
| 15101 | fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError { |
| 15102 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 15103 | |
| 15104 | if (!process.can_replace) return error.OperationUnsupported; |
| 15105 | |
| 15106 | t.scanEnviron(); // for PATH |
| 15107 | const PATH = t.environ.string.PATH orelse default_PATH; |
| 15108 | |
| 15109 | var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); |
| 15110 | defer arena_allocator.deinit(); |
| 15111 | const arena = arena_allocator.allocator(); |
| 15112 | |
| 15113 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); |
| 15114 | for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr; |
| 15115 | |
| 15116 | const env_block = env_block: { |
| 15117 | const prog_fd: i32 = -1; |
| 15118 | if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ |
| 15119 | .zig_progress_fd = prog_fd, |
| 15120 | }); |
| 15121 | break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ |
| 15122 | .zig_progress_fd = prog_fd, |
| 15123 | }); |
| 15124 | }; |
| 15125 | |
| 15126 | return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); |
| 15127 | } |
| 15128 | |
| 15129 | fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { |
| 15130 | if (!process.can_replace) return error.OperationUnsupported; |
| 15131 | _ = userdata; |
| 15132 | _ = dir; |
| 15133 | _ = options; |
| 15134 | @panic("TODO processReplacePath"); |
| 15135 | } |
| 15136 | |
| 15137 | fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 15138 | if (!process.can_spawn) return error.OperationUnsupported; |
| 15139 | _ = userdata; |
| 15140 | _ = dir; |
| 15141 | _ = options; |
| 15142 | @panic("TODO processSpawnPath"); |
| 15143 | } |
| 15144 | |
| 15145 | const processSpawn = switch (native_os) { |
| 15146 | .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported, |
| 15147 | .windows => processSpawnWindows, |
| 15148 | else => processSpawnPosix, |
| 15149 | }; |
| 15150 | |
| 15151 | fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 15152 | _ = userdata; |
| 15153 | _ = options; |
| 15154 | return error.OperationUnsupported; |
| 15155 | } |
| 15156 | |
| 15157 | const Spawned = struct { |
| 15158 | pid: posix.pid_t, |
| 15159 | err_fd: posix.fd_t, |
| 15160 | stdin: ?File, |
| 15161 | stdout: ?File, |
| 15162 | stderr: ?File, |
| 15163 | }; |
| 15164 | |
| 15165 | fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Spawned { |
| 15166 | // The child process does need to access (one end of) these pipes. However, |
| 15167 | // we must initially set CLOEXEC to avoid a race condition. If another thread |
| 15168 | // is racing to spawn a different child process, we don't want it to inherit |
| 15169 | // these FDs in any scenario; that would mean that, for instance, calls to |
| 15170 | // `poll` from the parent would not report the child's stdout as closing when |
| 15171 | // expected, since the other child may retain a reference to the write end of |
| 15172 | // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we |
| 15173 | // need to do something in the new child to make sure we preserve the reference |
| 15174 | // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it |
| 15175 | // turns out, we `dup2` everything anyway, so there's no need! |
| 15176 | const pipe_flags: posix.O = .{ .CLOEXEC = true }; |
| 15177 | |
| 15178 | const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined; |
| 15179 | errdefer if (options.stdin == .pipe) { |
| 15180 | destroyPipe(stdin_pipe); |
| 15181 | }; |
| 15182 | |
| 15183 | const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined; |
| 15184 | errdefer if (options.stdout == .pipe) { |
| 15185 | destroyPipe(stdout_pipe); |
| 15186 | }; |
| 15187 | |
| 15188 | const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined; |
| 15189 | errdefer if (options.stderr == .pipe) { |
| 15190 | destroyPipe(stderr_pipe); |
| 15191 | }; |
| 15192 | |
| 15193 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); |
| 15194 | const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; |
| 15195 | |
| 15196 | const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none) pipe: { |
| 15197 | // We use CLOEXEC for the same reason as in `pipe_flags`. |
| 15198 | const pipe = try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); |
| 15199 | switch (native_os) { |
| 15200 | .linux => _ = posix.system.fcntl(pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2)), |
| 15201 | else => {}, |
| 15202 | } |
| 15203 | break :pipe pipe; |
| 15204 | } else .{ -1, -1 }; |
| 15205 | errdefer destroyPipe(prog_pipe); |
| 15206 | |
| 15207 | var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); |
| 15208 | defer arena_allocator.deinit(); |
| 15209 | const arena = arena_allocator.allocator(); |
| 15210 | |
| 15211 | // The POSIX standard does not allow malloc() between fork() and execve(), |
| 15212 | // and this allocator may be a libc allocator. |
| 15213 | // I have personally observed the child process deadlocking when it tries |
| 15214 | // to call malloc() due to a heap allocation between fork() and execve(), |
| 15215 | // in musl v1.1.24. |
| 15216 | // Additionally, we want to reduce the number of possible ways things |
| 15217 | // can fail between fork() and execve(). |
| 15218 | // Therefore, we do all the allocation for the execve() before the fork(). |
| 15219 | // This means we must do the null-termination of argv and env vars here. |
| 15220 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); |
| 15221 | for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr; |
| 15222 | |
| 15223 | const prog_fileno = 3; |
| 15224 | comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); |
| 15225 | |
| 15226 | const env_block = env_block: { |
| 15227 | const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; |
| 15228 | if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ |
| 15229 | .zig_progress_fd = prog_fd, |
| 15230 | }); |
| 15231 | break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ |
| 15232 | .zig_progress_fd = prog_fd, |
| 15233 | }); |
| 15234 | }; |
| 15235 | |
| 15236 | // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. |
| 15237 | // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. |
| 15238 | const err_pipe = try pipe2(.{ .CLOEXEC = true }); |
| 15239 | errdefer destroyPipe(err_pipe); |
| 15240 | |
| 15241 | t.scanEnviron(); // for PATH |
| 15242 | const PATH = t.environ.string.PATH orelse default_PATH; |
| 15243 | |
| 15244 | const pid_result: posix.pid_t = fork: { |
| 15245 | const rc = posix.system.fork(); |
| 15246 | switch (posix.errno(rc)) { |
| 15247 | .SUCCESS => break :fork @intCast(rc), |
| 15248 | .AGAIN => return error.SystemResources, |
| 15249 | .NOMEM => return error.SystemResources, |
| 15250 | .NOSYS => return error.OperationUnsupported, |
| 15251 | else => |err| return posix.unexpectedErrno(err), |
| 15252 | } |
| 15253 | }; |
| 15254 | |
| 15255 | if (pid_result == 0) { |
| 15256 | defer comptime unreachable; // We are the child. |
| 15257 | if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; |
| 15258 | const ep1 = err_pipe[1]; |
| 15259 | |
| 15260 | setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); |
| 15261 | setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); |
| 15262 | setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); |
| 15263 | |
| 15264 | switch (options.cwd) { |
| 15265 | .inherit => {}, |
| 15266 | .dir => |cwd| { |
| 15267 | fchdir(cwd.handle) catch |err| forkBail(ep1, err); |
| 15268 | }, |
| 15269 | .path => |cwd| { |
| 15270 | chdir(cwd) catch |err| forkBail(ep1, err); |
| 15271 | }, |
| 15272 | } |
| 15273 | |
| 15274 | // Must happen after fchdir above, the cwd file descriptor might be |
| 15275 | // equal to prog_fileno and be clobbered by this dup2 call. |
| 15276 | if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err); |
| 15277 | |
| 15278 | if (options.gid) |gid| { |
| 15279 | switch (posix.errno(posix.system.setregid(gid, gid))) { |
| 15280 | .SUCCESS => {}, |
| 15281 | .AGAIN => forkBail(ep1, error.ResourceLimitReached), |
| 15282 | .INVAL => forkBail(ep1, error.InvalidUserId), |
| 15283 | .PERM => forkBail(ep1, error.PermissionDenied), |
| 15284 | else => forkBail(ep1, error.Unexpected), |
| 15285 | } |
| 15286 | } |
| 15287 | |
| 15288 | if (options.uid) |uid| { |
| 15289 | switch (posix.errno(posix.system.setreuid(uid, uid))) { |
| 15290 | .SUCCESS => {}, |
| 15291 | .AGAIN => forkBail(ep1, error.ResourceLimitReached), |
| 15292 | .INVAL => forkBail(ep1, error.InvalidUserId), |
| 15293 | .PERM => forkBail(ep1, error.PermissionDenied), |
| 15294 | else => forkBail(ep1, error.Unexpected), |
| 15295 | } |
| 15296 | } |
| 15297 | |
| 15298 | if (options.pgid) |pid| { |
| 15299 | switch (posix.errno(posix.system.setpgid(0, pid))) { |
| 15300 | .SUCCESS => {}, |
| 15301 | .ACCES => forkBail(ep1, error.ProcessAlreadyExec), |
| 15302 | .INVAL => forkBail(ep1, error.InvalidProcessGroupId), |
| 15303 | .PERM => forkBail(ep1, error.PermissionDenied), |
| 15304 | else => forkBail(ep1, error.Unexpected), |
| 15305 | } |
| 15306 | } |
| 15307 | |
| 15308 | if (options.start_suspended) { |
| 15309 | switch (posix.errno(posix.system.kill(0, .STOP))) { |
| 15310 | .SUCCESS => {}, |
| 15311 | .PERM => forkBail(ep1, error.PermissionDenied), |
| 15312 | else => forkBail(ep1, error.Unexpected), |
| 15313 | } |
| 15314 | } |
| 15315 | |
| 15316 | const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); |
| 15317 | forkBail(ep1, err); |
| 15318 | } |
| 15319 | |
| 15320 | const pid: posix.pid_t = @intCast(pid_result); // We are the parent. |
| 15321 | errdefer comptime unreachable; // The child is forked; we must not error from now on |
| 15322 | |
| 15323 | closeFd(err_pipe[1]); // make sure only the child holds the write end open |
| 15324 | |
| 15325 | if (options.stdin == .pipe) closeFd(stdin_pipe[0]); |
| 15326 | if (options.stdout == .pipe) closeFd(stdout_pipe[1]); |
| 15327 | if (options.stderr == .pipe) closeFd(stderr_pipe[1]); |
| 15328 | |
| 15329 | if (prog_pipe[1] != -1) closeFd(prog_pipe[1]); |
| 15330 | options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); |
| 15331 | |
| 15332 | return .{ |
| 15333 | .pid = pid, |
| 15334 | .err_fd = err_pipe[0], |
| 15335 | .stdin = switch (options.stdin) { |
| 15336 | .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } }, |
| 15337 | else => null, |
| 15338 | }, |
| 15339 | .stdout = switch (options.stdout) { |
| 15340 | .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } }, |
| 15341 | else => null, |
| 15342 | }, |
| 15343 | .stderr = switch (options.stderr) { |
| 15344 | .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } }, |
| 15345 | else => null, |
| 15346 | }, |
| 15347 | }; |
| 15348 | } |
| 15349 | |
| 15350 | fn getDevNullFd(t: *Threaded) !posix.fd_t { |
| 15351 | { |
| 15352 | mutexLock(&t.mutex); |
| 15353 | defer mutexUnlock(&t.mutex); |
| 15354 | if (t.null_file.fd != -1) return t.null_file.fd; |
| 15355 | } |
| 15356 | const mode: u32 = 0; |
| 15357 | const syscall: Syscall = try .start(); |
| 15358 | while (true) { |
| 15359 | const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, mode); |
| 15360 | switch (posix.errno(rc)) { |
| 15361 | .SUCCESS => { |
| 15362 | syscall.finish(); |
| 15363 | const fresh_fd: posix.fd_t = @intCast(rc); |
| 15364 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 15365 | defer mutexUnlock(&t.mutex); |
| 15366 | if (t.null_file.fd != -1) { |
| 15367 | closeFd(fresh_fd); |
| 15368 | return t.null_file.fd; |
| 15369 | } else { |
| 15370 | t.null_file.fd = fresh_fd; |
| 15371 | return fresh_fd; |
| 15372 | } |
| 15373 | }, |
| 15374 | .INTR => { |
| 15375 | try syscall.checkCancel(); |
| 15376 | continue; |
| 15377 | }, |
| 15378 | .ACCES => return syscall.fail(error.AccessDenied), |
| 15379 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| 15380 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), |
| 15381 | .NODEV => return syscall.fail(error.NoDevice), |
| 15382 | .NOENT => return syscall.fail(error.FileNotFound), |
| 15383 | .NOMEM => return syscall.fail(error.SystemResources), |
| 15384 | .PERM => return syscall.fail(error.PermissionDenied), |
| 15385 | else => |err| return syscall.unexpectedErrno(err), |
| 15386 | } |
| 15387 | } |
| 15388 | } |
| 15389 | |
| 15390 | fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 15391 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 15392 | const spawned = try spawnPosix(t, options); |
| 15393 | defer closeFd(spawned.err_fd); |
| 15394 | |
| 15395 | // Wait for the child to report any errors in or before `execvpe`. |
| 15396 | if (readIntFd(spawned.err_fd)) |child_err_int| { |
| 15397 | const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int)); |
| 15398 | return child_err; |
| 15399 | } else |read_err| switch (read_err) { |
| 15400 | error.EndOfStream => { |
| 15401 | // Write end closed by CLOEXEC at the time of the `execvpe` call, |
| 15402 | // indicating success. |
| 15403 | }, |
| 15404 | else => { |
| 15405 | // Problem reading the error from the error reporting pipe. We |
| 15406 | // don't know if the child is alive or dead. Better to assume it is |
| 15407 | // alive so the resource does not risk being leaked. |
| 15408 | }, |
| 15409 | } |
| 15410 | |
| 15411 | return .{ |
| 15412 | .id = spawned.pid, |
| 15413 | .thread_handle = {}, |
| 15414 | .stdin = spawned.stdin, |
| 15415 | .stdout = spawned.stdout, |
| 15416 | .stderr = spawned.stderr, |
| 15417 | .request_resource_usage_statistics = options.request_resource_usage_statistics, |
| 15418 | }; |
| 15419 | } |
| 15420 | |
| 15421 | fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 15422 | if (native_os == .wasi) unreachable; |
| 15423 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 15424 | _ = t; |
| 15425 | switch (native_os) { |
| 15426 | .windows => return childWaitWindows(child), |
| 15427 | else => return childWaitPosix(child), |
| 15428 | } |
| 15429 | } |
| 15430 | |
| 15431 | fn childKill(userdata: ?*anyopaque, child: *process.Child) void { |
| 15432 | if (native_os == .wasi) unreachable; |
| 15433 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 15434 | if (is_windows) { |
| 15435 | childKillWindows(t, child, 1) catch childCleanupWindows(child); |
| 15436 | } else { |
| 15437 | childKillPosix(child) catch {}; |
| 15438 | childCleanupPosix(child); |
| 15439 | } |
| 15440 | } |
| 15441 | |
| 15442 | fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { |
| 15443 | _ = t; // TODO cancelation |
| 15444 | const handle = child.id.?; |
| 15445 | _ = windows.ntdll.RtlReportSilentProcessExit(handle, @fromBackingInt(@intCast(exit_code))); |
| 15446 | switch (windows.ntdll.NtTerminateProcess(handle, @fromBackingInt(@intCast(exit_code)))) { |
| 15447 | .SUCCESS, .PROCESS_IS_TERMINATING => { |
| 15448 | _ = windows.ntdll.NtWaitForSingleObject(handle, .FALSE, null); |
| 15449 | childCleanupWindows(child); |
| 15450 | }, |
| 15451 | .ACCESS_DENIED => { |
| 15452 | // Usually when TerminateProcess triggers a ACCESS_DENIED error, it |
| 15453 | // indicates that the process has already exited, but there may be |
| 15454 | // some rare edge cases where our process handle no longer has the |
| 15455 | // PROCESS_TERMINATE access right, so let's do another check to make |
| 15456 | // sure the process is really no longer running: |
| 15457 | const minimal_timeout: windows.LARGE_INTEGER = -1; |
| 15458 | return switch (windows.ntdll.NtWaitForSingleObject(handle, .FALSE, &minimal_timeout)) { |
| 15459 | windows.NTSTATUS.WAIT_0 => error.AlreadyTerminated, |
| 15460 | else => error.AccessDenied, |
| 15461 | }; |
| 15462 | }, |
| 15463 | else => |status| return windows.unexpectedStatus(status), |
| 15464 | } |
| 15465 | } |
| 15466 | |
| 15467 | fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 15468 | const handle = child.id.?; |
| 15469 | |
| 15470 | const alertable_syscall: AlertableSyscall = try .start(); |
| 15471 | while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, .TRUE, null)) { |
| 15472 | windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(), |
| 15473 | .USER_APC, .ALERTED, .TIMEOUT => { |
| 15474 | try alertable_syscall.checkCancel(); |
| 15475 | continue; |
| 15476 | }, |
| 15477 | else => |status| return alertable_syscall.unexpectedNtstatus(status), |
| 15478 | }; |
| 15479 | |
| 15480 | var info: windows.PROCESS.BASIC_INFORMATION = undefined; |
| 15481 | const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess( |
| 15482 | handle, |
| 15483 | .BasicInformation, |
| 15484 | &info, |
| 15485 | @sizeOf(windows.PROCESS.BASIC_INFORMATION), |
| 15486 | null, |
| 15487 | )) { |
| 15488 | .SUCCESS => .{ .exited = @as(u8, @truncate(@backingInt(info.ExitStatus))) }, |
| 15489 | else => .{ .unknown = 0 }, |
| 15490 | }; |
| 15491 | |
| 15492 | childCleanupWindows(child); |
| 15493 | return term; |
| 15494 | } |
| 15495 | |
| 15496 | fn childCleanupWindows(child: *process.Child) void { |
| 15497 | const handle = child.id orelse return; |
| 15498 | |
| 15499 | if (child.request_resource_usage_statistics) { |
| 15500 | var vmc: windows.PROCESS.VM_COUNTERS = undefined; |
| 15501 | switch (windows.ntdll.NtQueryInformationProcess( |
| 15502 | handle, |
| 15503 | .VmCounters, |
| 15504 | &vmc, |
| 15505 | @sizeOf(windows.PROCESS.VM_COUNTERS), |
| 15506 | null, |
| 15507 | )) { |
| 15508 | .SUCCESS => child.resource_usage_statistics.rusage = vmc, |
| 15509 | else => child.resource_usage_statistics.rusage = null, |
| 15510 | } |
| 15511 | } |
| 15512 | |
| 15513 | windows.CloseHandle(handle); |
| 15514 | child.id = null; |
| 15515 | |
| 15516 | windows.CloseHandle(child.thread_handle); |
| 15517 | child.thread_handle = undefined; |
| 15518 | |
| 15519 | if (child.stdin) |stdin| { |
| 15520 | windows.CloseHandle(stdin.handle); |
| 15521 | child.stdin = null; |
| 15522 | } |
| 15523 | if (child.stdout) |stdout| { |
| 15524 | windows.CloseHandle(stdout.handle); |
| 15525 | child.stdout = null; |
| 15526 | } |
| 15527 | if (child.stderr) |stderr| { |
| 15528 | windows.CloseHandle(stderr.handle); |
| 15529 | child.stderr = null; |
| 15530 | } |
| 15531 | } |
| 15532 | |
| 15533 | fn childWaitPosix(child: *process.Child) process.Child.WaitError!process.Child.Term { |
| 15534 | defer childCleanupPosix(child); |
| 15535 | |
| 15536 | const pid = child.id.?; |
| 15537 | |
| 15538 | var ru: posix.rusage = undefined; |
| 15539 | const ru_ptr = if (child.request_resource_usage_statistics) &ru else null; |
| 15540 | |
| 15541 | if (have_wait4) { |
| 15542 | var status: if (builtin.link_libc) c_int else i32 = undefined; |
| 15543 | const syscall: Syscall = try .start(); |
| 15544 | while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) { |
| 15545 | .SUCCESS => { |
| 15546 | syscall.finish(); |
| 15547 | if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; |
| 15548 | return statusToTerm(@bitCast(status)); |
| 15549 | }, |
| 15550 | .INTR => { |
| 15551 | try syscall.checkCancel(); |
| 15552 | continue; |
| 15553 | }, |
| 15554 | .CHILD => |err| return syscall.errnoBug(err), // Double-free. |
| 15555 | else => |err| return syscall.unexpectedErrno(err), |
| 15556 | }; |
| 15557 | } |
| 15558 | |
| 15559 | if (have_waitid) { |
| 15560 | const linux = std.os.linux; // Bypass libc which has the wrong signature. |
| 15561 | var info: linux.siginfo_t = undefined; |
| 15562 | const syscall: Syscall = try .start(); |
| 15563 | while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) { |
| 15564 | .SUCCESS => { |
| 15565 | syscall.finish(); |
| 15566 | if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; |
| 15567 | const status: u32 = @bitCast(info.fields.common.second.sigchld.status); |
| 15568 | const code: linux.CLD = @fromBackingInt(@intCast(info.code)); |
| 15569 | return switch (code) { |
| 15570 | .EXITED => .{ .exited = @truncate(status) }, |
| 15571 | .KILLED, .DUMPED => .{ .signal = @fromBackingInt(@intCast(status)) }, |
| 15572 | .TRAPPED, .STOPPED => .{ .stopped = @fromBackingInt(@intCast(status)) }, |
| 15573 | _, .CONTINUED => .{ .unknown = status }, |
| 15574 | }; |
| 15575 | }, |
| 15576 | .INTR => { |
| 15577 | try syscall.checkCancel(); |
| 15578 | continue; |
| 15579 | }, |
| 15580 | .CHILD => |err| return syscall.errnoBug(err), // Double-free. |
| 15581 | else => |err| return syscall.unexpectedErrno(err), |
| 15582 | }; |
| 15583 | } |
| 15584 | |
| 15585 | var status: if (builtin.link_libc) c_int else i32 = undefined; |
| 15586 | const syscall: Syscall = try .start(); |
| 15587 | while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { |
| 15588 | .SUCCESS => { |
| 15589 | syscall.finish(); |
| 15590 | return statusToTerm(@bitCast(status)); |
| 15591 | }, |
| 15592 | .INTR => { |
| 15593 | try syscall.checkCancel(); |
| 15594 | continue; |
| 15595 | }, |
| 15596 | .CHILD => |err| return syscall.errnoBug(err), // Double-free. |
| 15597 | else => |err| return syscall.unexpectedErrno(err), |
| 15598 | }; |
| 15599 | } |
| 15600 | |
| 15601 | pub fn statusToTerm(status: u32) process.Child.Term { |
| 15602 | return if (posix.W.IFEXITED(status)) |
| 15603 | .{ .exited = posix.W.EXITSTATUS(status) } |
| 15604 | else if (posix.W.IFSIGNALED(status)) |
| 15605 | .{ .signal = posix.W.TERMSIG(status) } |
| 15606 | else if (posix.W.IFSTOPPED(status)) |
| 15607 | .{ .stopped = posix.W.STOPSIG(status) } |
| 15608 | else |
| 15609 | .{ .unknown = status }; |
| 15610 | } |
| 15611 | |
| 15612 | fn childKillPosix(child: *process.Child) !void { |
| 15613 | // Entire function body is intentionally uncancelable. |
| 15614 | |
| 15615 | const pid = child.id.?; |
| 15616 | |
| 15617 | while (true) switch (posix.errno(posix.system.kill(pid, .TERM))) { |
| 15618 | .SUCCESS => break, |
| 15619 | .INTR => continue, |
| 15620 | .PERM => return error.PermissionDenied, |
| 15621 | .INVAL => |err| return errnoBug(err), |
| 15622 | .SRCH => |err| return errnoBug(err), |
| 15623 | else => |err| return posix.unexpectedErrno(err), |
| 15624 | }; |
| 15625 | |
| 15626 | if (have_wait4) { |
| 15627 | var status: if (builtin.link_libc) c_int else i32 = undefined; |
| 15628 | while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, null))) { |
| 15629 | .SUCCESS => return, |
| 15630 | .INTR => continue, |
| 15631 | .CHILD => |err| return errnoBug(err), // Double-free. |
| 15632 | else => |err| return posix.unexpectedErrno(err), |
| 15633 | }; |
| 15634 | } |
| 15635 | |
| 15636 | if (have_waitid) { |
| 15637 | const linux = std.os.linux; // Bypass libc which has the wrong signature. |
| 15638 | var info: linux.siginfo_t = undefined; |
| 15639 | while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, null))) { |
| 15640 | .SUCCESS => return, |
| 15641 | .INTR => continue, |
| 15642 | .CHILD => |err| return errnoBug(err), // Double-free. |
| 15643 | else => |err| return posix.unexpectedErrno(err), |
| 15644 | }; |
| 15645 | } |
| 15646 | |
| 15647 | var status: if (builtin.link_libc) c_int else i32 = undefined; |
| 15648 | while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { |
| 15649 | .SUCCESS => return, |
| 15650 | .INTR => continue, |
| 15651 | .CHILD => |err| return errnoBug(err), // Double-free. |
| 15652 | else => |err| return posix.unexpectedErrno(err), |
| 15653 | }; |
| 15654 | } |
| 15655 | |
| 15656 | fn childCleanupPosix(child: *process.Child) void { |
| 15657 | if (child.stdin) |stdin| { |
| 15658 | closeFd(stdin.handle); |
| 15659 | child.stdin = null; |
| 15660 | } |
| 15661 | if (child.stdout) |stdout| { |
| 15662 | closeFd(stdout.handle); |
| 15663 | child.stdout = null; |
| 15664 | } |
| 15665 | if (child.stderr) |stderr| { |
| 15666 | closeFd(stderr.handle); |
| 15667 | child.stderr = null; |
| 15668 | } |
| 15669 | child.id = null; |
| 15670 | } |
| 15671 | |
| 15672 | /// Errors that can occur between fork() and execv() |
| 15673 | const ForkBailError = process.SpawnError || process.ReplaceError; |
| 15674 | |
| 15675 | /// Child of fork calls this to report an error to the fork parent. Then the |
| 15676 | /// child exits. |
| 15677 | fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn { |
| 15678 | writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {}; |
| 15679 | // If we're linking libc, some naughty applications may have registered atexit handlers |
| 15680 | // which we really do not want to run in the fork child. I caught LLVM doing this and |
| 15681 | // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne, |
| 15682 | // "Why'd you have to go and make things so complicated?" |
| 15683 | if (builtin.link_libc) { |
| 15684 | // The `_exit` function does nothing but make the exit syscall, unlike `exit`. |
| 15685 | std.c._exit(1); |
| 15686 | } else if (native_os == .linux and !builtin.single_threaded) { |
| 15687 | std.os.linux.exit_group(1); |
| 15688 | } else { |
| 15689 | posix.system.exit(1); |
| 15690 | } |
| 15691 | } |
| 15692 | |
| 15693 | fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void { |
| 15694 | var buffer: [8]u8 = undefined; |
| 15695 | std.mem.writeInt(u64, &buffer, value, .little); |
| 15696 | // Skip the cancel mechanism. |
| 15697 | var i: usize = 0; |
| 15698 | while (true) { |
| 15699 | const rc = posix.system.write(fd, buffer[i..].ptr, buffer.len - i); |
| 15700 | switch (posix.errno(rc)) { |
| 15701 | .SUCCESS => { |
| 15702 | const n: usize = @intCast(rc); |
| 15703 | i += n; |
| 15704 | if (buffer.len - i == 0) return; |
| 15705 | }, |
| 15706 | .INTR => continue, |
| 15707 | else => return error.SystemResources, |
| 15708 | } |
| 15709 | } |
| 15710 | } |
| 15711 | |
| 15712 | fn readIntFd(fd: posix.fd_t) !ErrInt { |
| 15713 | var buffer: [8]u8 = undefined; |
| 15714 | var i: usize = 0; |
| 15715 | while (true) { |
| 15716 | const rc = posix.system.read(fd, buffer[i..].ptr, buffer.len - i); |
| 15717 | switch (posix.errno(rc)) { |
| 15718 | .SUCCESS => { |
| 15719 | const n: usize = @intCast(rc); |
| 15720 | if (n == 0) break; |
| 15721 | i += n; |
| 15722 | continue; |
| 15723 | }, |
| 15724 | .INTR => continue, |
| 15725 | else => |err| return posix.unexpectedErrno(err), |
| 15726 | } |
| 15727 | } |
| 15728 | if (buffer.len - i != 0) return error.EndOfStream; |
| 15729 | return @intCast(std.mem.readInt(u64, &buffer, .little)); |
| 15730 | } |
| 15731 | |
| 15732 | const ErrInt = @Int(.unsigned, @sizeOf(anyerror) * 8); |
| 15733 | |
| 15734 | fn destroyPipe(pipe: [2]posix.fd_t) void { |
| 15735 | if (pipe[0] != -1) closeFd(pipe[0]); |
| 15736 | if (pipe[0] != pipe[1]) closeFd(pipe[1]); |
| 15737 | } |
| 15738 | |
| 15739 | fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { |
| 15740 | switch (stdio) { |
| 15741 | .pipe => try dup2(pipe_fd, std_fileno), |
| 15742 | .close => closeFd(std_fileno), |
| 15743 | .inherit => {}, |
| 15744 | .ignore => try dup2(dev_null_fd, std_fileno), |
| 15745 | .file => |file| try dup2(file.handle, std_fileno), |
| 15746 | } |
| 15747 | } |
| 15748 | |
| 15749 | fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 15750 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 15751 | |
| 15752 | const any_ignore = |
| 15753 | options.stdin == .ignore or |
| 15754 | options.stdout == .ignore or |
| 15755 | options.stderr == .ignore; |
| 15756 | const nul_handle = if (any_ignore) try getNulDevice(t) else undefined; |
| 15757 | |
| 15758 | const any_inherit = |
| 15759 | options.stdin == .inherit or |
| 15760 | options.stdout == .inherit or |
| 15761 | options.stderr == .inherit; |
| 15762 | const peb = if (any_inherit) windows.peb() else undefined; |
| 15763 | |
| 15764 | const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{ |
| 15765 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15766 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15767 | .outbound = true, |
| 15768 | }) else undefined; |
| 15769 | errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle); |
| 15770 | |
| 15771 | const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{ |
| 15772 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, |
| 15773 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15774 | .inbound = true, |
| 15775 | }) else undefined; |
| 15776 | errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle); |
| 15777 | |
| 15778 | const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{ |
| 15779 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, |
| 15780 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, |
| 15781 | .inbound = true, |
| 15782 | }) else undefined; |
| 15783 | errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle); |
| 15784 | |
| 15785 | const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{ |
| 15786 | .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, |
| 15787 | .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } }, |
| 15788 | .inbound = true, |
| 15789 | .quota = std.Progress.max_packet_len * 2, |
| 15790 | }) else undefined; |
| 15791 | errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle); |
| 15792 | |
| 15793 | var siStartInfo: windows.STARTUPINFOW = .{ |
| 15794 | .cb = @sizeOf(windows.STARTUPINFOW), |
| 15795 | .dwFlags = windows.STARTF_USESTDHANDLES, |
| 15796 | .hStdInput = switch (options.stdin) { |
| 15797 | .inherit => peb.ProcessParameters.hStdInput, |
| 15798 | .file => |file| try OpenFile(&.{}, .{ |
| 15799 | .access_mask = .{ |
| 15800 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 15801 | .GENERIC = .{ .READ = true }, |
| 15802 | }, |
| 15803 | .dir = file.handle, |
| 15804 | .sa = &.{ |
| 15805 | .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), |
| 15806 | .lpSecurityDescriptor = null, |
| 15807 | .bInheritHandle = .TRUE, |
| 15808 | }, |
| 15809 | .creation = .OPEN, |
| 15810 | }), |
| 15811 | .ignore => nul_handle, |
| 15812 | .pipe => stdin_pipe[1], |
| 15813 | .close => null, |
| 15814 | }, |
| 15815 | .hStdOutput = switch (options.stdout) { |
| 15816 | .inherit => peb.ProcessParameters.hStdOutput, |
| 15817 | .file => |file| try OpenFile(&.{}, .{ |
| 15818 | .access_mask = .{ |
| 15819 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 15820 | .GENERIC = .{ .WRITE = true }, |
| 15821 | }, |
| 15822 | .dir = file.handle, |
| 15823 | .sa = &.{ |
| 15824 | .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), |
| 15825 | .lpSecurityDescriptor = null, |
| 15826 | .bInheritHandle = .TRUE, |
| 15827 | }, |
| 15828 | .creation = .OPEN, |
| 15829 | }), |
| 15830 | .ignore => nul_handle, |
| 15831 | .pipe => stdout_pipe[1], |
| 15832 | .close => null, |
| 15833 | }, |
| 15834 | .hStdError = switch (options.stderr) { |
| 15835 | .inherit => peb.ProcessParameters.hStdError, |
| 15836 | .file => |file| try OpenFile(&.{}, .{ |
| 15837 | .access_mask = .{ |
| 15838 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 15839 | .GENERIC = .{ .WRITE = true }, |
| 15840 | }, |
| 15841 | .dir = file.handle, |
| 15842 | .sa = &.{ |
| 15843 | .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), |
| 15844 | .lpSecurityDescriptor = null, |
| 15845 | .bInheritHandle = .TRUE, |
| 15846 | }, |
| 15847 | .creation = .OPEN, |
| 15848 | }), |
| 15849 | .ignore => nul_handle, |
| 15850 | .pipe => stderr_pipe[1], |
| 15851 | .close => null, |
| 15852 | }, |
| 15853 | |
| 15854 | .lpReserved = null, |
| 15855 | .lpDesktop = null, |
| 15856 | .lpTitle = null, |
| 15857 | .dwX = 0, |
| 15858 | .dwY = 0, |
| 15859 | .dwXSize = 0, |
| 15860 | .dwYSize = 0, |
| 15861 | .dwXCountChars = 0, |
| 15862 | .dwYCountChars = 0, |
| 15863 | .dwFillAttribute = 0, |
| 15864 | .wShowWindow = 0, |
| 15865 | .cbReserved2 = 0, |
| 15866 | .lpReserved2 = null, |
| 15867 | }; |
| 15868 | var piProcInfo: windows.PROCESS.INFORMATION = undefined; |
| 15869 | |
| 15870 | var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); |
| 15871 | defer arena_allocator.deinit(); |
| 15872 | const arena = arena_allocator.allocator(); |
| 15873 | |
| 15874 | const cwd_w = cwd_w: { |
| 15875 | switch (options.cwd) { |
| 15876 | .inherit => break :cwd_w null, |
| 15877 | .dir => |cwd_dir| { |
| 15878 | var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); |
| 15879 | const dir_path = try GetFinalPathNameByHandle( |
| 15880 | cwd_dir.handle, |
| 15881 | .{}, |
| 15882 | dir_path_buffer[0..windows.PATH_MAX_WIDE], |
| 15883 | ); |
| 15884 | dir_path_buffer[dir_path.len] = 0; |
| 15885 | // Shrink the allocation down to just the path buffer + sentinel |
| 15886 | dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1); |
| 15887 | break :cwd_w dir_path_buffer[0..dir_path.len :0]; |
| 15888 | }, |
| 15889 | .path => |cwd| { |
| 15890 | break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); |
| 15891 | }, |
| 15892 | } |
| 15893 | }; |
| 15894 | const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; |
| 15895 | |
| 15896 | const env_block = env_block: { |
| 15897 | const prog_handle = if (options.progress_node.index != .none) |
| 15898 | prog_pipe[1] |
| 15899 | else |
| 15900 | windows.INVALID_HANDLE_VALUE; |
| 15901 | if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{ |
| 15902 | .zig_progress_handle = prog_handle, |
| 15903 | }); |
| 15904 | break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{ |
| 15905 | .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE, |
| 15906 | }); |
| 15907 | }; |
| 15908 | |
| 15909 | const app_name_wtf8 = options.argv[0]; |
| 15910 | const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); |
| 15911 | |
| 15912 | // The cwd provided by options is in effect when choosing the executable |
| 15913 | // path to match POSIX semantics. |
| 15914 | const cwd_path_w = x: { |
| 15915 | // If the app name is absolute, then we need to use its dirname as the cwd |
| 15916 | if (app_name_is_absolute) { |
| 15917 | const dir = Dir.path.dirname(app_name_wtf8).?; |
| 15918 | break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir); |
| 15919 | } else if (cwd_w) |cwd| { |
| 15920 | break :x cwd; |
| 15921 | } else { |
| 15922 | break :x &[_:0]u16{}; // empty for cwd |
| 15923 | } |
| 15924 | }; |
| 15925 | |
| 15926 | // If the app name has more than just a filename, then we need to separate |
| 15927 | // that into the basename and dirname and use the dirname as an addition to |
| 15928 | // the cwd path. This is because NtQueryDirectoryFile cannot accept |
| 15929 | // FileName params with path separators. |
| 15930 | const app_basename_wtf8 = Dir.path.basename(app_name_wtf8); |
| 15931 | // If the app name is absolute, then the cwd will already have the app's dirname in it, |
| 15932 | // so only populate app_dirname if app name is a relative path with > 0 path separators. |
| 15933 | const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null; |
| 15934 | const app_dirname_w: ?[:0]u16 = x: { |
| 15935 | if (maybe_app_dirname_wtf8) |app_dirname_wtf8| { |
| 15936 | break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8); |
| 15937 | } |
| 15938 | break :x null; |
| 15939 | }; |
| 15940 | const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8); |
| 15941 | |
| 15942 | const flags: windows.CreateProcessFlags = .{ |
| 15943 | .create_suspended = options.start_suspended, |
| 15944 | .create_unicode_environment = true, |
| 15945 | .create_no_window = options.create_no_window, |
| 15946 | }; |
| 15947 | |
| 15948 | run: { |
| 15949 | // We have to scan each time because the PEB environment pointer is not stable. |
| 15950 | const env_strings: WindowsEnvironStrings = .scan(); |
| 15951 | const PATH = env_strings.PATH orelse &[_:0]u16{}; |
| 15952 | const PATHEXT = env_strings.PATHEXT orelse &[_:0]u16{}; |
| 15953 | |
| 15954 | // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules |
| 15955 | // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously |
| 15956 | // constructed arguments. |
| 15957 | // |
| 15958 | // We'll need to wait until we're actually trying to run the command to know for sure |
| 15959 | // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually |
| 15960 | // serializing the command line until we determine how it should be serialized. |
| 15961 | var cmd_line_cache = WindowsCommandLineCache.init(arena, options.argv); |
| 15962 | |
| 15963 | var app_buf: std.ArrayList(u16) = .empty; |
| 15964 | try app_buf.appendSlice(arena, app_name_w); |
| 15965 | |
| 15966 | var dir_buf: std.ArrayList(u16) = .empty; |
| 15967 | |
| 15968 | if (cwd_path_w.len > 0) { |
| 15969 | try dir_buf.appendSlice(arena, cwd_path_w); |
| 15970 | } |
| 15971 | if (app_dirname_w) |app_dir| { |
| 15972 | if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep); |
| 15973 | try dir_buf.appendSlice(arena, app_dir); |
| 15974 | } |
| 15975 | |
| 15976 | windowsCreateProcessPathExt( |
| 15977 | arena, |
| 15978 | &dir_buf, |
| 15979 | &app_buf, |
| 15980 | PATHEXT, |
| 15981 | &cmd_line_cache, |
| 15982 | env_block, |
| 15983 | cwd_w_ptr, |
| 15984 | flags, |
| 15985 | &siStartInfo, |
| 15986 | &piProcInfo, |
| 15987 | ) catch |no_path_err| { |
| 15988 | const original_err = switch (no_path_err) { |
| 15989 | // argv[0] contains unsupported characters that will never resolve to a valid exe. |
| 15990 | error.InvalidArg0 => return error.FileNotFound, |
| 15991 | error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e, |
| 15992 | error.UnrecoverableInvalidExe => return error.InvalidExe, |
| 15993 | else => |e| return e, |
| 15994 | }; |
| 15995 | |
| 15996 | // If the app name had path separators, that disallows PATH searching, |
| 15997 | // and there's no need to search the PATH if the app name is absolute. |
| 15998 | // We still search the path if the cwd is absolute because of the |
| 15999 | // "cwd provided by options is in effect when choosing the executable path |
| 16000 | // to match posix semantics" behavior--we don't want to skip searching |
| 16001 | // the PATH just because we were trying to set the cwd of the child process. |
| 16002 | if (app_dirname_w != null or app_name_is_absolute) { |
| 16003 | return original_err; |
| 16004 | } |
| 16005 | |
| 16006 | var it = std.mem.tokenizeScalar(u16, PATH, ';'); |
| 16007 | while (it.next()) |search_path| { |
| 16008 | dir_buf.clearRetainingCapacity(); |
| 16009 | try dir_buf.appendSlice(arena, search_path); |
| 16010 | |
| 16011 | if (windowsCreateProcessPathExt( |
| 16012 | arena, |
| 16013 | &dir_buf, |
| 16014 | &app_buf, |
| 16015 | PATHEXT, |
| 16016 | &cmd_line_cache, |
| 16017 | env_block, |
| 16018 | cwd_w_ptr, |
| 16019 | flags, |
| 16020 | &siStartInfo, |
| 16021 | &piProcInfo, |
| 16022 | )) { |
| 16023 | break :run; |
| 16024 | } else |err| switch (err) { |
| 16025 | // argv[0] contains unsupported characters that will never resolve to a valid exe. |
| 16026 | error.InvalidArg0 => return error.FileNotFound, |
| 16027 | error.FileNotFound, error.AccessDenied, error.InvalidExe => continue, |
| 16028 | error.UnrecoverableInvalidExe => return error.InvalidExe, |
| 16029 | else => |e| return e, |
| 16030 | } |
| 16031 | } else { |
| 16032 | return original_err; |
| 16033 | } |
| 16034 | }; |
| 16035 | } |
| 16036 | |
| 16037 | if (options.progress_node.index != .none) { |
| 16038 | windows.CloseHandle(prog_pipe[1]); |
| 16039 | options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); |
| 16040 | } |
| 16041 | |
| 16042 | return .{ |
| 16043 | .id = piProcInfo.hProcess, |
| 16044 | .thread_handle = piProcInfo.hThread, |
| 16045 | .stdin = stdin: switch (options.stdin) { |
| 16046 | .file => { |
| 16047 | windows.CloseHandle(siStartInfo.hStdInput.?); |
| 16048 | break :stdin null; |
| 16049 | }, |
| 16050 | .pipe => { |
| 16051 | windows.CloseHandle(stdin_pipe[1]); |
| 16052 | break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } }; |
| 16053 | }, |
| 16054 | else => null, |
| 16055 | }, |
| 16056 | .stdout = stdout: switch (options.stdout) { |
| 16057 | .file => { |
| 16058 | windows.CloseHandle(siStartInfo.hStdOutput.?); |
| 16059 | break :stdout null; |
| 16060 | }, |
| 16061 | .pipe => { |
| 16062 | windows.CloseHandle(stdout_pipe[1]); |
| 16063 | break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } }; |
| 16064 | }, |
| 16065 | else => null, |
| 16066 | }, |
| 16067 | .stderr = stderr: switch (options.stderr) { |
| 16068 | .file => { |
| 16069 | windows.CloseHandle(siStartInfo.hStdError.?); |
| 16070 | break :stderr null; |
| 16071 | }, |
| 16072 | .pipe => { |
| 16073 | windows.CloseHandle(stderr_pipe[1]); |
| 16074 | break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } }; |
| 16075 | }, |
| 16076 | else => null, |
| 16077 | }, |
| 16078 | .request_resource_usage_statistics = options.request_resource_usage_statistics, |
| 16079 | }; |
| 16080 | } |
| 16081 | |
| 16082 | fn inheritFile() windows.HANDLE {} |
| 16083 | |
| 16084 | fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { |
| 16085 | { |
| 16086 | mutexLock(&t.mutex); |
| 16087 | defer mutexUnlock(&t.mutex); |
| 16088 | if (t.random_file.handle) |handle| return handle; |
| 16089 | } |
| 16090 | |
| 16091 | var fresh_handle: windows.HANDLE = undefined; |
| 16092 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 16093 | var syscall: Syscall = try .start(); |
| 16094 | while (true) switch (windows.ntdll.NtOpenFile( |
| 16095 | &fresh_handle, |
| 16096 | .{ |
| 16097 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 16098 | .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, |
| 16099 | }, |
| 16100 | &.{ .ObjectName = @constCast(&windows.UNICODE_STRING.init( |
| 16101 | &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }, |
| 16102 | )) }, |
| 16103 | &io_status_block, |
| 16104 | .VALID_FLAGS, |
| 16105 | .{ .IO = .SYNCHRONOUS_NONALERT }, |
| 16106 | )) { |
| 16107 | .SUCCESS => { |
| 16108 | syscall.finish(); |
| 16109 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 16110 | defer mutexUnlock(&t.mutex); |
| 16111 | if (t.random_file.handle) |prev_handle| { |
| 16112 | windows.CloseHandle(fresh_handle); |
| 16113 | return prev_handle; |
| 16114 | } else { |
| 16115 | t.random_file.handle = fresh_handle; |
| 16116 | return fresh_handle; |
| 16117 | } |
| 16118 | }, |
| 16119 | .CANCELLED => { |
| 16120 | try syscall.checkCancel(); |
| 16121 | continue; |
| 16122 | }, |
| 16123 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.EntropyUnavailable), // Observed on wine 10.0 |
| 16124 | else => return syscall.fail(error.EntropyUnavailable), |
| 16125 | }; |
| 16126 | } |
| 16127 | |
| 16128 | fn getNulDevice(t: *Threaded) !windows.HANDLE { |
| 16129 | { |
| 16130 | mutexLock(&t.mutex); |
| 16131 | defer mutexUnlock(&t.mutex); |
| 16132 | if (t.null_file.handle) |handle| return handle; |
| 16133 | } |
| 16134 | |
| 16135 | var fresh_handle: windows.HANDLE = undefined; |
| 16136 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 16137 | var syscall: Syscall = try .start(); |
| 16138 | while (true) switch (windows.ntdll.NtOpenFile( |
| 16139 | &fresh_handle, |
| 16140 | .{ |
| 16141 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 16142 | .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } }, |
| 16143 | }, |
| 16144 | &.{ |
| 16145 | .Attributes = .{ .INHERIT = true }, |
| 16146 | .ObjectName = @constCast(&windows.UNICODE_STRING.init( |
| 16147 | &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, |
| 16148 | )), |
| 16149 | }, |
| 16150 | &io_status_block, |
| 16151 | .VALID_FLAGS, |
| 16152 | .{ .IO = .SYNCHRONOUS_NONALERT }, |
| 16153 | )) { |
| 16154 | .SUCCESS => { |
| 16155 | syscall.finish(); |
| 16156 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 16157 | defer mutexUnlock(&t.mutex); |
| 16158 | if (t.null_file.handle) |prev_handle| { |
| 16159 | windows.CloseHandle(fresh_handle); |
| 16160 | return prev_handle; |
| 16161 | } else { |
| 16162 | t.null_file.handle = fresh_handle; |
| 16163 | return fresh_handle; |
| 16164 | } |
| 16165 | }, |
| 16166 | .CANCELLED => { |
| 16167 | try syscall.checkCancel(); |
| 16168 | continue; |
| 16169 | }, |
| 16170 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 16171 | .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), |
| 16172 | .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), |
| 16173 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 16174 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 16175 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 16176 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 16177 | .SHARING_VIOLATION => return syscall.fail(error.AccessDenied), |
| 16178 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 16179 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), |
| 16180 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 16181 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 16182 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 16183 | else => |status| return syscall.unexpectedNtstatus(status), |
| 16184 | }; |
| 16185 | } |
| 16186 | |
| 16187 | fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { |
| 16188 | { |
| 16189 | mutexLock(&t.mutex); |
| 16190 | defer mutexUnlock(&t.mutex); |
| 16191 | if (t.pipe_file.handle) |handle| return handle; |
| 16192 | } |
| 16193 | |
| 16194 | var fresh_handle: windows.HANDLE = undefined; |
| 16195 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 16196 | var syscall: Syscall = try .start(); |
| 16197 | while (true) switch (windows.ntdll.NtOpenFile( |
| 16198 | &fresh_handle, |
| 16199 | .{ .STANDARD = .{ .SYNCHRONIZE = true } }, |
| 16200 | &.{ |
| 16201 | .ObjectName = @constCast(&windows.UNICODE_STRING.init( |
| 16202 | &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' }, |
| 16203 | )), |
| 16204 | }, |
| 16205 | &io_status_block, |
| 16206 | .VALID_FLAGS, |
| 16207 | .{ .IO = .SYNCHRONOUS_NONALERT }, |
| 16208 | )) { |
| 16209 | .SUCCESS => { |
| 16210 | syscall.finish(); |
| 16211 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 16212 | defer mutexUnlock(&t.mutex); |
| 16213 | if (t.pipe_file.handle) |prev_handle| { |
| 16214 | windows.CloseHandle(fresh_handle); |
| 16215 | return prev_handle; |
| 16216 | } else { |
| 16217 | t.pipe_file.handle = fresh_handle; |
| 16218 | return fresh_handle; |
| 16219 | } |
| 16220 | }, |
| 16221 | .DELETE_PENDING => { |
| 16222 | // This error means that there *was* a file in this location on |
| 16223 | // the file system, but it was deleted. However, the OS is not |
| 16224 | // finished with the deletion operation, and so this CreateFile |
| 16225 | // call has failed. There is not really a sane way to handle |
| 16226 | // this other than retrying the creation after the OS finishes |
| 16227 | // the deletion. |
| 16228 | syscall.finish(); |
| 16229 | try parking_sleep.sleep(.{ .duration = .{ |
| 16230 | .raw = .fromMilliseconds(1), |
| 16231 | .clock = .awake, |
| 16232 | } }); |
| 16233 | syscall = try .start(); |
| 16234 | continue; |
| 16235 | }, |
| 16236 | .CANCELLED => { |
| 16237 | try syscall.checkCancel(); |
| 16238 | continue; |
| 16239 | }, |
| 16240 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 16241 | .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), |
| 16242 | .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), |
| 16243 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 16244 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 16245 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 16246 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 16247 | .SHARING_VIOLATION => return syscall.fail(error.AccessDenied), |
| 16248 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 16249 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), |
| 16250 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 16251 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 16252 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 16253 | else => |status| return syscall.unexpectedNtstatus(status), |
| 16254 | }; |
| 16255 | } |
| 16256 | |
| 16257 | /// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path. |
| 16258 | /// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path. |
| 16259 | /// Note: `app_buf` should not contain any leading path separators. |
| 16260 | /// Note: If the dir is the cwd, dir_buf should be empty (len = 0). |
| 16261 | fn windowsCreateProcessPathExt( |
| 16262 | arena: Allocator, |
| 16263 | dir_buf: *std.ArrayList(u16), |
| 16264 | app_buf: *std.ArrayList(u16), |
| 16265 | pathext: [:0]const u16, |
| 16266 | cmd_line_cache: *WindowsCommandLineCache, |
| 16267 | env_block: ?process.Environ.WindowsBlock, |
| 16268 | cwd_ptr: ?[*:0]u16, |
| 16269 | flags: windows.CreateProcessFlags, |
| 16270 | lpStartupInfo: *windows.STARTUPINFOW, |
| 16271 | lpProcessInformation: *windows.PROCESS.INFORMATION, |
| 16272 | ) !void { |
| 16273 | const app_name_len = app_buf.items.len; |
| 16274 | const dir_path_len = dir_buf.items.len; |
| 16275 | |
| 16276 | if (app_name_len == 0) return error.FileNotFound; |
| 16277 | |
| 16278 | defer app_buf.shrinkRetainingCapacity(app_name_len); |
| 16279 | defer dir_buf.shrinkRetainingCapacity(dir_path_len); |
| 16280 | |
| 16281 | // The name of the game here is to avoid CreateProcessW calls at all costs, |
| 16282 | // and only ever try calling it when we have a real candidate for execution. |
| 16283 | // Secondarily, we want to minimize the number of syscalls used when checking |
| 16284 | // for each PATHEXT-appended version of the app name. |
| 16285 | // |
| 16286 | // An overview of the technique used: |
| 16287 | // - Open the search directory for iteration (either cwd or a path from PATH) |
| 16288 | // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to |
| 16289 | // check if anything that could possibly match either the unappended version |
| 16290 | // of the app name or any of the versions with a PATHEXT value appended exists. |
| 16291 | // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early |
| 16292 | // without needing to use PATHEXT at all. |
| 16293 | // |
| 16294 | // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence |
| 16295 | // for any directory that doesn't contain any possible matches, instead of having |
| 16296 | // to use a separate look up for each individual filename combination (unappended + |
| 16297 | // each PATHEXT appended). For directories where the wildcard *does* match something, |
| 16298 | // we iterate the matches and take note of any that are either the unappended version, |
| 16299 | // or a version with a supported PATHEXT appended. We then try calling CreateProcessW |
| 16300 | // with the found versions in the appropriate order. |
| 16301 | const dir = dir: { |
| 16302 | // needs to be null-terminated |
| 16303 | try dir_buf.append(arena, 0); |
| 16304 | defer dir_buf.shrinkRetainingCapacity(dir_path_len); |
| 16305 | const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 16306 | const prefixed_path = try wToPrefixedFileW(null, dir_path_z, .{}); |
| 16307 | break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ |
| 16308 | .iterate = true, |
| 16309 | }) catch |err| switch (err) { |
| 16310 | // These errors must not be ignored because they should not be able |
| 16311 | // to affect which file is chosen to execute. Also `error.Canceled` |
| 16312 | // must never be swallowed. |
| 16313 | error.Canceled, |
| 16314 | error.SystemResources, |
| 16315 | error.Unexpected, |
| 16316 | error.ProcessFdQuotaExceeded, |
| 16317 | error.SystemFdQuotaExceeded, |
| 16318 | => |e| return e, |
| 16319 | |
| 16320 | error.AccessDenied, |
| 16321 | error.PermissionDenied, |
| 16322 | error.SymLinkLoop, |
| 16323 | error.FileNotFound, |
| 16324 | error.NotDir, |
| 16325 | error.NoDevice, |
| 16326 | error.NetworkNotFound, |
| 16327 | error.NameTooLong, |
| 16328 | error.BadPathName, |
| 16329 | => return error.FileNotFound, |
| 16330 | }; |
| 16331 | }; |
| 16332 | defer windows.CloseHandle(dir.handle); |
| 16333 | |
| 16334 | // Add wildcard and null-terminator |
| 16335 | try app_buf.append(arena, '*'); |
| 16336 | try app_buf.append(arena, 0); |
| 16337 | const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0]; |
| 16338 | |
| 16339 | // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries |
| 16340 | // returned per NtQueryDirectoryFile call. |
| 16341 | var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined; |
| 16342 | const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2); |
| 16343 | if (file_information_buf.len < file_info_maximum_single_entry_size) { |
| 16344 | @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry"); |
| 16345 | } |
| 16346 | var io_status: windows.IO_STATUS_BLOCK = undefined; |
| 16347 | |
| 16348 | const num_supported_pathext = @typeInfo(process.WindowsExtension).@"enum".field_names.len; |
| 16349 | var pathext_seen: [num_supported_pathext]bool = @splat(false); |
| 16350 | var any_pathext_seen = false; |
| 16351 | var unappended_exists = false; |
| 16352 | |
| 16353 | // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions |
| 16354 | // of the app_name we should try to spawn. |
| 16355 | // Note: This is necessary because the order of the files returned is filesystem-dependent: |
| 16356 | // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists. |
| 16357 | // On FAT32, it's possible for something like `blah.exe.obj` to be returned first. |
| 16358 | while (true) { |
| 16359 | // If we get nothing with the wildcard, then we can just bail out |
| 16360 | // as we know appending PATHEXT will not yield anything. |
| 16361 | switch (windows.ntdll.NtQueryDirectoryFile( |
| 16362 | dir.handle, |
| 16363 | null, |
| 16364 | null, |
| 16365 | null, |
| 16366 | &io_status, |
| 16367 | &file_information_buf, |
| 16368 | file_information_buf.len, |
| 16369 | .Directory, |
| 16370 | .FALSE, // single result |
| 16371 | &.init(app_name_wildcard), |
| 16372 | .FALSE, // restart iteration |
| 16373 | )) { |
| 16374 | .SUCCESS => {}, |
| 16375 | .NO_SUCH_FILE => return error.FileNotFound, |
| 16376 | .NO_MORE_FILES => break, |
| 16377 | .ACCESS_DENIED => return error.AccessDenied, |
| 16378 | else => |status| return windows.unexpectedStatus(status), |
| 16379 | } |
| 16380 | |
| 16381 | // According to the docs, this can only happen if there is not enough room in the |
| 16382 | // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry. |
| 16383 | // Therefore, this condition should not be possible to hit with the buffer size we use. |
| 16384 | std.debug.assert(io_status.Information != 0); |
| 16385 | |
| 16386 | var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf }; |
| 16387 | while (it.next()) |info| { |
| 16388 | // Skip directories |
| 16389 | if (info.FileAttributes.DIRECTORY) continue; |
| 16390 | const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2]; |
| 16391 | // Because all results start with the app_name since we're using the wildcard `app_name*`, |
| 16392 | // if the length is equal to app_name then this is an exact match |
| 16393 | if (filename.len == app_name_len) { |
| 16394 | // Note: We can't break early here because it's possible that the unappended version |
| 16395 | // fails to spawn, in which case we still want to try the PATHEXT appended versions. |
| 16396 | unappended_exists = true; |
| 16397 | } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| { |
| 16398 | pathext_seen[@backingInt(pathext_ext)] = true; |
| 16399 | any_pathext_seen = true; |
| 16400 | } |
| 16401 | } |
| 16402 | } |
| 16403 | |
| 16404 | const unappended_err = unappended: { |
| 16405 | if (unappended_exists) { |
| 16406 | if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { |
| 16407 | '/', '\\' => {}, |
| 16408 | else => try dir_buf.append(arena, Dir.path.sep), |
| 16409 | }; |
| 16410 | try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); |
| 16411 | try dir_buf.append(arena, 0); |
| 16412 | const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 16413 | |
| 16414 | const is_bat_or_cmd = bat_or_cmd: { |
| 16415 | const app_name = app_buf.items[0..app_name_len]; |
| 16416 | const ext_start = std.mem.findScalarLast(u16, app_name, '.') orelse break :bat_or_cmd false; |
| 16417 | const ext = app_name[ext_start..]; |
| 16418 | const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false; |
| 16419 | switch (ext_enum) { |
| 16420 | .cmd, .bat => break :bat_or_cmd true, |
| 16421 | else => break :bat_or_cmd false, |
| 16422 | } |
| 16423 | }; |
| 16424 | const cmd_line_w = if (is_bat_or_cmd) |
| 16425 | try cmd_line_cache.scriptCommandLine(full_app_name) |
| 16426 | else |
| 16427 | try cmd_line_cache.commandLine(); |
| 16428 | const app_name_w = if (is_bat_or_cmd) |
| 16429 | try cmd_line_cache.cmdExePath() |
| 16430 | else |
| 16431 | full_app_name; |
| 16432 | |
| 16433 | if (windowsCreateProcess( |
| 16434 | app_name_w.ptr, |
| 16435 | cmd_line_w.ptr, |
| 16436 | env_block, |
| 16437 | cwd_ptr, |
| 16438 | flags, |
| 16439 | lpStartupInfo, |
| 16440 | lpProcessInformation, |
| 16441 | )) |_| { |
| 16442 | return; |
| 16443 | } else |err| switch (err) { |
| 16444 | error.FileNotFound, |
| 16445 | error.AccessDenied, |
| 16446 | => break :unappended err, |
| 16447 | error.InvalidExe => { |
| 16448 | // On InvalidExe, if the extension of the app name is .exe then |
| 16449 | // it's treated as an unrecoverable error. Otherwise, it'll be |
| 16450 | // skipped as normal. |
| 16451 | const app_name = app_buf.items[0..app_name_len]; |
| 16452 | const ext_start = std.mem.findScalarLast(u16, app_name, '.') orelse break :unappended err; |
| 16453 | const ext = app_name[ext_start..]; |
| 16454 | if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { |
| 16455 | return error.UnrecoverableInvalidExe; |
| 16456 | } |
| 16457 | break :unappended err; |
| 16458 | }, |
| 16459 | else => return err, |
| 16460 | } |
| 16461 | } |
| 16462 | break :unappended error.FileNotFound; |
| 16463 | }; |
| 16464 | |
| 16465 | if (!any_pathext_seen) return unappended_err; |
| 16466 | |
| 16467 | // Now try any PATHEXT appended versions that we've seen |
| 16468 | var ext_it = std.mem.tokenizeScalar(u16, pathext, ';'); |
| 16469 | while (ext_it.next()) |ext| { |
| 16470 | const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue; |
| 16471 | if (!pathext_seen[@backingInt(ext_enum)]) continue; |
| 16472 | |
| 16473 | dir_buf.shrinkRetainingCapacity(dir_path_len); |
| 16474 | if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { |
| 16475 | '/', '\\' => {}, |
| 16476 | else => try dir_buf.append(arena, Dir.path.sep), |
| 16477 | }; |
| 16478 | try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); |
| 16479 | try dir_buf.appendSlice(arena, ext); |
| 16480 | try dir_buf.append(arena, 0); |
| 16481 | const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; |
| 16482 | |
| 16483 | const is_bat_or_cmd = switch (ext_enum) { |
| 16484 | .cmd, .bat => true, |
| 16485 | else => false, |
| 16486 | }; |
| 16487 | const cmd_line_w = if (is_bat_or_cmd) |
| 16488 | try cmd_line_cache.scriptCommandLine(full_app_name) |
| 16489 | else |
| 16490 | try cmd_line_cache.commandLine(); |
| 16491 | const app_name_w = if (is_bat_or_cmd) |
| 16492 | try cmd_line_cache.cmdExePath() |
| 16493 | else |
| 16494 | full_app_name; |
| 16495 | |
| 16496 | if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { |
| 16497 | return; |
| 16498 | } else |err| switch (err) { |
| 16499 | error.FileNotFound => continue, |
| 16500 | error.AccessDenied => continue, |
| 16501 | error.InvalidExe => { |
| 16502 | // On InvalidExe, if the extension of the app name is .exe then |
| 16503 | // it's treated as an unrecoverable error. Otherwise, it'll be |
| 16504 | // skipped as normal. |
| 16505 | if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { |
| 16506 | return error.UnrecoverableInvalidExe; |
| 16507 | } |
| 16508 | continue; |
| 16509 | }, |
| 16510 | else => return err, |
| 16511 | } |
| 16512 | } |
| 16513 | |
| 16514 | return unappended_err; |
| 16515 | } |
| 16516 | |
| 16517 | fn windowsCreateProcess( |
| 16518 | app_name: [*:0]u16, |
| 16519 | cmd_line: [*:0]u16, |
| 16520 | env_block: ?process.Environ.WindowsBlock, |
| 16521 | cwd_ptr: ?[*:0]u16, |
| 16522 | flags: windows.CreateProcessFlags, |
| 16523 | lpStartupInfo: *windows.STARTUPINFOW, |
| 16524 | lpProcessInformation: *windows.PROCESS.INFORMATION, |
| 16525 | ) !void { |
| 16526 | const syscall: Syscall = try .start(); |
| 16527 | while (true) { |
| 16528 | if (windows.kernel32.CreateProcessW( |
| 16529 | app_name, |
| 16530 | cmd_line, |
| 16531 | null, |
| 16532 | null, |
| 16533 | .TRUE, |
| 16534 | flags, |
| 16535 | if (env_block) |block| block.slice.ptr else null, |
| 16536 | cwd_ptr, |
| 16537 | lpStartupInfo, |
| 16538 | lpProcessInformation, |
| 16539 | ).toBool()) { |
| 16540 | return syscall.finish(); |
| 16541 | } else switch (windows.GetLastError()) { |
| 16542 | .INVALID_PARAMETER => unreachable, |
| 16543 | .OPERATION_ABORTED => { |
| 16544 | try syscall.checkCancel(); |
| 16545 | continue; |
| 16546 | }, |
| 16547 | .FILE_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 16548 | .PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 16549 | .DIRECTORY => return syscall.fail(error.FileNotFound), |
| 16550 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 16551 | .INVALID_NAME => return syscall.fail(error.InvalidName), |
| 16552 | .FILENAME_EXCED_RANGE => return syscall.fail(error.NameTooLong), |
| 16553 | .SHARING_VIOLATION => return syscall.fail(error.FileBusy), |
| 16554 | .COMMITMENT_LIMIT => return syscall.fail(error.SystemResources), |
| 16555 | |
| 16556 | // These are all the system errors that are mapped to ENOEXEC by |
| 16557 | // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error |
| 16558 | // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) |
| 16559 | // or urt/misc/errno.cpp (newer SDK) in the Windows SDK. |
| 16560 | .BAD_FORMAT, |
| 16561 | .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp |
| 16562 | .INVALID_STACKSEG, |
| 16563 | .INVALID_MODULETYPE, |
| 16564 | .INVALID_EXE_SIGNATURE, |
| 16565 | .EXE_MARKED_INVALID, |
| 16566 | .BAD_EXE_FORMAT, |
| 16567 | .ITERATED_DATA_EXCEEDS_64k, |
| 16568 | .INVALID_MINALLOCSIZE, |
| 16569 | .DYNLINK_FROM_INVALID_RING, |
| 16570 | .IOPL_NOT_ENABLED, |
| 16571 | .INVALID_SEGDPL, |
| 16572 | .AUTODATASEG_EXCEEDS_64k, |
| 16573 | .RING2SEG_MUST_BE_MOVABLE, |
| 16574 | .RELOC_CHAIN_XEEDS_SEGLIM, |
| 16575 | .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp |
| 16576 | // This one is not mapped to ENOEXEC but it is possible, for example |
| 16577 | // when calling CreateProcessW on a plain text file with a .exe extension |
| 16578 | .EXE_MACHINE_TYPE_MISMATCH, |
| 16579 | => return syscall.fail(error.InvalidExe), |
| 16580 | |
| 16581 | else => |err| { |
| 16582 | syscall.finish(); |
| 16583 | return windows.unexpectedError(err); |
| 16584 | }, |
| 16585 | } |
| 16586 | } |
| 16587 | } |
| 16588 | |
| 16589 | /// Case-insensitive WTF-16 lookup |
| 16590 | fn windowsCreateProcessSupportsExtension(ext: []const u16) ?process.WindowsExtension { |
| 16591 | comptime { |
| 16592 | // Ensures keeping this function in sync with the enum. |
| 16593 | const field_names = @typeInfo(process.WindowsExtension).@"enum".field_names; |
| 16594 | assert(field_names.len == 4); |
| 16595 | assert(@backingInt(process.WindowsExtension.bat) == 0); |
| 16596 | assert(@backingInt(process.WindowsExtension.cmd) == 1); |
| 16597 | assert(@backingInt(process.WindowsExtension.com) == 2); |
| 16598 | assert(@backingInt(process.WindowsExtension.exe) == 3); |
| 16599 | } |
| 16600 | |
| 16601 | if (ext.len != 4) return null; |
| 16602 | const State = enum { |
| 16603 | start, |
| 16604 | dot, |
| 16605 | b, |
| 16606 | ba, |
| 16607 | c, |
| 16608 | cm, |
| 16609 | co, |
| 16610 | e, |
| 16611 | ex, |
| 16612 | }; |
| 16613 | var state: State = .start; |
| 16614 | for (ext) |c| switch (state) { |
| 16615 | .start => switch (c) { |
| 16616 | '.' => state = .dot, |
| 16617 | else => return null, |
| 16618 | }, |
| 16619 | .dot => switch (c) { |
| 16620 | 'b', 'B' => state = .b, |
| 16621 | 'c', 'C' => state = .c, |
| 16622 | 'e', 'E' => state = .e, |
| 16623 | else => return null, |
| 16624 | }, |
| 16625 | .b => switch (c) { |
| 16626 | 'a', 'A' => state = .ba, |
| 16627 | else => return null, |
| 16628 | }, |
| 16629 | .c => switch (c) { |
| 16630 | 'm', 'M' => state = .cm, |
| 16631 | 'o', 'O' => state = .co, |
| 16632 | else => return null, |
| 16633 | }, |
| 16634 | .e => switch (c) { |
| 16635 | 'x', 'X' => state = .ex, |
| 16636 | else => return null, |
| 16637 | }, |
| 16638 | .ba => switch (c) { |
| 16639 | 't', 'T' => return .bat, |
| 16640 | else => return null, |
| 16641 | }, |
| 16642 | .cm => switch (c) { |
| 16643 | 'd', 'D' => return .cmd, |
| 16644 | else => return null, |
| 16645 | }, |
| 16646 | .co => switch (c) { |
| 16647 | 'm', 'M' => return .com, |
| 16648 | else => return null, |
| 16649 | }, |
| 16650 | .ex => switch (c) { |
| 16651 | 'e', 'E' => return .exe, |
| 16652 | else => return null, |
| 16653 | }, |
| 16654 | }; |
| 16655 | return null; |
| 16656 | } |
| 16657 | |
| 16658 | test windowsCreateProcessSupportsExtension { |
| 16659 | try std.testing.expectEqual(process.WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?); |
| 16660 | try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null); |
| 16661 | } |
| 16662 | |
| 16663 | /// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW. |
| 16664 | /// |
| 16665 | /// Serialization is done on-demand and the result is cached in order to allow for: |
| 16666 | /// - Only serializing the particular type of command line needed (`.bat`/`.cmd` |
| 16667 | /// command line serialization is different from `.exe`/etc) |
| 16668 | /// - Reusing the serialized command lines if necessary (i.e. if the execution |
| 16669 | /// of a command fails and the PATH is going to be continued to be searched |
| 16670 | /// for more candidates) |
| 16671 | const WindowsCommandLineCache = struct { |
| 16672 | cmd_line: ?[:0]u16 = null, |
| 16673 | script_cmd_line: ?[:0]u16 = null, |
| 16674 | cmd_exe_path: ?[:0]u16 = null, |
| 16675 | argv: []const []const u8, |
| 16676 | allocator: Allocator, |
| 16677 | |
| 16678 | fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache { |
| 16679 | return .{ |
| 16680 | .allocator = allocator, |
| 16681 | .argv = argv, |
| 16682 | }; |
| 16683 | } |
| 16684 | |
| 16685 | fn deinit(self: *WindowsCommandLineCache) void { |
| 16686 | if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line); |
| 16687 | if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line); |
| 16688 | if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path); |
| 16689 | } |
| 16690 | |
| 16691 | fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 { |
| 16692 | if (self.cmd_line == null) { |
| 16693 | self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv); |
| 16694 | } |
| 16695 | return self.cmd_line.?; |
| 16696 | } |
| 16697 | |
| 16698 | /// Not cached, since the path to the batch script will change during PATH searching. |
| 16699 | /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched, |
| 16700 | /// then script_path should include both the search path and the script filename |
| 16701 | /// (this allows avoiding cmd.exe having to search the PATH again). |
| 16702 | fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 { |
| 16703 | if (self.script_cmd_line) |v| self.allocator.free(v); |
| 16704 | self.script_cmd_line = try argvToScriptCommandLineWindows( |
| 16705 | self.allocator, |
| 16706 | script_path, |
| 16707 | self.argv[1..], |
| 16708 | ); |
| 16709 | return self.script_cmd_line.?; |
| 16710 | } |
| 16711 | |
| 16712 | fn cmdExePath(self: *WindowsCommandLineCache) Allocator.Error![:0]u16 { |
| 16713 | if (self.cmd_exe_path == null) { |
| 16714 | // Remove trailing slash from system directory path; we'll re-add it below |
| 16715 | const system_dir = std.mem.trimEnd(u16, windows.getSystemDirectoryWtf16Le(), &.{ '/', '\\' }); |
| 16716 | const suffix = std.unicode.utf8ToUtf16LeStringLiteral("\\cmd.exe"); |
| 16717 | const buf = try self.allocator.allocSentinel(u16, system_dir.len + suffix.len, 0); |
| 16718 | errdefer comptime unreachable; |
| 16719 | @memcpy(buf[0..system_dir.len], system_dir); |
| 16720 | @memcpy(buf[system_dir.len..], suffix); |
| 16721 | self.cmd_exe_path = buf; |
| 16722 | } |
| 16723 | return self.cmd_exe_path.?; |
| 16724 | } |
| 16725 | }; |
| 16726 | |
| 16727 | const ArgvToScriptCommandLineError = error{ |
| 16728 | OutOfMemory, |
| 16729 | InvalidWtf8, |
| 16730 | /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed |
| 16731 | /// within arguments when executing a `.bat`/`.cmd` script. |
| 16732 | /// - NUL/LF signifiies end of arguments, so anything afterwards |
| 16733 | /// would be lost after execution. |
| 16734 | /// - CR is stripped by `cmd.exe`, so any CR codepoints |
| 16735 | /// would be lost after execution. |
| 16736 | InvalidBatchScriptArg, |
| 16737 | }; |
| 16738 | |
| 16739 | /// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific |
| 16740 | /// escaping rules. The caller owns the returned slice. |
| 16741 | /// |
| 16742 | /// Escapes `argv` using the suggested mitigation against arbitrary command execution from: |
| 16743 | /// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ |
| 16744 | /// |
| 16745 | /// The return of this function will look like |
| 16746 | /// `cmd.exe /d /e:ON /v:OFF /c "<escaped command line>"` |
| 16747 | /// and should be used as the `lpCommandLine` of `CreateProcessW`, while the return of |
| 16748 | /// `WindowsCommandLineCache.cmdExePath` should be used as `lpApplicationName`. |
| 16749 | /// |
| 16750 | /// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise. |
| 16751 | /// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem. |
| 16752 | fn argvToScriptCommandLineWindows( |
| 16753 | allocator: Allocator, |
| 16754 | /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD. |
| 16755 | /// The script must have been verified to exist at this path before calling this function. |
| 16756 | script_path: []const u16, |
| 16757 | /// Arguments, not including the script name itself. Expected to be encoded as WTF-8. |
| 16758 | script_args: []const []const u8, |
| 16759 | ) ArgvToScriptCommandLineError![:0]u16 { |
| 16760 | var buf = try std.array_list.Managed(u8).initCapacity(allocator, 64); |
| 16761 | defer buf.deinit(); |
| 16762 | |
| 16763 | // `/d` disables execution of AutoRun commands. |
| 16764 | // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation: |
| 16765 | // > If delayed expansion is enabled via the registry value DelayedExpansion, |
| 16766 | // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option. |
| 16767 | // > Escaping for % requires the command extension to be enabled. |
| 16768 | // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option. |
| 16769 | // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ |
| 16770 | buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \""); |
| 16771 | |
| 16772 | // Always quote the path to the script arg |
| 16773 | buf.appendAssumeCapacity('"'); |
| 16774 | // We always want the path to the batch script to include a path separator in order to |
| 16775 | // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary |
| 16776 | // command execution mitigation, we just know exactly what script we want to execute |
| 16777 | // at this point, and potentially making cmd.exe re-find it is unnecessary. |
| 16778 | // |
| 16779 | // If the script path does not have a path separator, then we know its relative to CWD and |
| 16780 | // we can just put `.\` in the front. |
| 16781 | if (std.mem.findAny(u16, script_path, &[_]u16{ |
| 16782 | std.mem.nativeToLittle(u16, '\\'), std.mem.nativeToLittle(u16, '/'), |
| 16783 | }) == null) { |
| 16784 | try buf.appendSlice(".\\"); |
| 16785 | } |
| 16786 | // Note that we don't do any escaping/mitigations for this argument, since the relevant |
| 16787 | // characters (", %, etc) are illegal in file paths and this function should only be called |
| 16788 | // with script paths that have been verified to exist. |
| 16789 | try std.unicode.wtf16LeToWtf8ArrayList(&buf, script_path); |
| 16790 | buf.appendAssumeCapacity('"'); |
| 16791 | |
| 16792 | for (script_args) |arg| { |
| 16793 | // Literal carriage returns get stripped when run through cmd.exe |
| 16794 | // and NUL/newlines act as 'end of command.' Because of this, it's basically |
| 16795 | // always a mistake to include these characters in argv, so it's |
| 16796 | // an error condition in order to ensure that the return of this |
| 16797 | // function can always roundtrip through cmd.exe. |
| 16798 | if (std.mem.findAny(u8, arg, "\x00\r\n") != null) { |
| 16799 | return error.InvalidBatchScriptArg; |
| 16800 | } |
| 16801 | |
| 16802 | // Separate args with a space. |
| 16803 | try buf.append(' '); |
| 16804 | |
| 16805 | // Need to quote if the argument is empty (otherwise the arg would just be lost) |
| 16806 | // or if the last character is a `\`, since then something like "%~2" in a .bat |
| 16807 | // script would cause the closing " to be escaped which we don't want. |
| 16808 | var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\'; |
| 16809 | if (!needs_quotes) { |
| 16810 | for (arg) |c| { |
| 16811 | switch (c) { |
| 16812 | // Known good characters that don't need to be quoted |
| 16813 | 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {}, |
| 16814 | // When in doubt, quote |
| 16815 | else => { |
| 16816 | needs_quotes = true; |
| 16817 | break; |
| 16818 | }, |
| 16819 | } |
| 16820 | } |
| 16821 | } |
| 16822 | if (needs_quotes) { |
| 16823 | try buf.append('"'); |
| 16824 | } |
| 16825 | var backslashes: usize = 0; |
| 16826 | for (arg) |c| { |
| 16827 | switch (c) { |
| 16828 | '\\' => { |
| 16829 | backslashes += 1; |
| 16830 | }, |
| 16831 | '"' => { |
| 16832 | try buf.appendNTimes('\\', backslashes); |
| 16833 | try buf.append('"'); |
| 16834 | backslashes = 0; |
| 16835 | }, |
| 16836 | // Replace `%` with `%%cd:~,%`. |
| 16837 | // |
| 16838 | // cmd.exe allows extracting a substring from an environment |
| 16839 | // variable with the syntax: `%foo:~<start_index>,<end_index>%`. |
| 16840 | // Therefore, `%cd:~,%` will always expand to an empty string |
| 16841 | // since both the start and end index are blank, and it is assumed |
| 16842 | // that `%cd%` is always available since it is a built-in variable |
| 16843 | // that corresponds to the current directory. |
| 16844 | // |
| 16845 | // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%` |
| 16846 | // will stop `%foo%` from being expanded and *after* expansion |
| 16847 | // we'll still be left with `%foo%` (the literal string). |
| 16848 | '%' => { |
| 16849 | // the trailing `%` is appended outside the switch |
| 16850 | try buf.appendSlice("%%cd:~,"); |
| 16851 | backslashes = 0; |
| 16852 | }, |
| 16853 | else => { |
| 16854 | backslashes = 0; |
| 16855 | }, |
| 16856 | } |
| 16857 | try buf.append(c); |
| 16858 | } |
| 16859 | if (needs_quotes) { |
| 16860 | try buf.appendNTimes('\\', backslashes); |
| 16861 | try buf.append('"'); |
| 16862 | } |
| 16863 | } |
| 16864 | |
| 16865 | try buf.append('"'); |
| 16866 | |
| 16867 | return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); |
| 16868 | } |
| 16869 | |
| 16870 | const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 }; |
| 16871 | |
| 16872 | /// Serializes `argv` to a Windows command-line string suitable for passing to a child process and |
| 16873 | /// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice. |
| 16874 | /// |
| 16875 | /// To avoid arbitrary command execution, this function should not be used when spawning `.bat`/`.cmd` scripts. |
| 16876 | /// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ |
| 16877 | /// |
| 16878 | /// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead. |
| 16879 | fn argvToCommandLineWindows( |
| 16880 | allocator: Allocator, |
| 16881 | argv: []const []const u8, |
| 16882 | ) ArgvToCommandLineError![:0]u16 { |
| 16883 | var buf = std.array_list.Managed(u8).init(allocator); |
| 16884 | defer buf.deinit(); |
| 16885 | |
| 16886 | if (argv.len != 0) { |
| 16887 | const arg0 = argv[0]; |
| 16888 | |
| 16889 | // The first argument must be quoted if it contains spaces or ASCII control characters |
| 16890 | // (excluding DEL). It also follows special quoting rules where backslashes have no special |
| 16891 | // interpretation, which makes it impossible to pass certain first arguments containing |
| 16892 | // double quotes to a child process without characters from the first argument leaking into |
| 16893 | // subsequent ones (which could have security implications). |
| 16894 | // |
| 16895 | // Empty arguments technically don't need quotes, but we quote them anyway for maximum |
| 16896 | // compatibility with different implementations of the 'CommandLineToArgvW' algorithm. |
| 16897 | // |
| 16898 | // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject |
| 16899 | // all first arguments containing double quotes, even ones that we could theoretically |
| 16900 | // serialize in unquoted form. |
| 16901 | var needs_quotes = arg0.len == 0; |
| 16902 | for (arg0) |c| { |
| 16903 | if (c <= ' ') { |
| 16904 | needs_quotes = true; |
| 16905 | } else if (c == '"') { |
| 16906 | return error.InvalidArg0; |
| 16907 | } |
| 16908 | } |
| 16909 | if (needs_quotes) { |
| 16910 | try buf.append('"'); |
| 16911 | try buf.appendSlice(arg0); |
| 16912 | try buf.append('"'); |
| 16913 | } else { |
| 16914 | try buf.appendSlice(arg0); |
| 16915 | } |
| 16916 | |
| 16917 | for (argv[1..]) |arg| { |
| 16918 | try buf.append(' '); |
| 16919 | |
| 16920 | // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes, |
| 16921 | // or if they are empty. For simplicity and for maximum compatibility with different |
| 16922 | // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII |
| 16923 | // control characters (again, excluding DEL). |
| 16924 | needs_quotes = for (arg) |c| { |
| 16925 | if (c <= ' ' or c == '"') { |
| 16926 | break true; |
| 16927 | } |
| 16928 | } else arg.len == 0; |
| 16929 | if (!needs_quotes) { |
| 16930 | try buf.appendSlice(arg); |
| 16931 | continue; |
| 16932 | } |
| 16933 | |
| 16934 | try buf.append('"'); |
| 16935 | var backslash_count: usize = 0; |
| 16936 | for (arg) |byte| { |
| 16937 | switch (byte) { |
| 16938 | '\\' => { |
| 16939 | backslash_count += 1; |
| 16940 | }, |
| 16941 | '"' => { |
| 16942 | try buf.appendNTimes('\\', backslash_count * 2 + 1); |
| 16943 | try buf.append('"'); |
| 16944 | backslash_count = 0; |
| 16945 | }, |
| 16946 | else => { |
| 16947 | try buf.appendNTimes('\\', backslash_count); |
| 16948 | try buf.append(byte); |
| 16949 | backslash_count = 0; |
| 16950 | }, |
| 16951 | } |
| 16952 | } |
| 16953 | try buf.appendNTimes('\\', backslash_count * 2); |
| 16954 | try buf.append('"'); |
| 16955 | } |
| 16956 | } |
| 16957 | |
| 16958 | return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); |
| 16959 | } |
| 16960 | |
| 16961 | test argvToCommandLineWindows { |
| 16962 | const t = testArgvToCommandLineWindows; |
| 16963 | |
| 16964 | try t(&.{ |
| 16965 | \\C:\Program Files\zig\zig.exe |
| 16966 | , |
| 16967 | \\run |
| 16968 | , |
| 16969 | \\.\src\main.zig |
| 16970 | , |
| 16971 | \\-target |
| 16972 | , |
| 16973 | \\x86_64-windows-gnu |
| 16974 | , |
| 16975 | \\-O |
| 16976 | , |
| 16977 | \\safe |
| 16978 | , |
| 16979 | \\-- |
| 16980 | , |
| 16981 | \\--emoji=🗿 |
| 16982 | , |
| 16983 | \\--eval=new Regex("Dwayne \"The Rock\" Johnson") |
| 16984 | , |
| 16985 | }, |
| 16986 | \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O safe -- --emoji=🗿 "--eval=new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" |
| 16987 | ); |
| 16988 | |
| 16989 | try t(&.{}, ""); |
| 16990 | try t(&.{""}, "\"\""); |
| 16991 | try t(&.{" "}, "\" \""); |
| 16992 | try t(&.{"\t"}, "\"\t\""); |
| 16993 | try t(&.{"\x07"}, "\"\x07\""); |
| 16994 | try t(&.{"🦎"}, "🦎"); |
| 16995 | |
| 16996 | try t( |
| 16997 | &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" }, |
| 16998 | "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee", |
| 16999 | ); |
| 17000 | |
| 17001 | try t( |
| 17002 | &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" }, |
| 17003 | "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"", |
| 17004 | ); |
| 17005 | |
| 17006 | try std.testing.expectError( |
| 17007 | error.InvalidArg0, |
| 17008 | argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}), |
| 17009 | ); |
| 17010 | try std.testing.expectError( |
| 17011 | error.InvalidArg0, |
| 17012 | argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}), |
| 17013 | ); |
| 17014 | try std.testing.expectError( |
| 17015 | error.InvalidArg0, |
| 17016 | argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}), |
| 17017 | ); |
| 17018 | } |
| 17019 | |
| 17020 | fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void { |
| 17021 | const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv); |
| 17022 | defer std.testing.allocator.free(cmd_line_w); |
| 17023 | |
| 17024 | const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w); |
| 17025 | defer std.testing.allocator.free(cmd_line); |
| 17026 | |
| 17027 | try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); |
| 17028 | } |
| 17029 | |
| 17030 | fn posixExecv( |
| 17031 | arg0_expand: process.ArgExpansion, |
| 17032 | file: [*:0]const u8, |
| 17033 | child_argv: [*:null]?[*:0]const u8, |
| 17034 | env_block: process.Environ.PosixBlock, |
| 17035 | PATH: []const u8, |
| 17036 | ) process.ReplaceError { |
| 17037 | const file_slice = std.mem.sliceTo(file, 0); |
| 17038 | if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block); |
| 17039 | |
| 17040 | // Use of PATH_MAX here is valid as the path_buf will be passed |
| 17041 | // directly to the operating system in posixExecvPath. |
| 17042 | var path_buf: [posix.PATH_MAX]u8 = undefined; |
| 17043 | var it = std.mem.tokenizeScalar(u8, PATH, ':'); |
| 17044 | var seen_eacces = false; |
| 17045 | var err: process.ReplaceError = error.FileNotFound; |
| 17046 | |
| 17047 | // In case of expanding arg0 we must put it back if we return with an error. |
| 17048 | const prev_arg0 = child_argv[0]; |
| 17049 | defer switch (arg0_expand) { |
| 17050 | .expand => child_argv[0] = prev_arg0, |
| 17051 | .no_expand => {}, |
| 17052 | }; |
| 17053 | |
| 17054 | while (it.next()) |search_path| { |
| 17055 | const path_len = search_path.len + file_slice.len + 1; |
| 17056 | if (path_buf.len < path_len + 1) return error.NameTooLong; |
| 17057 | @memcpy(path_buf[0..search_path.len], search_path); |
| 17058 | path_buf[search_path.len] = '/'; |
| 17059 | @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); |
| 17060 | path_buf[path_len] = 0; |
| 17061 | const full_path = path_buf[0..path_len :0].ptr; |
| 17062 | switch (arg0_expand) { |
| 17063 | .expand => child_argv[0] = full_path, |
| 17064 | .no_expand => {}, |
| 17065 | } |
| 17066 | err = posixExecvPath(full_path, child_argv, env_block); |
| 17067 | switch (err) { |
| 17068 | error.AccessDenied => seen_eacces = true, |
| 17069 | error.FileNotFound, error.NotDir => {}, |
| 17070 | else => |e| return e, |
| 17071 | } |
| 17072 | } |
| 17073 | if (seen_eacces) return error.AccessDenied; |
| 17074 | return err; |
| 17075 | } |
| 17076 | |
| 17077 | /// This function ignores PATH environment variable. |
| 17078 | pub fn posixExecvPath( |
| 17079 | path: [*:0]const u8, |
| 17080 | child_argv: [*:null]const ?[*:0]const u8, |
| 17081 | env_block: process.Environ.PosixBlock, |
| 17082 | ) process.ReplaceError { |
| 17083 | try Thread.checkCancel(); |
| 17084 | switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) { |
| 17085 | .FAULT => |err| return errnoBug(err), // Bad pointer parameter. |
| 17086 | .@"2BIG" => return error.SystemResources, |
| 17087 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 17088 | .NAMETOOLONG => return error.NameTooLong, |
| 17089 | .NFILE => return error.SystemFdQuotaExceeded, |
| 17090 | .NOMEM => return error.SystemResources, |
| 17091 | .ACCES => return error.AccessDenied, |
| 17092 | .PERM => return error.PermissionDenied, |
| 17093 | .INVAL => return error.InvalidExe, |
| 17094 | .NOEXEC => return error.InvalidExe, |
| 17095 | .IO => return error.FileSystem, |
| 17096 | .LOOP => return error.FileSystem, |
| 17097 | .ISDIR => return error.IsDir, |
| 17098 | .NOENT => return error.FileNotFound, |
| 17099 | .NOTDIR => return error.NotDir, |
| 17100 | .TXTBSY => return error.FileBusy, |
| 17101 | else => |err| switch (native_os) { |
| 17102 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) { |
| 17103 | .BADEXEC => return error.InvalidExe, |
| 17104 | .BADARCH => return error.InvalidExe, |
| 17105 | else => return posix.unexpectedErrno(err), |
| 17106 | }, |
| 17107 | .linux => switch (err) { |
| 17108 | .LIBBAD => return error.InvalidExe, |
| 17109 | else => return posix.unexpectedErrno(err), |
| 17110 | }, |
| 17111 | else => return posix.unexpectedErrno(err), |
| 17112 | }, |
| 17113 | } |
| 17114 | } |
| 17115 | |
| 17116 | pub const CreatePipeOptions = struct { |
| 17117 | server: End, |
| 17118 | client: End, |
| 17119 | inbound: bool = false, |
| 17120 | outbound: bool = false, |
| 17121 | maximum_instances: u32 = 1, |
| 17122 | quota: u32 = 4096, |
| 17123 | default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100, |
| 17124 | |
| 17125 | pub const End = struct { |
| 17126 | attributes: windows.OBJECT.ATTRIBUTES.Flags = .{}, |
| 17127 | mode: windows.FILE.MODE, |
| 17128 | }; |
| 17129 | }; |
| 17130 | pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE { |
| 17131 | const named_pipe_device = try t.getNamedPipeDevice(); |
| 17132 | const server_handle = server_handle: { |
| 17133 | var handle: windows.HANDLE = undefined; |
| 17134 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 17135 | const syscall: Syscall = try .start(); |
| 17136 | while (true) switch (windows.ntdll.NtCreateNamedPipeFile( |
| 17137 | &handle, |
| 17138 | .{ |
| 17139 | .SPECIFIC = .{ .FILE_PIPE = .{ |
| 17140 | .READ_DATA = options.inbound, |
| 17141 | .WRITE_DATA = options.outbound, |
| 17142 | .WRITE_ATTRIBUTES = true, |
| 17143 | } }, |
| 17144 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 17145 | }, |
| 17146 | &.{ |
| 17147 | .RootDirectory = named_pipe_device, |
| 17148 | .Attributes = options.server.attributes, |
| 17149 | }, |
| 17150 | &io_status_block, |
| 17151 | .{ .READ = true, .WRITE = true }, |
| 17152 | .CREATE, |
| 17153 | options.server.mode, |
| 17154 | .{ .TYPE = .BYTE_STREAM }, |
| 17155 | .{ .MODE = .BYTE_STREAM }, |
| 17156 | .{ .OPERATION = .QUEUE }, |
| 17157 | options.maximum_instances, |
| 17158 | if (options.inbound) options.quota else 0, |
| 17159 | if (options.outbound) options.quota else 0, |
| 17160 | &options.default_timeout, |
| 17161 | )) { |
| 17162 | .SUCCESS => break syscall.finish(), |
| 17163 | .CANCELLED => { |
| 17164 | try syscall.checkCancel(); |
| 17165 | continue; |
| 17166 | }, |
| 17167 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 17168 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 17169 | else => |status| return syscall.unexpectedNtstatus(status), |
| 17170 | }; |
| 17171 | break :server_handle handle; |
| 17172 | }; |
| 17173 | errdefer windows.CloseHandle(server_handle); |
| 17174 | const client_handle = client_handle: { |
| 17175 | var handle: windows.HANDLE = undefined; |
| 17176 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 17177 | const syscall: Syscall = try .start(); |
| 17178 | while (true) switch (windows.ntdll.NtOpenFile( |
| 17179 | &handle, |
| 17180 | .{ |
| 17181 | .SPECIFIC = .{ .FILE_PIPE = .{ |
| 17182 | .READ_DATA = options.outbound, |
| 17183 | .WRITE_DATA = options.inbound, |
| 17184 | .WRITE_ATTRIBUTES = true, |
| 17185 | } }, |
| 17186 | .STANDARD = .{ .SYNCHRONIZE = true }, |
| 17187 | }, |
| 17188 | &.{ |
| 17189 | .RootDirectory = server_handle, |
| 17190 | .Attributes = options.client.attributes, |
| 17191 | }, |
| 17192 | &io_status_block, |
| 17193 | .{ .READ = true, .WRITE = true }, |
| 17194 | options.client.mode, |
| 17195 | )) { |
| 17196 | .SUCCESS => break syscall.finish(), |
| 17197 | .CANCELLED => { |
| 17198 | try syscall.checkCancel(); |
| 17199 | continue; |
| 17200 | }, |
| 17201 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 17202 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 17203 | else => |status| return syscall.unexpectedNtstatus(status), |
| 17204 | }; |
| 17205 | break :client_handle handle; |
| 17206 | }; |
| 17207 | errdefer windows.CloseHandle(client_handle); |
| 17208 | return .{ server_handle, client_handle }; |
| 17209 | } |
| 17210 | |
| 17211 | fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { |
| 17212 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 17213 | t.scanEnviron(); |
| 17214 | return t.environ.zig_progress_file; |
| 17215 | } |
| 17216 | |
| 17217 | pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { |
| 17218 | t.scanEnviron(); |
| 17219 | return @field(t.environ.string, name); |
| 17220 | } |
| 17221 | |
| 17222 | fn random(userdata: ?*anyopaque, buffer: []u8) void { |
| 17223 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 17224 | const thread = Thread.current orelse return randomMainThread(t, buffer); |
| 17225 | if (!thread.csprng.isInitialized()) { |
| 17226 | @branchHint(.unlikely); |
| 17227 | var seed: [Csprng.seed_len]u8 = undefined; |
| 17228 | randomMainThread(t, &seed); |
| 17229 | thread.csprng.rng = .init(seed); |
| 17230 | } |
| 17231 | thread.csprng.rng.fill(buffer); |
| 17232 | } |
| 17233 | |
| 17234 | fn randomMainThread(t: *Threaded, buffer: []u8) void { |
| 17235 | mutexLock(&t.mutex); |
| 17236 | defer mutexUnlock(&t.mutex); |
| 17237 | |
| 17238 | if (!t.csprng.isInitialized()) { |
| 17239 | @branchHint(.unlikely); |
| 17240 | var seed: [Csprng.seed_len]u8 = undefined; |
| 17241 | { |
| 17242 | mutexUnlock(&t.mutex); |
| 17243 | defer mutexLock(&t.mutex); |
| 17244 | |
| 17245 | const prev = swapCancelProtection(t, .blocked); |
| 17246 | defer _ = swapCancelProtection(t, prev); |
| 17247 | |
| 17248 | randomSecure(t, &seed) catch |err| switch (err) { |
| 17249 | error.Canceled => unreachable, |
| 17250 | error.EntropyUnavailable => fallbackSeed(t, &seed), |
| 17251 | }; |
| 17252 | } |
| 17253 | t.csprng.rng = .init(seed); |
| 17254 | } |
| 17255 | |
| 17256 | t.csprng.rng.fill(buffer); |
| 17257 | } |
| 17258 | |
| 17259 | pub fn fallbackSeed(aslr_addr: ?*anyopaque, seed: *[Csprng.seed_len]u8) void { |
| 17260 | @memset(seed, 0); |
| 17261 | std.mem.writeInt(usize, seed[seed.len - @sizeOf(usize) ..][0..@sizeOf(usize)], @intFromPtr(aslr_addr), .native); |
| 17262 | const fallbackSeedImpl = switch (native_os) { |
| 17263 | .windows => fallbackSeedWindows, |
| 17264 | .wasi => if (builtin.link_libc) fallbackSeedPosix else fallbackSeedWasi, |
| 17265 | else => fallbackSeedPosix, |
| 17266 | }; |
| 17267 | fallbackSeedImpl(seed); |
| 17268 | } |
| 17269 | |
| 17270 | fn fallbackSeedPosix(seed: *[Csprng.seed_len]u8) void { |
| 17271 | std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native); |
| 17272 | const i_1 = @sizeOf(posix.pid_t); |
| 17273 | |
| 17274 | var ts: posix.timespec = undefined; |
| 17275 | const Sec = @TypeOf(ts.sec); |
| 17276 | const Nsec = @TypeOf(ts.nsec); |
| 17277 | const i_2 = i_1 + @sizeOf(Sec); |
| 17278 | switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) { |
| 17279 | .SUCCESS => { |
| 17280 | std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native); |
| 17281 | std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native); |
| 17282 | }, |
| 17283 | else => {}, |
| 17284 | } |
| 17285 | } |
| 17286 | |
| 17287 | fn fallbackSeedWindows(seed: *[Csprng.seed_len]u8) void { |
| 17288 | var pc: windows.LARGE_INTEGER = undefined; |
| 17289 | _ = windows.ntdll.RtlQueryPerformanceCounter(&pc); |
| 17290 | std.mem.writeInt(windows.LARGE_INTEGER, seed[0..@sizeOf(windows.LARGE_INTEGER)], pc, .native); |
| 17291 | } |
| 17292 | |
| 17293 | fn fallbackSeedWasi(seed: *[Csprng.seed_len]u8) void { |
| 17294 | var ts: std.os.wasi.timestamp_t = undefined; |
| 17295 | if (std.os.wasi.clock_time_get(.REALTIME, 1, &ts) == .SUCCESS) { |
| 17296 | std.mem.writeInt(std.os.wasi.timestamp_t, seed[0..@sizeOf(std.os.wasi.timestamp_t)], ts, .native); |
| 17297 | } |
| 17298 | } |
| 17299 | |
| 17300 | fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { |
| 17301 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 17302 | |
| 17303 | if (is_windows) { |
| 17304 | if (buffer.len == 0) return; |
| 17305 | // ProcessPrng from bcryptprimitives.dll has the following properties: |
| 17306 | // * introduces a dependency on bcryptprimitives.dll, which apparently |
| 17307 | // runs a test suite every time it is loaded |
| 17308 | // * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL |
| 17309 | // despite the function being documented to always return TRUE |
| 17310 | // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG |
| 17311 | // Therefore, that function is avoided in favor of using the device directly. |
| 17312 | const cng_device = try getCngDevice(t); |
| 17313 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 17314 | var i: usize = 0; |
| 17315 | const syscall: Syscall = try .start(); |
| 17316 | while (true) { |
| 17317 | const remaining_len = std.math.lossyCast(u32, buffer.len - i); |
| 17318 | switch (windows.ntdll.NtDeviceIoControlFile( |
| 17319 | cng_device, |
| 17320 | null, |
| 17321 | null, |
| 17322 | null, |
| 17323 | &io_status_block, |
| 17324 | windows.IOCTL.KSEC.GEN_RANDOM, |
| 17325 | null, |
| 17326 | 0, |
| 17327 | buffer[i..].ptr, |
| 17328 | remaining_len, |
| 17329 | )) { |
| 17330 | .SUCCESS => { |
| 17331 | i += remaining_len; |
| 17332 | if (buffer.len - i == 0) { |
| 17333 | return syscall.finish(); |
| 17334 | } else { |
| 17335 | try syscall.checkCancel(); |
| 17336 | continue; |
| 17337 | } |
| 17338 | }, |
| 17339 | .CANCELLED => { |
| 17340 | try syscall.checkCancel(); |
| 17341 | continue; |
| 17342 | }, |
| 17343 | else => return syscall.fail(error.EntropyUnavailable), |
| 17344 | } |
| 17345 | } |
| 17346 | } |
| 17347 | |
| 17348 | if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) { |
| 17349 | if (buffer.len == 0) return; |
| 17350 | posix.system.arc4random_buf(buffer.ptr, buffer.len); |
| 17351 | return; |
| 17352 | } |
| 17353 | |
| 17354 | if (native_os == .wasi) { |
| 17355 | if (buffer.len == 0) return; |
| 17356 | const syscall: Syscall = try .start(); |
| 17357 | while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) { |
| 17358 | .SUCCESS => return syscall.finish(), |
| 17359 | .INTR => { |
| 17360 | try syscall.checkCancel(); |
| 17361 | continue; |
| 17362 | }, |
| 17363 | else => return syscall.fail(error.EntropyUnavailable), |
| 17364 | }; |
| 17365 | } |
| 17366 | |
| 17367 | if (@TypeOf(posix.system.getrandom) != void) { |
| 17368 | const getrandom = if (use_libc_getrandom) std.c.getrandom else std.os.linux.getrandom; |
| 17369 | var i: usize = 0; |
| 17370 | const syscall: Syscall = try .start(); |
| 17371 | while (buffer.len - i != 0) { |
| 17372 | const buf = buffer[i..]; |
| 17373 | const rc = getrandom(buf.ptr, buf.len, 0); |
| 17374 | switch (posix.errno(rc)) { |
| 17375 | .SUCCESS => { |
| 17376 | syscall.finish(); |
| 17377 | const n: usize = @intCast(rc); |
| 17378 | i += n; |
| 17379 | continue; |
| 17380 | }, |
| 17381 | .INTR => { |
| 17382 | try syscall.checkCancel(); |
| 17383 | continue; |
| 17384 | }, |
| 17385 | else => return syscall.fail(error.EntropyUnavailable), |
| 17386 | } |
| 17387 | } |
| 17388 | return; |
| 17389 | } |
| 17390 | |
| 17391 | if (native_os == .emscripten) { |
| 17392 | if (buffer.len == 0) return; |
| 17393 | const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len)); |
| 17394 | switch (err) { |
| 17395 | .SUCCESS => return, |
| 17396 | else => return error.EntropyUnavailable, |
| 17397 | } |
| 17398 | } |
| 17399 | |
| 17400 | if (native_os == .linux) { |
| 17401 | comptime assert(use_dev_urandom); |
| 17402 | const urandom_fd = try getRandomFd(t); |
| 17403 | |
| 17404 | var i: usize = 0; |
| 17405 | while (buffer.len - i != 0) { |
| 17406 | const syscall: Syscall = try .start(); |
| 17407 | const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i); |
| 17408 | switch (posix.errno(rc)) { |
| 17409 | .SUCCESS => { |
| 17410 | syscall.finish(); |
| 17411 | const n: usize = @intCast(rc); |
| 17412 | if (n == 0) return error.EntropyUnavailable; |
| 17413 | i += n; |
| 17414 | continue; |
| 17415 | }, |
| 17416 | .INTR => { |
| 17417 | try syscall.checkCancel(); |
| 17418 | continue; |
| 17419 | }, |
| 17420 | else => return syscall.fail(error.EntropyUnavailable), |
| 17421 | } |
| 17422 | } |
| 17423 | } |
| 17424 | |
| 17425 | return error.EntropyUnavailable; |
| 17426 | } |
| 17427 | |
| 17428 | fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { |
| 17429 | { |
| 17430 | mutexLock(&t.mutex); |
| 17431 | defer mutexUnlock(&t.mutex); |
| 17432 | |
| 17433 | if (t.random_file.fd == -2) return error.EntropyUnavailable; |
| 17434 | if (t.random_file.fd != -1) return t.random_file.fd; |
| 17435 | } |
| 17436 | |
| 17437 | const mode: posix.mode_t = 0; |
| 17438 | |
| 17439 | const fd: posix.fd_t = fd: { |
| 17440 | const syscall: Syscall = try .start(); |
| 17441 | while (true) { |
| 17442 | const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{ |
| 17443 | .ACCMODE = .RDONLY, |
| 17444 | .CLOEXEC = true, |
| 17445 | }, mode); |
| 17446 | switch (posix.errno(rc)) { |
| 17447 | .SUCCESS => { |
| 17448 | syscall.finish(); |
| 17449 | break :fd @intCast(rc); |
| 17450 | }, |
| 17451 | .INTR => { |
| 17452 | try syscall.checkCancel(); |
| 17453 | continue; |
| 17454 | }, |
| 17455 | else => return syscall.fail(error.EntropyUnavailable), |
| 17456 | } |
| 17457 | } |
| 17458 | }; |
| 17459 | errdefer closeFd(fd); |
| 17460 | |
| 17461 | switch (native_os) { |
| 17462 | .linux => { |
| 17463 | const sys = if (statx_use_c) std.c else std.os.linux; |
| 17464 | const syscall: Syscall = try .start(); |
| 17465 | while (true) { |
| 17466 | var statx = std.mem.zeroes(std.os.linux.Statx); |
| 17467 | switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { |
| 17468 | .SUCCESS => { |
| 17469 | syscall.finish(); |
| 17470 | if (!statx.mask.TYPE) return error.EntropyUnavailable; |
| 17471 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 17472 | defer mutexUnlock(&t.mutex); |
| 17473 | if (t.random_file.fd >= 0) { |
| 17474 | closeFd(fd); |
| 17475 | return t.random_file.fd; |
| 17476 | } else if (!posix.S.ISCHR(statx.mode)) { |
| 17477 | t.random_file.fd = -2; |
| 17478 | return error.EntropyUnavailable; |
| 17479 | } else { |
| 17480 | t.random_file.fd = fd; |
| 17481 | return fd; |
| 17482 | } |
| 17483 | }, |
| 17484 | .INTR => { |
| 17485 | try syscall.checkCancel(); |
| 17486 | continue; |
| 17487 | }, |
| 17488 | else => return syscall.fail(error.EntropyUnavailable), |
| 17489 | } |
| 17490 | } |
| 17491 | }, |
| 17492 | else => { |
| 17493 | const syscall: Syscall = try .start(); |
| 17494 | while (true) { |
| 17495 | var stat = std.mem.zeroes(posix.Stat); |
| 17496 | switch (posix.errno(fstat_sym(fd, &stat))) { |
| 17497 | .SUCCESS => { |
| 17498 | syscall.finish(); |
| 17499 | mutexLock(&t.mutex); // Another thread might have won the race. |
| 17500 | defer mutexUnlock(&t.mutex); |
| 17501 | if (t.random_file.fd >= 0) { |
| 17502 | closeFd(fd); |
| 17503 | return t.random_file.fd; |
| 17504 | } else if (!posix.S.ISCHR(stat.mode)) { |
| 17505 | t.random_file.fd = -2; |
| 17506 | return error.EntropyUnavailable; |
| 17507 | } else { |
| 17508 | t.random_file.fd = fd; |
| 17509 | return fd; |
| 17510 | } |
| 17511 | }, |
| 17512 | .INTR => { |
| 17513 | try syscall.checkCancel(); |
| 17514 | continue; |
| 17515 | }, |
| 17516 | else => return syscall.fail(error.EntropyUnavailable), |
| 17517 | } |
| 17518 | } |
| 17519 | }, |
| 17520 | } |
| 17521 | } |
| 17522 | |
| 17523 | test { |
| 17524 | _ = @import("Threaded/test.zig"); |
| 17525 | } |
| 17526 | |
| 17527 | const use_parking_futex = switch (native_os) { |
| 17528 | .windows => true, // RtlWaitOnAddress is a userland implementation anyway |
| 17529 | .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now. |
| 17530 | .illumos => true, // Illumos has no futex mechanism |
| 17531 | .haiku => true, // Haiku has no futex mechanism |
| 17532 | else => false, |
| 17533 | }; |
| 17534 | const use_parking_sleep = switch (native_os) { |
| 17535 | // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in |
| 17536 | // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the |
| 17537 | // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm |
| 17538 | // also more confident that it will always correctly handle the cancelation race (so "unpark" |
| 17539 | // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired |
| 17540 | // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might |
| 17541 | // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger |
| 17542 | // this behavior because `RtlWaitOnAddress` relies on it. |
| 17543 | .windows => true, |
| 17544 | |
| 17545 | // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better |
| 17546 | // cancelation mechanism. |
| 17547 | .netbsd, |
| 17548 | .illumos, |
| 17549 | => true, |
| 17550 | |
| 17551 | else => false, |
| 17552 | }; |
| 17553 | |
| 17554 | const parking_futex = struct { |
| 17555 | comptime { |
| 17556 | assert(use_parking_futex); |
| 17557 | } |
| 17558 | |
| 17559 | const Bucket = struct { |
| 17560 | /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no |
| 17561 | /// waiters. It is important for `wait` to increment this *before* checking the futex value to |
| 17562 | /// avoid a race. |
| 17563 | num_waiters: std.atomic.Value(u32), |
| 17564 | /// Protects `waiters`. |
| 17565 | mutex: ParkingMutex, |
| 17566 | waiters: std.DoublyLinkedList, |
| 17567 | |
| 17568 | /// Prevent false sharing between buckets. |
| 17569 | _: void align(std.atomic.cache_line) = {}, |
| 17570 | |
| 17571 | const init: Bucket = .{ .num_waiters = .init(0), .mutex = .init, .waiters = .{} }; |
| 17572 | }; |
| 17573 | |
| 17574 | const Waiter = struct { |
| 17575 | node: std.DoublyLinkedList.Node, |
| 17576 | address: usize, |
| 17577 | tid: ParkTid, |
| 17578 | /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread |
| 17579 | /// which atomically updates it (to `.none` or `.canceling`) is responsible for: |
| 17580 | /// |
| 17581 | /// * Removing the `Waiter` from `Bucket.waiters` |
| 17582 | /// * Decrementing `Bucket.num_waiters` |
| 17583 | /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope |
| 17584 | /// while it is still in the `Bucket`). |
| 17585 | thread_status: *std.atomic.Value(Thread.Status), |
| 17586 | unpark_flag: if (need_unpark_flag) *UnparkFlag else void, |
| 17587 | }; |
| 17588 | |
| 17589 | fn bucketForAddress(address: usize) *Bucket { |
| 17590 | const global = struct { |
| 17591 | /// Length must be a power of two. The longer this array, the less likely contention is |
| 17592 | /// between different futexes. This length seems like it'll provide a reasonable balance |
| 17593 | /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line |
| 17594 | /// alignment), this uses 32 KiB of memory. |
| 17595 | var buckets: [256]Bucket = @splat(.init); |
| 17596 | }; |
| 17597 | |
| 17598 | // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input |
| 17599 | // values across a range, giving a poor, but extremely quick to compute, hash. |
| 17600 | |
| 17601 | // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The |
| 17602 | // shift then converts it to '2^b / phi', where 'b' is the pointer bit width. |
| 17603 | const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize)); |
| 17604 | const hashed = address *% fibonacci_multiplier; |
| 17605 | |
| 17606 | comptime assert(std.math.isPowerOfTwo(global.buckets.len)); |
| 17607 | // The high bits of `hashed` have better entropy than the low bits. |
| 17608 | const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len)); |
| 17609 | |
| 17610 | return &global.buckets[index]; |
| 17611 | } |
| 17612 | |
| 17613 | fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void { |
| 17614 | const bucket = bucketForAddress(@intFromPtr(ptr)); |
| 17615 | |
| 17616 | // Put the threadlocal access outside of the critical section. |
| 17617 | const opt_thread = Thread.current; |
| 17618 | const self_tid = getParkTid(); |
| 17619 | |
| 17620 | var waiter: Waiter = .{ |
| 17621 | .node = undefined, // populated by list append |
| 17622 | .address = @intFromPtr(ptr), |
| 17623 | .tid = self_tid, |
| 17624 | .thread_status = undefined, // populated in critical section |
| 17625 | .unpark_flag = undefined, // populated in critical section |
| 17626 | }; |
| 17627 | |
| 17628 | var status_buf: std.atomic.Value(Thread.Status) = undefined; |
| 17629 | var unpark_flag_buf: UnparkFlag = unpark_flag_init; |
| 17630 | |
| 17631 | { |
| 17632 | bucket.mutex.lock(); |
| 17633 | defer bucket.mutex.unlock(); |
| 17634 | |
| 17635 | _ = bucket.num_waiters.fetchAdd(1, .acquire); |
| 17636 | |
| 17637 | if (@atomicLoad(u32, ptr, .monotonic) != expect) { |
| 17638 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17639 | return; |
| 17640 | } |
| 17641 | |
| 17642 | // This is in the critical section to avoid marking the thread as parked until we're |
| 17643 | // certain that we're actually going to park. |
| 17644 | waiter.thread_status, waiter.unpark_flag = status: { |
| 17645 | cancelable: { |
| 17646 | if (uncancelable) break :cancelable; |
| 17647 | const thread = opt_thread orelse break :cancelable; |
| 17648 | switch (thread.cancel_protection) { |
| 17649 | .blocked => break :cancelable, |
| 17650 | .unblocked => {}, |
| 17651 | } |
| 17652 | thread.futex_waiter = &waiter; |
| 17653 | const old_status = thread.status.fetchOr( |
| 17654 | .{ .cancelation = @fromBackingInt(@intCast(0b001)), .awaitable = .null }, |
| 17655 | .release, // release `thread.futex_waiter` |
| 17656 | ); |
| 17657 | switch (old_status.cancelation) { |
| 17658 | .none => {}, // status is now `.parked` |
| 17659 | .canceling => { |
| 17660 | // status is now `.canceled` |
| 17661 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17662 | return error.Canceled; |
| 17663 | }, |
| 17664 | .canceled => break :cancelable, // status is still `.canceled` |
| 17665 | .parked => unreachable, |
| 17666 | .blocked => unreachable, |
| 17667 | .blocked_alertable => unreachable, |
| 17668 | .blocked_alertable_canceling => unreachable, |
| 17669 | .blocked_canceling => unreachable, |
| 17670 | } |
| 17671 | // We could now be unparked for a cancelation at any time! |
| 17672 | break :status .{ &thread.status, if (need_unpark_flag) &thread.unpark_flag }; |
| 17673 | } |
| 17674 | // This is an uncancelable wait, so just use `status_buf`. Note that the value of |
| 17675 | // `status_buf.awaitable` is irrelevant because this is only visible to futex code, |
| 17676 | // while only cancelation cares about `awaitable`. |
| 17677 | status_buf.raw = .{ .cancelation = .parked, .awaitable = .null }; |
| 17678 | break :status .{ &status_buf, if (need_unpark_flag) &unpark_flag_buf }; |
| 17679 | }; |
| 17680 | |
| 17681 | bucket.waiters.append(&waiter.node); |
| 17682 | } |
| 17683 | |
| 17684 | if (park(timeout, ptr, waiter.unpark_flag)) { |
| 17685 | // We were unparked by either `wake` or cancelation, so our current status is either |
| 17686 | // `.none` or `.canceling`. In either case, they've already removed `waiter` from |
| 17687 | // `bucket`, so we have nothing more to do! |
| 17688 | } else |err| switch (err) { |
| 17689 | error.Timeout => { |
| 17690 | // We're not out of the woods yet: an unpark could race with the timeout. |
| 17691 | const old_status = waiter.thread_status.fetchAnd( |
| 17692 | .{ .cancelation = @fromBackingInt(@intCast(0b110)), .awaitable = .all_ones }, |
| 17693 | .monotonic, |
| 17694 | ); |
| 17695 | switch (old_status.cancelation) { |
| 17696 | .parked => { |
| 17697 | // No race. It is our responsibility to remove `waiter` from `bucket`. |
| 17698 | // New status is `.none`. |
| 17699 | bucket.mutex.lock(); |
| 17700 | defer bucket.mutex.unlock(); |
| 17701 | bucket.waiters.remove(&waiter.node); |
| 17702 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17703 | }, |
| 17704 | .none, .canceling => { |
| 17705 | // Race condition: the timeout was reached, then `wake` or a canceler tried |
| 17706 | // to unpark us. Whoever did that will remove us from `bucket`. Wait for |
| 17707 | // that (and drop the unpark request in doing so). |
| 17708 | // New status is `.none` or `.canceling` respectively. |
| 17709 | park(.none, ptr, waiter.unpark_flag) catch |e| switch (e) { |
| 17710 | error.Timeout => unreachable, |
| 17711 | }; |
| 17712 | }, |
| 17713 | .canceled => unreachable, |
| 17714 | .blocked => unreachable, |
| 17715 | .blocked_alertable => unreachable, |
| 17716 | .blocked_canceling => unreachable, |
| 17717 | .blocked_alertable_canceling => unreachable, |
| 17718 | } |
| 17719 | }, |
| 17720 | } |
| 17721 | } |
| 17722 | |
| 17723 | fn wake(ptr: *const u32, max_waiters: u32) void { |
| 17724 | if (max_waiters == 0) return; |
| 17725 | |
| 17726 | const bucket = bucketForAddress(@intFromPtr(ptr)); |
| 17727 | |
| 17728 | // To ensure the store to `ptr` is ordered before this check, we effectively want a `.release` |
| 17729 | // load, but that doesn't exist in the C11 memory model, so emulate it with a non-mutating rmw. |
| 17730 | if (bucket.num_waiters.fetchAdd(0, .release) == 0) { |
| 17731 | @branchHint(.likely); |
| 17732 | return; // no waiters |
| 17733 | } |
| 17734 | |
| 17735 | // Waiters removed from the linked list under the mutex so we can unpark their threads outside |
| 17736 | // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. |
| 17737 | var waking_head: ?*std.DoublyLinkedList.Node = null; |
| 17738 | { |
| 17739 | bucket.mutex.lock(); |
| 17740 | defer bucket.mutex.unlock(); |
| 17741 | |
| 17742 | var num_removed: u32 = 0; |
| 17743 | var it = bucket.waiters.first; |
| 17744 | while (num_removed < max_waiters) { |
| 17745 | const waiter: *Waiter = @fieldParentPtr("node", it orelse break); |
| 17746 | it = waiter.node.next; |
| 17747 | if (waiter.address != @intFromPtr(ptr)) continue; |
| 17748 | const old_status = waiter.thread_status.fetchAnd( |
| 17749 | .{ .cancelation = @fromBackingInt(@intCast(0b110)), .awaitable = .all_ones }, |
| 17750 | .monotonic, |
| 17751 | ); |
| 17752 | switch (old_status.cancelation) { |
| 17753 | .parked => {}, // state updated to `.none` |
| 17754 | .none => continue, // race with timeout; they are about to lock `bucket.mutex` and remove themselves from the bucket |
| 17755 | .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet |
| 17756 | .canceled => unreachable, |
| 17757 | .blocked => unreachable, |
| 17758 | .blocked_alertable => unreachable, |
| 17759 | .blocked_alertable_canceling => unreachable, |
| 17760 | .blocked_canceling => unreachable, |
| 17761 | } |
| 17762 | // We're waking this waiter. Remove them from the bucket and add them to our local list. |
| 17763 | bucket.waiters.remove(&waiter.node); |
| 17764 | waiter.node.next = waking_head; |
| 17765 | waking_head = &waiter.node; |
| 17766 | num_removed += 1; |
| 17767 | } |
| 17768 | _ = bucket.num_waiters.fetchSub(num_removed, .monotonic); |
| 17769 | } |
| 17770 | |
| 17771 | var unpark_buf: [128]UnparkTid = undefined; |
| 17772 | var unpark_len: usize = 0; |
| 17773 | |
| 17774 | // Finally, unpark the threads. |
| 17775 | while (waking_head) |node| { |
| 17776 | waking_head = node.next; |
| 17777 | const waiter: *Waiter = @fieldParentPtr("node", node); |
| 17778 | unpark_buf[unpark_len] = waiter.tid; |
| 17779 | if (need_unpark_flag) setUnparkFlag(waiter.unpark_flag); |
| 17780 | unpark_len += 1; |
| 17781 | if (unpark_len == unpark_buf.len) { |
| 17782 | unpark(&unpark_buf, ptr); |
| 17783 | unpark_len = 0; |
| 17784 | } |
| 17785 | } |
| 17786 | if (unpark_len > 0) { |
| 17787 | unpark(unpark_buf[0..unpark_len], ptr); |
| 17788 | } |
| 17789 | } |
| 17790 | |
| 17791 | fn removeCanceledWaiter(waiter: *Waiter) void { |
| 17792 | const bucket = bucketForAddress(waiter.address); |
| 17793 | bucket.mutex.lock(); |
| 17794 | defer bucket.mutex.unlock(); |
| 17795 | bucket.waiters.remove(&waiter.node); |
| 17796 | assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); |
| 17797 | } |
| 17798 | }; |
| 17799 | const parking_sleep = struct { |
| 17800 | comptime { |
| 17801 | assert(use_parking_sleep); |
| 17802 | } |
| 17803 | fn sleep(timeout: Io.Timeout) Io.Cancelable!void { |
| 17804 | const opt_thread = Thread.current; |
| 17805 | cancelable: { |
| 17806 | const thread = opt_thread orelse break :cancelable; |
| 17807 | switch (thread.cancel_protection) { |
| 17808 | .blocked => break :cancelable, |
| 17809 | .unblocked => {}, |
| 17810 | } |
| 17811 | thread.futex_waiter = null; |
| 17812 | { |
| 17813 | const old_status = thread.status.fetchOr( |
| 17814 | .{ .cancelation = @fromBackingInt(@intCast(0b001)), .awaitable = .null }, |
| 17815 | .release, // release `thread.futex_waiter` |
| 17816 | ); |
| 17817 | switch (old_status.cancelation) { |
| 17818 | .none => {}, // status is now `.parked` |
| 17819 | .canceling => return error.Canceled, // status is now `.canceled` |
| 17820 | .canceled => break :cancelable, // status is still `.canceled` |
| 17821 | .parked => unreachable, |
| 17822 | .blocked => unreachable, |
| 17823 | .blocked_alertable => unreachable, |
| 17824 | .blocked_alertable_canceling => unreachable, |
| 17825 | .blocked_canceling => unreachable, |
| 17826 | } |
| 17827 | } |
| 17828 | if (park(timeout, null, if (need_unpark_flag) &thread.unpark_flag)) { |
| 17829 | // The only reason this could possibly happen is cancelation. |
| 17830 | const old_status = thread.status.load(.monotonic); |
| 17831 | assert(old_status.cancelation == .canceling); |
| 17832 | thread.status.store( |
| 17833 | .{ .cancelation = .canceled, .awaitable = old_status.awaitable }, |
| 17834 | .monotonic, |
| 17835 | ); |
| 17836 | return error.Canceled; |
| 17837 | } else |err| switch (err) { |
| 17838 | error.Timeout => { |
| 17839 | // We're not out of the woods yet: an unpark could race with the timeout. |
| 17840 | const old_status = thread.status.fetchAnd( |
| 17841 | .{ .cancelation = @fromBackingInt(@intCast(0b110)), .awaitable = .all_ones }, |
| 17842 | .monotonic, |
| 17843 | ); |
| 17844 | switch (old_status.cancelation) { |
| 17845 | .parked => return, // No race; new status is `.none` |
| 17846 | .canceling => { |
| 17847 | // Race condition: the timeout was reached, then someone tried to unpark |
| 17848 | // us for a cancelation. Whoever did that will have called `unpark`, so |
| 17849 | // drop that unpark request by waiting for it. |
| 17850 | // Status is still `.canceling`. |
| 17851 | park(.none, null, if (need_unpark_flag) &thread.unpark_flag) catch |e| switch (e) { |
| 17852 | error.Timeout => unreachable, |
| 17853 | }; |
| 17854 | return; |
| 17855 | }, |
| 17856 | .none => unreachable, |
| 17857 | .canceled => unreachable, |
| 17858 | .blocked => unreachable, |
| 17859 | .blocked_alertable => unreachable, |
| 17860 | .blocked_canceling => unreachable, |
| 17861 | .blocked_alertable_canceling => unreachable, |
| 17862 | } |
| 17863 | }, |
| 17864 | } |
| 17865 | } |
| 17866 | |
| 17867 | // Uncancelable sleep; we expect not to be manually unparked. |
| 17868 | |
| 17869 | // On systems where parking the thread requires a one-time setup operation (e.g. creating a |
| 17870 | // semaphore), we need to ensure that setup is done before we call `park`. |
| 17871 | _ = getParkTid(); |
| 17872 | var dummy_flag: UnparkFlag = unpark_flag_init; |
| 17873 | if (park(timeout, null, if (need_unpark_flag) &dummy_flag)) { |
| 17874 | unreachable; // unexpected unpark |
| 17875 | } else |err| switch (err) { |
| 17876 | error.Timeout => return, |
| 17877 | } |
| 17878 | } |
| 17879 | }; |
| 17880 | const ParkingMutex = struct { |
| 17881 | state: std.atomic.Value(State), |
| 17882 | |
| 17883 | const init: ParkingMutex = .{ .state = .init(.unlocked) }; |
| 17884 | |
| 17885 | comptime { |
| 17886 | assert(use_parking_futex); |
| 17887 | } |
| 17888 | |
| 17889 | const State = enum(usize) { |
| 17890 | unlocked = 1, |
| 17891 | /// This value is intentionally 0 so that `waiter` returns `null`. |
| 17892 | locked_once = 0, |
| 17893 | /// Contended; value is a `*Waiter`. |
| 17894 | _, |
| 17895 | /// Returns the head of the waiter list. Illegal to call if `s == .unlocked`. |
| 17896 | fn waiter(s: State) ?*Waiter { |
| 17897 | return @ptrFromInt(@backingInt(s)); |
| 17898 | } |
| 17899 | /// Returns a locked state where `w` is contending the lock. |
| 17900 | /// If `w` is `null`, returns `.locked_once`. |
| 17901 | fn fromWaiter(w: ?*Waiter) State { |
| 17902 | return @fromBackingInt(@intCast(@intFromPtr(w))); |
| 17903 | } |
| 17904 | }; |
| 17905 | const Waiter = struct { |
| 17906 | unpark_flag: UnparkFlag, |
| 17907 | /// Never modified once the `Waiter` is in the linked list. |
| 17908 | next: ?*Waiter, |
| 17909 | /// Never modified once the `Waiter` is in the linked list. |
| 17910 | tid: ParkTid, |
| 17911 | }; |
| 17912 | fn lock(m: *ParkingMutex) void { |
| 17913 | state: switch (State.unlocked) { // assume 'unlocked' to optimize for uncontended case |
| 17914 | .unlocked => continue :state m.state.cmpxchgWeak( |
| 17915 | .unlocked, |
| 17916 | .locked_once, |
| 17917 | .acquire, // acquire lock |
| 17918 | .monotonic, |
| 17919 | ) orelse { |
| 17920 | @branchHint(.likely); |
| 17921 | return; |
| 17922 | }, |
| 17923 | |
| 17924 | .locked_once, _ => |last_state| { |
| 17925 | const old_waiter = last_state.waiter(); |
| 17926 | const self_tid = getParkTid(); |
| 17927 | var waiter: Waiter = .{ |
| 17928 | .next = old_waiter, |
| 17929 | .unpark_flag = unpark_flag_init, |
| 17930 | .tid = self_tid, |
| 17931 | }; |
| 17932 | if (m.state.cmpxchgWeak( |
| 17933 | .fromWaiter(old_waiter), |
| 17934 | .fromWaiter(&waiter), |
| 17935 | .release, // release `waiter` |
| 17936 | .monotonic, |
| 17937 | )) |new_state| { |
| 17938 | continue :state new_state; |
| 17939 | } |
| 17940 | // We're now in the list of waiters---park until we're given the lock. |
| 17941 | park(.none, m, if (need_unpark_flag) &waiter.unpark_flag) catch |err| switch (err) { |
| 17942 | error.Timeout => unreachable, |
| 17943 | }; |
| 17944 | return; |
| 17945 | }, |
| 17946 | } |
| 17947 | } |
| 17948 | fn unlock(m: *ParkingMutex) void { |
| 17949 | state: switch (State.locked_once) { // assume 'locked_once' to optimize for uncontended case |
| 17950 | .unlocked => unreachable, // we hold the lock |
| 17951 | |
| 17952 | .locked_once => continue :state m.state.cmpxchgWeak( |
| 17953 | .locked_once, |
| 17954 | .unlocked, |
| 17955 | .release, // release lock |
| 17956 | .acquire, // acquire any `Waiter` memory |
| 17957 | ) orelse { |
| 17958 | @branchHint(.likely); |
| 17959 | return; |
| 17960 | }, |
| 17961 | |
| 17962 | _ => |last_state| { |
| 17963 | // The logic here does not have ABA problems, and does some accesses non-atomically, |
| 17964 | // because `Waiter.next` is owned by the lock holder (that's us!) once the waiter is |
| 17965 | // in the linked list, up until we unpark the waiter. |
| 17966 | |
| 17967 | // Run through the waiter list to the end to ensure fairness. This is obviously not |
| 17968 | // ideal, but it shouldn't be a big deal in practice provided the critical section |
| 17969 | // is fairly small (so we won't get too many threads contending the mutex at once). |
| 17970 | // There's a *chance* we could get away with a LIFO queue for our use case, but I |
| 17971 | // don't wanna risk that. |
| 17972 | var parent: ?*Waiter = null; |
| 17973 | var waiter: *Waiter = last_state.waiter().?; |
| 17974 | while (waiter.next) |next| { |
| 17975 | parent = waiter; |
| 17976 | waiter = next; |
| 17977 | } |
| 17978 | // `waiter` is next in line for the lock. Remove them from the list. |
| 17979 | if (parent) |p| { |
| 17980 | assert(p.next == waiter); |
| 17981 | p.next = null; |
| 17982 | } else { |
| 17983 | // We're waking the last waiter, so clear the list head. |
| 17984 | if (m.state.cmpxchgWeak( |
| 17985 | .fromWaiter(last_state.waiter().?), |
| 17986 | .locked_once, |
| 17987 | .acquire, |
| 17988 | .acquire, // acquire any new `Waiter` memory |
| 17989 | )) |new_state| { |
| 17990 | continue :state new_state; |
| 17991 | } |
| 17992 | } |
| 17993 | // Now we're ready to actually hand the lock over to them. |
| 17994 | const tid = waiter.tid; // load before the unpark below potentially invalidates `waiter` |
| 17995 | if (need_unpark_flag) setUnparkFlag(&waiter.unpark_flag); |
| 17996 | unpark(&.{tid}, m); |
| 17997 | return; |
| 17998 | }, |
| 17999 | } |
| 18000 | } |
| 18001 | }; |
| 18002 | |
| 18003 | fn timeoutToWindowsInterval(timeout: Io.Timeout) ?windows.LARGE_INTEGER { |
| 18004 | // ntdll only supports two combinations: |
| 18005 | // * real-time (`.real`) sleeps with absolute deadlines |
| 18006 | // * monotonic (`.awake`/`.boot`) sleeps with relative durations |
| 18007 | const clock = switch (timeout) { |
| 18008 | .none => return null, |
| 18009 | .duration => |d| d.clock, |
| 18010 | .deadline => |d| d.clock, |
| 18011 | }; |
| 18012 | switch (clock) { |
| 18013 | .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time |
| 18014 | .real => { |
| 18015 | const deadline = switch (timeout) { |
| 18016 | .none => unreachable, |
| 18017 | .duration => |d| nowWindows(clock).addDuration(d.raw), |
| 18018 | .deadline => |d| d.raw, |
| 18019 | }; |
| 18020 | const epoch_ns = std.time.epoch.windows * std.time.ns_per_s; |
| 18021 | return @intCast(@max(@divTrunc(deadline.nanoseconds - epoch_ns, 100), 0)); |
| 18022 | }, |
| 18023 | .awake, .boot => { |
| 18024 | const duration = switch (timeout) { |
| 18025 | .none => unreachable, |
| 18026 | .duration => |d| d.raw, |
| 18027 | .deadline => |d| nowWindows(clock).durationTo(d.raw), |
| 18028 | }; |
| 18029 | return @intCast(@min(@divTrunc(-duration.nanoseconds, 100), -1)); |
| 18030 | }, |
| 18031 | } |
| 18032 | } |
| 18033 | |
| 18034 | /// The API on NetBSD and Illumos sucks and can unpark spuriously (well, it *can't*, but signals |
| 18035 | /// cause an indistinguishable unblock, and libpthread really likes to leave unparks pending). |
| 18036 | /// As such, on these targets only, we need to pass around a flag to track whether a thread is |
| 18037 | /// "actually" being unparked. |
| 18038 | const need_unpark_flag = switch (native_os) { |
| 18039 | .netbsd, .illumos => true, |
| 18040 | else => false, |
| 18041 | }; |
| 18042 | const UnparkFlag = if (need_unpark_flag) std.atomic.Value(bool) else void; |
| 18043 | const unpark_flag_init: UnparkFlag = if (need_unpark_flag) .init(false); |
| 18044 | /// Must be called before `unpark`. After this function is called, the thread may be unparked at any |
| 18045 | /// time, so the caller must not reference values on its stack. |
| 18046 | fn setUnparkFlag(f: *UnparkFlag) void { |
| 18047 | f.store(true, .release); |
| 18048 | } |
| 18049 | |
| 18050 | /// The type passed into `unpark` for the thread ID. You'd think this was just a `std.Thread.Id`, |
| 18051 | /// but it seems that someone at Microsoft forgot how big their TIDs are supposed to be. |
| 18052 | const UnparkTid = switch (native_os) { |
| 18053 | .windows => usize, |
| 18054 | else => ParkTid, |
| 18055 | }; |
| 18056 | |
| 18057 | const ParkTid = switch (native_os) { |
| 18058 | .haiku => std.c.sem_id, |
| 18059 | else => std.Thread.Id, |
| 18060 | }; |
| 18061 | |
| 18062 | threadlocal var park_sem: std.c.sem_id = -1; |
| 18063 | |
| 18064 | fn getParkTid() ParkTid { |
| 18065 | switch (native_os) { |
| 18066 | .haiku => { |
| 18067 | if (park_sem == -1) { |
| 18068 | park_sem = std.c._kern_create_sem(0, null); |
| 18069 | if (park_sem < 0) @panic("_kern_create_sem failed"); |
| 18070 | _ = std.c.on_exit_thread(destroyParkSem, null); |
| 18071 | } |
| 18072 | return park_sem; |
| 18073 | }, |
| 18074 | else => { |
| 18075 | return if (Thread.current) |thread| thread.id else std.Thread.getCurrentId(); |
| 18076 | }, |
| 18077 | } |
| 18078 | } |
| 18079 | |
| 18080 | fn destroyParkSem(_: ?*anyopaque) callconv(.c) void { |
| 18081 | _ = std.c._kern_delete_sem(park_sem); |
| 18082 | } |
| 18083 | |
| 18084 | fn park( |
| 18085 | timeout: Io.Timeout, |
| 18086 | /// This value has no semantic effect, but may allow the OS to optimize the operation. |
| 18087 | addr_hint: ?*const anyopaque, |
| 18088 | unpark_flag: if (need_unpark_flag) *UnparkFlag else void, |
| 18089 | ) error{Timeout}!void { |
| 18090 | comptime assert(use_parking_futex or use_parking_sleep); |
| 18091 | switch (native_os) { |
| 18092 | .windows => { |
| 18093 | const raw_timeout = timeoutToWindowsInterval(timeout); |
| 18094 | // `RtlWaitOnAddress` passes the futex address in as the first argument to this call, |
| 18095 | // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId` |
| 18096 | // does *not* accept the address so the kernel can't really be using it as a hint. An |
| 18097 | // old Microsoft blog post discusses a more traditional futex-like mechanism in the |
| 18098 | // kernel which definitely isn't how `RtlWaitOnAddress` works today: |
| 18099 | // |
| 18100 | // https://devblogs.microsoft.com/oldnewthing/20160826-00/?p=94185 |
| 18101 | // |
| 18102 | // ...so it's possible this argument is simply a remnant which no longer does anything |
| 18103 | // (perhaps the implementation changed during development but someone forgot to remove |
| 18104 | // this parameter). However, to err on the side of caution, let's match the behavior of |
| 18105 | // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something |
| 18106 | // stupid such as trying to dereference it. |
| 18107 | switch (windows.ntdll.NtWaitForAlertByThreadId( |
| 18108 | addr_hint, |
| 18109 | if (raw_timeout) |*t| t else null, |
| 18110 | )) { |
| 18111 | .ALERTED => return, |
| 18112 | .TIMEOUT => return error.Timeout, |
| 18113 | else => unreachable, |
| 18114 | } |
| 18115 | }, |
| 18116 | .netbsd => { |
| 18117 | var ts_buf: posix.timespec = undefined; |
| 18118 | const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) { |
| 18119 | .none => .{ null, false, false }, |
| 18120 | .deadline => |timestamp| timeout: { |
| 18121 | ts_buf = timestampToPosix(timestamp.raw.nanoseconds); |
| 18122 | break :timeout .{ &ts_buf, true, timestamp.clock == .real }; |
| 18123 | }, |
| 18124 | .duration => |duration| timeout: { |
| 18125 | ts_buf = timestampToPosix(duration.raw.nanoseconds); |
| 18126 | break :timeout .{ &ts_buf, false, duration.clock == .real }; |
| 18127 | }, |
| 18128 | }; |
| 18129 | // It's okay to pass the same timeout in a loop. If it's a duration, the OS actually |
| 18130 | // writes the remaining time into the buffer when the syscall returns. |
| 18131 | while (!unpark_flag.swap(false, .acquire)) { |
| 18132 | switch (posix.errno(std.c._lwp_park( |
| 18133 | if (clock_real) .REALTIME else .MONOTONIC, |
| 18134 | .{ .ABSTIME = abstime }, |
| 18135 | ts, |
| 18136 | 0, |
| 18137 | addr_hint, |
| 18138 | null, |
| 18139 | ))) { |
| 18140 | .SUCCESS, .ALREADY, .INTR => {}, |
| 18141 | .TIMEDOUT => return error.Timeout, |
| 18142 | .INVAL => unreachable, |
| 18143 | .SRCH => unreachable, |
| 18144 | else => unreachable, |
| 18145 | } |
| 18146 | } |
| 18147 | }, |
| 18148 | .illumos => @panic("TODO: illumos lwp_park"), |
| 18149 | .haiku => { |
| 18150 | const timeout_flags: u32, const timeout_us = switch (timeout) { |
| 18151 | .none => .{ 0, 0 }, |
| 18152 | .deadline => |deadline| .{ |
| 18153 | if (deadline.clock == .real) std.c.B_ABSOLUTE_TIMEOUT | std.c.B_TIMEOUT_REAL_TIME_BASE else std.c.B_ABSOLUTE_TIMEOUT, |
| 18154 | deadline.raw.toMicroseconds(), |
| 18155 | }, |
| 18156 | .duration => |duration| .{ |
| 18157 | if (duration.clock == .real) std.c.B_ABSOLUTE_TIMEOUT | std.c.B_TIMEOUT_REAL_TIME_BASE else std.c.B_ABSOLUTE_TIMEOUT, |
| 18158 | nowPosix(duration.clock).addDuration(duration.raw).toMicroseconds(), |
| 18159 | }, |
| 18160 | }; |
| 18161 | while (true) { |
| 18162 | switch (std.c._kern_acquire_sem_etc(park_sem, 1, timeout_flags, timeout_us)) { |
| 18163 | 0 => return, |
| 18164 | std.c.E.B_TIMED_OUT => return error.Timeout, |
| 18165 | std.c.E.B_INTERRUPTED => {}, |
| 18166 | else => unreachable, |
| 18167 | } |
| 18168 | } |
| 18169 | }, |
| 18170 | else => comptime unreachable, |
| 18171 | } |
| 18172 | } |
| 18173 | /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. |
| 18174 | fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { |
| 18175 | comptime assert(use_parking_futex or use_parking_sleep); |
| 18176 | switch (native_os) { |
| 18177 | .windows => { |
| 18178 | // TODO: this condition is currently disabled because mingw-w64 does not contain this |
| 18179 | // symbol. Once it's added, enable this check to use the new bulk API where possible. |
| 18180 | if (false and (builtin.os.version_range.windows.isAtLeast(.win11_dt) orelse false)) { |
| 18181 | _ = windows.ntdll.NtAlertMultipleThreadByThreadId(tids.ptr, @intCast(tids.len), null, null); |
| 18182 | } else { |
| 18183 | for (tids) |tid| { |
| 18184 | _ = windows.ntdll.NtAlertThreadByThreadId(@intCast(tid)); |
| 18185 | } |
| 18186 | } |
| 18187 | }, |
| 18188 | .netbsd => { |
| 18189 | switch (posix.errno(std.c._lwp_unpark_all(@ptrCast(tids.ptr), tids.len, addr_hint))) { |
| 18190 | .SUCCESS => return, |
| 18191 | // For errors, fall through to a loop over `tids`, though this is only expected to |
| 18192 | // be possible for ENOMEM (even that is questionable) and ESRCH (see comment below). |
| 18193 | .SRCH => {}, |
| 18194 | .FAULT => recoverableOsBugDetected(), |
| 18195 | .INVAL => recoverableOsBugDetected(), |
| 18196 | .NOMEM => {}, |
| 18197 | else => recoverableOsBugDetected(), |
| 18198 | } |
| 18199 | for (tids) |tid| { |
| 18200 | switch (posix.errno(std.c._lwp_unpark(@bitCast(tid), addr_hint))) { |
| 18201 | .SUCCESS => {}, |
| 18202 | .SRCH => { |
| 18203 | // This can happen in a rare race: the thread might have been spuriously |
| 18204 | // unparked, so already observed the changing status, and from there have |
| 18205 | // exited. That's okay, because the thread has woken up like we wanted. |
| 18206 | }, |
| 18207 | else => recoverableOsBugDetected(), |
| 18208 | } |
| 18209 | } |
| 18210 | }, |
| 18211 | .illumos => @panic("TODO: illumos lwp_unpark"), |
| 18212 | .haiku => { |
| 18213 | for (tids) |tid| { |
| 18214 | switch (std.c._kern_release_sem_etc(tid, 1, 0)) { |
| 18215 | 0 => {}, |
| 18216 | else => recoverableOsBugDetected(), |
| 18217 | } |
| 18218 | } |
| 18219 | }, |
| 18220 | else => comptime unreachable, |
| 18221 | } |
| 18222 | } |
| 18223 | |
| 18224 | pub const PipeError = error{ |
| 18225 | SystemFdQuotaExceeded, |
| 18226 | ProcessFdQuotaExceeded, |
| 18227 | } || Io.UnexpectedError; |
| 18228 | |
| 18229 | pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t { |
| 18230 | var fds: [2]posix.fd_t = undefined; |
| 18231 | |
| 18232 | if (@TypeOf(posix.system.pipe2) != void) { |
| 18233 | switch (posix.errno(posix.system.pipe2(&fds, flags))) { |
| 18234 | .SUCCESS => return fds, |
| 18235 | .INVAL => |err| return errnoBug(err), // Invalid flags |
| 18236 | .NFILE => return error.SystemFdQuotaExceeded, |
| 18237 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 18238 | else => |err| return posix.unexpectedErrno(err), |
| 18239 | } |
| 18240 | } |
| 18241 | |
| 18242 | switch (posix.errno(posix.system.pipe(&fds))) { |
| 18243 | .SUCCESS => {}, |
| 18244 | .NFILE => return error.SystemFdQuotaExceeded, |
| 18245 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 18246 | else => |err| return posix.unexpectedErrno(err), |
| 18247 | } |
| 18248 | errdefer { |
| 18249 | closeFd(fds[0]); |
| 18250 | closeFd(fds[1]); |
| 18251 | } |
| 18252 | |
| 18253 | // https://github.com/ziglang/zig/issues/18882 |
| 18254 | if (@as(u32, @bitCast(flags)) == 0) return fds; |
| 18255 | |
| 18256 | // CLOEXEC is special, it's a file descriptor flag and must be set using |
| 18257 | // F.SETFD. |
| 18258 | if (flags.CLOEXEC) for (fds) |fd| { |
| 18259 | switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(u32, posix.FD_CLOEXEC)))) { |
| 18260 | .SUCCESS => {}, |
| 18261 | else => |err| return posix.unexpectedErrno(err), |
| 18262 | } |
| 18263 | }; |
| 18264 | |
| 18265 | const new_flags: u32 = f: { |
| 18266 | var new_flags = flags; |
| 18267 | new_flags.CLOEXEC = false; |
| 18268 | break :f @bitCast(new_flags); |
| 18269 | }; |
| 18270 | |
| 18271 | // Set every other flag affecting the file status using F.SETFL. |
| 18272 | if (new_flags != 0) for (fds) |fd| { |
| 18273 | switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, new_flags))) { |
| 18274 | .SUCCESS => {}, |
| 18275 | .INVAL => |err| return errnoBug(err), |
| 18276 | else => |err| return posix.unexpectedErrno(err), |
| 18277 | } |
| 18278 | }; |
| 18279 | |
| 18280 | return fds; |
| 18281 | } |
| 18282 | |
| 18283 | pub const DupError = error{ |
| 18284 | ProcessFdQuotaExceeded, |
| 18285 | SystemResources, |
| 18286 | } || Io.UnexpectedError || Io.Cancelable; |
| 18287 | |
| 18288 | pub fn dup2(old_fd: posix.fd_t, new_fd: posix.fd_t) DupError!void { |
| 18289 | const syscall: Syscall = try .start(); |
| 18290 | while (true) switch (posix.errno(posix.system.dup2(old_fd, new_fd))) { |
| 18291 | .SUCCESS => return syscall.finish(), |
| 18292 | .BUSY, .INTR => { |
| 18293 | try syscall.checkCancel(); |
| 18294 | continue; |
| 18295 | }, |
| 18296 | .INVAL => |err| return syscall.errnoBug(err), // invalid parameters |
| 18297 | .BADF => |err| return syscall.errnoBug(err), // use after free |
| 18298 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| 18299 | .NOMEM => return syscall.fail(error.SystemResources), |
| 18300 | else => |err| return syscall.unexpectedErrno(err), |
| 18301 | }; |
| 18302 | } |
| 18303 | |
| 18304 | pub const FchdirError = error{ |
| 18305 | AccessDenied, |
| 18306 | NotDir, |
| 18307 | FileSystem, |
| 18308 | } || Io.Cancelable || Io.UnexpectedError; |
| 18309 | |
| 18310 | pub fn fchdir(fd: posix.fd_t) FchdirError!void { |
| 18311 | if (fd == posix.AT.FDCWD) return; |
| 18312 | const syscall: Syscall = try .start(); |
| 18313 | while (true) switch (posix.errno(posix.system.fchdir(fd))) { |
| 18314 | .SUCCESS => return syscall.finish(), |
| 18315 | .INTR => { |
| 18316 | try syscall.checkCancel(); |
| 18317 | continue; |
| 18318 | }, |
| 18319 | .ACCES => return syscall.fail(error.AccessDenied), |
| 18320 | .NOTDIR => return syscall.fail(error.NotDir), |
| 18321 | .IO => return syscall.fail(error.FileSystem), |
| 18322 | .BADF => |err| return syscall.errnoBug(err), |
| 18323 | else => |err| return syscall.unexpectedErrno(err), |
| 18324 | }; |
| 18325 | } |
| 18326 | |
| 18327 | pub const ChdirError = error{ |
| 18328 | AccessDenied, |
| 18329 | FileSystem, |
| 18330 | SymLinkLoop, |
| 18331 | NameTooLong, |
| 18332 | FileNotFound, |
| 18333 | SystemResources, |
| 18334 | NotDir, |
| 18335 | BadPathName, |
| 18336 | } || Io.Cancelable || Io.UnexpectedError; |
| 18337 | |
| 18338 | pub fn chdir(dir_path: []const u8) ChdirError!void { |
| 18339 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 18340 | const dir_path_posix = try pathToPosix(dir_path, &path_buffer); |
| 18341 | const syscall: Syscall = try .start(); |
| 18342 | while (true) switch (posix.errno(posix.system.chdir(dir_path_posix))) { |
| 18343 | .SUCCESS => return syscall.finish(), |
| 18344 | .INTR => { |
| 18345 | try syscall.checkCancel(); |
| 18346 | continue; |
| 18347 | }, |
| 18348 | .ACCES => return syscall.fail(error.AccessDenied), |
| 18349 | .IO => return syscall.fail(error.FileSystem), |
| 18350 | .LOOP => return syscall.fail(error.SymLinkLoop), |
| 18351 | .NAMETOOLONG => return syscall.fail(error.NameTooLong), |
| 18352 | .NOENT => return syscall.fail(error.FileNotFound), |
| 18353 | .NOMEM => return syscall.fail(error.SystemResources), |
| 18354 | .NOTDIR => return syscall.fail(error.NotDir), |
| 18355 | .ILSEQ => return syscall.fail(error.BadPathName), |
| 18356 | .FAULT => |err| return syscall.errnoBug(err), |
| 18357 | else => |err| return syscall.unexpectedErrno(err), |
| 18358 | }; |
| 18359 | } |
| 18360 | |
| 18361 | fn fileMemoryMapCreate( |
| 18362 | userdata: ?*anyopaque, |
| 18363 | file: File, |
| 18364 | options: File.MemoryMap.CreateOptions, |
| 18365 | ) File.MemoryMap.CreateError!File.MemoryMap { |
| 18366 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 18367 | const offset = options.offset; |
| 18368 | const len = options.len; |
| 18369 | |
| 18370 | if (!t.disable_memory_mapping) { |
| 18371 | if (createFileMap(file, options.protection, offset, options.populate, len)) |result| { |
| 18372 | return result; |
| 18373 | } else |err| switch (err) { |
| 18374 | error.Unseekable, error.Canceled, error.AccessDenied => |e| return e, |
| 18375 | error.OperationUnsupported => {}, |
| 18376 | else => { |
| 18377 | if (builtin.mode == .debug) |
| 18378 | std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err}); |
| 18379 | }, |
| 18380 | } |
| 18381 | } |
| 18382 | |
| 18383 | const gpa = t.allocator; |
| 18384 | const page_size = std.heap.pageSize(); |
| 18385 | const alignment: Alignment = .fromByteUnits(page_size); |
| 18386 | const memory = m: { |
| 18387 | const ptr = gpa.rawAlloc(len, alignment, @returnAddress()) orelse return error.OutOfMemory; |
| 18388 | break :m ptr[0..len]; |
| 18389 | }; |
| 18390 | errdefer gpa.rawFree(memory, alignment, @returnAddress()); |
| 18391 | |
| 18392 | if (!options.undefined_contents) try mmSyncRead(file, memory, offset); |
| 18393 | |
| 18394 | return .{ |
| 18395 | .file = file, |
| 18396 | .offset = offset, |
| 18397 | .memory = @alignCast(memory), |
| 18398 | .section = null, |
| 18399 | }; |
| 18400 | } |
| 18401 | |
| 18402 | const CreateFileMapError = error{ |
| 18403 | /// MaximumSize is greater than the system-defined maximum for sections, or |
| 18404 | /// greater than the specified file and the section is not writable. |
| 18405 | SectionOversize, |
| 18406 | /// A file descriptor refers to a non-regular file. Or a file mapping was requested, |
| 18407 | /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested |
| 18408 | /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode. |
| 18409 | /// Or `PROT_WRITE` is set, but the file is append-only. |
| 18410 | AccessDenied, |
| 18411 | /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on |
| 18412 | /// a filesystem that was mounted no-exec. |
| 18413 | PermissionDenied, |
| 18414 | FileBusy, |
| 18415 | LockedMemoryLimitExceeded, |
| 18416 | OperationUnsupported, |
| 18417 | ProcessFdQuotaExceeded, |
| 18418 | SystemFdQuotaExceeded, |
| 18419 | OutOfMemory, |
| 18420 | MappingAlreadyExists, |
| 18421 | Unseekable, |
| 18422 | LockViolation, |
| 18423 | } || Io.Cancelable || Io.UnexpectedError; |
| 18424 | |
| 18425 | fn createFileMap( |
| 18426 | file: File, |
| 18427 | protection: std.process.MemoryProtection, |
| 18428 | offset: u64, |
| 18429 | populate: bool, |
| 18430 | len: usize, |
| 18431 | ) CreateFileMapError!File.MemoryMap { |
| 18432 | if (is_windows) { |
| 18433 | try Thread.checkCancel(); |
| 18434 | |
| 18435 | var section = windows.INVALID_HANDLE_VALUE; |
| 18436 | const section_size: windows.LARGE_INTEGER = @intCast(len); |
| 18437 | const page = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied; |
| 18438 | switch (windows.ntdll.NtCreateSection( |
| 18439 | &section, |
| 18440 | .{ |
| 18441 | .SPECIFIC = .{ .SECTION = .{ |
| 18442 | .QUERY = true, |
| 18443 | .MAP_WRITE = protection.write, |
| 18444 | .MAP_READ = protection.read, |
| 18445 | .MAP_EXECUTE = protection.execute, |
| 18446 | .EXTEND_SIZE = true, |
| 18447 | } }, |
| 18448 | .STANDARD = .{ .RIGHTS = .REQUIRED }, |
| 18449 | }, |
| 18450 | null, |
| 18451 | &section_size, |
| 18452 | page, |
| 18453 | .{ .COMMIT = populate }, |
| 18454 | file.handle, |
| 18455 | )) { |
| 18456 | .SUCCESS => {}, |
| 18457 | .FILE_LOCK_CONFLICT => return error.LockViolation, |
| 18458 | .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported, |
| 18459 | .ACCESS_DENIED => return error.AccessDenied, |
| 18460 | .SECTION_TOO_BIG => return error.SectionOversize, |
| 18461 | else => |status| return windows.unexpectedStatus(status), |
| 18462 | } |
| 18463 | var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null; |
| 18464 | var contents_len = len; |
| 18465 | switch (windows.ntdll.NtMapViewOfSection( |
| 18466 | section, |
| 18467 | windows.current_process, |
| 18468 | @ptrCast(&contents_ptr), |
| 18469 | null, |
| 18470 | 0, |
| 18471 | null, |
| 18472 | &contents_len, |
| 18473 | .Unmap, |
| 18474 | .{}, |
| 18475 | page, |
| 18476 | )) { |
| 18477 | .SUCCESS => {}, |
| 18478 | .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists, |
| 18479 | .SECTION_PROTECTION => return error.PermissionDenied, |
| 18480 | .ACCESS_DENIED => return error.AccessDenied, |
| 18481 | .INVALID_VIEW_SIZE => |status| return windows.statusBug(status), |
| 18482 | else => |status| return windows.unexpectedStatus(status), |
| 18483 | } |
| 18484 | if (builtin.mode == .debug) { |
| 18485 | const page_size = std.heap.pageSize(); |
| 18486 | const alignment: Alignment = .fromByteUnits(page_size); |
| 18487 | assert(contents_len == alignment.forward(len)); |
| 18488 | } |
| 18489 | return .{ |
| 18490 | .file = file, |
| 18491 | .offset = offset, |
| 18492 | .memory = contents_ptr.?[0..len], |
| 18493 | .section = section, |
| 18494 | }; |
| 18495 | } else if (have_mmap) { |
| 18496 | const prot: posix.PROT = .{ |
| 18497 | .READ = protection.read, |
| 18498 | .WRITE = protection.write, |
| 18499 | .EXEC = protection.execute, |
| 18500 | }; |
| 18501 | const flags: posix.MAP = switch (native_os) { |
| 18502 | .linux => .{ |
| 18503 | .TYPE = .SHARED_VALIDATE, |
| 18504 | .POPULATE = populate, |
| 18505 | }, |
| 18506 | else => .{ |
| 18507 | .TYPE = .SHARED, |
| 18508 | }, |
| 18509 | }; |
| 18510 | |
| 18511 | const page_align = std.heap.page_size_min; |
| 18512 | |
| 18513 | const contents = while (true) { |
| 18514 | const syscall: Syscall = try .start(); |
| 18515 | const casted_offset = std.math.cast(i64, offset) orelse return error.Unseekable; |
| 18516 | const rc = mmap_sym(null, len, prot, flags, file.handle, casted_offset); |
| 18517 | syscall.finish(); |
| 18518 | const err: posix.E = if (builtin.link_libc) e: { |
| 18519 | if (rc != std.c.MAP_FAILED) { |
| 18520 | break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..len]; |
| 18521 | } |
| 18522 | break :e @fromBackingInt(@intCast(posix.system._errno().*)); |
| 18523 | } else e: { |
| 18524 | const err = posix.errno(rc); |
| 18525 | if (err == .SUCCESS) { |
| 18526 | break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..len]; |
| 18527 | } |
| 18528 | break :e err; |
| 18529 | }; |
| 18530 | switch (err) { |
| 18531 | .SUCCESS => unreachable, |
| 18532 | .INTR => continue, |
| 18533 | .ACCES => return error.AccessDenied, |
| 18534 | .AGAIN => return error.LockedMemoryLimitExceeded, |
| 18535 | .EXIST => return error.MappingAlreadyExists, |
| 18536 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 18537 | .NFILE => return error.SystemFdQuotaExceeded, |
| 18538 | .NODEV => return error.OperationUnsupported, |
| 18539 | .NOMEM => return error.OutOfMemory, |
| 18540 | .PERM => return error.PermissionDenied, |
| 18541 | .TXTBSY => return error.FileBusy, |
| 18542 | .OVERFLOW => return error.Unseekable, |
| 18543 | .BADF => return errnoBug(err), // Always a race condition. |
| 18544 | .INVAL => return errnoBug(err), // Invalid parameters to mmap() |
| 18545 | .OPNOTSUPP => return errnoBug(err), // Bad flags with MAP.SHARED_VALIDATE on Linux. |
| 18546 | else => return posix.unexpectedErrno(err), |
| 18547 | } |
| 18548 | }; |
| 18549 | return .{ |
| 18550 | .file = file, |
| 18551 | .offset = offset, |
| 18552 | .memory = contents, |
| 18553 | .section = {}, |
| 18554 | }; |
| 18555 | } |
| 18556 | |
| 18557 | return error.OperationUnsupported; |
| 18558 | } |
| 18559 | |
| 18560 | fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void { |
| 18561 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 18562 | const memory = mm.memory; |
| 18563 | if (mm.section) |section| switch (native_os) { |
| 18564 | .windows => { |
| 18565 | if (section == windows.INVALID_HANDLE_VALUE) return; |
| 18566 | _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr); |
| 18567 | windows.CloseHandle(section); |
| 18568 | }, |
| 18569 | .wasi => unreachable, |
| 18570 | else => { |
| 18571 | if (memory.len == 0) return; |
| 18572 | switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) { |
| 18573 | .SUCCESS => {}, |
| 18574 | else => |e| { |
| 18575 | if (builtin.mode == .debug) |
| 18576 | std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e }); |
| 18577 | }, |
| 18578 | } |
| 18579 | }, |
| 18580 | } else { |
| 18581 | const gpa = t.allocator; |
| 18582 | gpa.rawFree(memory, .fromByteUnits(std.heap.pageSize()), @returnAddress()); |
| 18583 | } |
| 18584 | mm.* = undefined; |
| 18585 | } |
| 18586 | |
| 18587 | fn fileMemoryMapSetLength( |
| 18588 | userdata: ?*anyopaque, |
| 18589 | mm: *File.MemoryMap, |
| 18590 | new_len: usize, |
| 18591 | ) File.MemoryMap.SetLengthError!void { |
| 18592 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 18593 | const page_size = std.heap.pageSize(); |
| 18594 | const alignment: Alignment = .fromByteUnits(page_size); |
| 18595 | const page_align = std.heap.page_size_min; |
| 18596 | const old_memory = mm.memory; |
| 18597 | |
| 18598 | if (mm.section) |section| { |
| 18599 | _ = section; |
| 18600 | if (alignment.forward(new_len) == alignment.forward(old_memory.len)) { |
| 18601 | mm.memory.len = new_len; |
| 18602 | return; |
| 18603 | } |
| 18604 | switch (native_os) { |
| 18605 | .wasi => unreachable, |
| 18606 | .linux => { |
| 18607 | const flags: posix.MREMAP = .{ .MAYMOVE = true }; |
| 18608 | const addr_hint: ?[*]const u8 = null; |
| 18609 | const new_memory = while (true) { |
| 18610 | const syscall: Syscall = try .start(); |
| 18611 | const rc = posix.system.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint); |
| 18612 | syscall.finish(); |
| 18613 | const err: posix.E = if (builtin.link_libc) e: { |
| 18614 | if (rc != std.c.MAP_FAILED) break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..new_len]; |
| 18615 | break :e @fromBackingInt(@intCast(posix.system._errno().*)); |
| 18616 | } else e: { |
| 18617 | const err = posix.errno(rc); |
| 18618 | if (err == .SUCCESS) break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len]; |
| 18619 | break :e err; |
| 18620 | }; |
| 18621 | switch (err) { |
| 18622 | .SUCCESS => unreachable, |
| 18623 | .INTR => continue, |
| 18624 | .AGAIN => return error.LockedMemoryLimitExceeded, |
| 18625 | .NOMEM => return error.OutOfMemory, |
| 18626 | .INVAL => return errnoBug(err), |
| 18627 | .FAULT => return errnoBug(err), |
| 18628 | else => return posix.unexpectedErrno(err), |
| 18629 | } |
| 18630 | }; |
| 18631 | mm.memory = new_memory; |
| 18632 | return; |
| 18633 | }, |
| 18634 | else => return error.OperationUnsupported, |
| 18635 | } |
| 18636 | } else { |
| 18637 | const gpa = t.allocator; |
| 18638 | if (gpa.rawRemap(old_memory, alignment, new_len, @returnAddress())) |new_ptr| { |
| 18639 | mm.memory = @alignCast(new_ptr[0..new_len]); |
| 18640 | } else { |
| 18641 | const new_ptr: [*]align(page_align) u8 = @alignCast( |
| 18642 | gpa.rawAlloc(new_len, alignment, @returnAddress()) orelse return error.OutOfMemory, |
| 18643 | ); |
| 18644 | const copy_len = @min(new_len, old_memory.len); |
| 18645 | @memcpy(new_ptr[0..copy_len], old_memory[0..copy_len]); |
| 18646 | mm.memory = new_ptr[0..new_len]; |
| 18647 | gpa.rawFree(old_memory, alignment, @returnAddress()); |
| 18648 | } |
| 18649 | } |
| 18650 | } |
| 18651 | |
| 18652 | fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositionalError!void { |
| 18653 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 18654 | _ = t; |
| 18655 | const section = mm.section orelse return mmSyncRead(mm.file, mm.memory, mm.offset); |
| 18656 | _ = section; |
| 18657 | } |
| 18658 | |
| 18659 | fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void { |
| 18660 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 18661 | _ = t; |
| 18662 | const section = mm.section orelse return mmSyncWrite(mm.file, mm.memory, mm.offset); |
| 18663 | _ = section; |
| 18664 | } |
| 18665 | |
| 18666 | fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void { |
| 18667 | if (is_windows) { |
| 18668 | var i: usize = 0; |
| 18669 | while (true) { |
| 18670 | const buf = memory[i..]; |
| 18671 | if (buf.len == 0) break; |
| 18672 | const n = try readFilePositionalWindows(file, buf, offset + i); |
| 18673 | if (n == 0) { |
| 18674 | @memset(memory[i..], 0); |
| 18675 | break; |
| 18676 | } |
| 18677 | i += n; |
| 18678 | } |
| 18679 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 18680 | var i: usize = 0; |
| 18681 | const syscall: Syscall = try .start(); |
| 18682 | while (true) { |
| 18683 | const buf = memory[i..]; |
| 18684 | if (buf.len == 0) { |
| 18685 | syscall.finish(); |
| 18686 | break; |
| 18687 | } |
| 18688 | var n: usize = undefined; |
| 18689 | const vec: std.os.wasi.iovec_t = .{ .base = buf.ptr, .len = buf.len }; |
| 18690 | switch (std.os.wasi.fd_pread(file.handle, (&vec)[0..1], 1, offset + i, &n)) { |
| 18691 | .SUCCESS => { |
| 18692 | if (n == 0) { |
| 18693 | syscall.finish(); |
| 18694 | @memset(memory[i..], 0); |
| 18695 | break; |
| 18696 | } |
| 18697 | i += n; |
| 18698 | try syscall.checkCancel(); |
| 18699 | continue; |
| 18700 | }, |
| 18701 | .INTR, .TIMEDOUT => { |
| 18702 | try syscall.checkCancel(); |
| 18703 | continue; |
| 18704 | }, |
| 18705 | .NOTCONN => |err| return syscall.errnoBug(err), // not a socket |
| 18706 | .CONNRESET => |err| return syscall.errnoBug(err), // not a socket |
| 18707 | .BADF => |err| return syscall.errnoBug(err), // use after free |
| 18708 | .INVAL => |err| return syscall.errnoBug(err), |
| 18709 | .FAULT => |err| return syscall.errnoBug(err), // segmentation fault |
| 18710 | .AGAIN => |err| return syscall.errnoBug(err), |
| 18711 | .IO => return syscall.fail(error.InputOutput), |
| 18712 | .ISDIR => return syscall.fail(error.IsDir), |
| 18713 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 18714 | .NOMEM => return syscall.fail(error.SystemResources), |
| 18715 | .NXIO => return syscall.fail(error.Unseekable), |
| 18716 | .SPIPE => return syscall.fail(error.Unseekable), |
| 18717 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 18718 | .NOTCAPABLE => return syscall.fail(error.AccessDenied), |
| 18719 | else => |err| return syscall.unexpectedErrno(err), |
| 18720 | } |
| 18721 | } |
| 18722 | } else { |
| 18723 | var i: usize = 0; |
| 18724 | const syscall: Syscall = try .start(); |
| 18725 | while (true) { |
| 18726 | const buf = memory[i..]; |
| 18727 | if (buf.len == 0) { |
| 18728 | syscall.finish(); |
| 18729 | break; |
| 18730 | } |
| 18731 | const rc = pread_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i)); |
| 18732 | switch (posix.errno(rc)) { |
| 18733 | .SUCCESS => { |
| 18734 | const n: usize = @intCast(rc); |
| 18735 | if (n == 0) { |
| 18736 | syscall.finish(); |
| 18737 | @memset(memory[i..], 0); |
| 18738 | break; |
| 18739 | } |
| 18740 | i += n; |
| 18741 | try syscall.checkCancel(); |
| 18742 | continue; |
| 18743 | }, |
| 18744 | .INTR, .TIMEDOUT => { |
| 18745 | try syscall.checkCancel(); |
| 18746 | continue; |
| 18747 | }, |
| 18748 | .NXIO => return syscall.fail(error.Unseekable), |
| 18749 | .SPIPE => return syscall.fail(error.Unseekable), |
| 18750 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 18751 | .NOBUFS => return syscall.fail(error.SystemResources), |
| 18752 | .NOMEM => return syscall.fail(error.SystemResources), |
| 18753 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 18754 | .IO => return syscall.fail(error.InputOutput), |
| 18755 | .ISDIR => return syscall.fail(error.IsDir), |
| 18756 | .NOTCONN => |err| return syscall.errnoBug(err), // not a socket |
| 18757 | .CONNRESET => |err| return syscall.errnoBug(err), // not a socket |
| 18758 | .INVAL => |err| return syscall.errnoBug(err), |
| 18759 | .FAULT => |err| return syscall.errnoBug(err), |
| 18760 | .BADF => |err| return syscall.errnoBug(err), // use after free |
| 18761 | else => |err| return syscall.unexpectedErrno(err), |
| 18762 | } |
| 18763 | } |
| 18764 | } |
| 18765 | } |
| 18766 | |
| 18767 | fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError!void { |
| 18768 | if (is_windows) { |
| 18769 | var i: usize = 0; |
| 18770 | while (true) { |
| 18771 | const buf = memory[i..]; |
| 18772 | if (buf.len == 0) break; |
| 18773 | i += try writeFilePositionalWindows(file, memory[i..], offset + i); |
| 18774 | } |
| 18775 | } else if (native_os == .wasi and !builtin.link_libc) { |
| 18776 | var i: usize = 0; |
| 18777 | var n: usize = undefined; |
| 18778 | const syscall: Syscall = try .start(); |
| 18779 | while (true) { |
| 18780 | const buf = memory[i..]; |
| 18781 | if (buf.len == 0) { |
| 18782 | syscall.finish(); |
| 18783 | break; |
| 18784 | } |
| 18785 | const iovec: std.os.wasi.ciovec_t = .{ .base = buf.ptr, .len = buf.len }; |
| 18786 | switch (std.os.wasi.fd_pwrite(file.handle, (&iovec)[0..1], 1, offset + i, &n)) { |
| 18787 | .SUCCESS => { |
| 18788 | i += n; |
| 18789 | try syscall.checkCancel(); |
| 18790 | continue; |
| 18791 | }, |
| 18792 | .INTR => { |
| 18793 | try syscall.checkCancel(); |
| 18794 | continue; |
| 18795 | }, |
| 18796 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 18797 | .FBIG => return syscall.fail(error.FileTooBig), |
| 18798 | .IO => return syscall.fail(error.InputOutput), |
| 18799 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 18800 | .PERM => return syscall.fail(error.PermissionDenied), |
| 18801 | .PIPE => return syscall.fail(error.BrokenPipe), |
| 18802 | .NOTCAPABLE => return syscall.fail(error.AccessDenied), |
| 18803 | .NXIO => return syscall.fail(error.Unseekable), |
| 18804 | .SPIPE => return syscall.fail(error.Unseekable), |
| 18805 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 18806 | .INVAL => |err| return syscall.errnoBug(err), |
| 18807 | .FAULT => |err| return syscall.errnoBug(err), |
| 18808 | .AGAIN => |err| return syscall.errnoBug(err), |
| 18809 | .BADF => |err| return syscall.errnoBug(err), // use after free |
| 18810 | .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket |
| 18811 | else => |err| return syscall.unexpectedErrno(err), |
| 18812 | } |
| 18813 | } |
| 18814 | } else { |
| 18815 | var i: usize = 0; |
| 18816 | const syscall: Syscall = try .start(); |
| 18817 | while (true) { |
| 18818 | const buf = memory[i..]; |
| 18819 | if (buf.len == 0) { |
| 18820 | syscall.finish(); |
| 18821 | break; |
| 18822 | } |
| 18823 | const rc = pwrite_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i)); |
| 18824 | switch (posix.errno(rc)) { |
| 18825 | .SUCCESS => { |
| 18826 | const n: usize = @bitCast(rc); |
| 18827 | i += n; |
| 18828 | try syscall.checkCancel(); |
| 18829 | continue; |
| 18830 | }, |
| 18831 | .INTR => { |
| 18832 | try syscall.checkCancel(); |
| 18833 | continue; |
| 18834 | }, |
| 18835 | .INVAL => |err| return syscall.errnoBug(err), |
| 18836 | .FAULT => |err| return syscall.errnoBug(err), |
| 18837 | .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket |
| 18838 | .CONNRESET => |err| return syscall.errnoBug(err), // not a socket |
| 18839 | .BADF => return syscall.fail(error.NotOpenForWriting), |
| 18840 | .AGAIN => return syscall.fail(error.WouldBlock), |
| 18841 | .DQUOT => return syscall.fail(error.DiskQuota), |
| 18842 | .FBIG => return syscall.fail(error.FileTooBig), |
| 18843 | .IO => return syscall.fail(error.InputOutput), |
| 18844 | .NOSPC => return syscall.fail(error.NoSpaceLeft), |
| 18845 | .PERM => return syscall.fail(error.PermissionDenied), |
| 18846 | .PIPE => return syscall.fail(error.BrokenPipe), |
| 18847 | .BUSY => return syscall.fail(error.DeviceBusy), |
| 18848 | .TXTBSY => return syscall.fail(error.FileBusy), |
| 18849 | .NXIO => return syscall.fail(error.Unseekable), |
| 18850 | .SPIPE => return syscall.fail(error.Unseekable), |
| 18851 | .OVERFLOW => return syscall.fail(error.Unseekable), |
| 18852 | else => |err| return syscall.unexpectedErrno(err), |
| 18853 | } |
| 18854 | } |
| 18855 | } |
| 18856 | } |
| 18857 | |
| 18858 | fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { |
| 18859 | if (is_windows) { |
| 18860 | const NtControlFile = switch (o.code.DeviceType) { |
| 18861 | .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, |
| 18862 | else => &windows.ntdll.NtDeviceIoControlFile, |
| 18863 | }; |
| 18864 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 18865 | if (o.file.flags.nonblocking) { |
| 18866 | var done: bool = false; |
| 18867 | switch (NtControlFile( |
| 18868 | o.file.handle, |
| 18869 | null, // event |
| 18870 | flagApc, |
| 18871 | &done, // APC context |
| 18872 | &iosb, |
| 18873 | o.code, |
| 18874 | if (o.in.len > 0) o.in.ptr else null, |
| 18875 | @intCast(o.in.len), |
| 18876 | if (o.out.len > 0) o.out.ptr else null, |
| 18877 | @intCast(o.out.len), |
| 18878 | )) { |
| 18879 | // We must wait for the APC routine. |
| 18880 | .PENDING, .SUCCESS => while (!done) { |
| 18881 | // Once we get here we must not return from the function until the |
| 18882 | // operation completes, thereby releasing reference to io_status_block. |
| 18883 | const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { |
| 18884 | error.Canceled => |e| { |
| 18885 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; |
| 18886 | _ = windows.ntdll.NtCancelIoFileEx(o.file.handle, &iosb, &cancel_iosb); |
| 18887 | while (!done) waitForApcOrAlert(); |
| 18888 | return e; |
| 18889 | }, |
| 18890 | }; |
| 18891 | waitForApcOrAlert(); |
| 18892 | alertable_syscall.finish(); |
| 18893 | }, |
| 18894 | else => |status| iosb.u.Status = status, |
| 18895 | } |
| 18896 | } else { |
| 18897 | const syscall: Syscall = try .start(); |
| 18898 | while (true) switch (NtControlFile( |
| 18899 | o.file.handle, |
| 18900 | null, // event |
| 18901 | null, // APC routine |
| 18902 | null, // APC context |
| 18903 | &iosb, |
| 18904 | o.code, |
| 18905 | if (o.in.len > 0) o.in.ptr else null, |
| 18906 | @intCast(o.in.len), |
| 18907 | if (o.out.len > 0) o.out.ptr else null, |
| 18908 | @intCast(o.out.len), |
| 18909 | )) { |
| 18910 | .PENDING => unreachable, // unrecoverable: wrong asynchronous flag |
| 18911 | .CANCELLED => { |
| 18912 | try syscall.checkCancel(); |
| 18913 | continue; |
| 18914 | }, |
| 18915 | else => |status| { |
| 18916 | syscall.finish(); |
| 18917 | iosb.u.Status = status; |
| 18918 | break; |
| 18919 | }, |
| 18920 | }; |
| 18921 | } |
| 18922 | return iosb; |
| 18923 | } else { |
| 18924 | const syscall: Syscall = try .start(); |
| 18925 | while (true) { |
| 18926 | const rc = posix.system.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg)); |
| 18927 | switch (posix.errno(rc)) { |
| 18928 | .SUCCESS => { |
| 18929 | syscall.finish(); |
| 18930 | if (@TypeOf(rc) == usize) return @bitCast(@as(u32, @truncate(rc))); |
| 18931 | return rc; |
| 18932 | }, |
| 18933 | .INTR => { |
| 18934 | try syscall.checkCancel(); |
| 18935 | continue; |
| 18936 | }, |
| 18937 | else => |err| { |
| 18938 | syscall.finish(); |
| 18939 | return -@as(i32, @backingInt(err)); |
| 18940 | }, |
| 18941 | } |
| 18942 | } |
| 18943 | } |
| 18944 | } |
| 18945 | |
| 18946 | const WaitGroup = struct { |
| 18947 | state: std.atomic.Value(usize), |
| 18948 | event: Io.Event, |
| 18949 | |
| 18950 | const init: WaitGroup = .{ .state = .{ .raw = 0 }, .event = .unset }; |
| 18951 | |
| 18952 | const is_waiting: usize = 1 << 0; |
| 18953 | const one_pending: usize = 1 << 1; |
| 18954 | |
| 18955 | fn start(wg: *WaitGroup) void { |
| 18956 | const prev_state = wg.state.fetchAdd(one_pending, .monotonic); |
| 18957 | assert((prev_state / one_pending) < (std.math.maxInt(usize) / one_pending)); |
| 18958 | } |
| 18959 | |
| 18960 | fn value(wg: *WaitGroup) usize { |
| 18961 | return wg.state.load(.monotonic) / one_pending; |
| 18962 | } |
| 18963 | |
| 18964 | fn wait(wg: *WaitGroup) void { |
| 18965 | const prev_state = wg.state.fetchAdd(is_waiting, .acquire); |
| 18966 | assert(prev_state & is_waiting == 0); |
| 18967 | if ((prev_state / one_pending) > 0) eventWait(&wg.event); |
| 18968 | } |
| 18969 | |
| 18970 | fn finish(wg: *WaitGroup) void { |
| 18971 | const state = wg.state.fetchSub(one_pending, .acq_rel); |
| 18972 | assert((state / one_pending) > 0); |
| 18973 | |
| 18974 | if (state == (one_pending | is_waiting)) { |
| 18975 | eventSet(&wg.event); |
| 18976 | } |
| 18977 | } |
| 18978 | }; |
| 18979 | |
| 18980 | /// Same as `Io.Event.wait` but avoids the VTable. |
| 18981 | fn eventWait(event: *Io.Event) void { |
| 18982 | if (@cmpxchgStrong(Io.Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) { |
| 18983 | .unset => unreachable, |
| 18984 | .waiting => {}, |
| 18985 | .is_set => return, |
| 18986 | }; |
| 18987 | while (true) { |
| 18988 | Thread.futexWaitUncancelable(@ptrCast(event), @backingInt(Io.Event.waiting), null); |
| 18989 | switch (@atomicLoad(Io.Event, event, .acquire)) { |
| 18990 | .unset => unreachable, // `reset` called before pending `wait` returned |
| 18991 | .waiting => continue, |
| 18992 | .is_set => return, |
| 18993 | } |
| 18994 | } |
| 18995 | } |
| 18996 | |
| 18997 | /// Same as `Io.Event.set` but avoids the VTable. |
| 18998 | fn eventSet(event: *Io.Event) void { |
| 18999 | switch (@atomicRmw(Io.Event, event, .Xchg, .is_set, .release)) { |
| 19000 | .unset, .is_set => {}, |
| 19001 | .waiting => Thread.futexWake(@ptrCast(event), std.math.maxInt(u32)), |
| 19002 | } |
| 19003 | } |
| 19004 | |
| 19005 | /// Same as `Io.Condition.broadcast` but avoids the VTable. |
| 19006 | fn condBroadcast(cond: *Io.Condition) void { |
| 19007 | var prev_state = cond.state.load(.monotonic); |
| 19008 | while (prev_state.waiters > prev_state.signals) { |
| 19009 | @branchHint(.unlikely); |
| 19010 | prev_state = cond.state.cmpxchgWeak(prev_state, .{ |
| 19011 | .waiters = prev_state.waiters, |
| 19012 | .signals = prev_state.waiters, |
| 19013 | }, .release, .monotonic) orelse { |
| 19014 | // Update the epoch to tell the waiting threads that there are new signals for them. |
| 19015 | // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen |
| 19016 | // between it observing the epoch and sleeping on it, but this is extraordinarily |
| 19017 | // unlikely due to the precise number of calls required. |
| 19018 | _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update |
| 19019 | Thread.futexWake(&cond.epoch.raw, prev_state.waiters - prev_state.signals); |
| 19020 | return; |
| 19021 | }; |
| 19022 | } |
| 19023 | } |
| 19024 | |
| 19025 | /// Same as `Io.Condition.signal` but avoids the VTable. |
| 19026 | fn condSignal(cond: *Io.Condition) void { |
| 19027 | var prev_state = cond.state.load(.monotonic); |
| 19028 | while (prev_state.waiters > prev_state.signals) { |
| 19029 | @branchHint(.unlikely); |
| 19030 | prev_state = cond.state.cmpxchgWeak(prev_state, .{ |
| 19031 | .waiters = prev_state.waiters, |
| 19032 | .signals = prev_state.signals + 1, |
| 19033 | }, .release, .monotonic) orelse { |
| 19034 | // Update the epoch to tell the waiting threads that there are new signals for them. |
| 19035 | // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen |
| 19036 | // between it observing the epoch and sleeping on it, but this is extraordinarily |
| 19037 | // unlikely due to the precise number of calls required. |
| 19038 | _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update |
| 19039 | Thread.futexWake(&cond.epoch.raw, 1); |
| 19040 | return; |
| 19041 | }; |
| 19042 | } |
| 19043 | } |
| 19044 | |
| 19045 | /// Same as `Io.Condition.waitUncancelable` but avoids the VTable. |
| 19046 | fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { |
| 19047 | var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load |
| 19048 | |
| 19049 | { |
| 19050 | const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic); |
| 19051 | assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters |
| 19052 | } |
| 19053 | |
| 19054 | mutexUnlock(mutex); |
| 19055 | defer mutexLock(mutex); |
| 19056 | |
| 19057 | while (true) { |
| 19058 | Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null); |
| 19059 | |
| 19060 | epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod |
| 19061 | |
| 19062 | var prev_state = cond.state.load(.monotonic); |
| 19063 | while (prev_state.signals > 0) { |
| 19064 | prev_state = cond.state.cmpxchgWeak(prev_state, .{ |
| 19065 | .waiters = prev_state.waiters - 1, |
| 19066 | .signals = prev_state.signals - 1, |
| 19067 | }, .acquire, .monotonic) orelse { |
| 19068 | // We successfully consumed a signal. |
| 19069 | return; |
| 19070 | }; |
| 19071 | } |
| 19072 | } |
| 19073 | } |
| 19074 | |
| 19075 | /// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. |
| 19076 | pub fn mutexLock(m: *Io.Mutex) void { |
| 19077 | const initial_state = m.state.cmpxchgStrong( |
| 19078 | .unlocked, |
| 19079 | .locked_once, |
| 19080 | .acquire, |
| 19081 | .monotonic, |
| 19082 | ) orelse { |
| 19083 | @branchHint(.likely); |
| 19084 | return; |
| 19085 | }; |
| 19086 | if (initial_state == .contended) { |
| 19087 | Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null); |
| 19088 | } |
| 19089 | while (m.state.swap(.contended, .acquire) != .unlocked) { |
| 19090 | Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @backingInt(Io.Mutex.State.contended), null); |
| 19091 | } |
| 19092 | } |
| 19093 | |
| 19094 | /// Same as `Io.Mutex.unlock` but avoids the VTable. |
| 19095 | pub fn mutexUnlock(m: *Io.Mutex) void { |
| 19096 | switch (m.state.swap(.unlocked, .release)) { |
| 19097 | .unlocked => unreachable, |
| 19098 | .locked_once => {}, |
| 19099 | .contended => { |
| 19100 | @branchHint(.unlikely); |
| 19101 | Thread.futexWake(@ptrCast(&m.state.raw), 1); |
| 19102 | }, |
| 19103 | } |
| 19104 | } |
| 19105 | |
| 19106 | const OpenError = error{ |
| 19107 | IsDir, |
| 19108 | NotDir, |
| 19109 | FileNotFound, |
| 19110 | NoDevice, |
| 19111 | AccessDenied, |
| 19112 | PipeBusy, |
| 19113 | PathAlreadyExists, |
| 19114 | WouldBlock, |
| 19115 | NetworkNotFound, |
| 19116 | AntivirusInterference, |
| 19117 | FileBusy, |
| 19118 | } || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; |
| 19119 | |
| 19120 | const OpenFileOptions = struct { |
| 19121 | access_mask: windows.ACCESS_MASK, |
| 19122 | dir: ?windows.HANDLE = null, |
| 19123 | sa: ?*const windows.SECURITY_ATTRIBUTES = null, |
| 19124 | share_access: windows.FILE.SHARE = .VALID_FLAGS, |
| 19125 | creation: windows.FILE.CREATE_DISPOSITION, |
| 19126 | filter: Filter = .non_directory_only, |
| 19127 | /// If false, tries to open path as a reparse point without dereferencing it. |
| 19128 | /// Defaults to true. |
| 19129 | follow_symlinks: bool = true, |
| 19130 | |
| 19131 | pub const Filter = enum { |
| 19132 | /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory. |
| 19133 | non_directory_only, |
| 19134 | /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory. |
| 19135 | dir_only, |
| 19136 | /// `OpenFile` does not discriminate between opening files and directories. |
| 19137 | any, |
| 19138 | }; |
| 19139 | }; |
| 19140 | |
| 19141 | /// TODO: inline this logic everywhere and delete this function |
| 19142 | fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows.HANDLE { |
| 19143 | if (std.mem.eql(u16, sub_path_w, &.{'.'}) and options.filter == .non_directory_only) { |
| 19144 | return error.IsDir; |
| 19145 | } |
| 19146 | if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' }) and options.filter == .non_directory_only) { |
| 19147 | return error.IsDir; |
| 19148 | } |
| 19149 | |
| 19150 | var result: windows.HANDLE = undefined; |
| 19151 | |
| 19152 | const attr: windows.OBJECT.ATTRIBUTES = .{ |
| 19153 | .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, |
| 19154 | .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle.toBool() else false }, |
| 19155 | .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w)), |
| 19156 | .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, |
| 19157 | }; |
| 19158 | |
| 19159 | var iosb: windows.IO_STATUS_BLOCK = undefined; |
| 19160 | var attempt: u5 = 0; |
| 19161 | var syscall: Syscall = try .start(); |
| 19162 | while (true) { |
| 19163 | switch (windows.ntdll.NtCreateFile( |
| 19164 | &result, |
| 19165 | options.access_mask, |
| 19166 | &attr, |
| 19167 | &iosb, |
| 19168 | null, |
| 19169 | .{ .NORMAL = true }, |
| 19170 | options.share_access, |
| 19171 | options.creation, |
| 19172 | .{ |
| 19173 | .DIRECTORY_FILE = options.filter == .dir_only, |
| 19174 | .NON_DIRECTORY_FILE = options.filter == .non_directory_only, |
| 19175 | .IO = .SYNCHRONOUS_NONALERT, |
| 19176 | .OPEN_REPARSE_POINT = !options.follow_symlinks, |
| 19177 | }, |
| 19178 | null, |
| 19179 | 0, |
| 19180 | )) { |
| 19181 | .SUCCESS => { |
| 19182 | syscall.finish(); |
| 19183 | return result; |
| 19184 | }, |
| 19185 | .CANCELLED => { |
| 19186 | try syscall.checkCancel(); |
| 19187 | continue; |
| 19188 | }, |
| 19189 | .SHARING_VIOLATION => { |
| 19190 | // This occurs if the file attempting to be opened is a running |
| 19191 | // executable. However, there's a kernel bug: the error may be |
| 19192 | // incorrectly returned for an indeterminate amount of time |
| 19193 | // after an executable file is closed. Here we work around the |
| 19194 | // kernel bug with retry attempts. |
| 19195 | syscall.finish(); |
| 19196 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 19197 | try parking_sleep.sleep(.{ .duration = .{ |
| 19198 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 19199 | .clock = .awake, |
| 19200 | } }); |
| 19201 | attempt += 1; |
| 19202 | syscall = try .start(); |
| 19203 | continue; |
| 19204 | }, |
| 19205 | .DELETE_PENDING => { |
| 19206 | // This error means that there *was* a file in this location on |
| 19207 | // the file system, but it was deleted. However, the OS is not |
| 19208 | // finished with the deletion operation, and so this CreateFile |
| 19209 | // call has failed. There is not really a sane way to handle |
| 19210 | // this other than retrying the creation after the OS finishes |
| 19211 | // the deletion. |
| 19212 | syscall.finish(); |
| 19213 | if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; |
| 19214 | try parking_sleep.sleep(.{ .duration = .{ |
| 19215 | .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), |
| 19216 | .clock = .awake, |
| 19217 | } }); |
| 19218 | attempt += 1; |
| 19219 | syscall = try .start(); |
| 19220 | continue; |
| 19221 | }, |
| 19222 | .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), |
| 19223 | .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 19224 | .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), |
| 19225 | .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found |
| 19226 | .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't |
| 19227 | .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), |
| 19228 | .ACCESS_DENIED => return syscall.fail(error.AccessDenied), |
| 19229 | .PIPE_BUSY => return syscall.fail(error.PipeBusy), |
| 19230 | .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), |
| 19231 | .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), |
| 19232 | .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), |
| 19233 | .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), |
| 19234 | .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), |
| 19235 | .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), |
| 19236 | .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), |
| 19237 | .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), |
| 19238 | .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), |
| 19239 | else => |status| return syscall.unexpectedNtstatus(status), |
| 19240 | } |
| 19241 | } |
| 19242 | } |
| 19243 | |
| 19244 | pub fn closeFd(fd: posix.fd_t) void { |
| 19245 | if (native_os == .wasi and !builtin.link_libc) { |
| 19246 | switch (std.os.wasi.fd_close(fd)) { |
| 19247 | .SUCCESS, .INTR => {}, |
| 19248 | .BADF => recoverableOsBugDetected(), // use after free |
| 19249 | else => recoverableOsBugDetected(), // unexpected failure |
| 19250 | } |
| 19251 | } else switch (posix.errno(posix.system.close(fd))) { |
| 19252 | .SUCCESS, .INTR => {}, // INTR still a success, see https://github.com/ziglang/zig/issues/2425 |
| 19253 | .BADF => recoverableOsBugDetected(), // use after free |
| 19254 | else => recoverableOsBugDetected(), // unexpected failure |
| 19255 | } |
| 19256 | } |