| ... | @@ -64,7 +64,7 @@ stderr_writer_initialized: bool = false, | ... | @@ -64,7 +64,7 @@ stderr_writer_initialized: bool = false, |
| 64 | argv0: Argv0, | 64 | argv0: Argv0, |
| 65 | environ: Environ, | 65 | environ: Environ, |
| 66 | | 66 | |
| 67 | nul_handle: if (is_windows) ?windows.HANDLE else void = if (is_windows) null else {}, | 67 | null_file: NullFile = .{}, |
| 68 | | 68 | |
| 69 | pub const Argv0 = switch (native_os) { | 69 | pub const Argv0 = switch (native_os) { |
| 70 | .openbsd, .haiku => struct { | 70 | .openbsd, .haiku => struct { |
| ... | @@ -123,6 +123,34 @@ const Environ = struct { | ... | @@ -123,6 +123,34 @@ const Environ = struct { |
| 123 | }; | 123 | }; |
| 124 | }; | 124 | }; |
| 125 | | 125 | |
| | 126 | pub const NullFile = switch (native_os) { |
| | 127 | .windows => struct { |
| | 128 | handle: ?windows.HANDLE = null, |
| | 129 | |
| | 130 | fn deinit(this: *@This()) void { |
| | 131 | if (this.handle) |handle| { |
| | 132 | windows.CloseHandle(handle); |
| | 133 | this.handle = null; |
| | 134 | } |
| | 135 | } |
| | 136 | }, |
| | 137 | .wasi, .ios, .tvos, .visionos, .watchos => struct { |
| | 138 | fn deinit(this: @This()) void { |
| | 139 | _ = this; |
| | 140 | } |
| | 141 | }, |
| | 142 | else => struct { |
| | 143 | fd: posix.fd_t = -1, |
| | 144 | |
| | 145 | fn deinit(this: *@This()) void { |
| | 146 | if (this.fd >= 0) { |
| | 147 | posix.close(this.fd); |
| | 148 | this.fd = -1; |
| | 149 | } |
| | 150 | } |
| | 151 | }, |
| | 152 | }; |
| | 153 | |
| 126 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { | 154 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 127 | unknown = 0, | 155 | unknown = 0, |
| 128 | _, | 156 | _, |
| ... | @@ -1249,18 +1277,14 @@ pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { | ... | @@ -1249,18 +1277,14 @@ pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { |
| 1249 | | 1277 | |
| 1250 | pub fn deinit(t: *Threaded) void { | 1278 | pub fn deinit(t: *Threaded) void { |
| 1251 | t.join(); | 1279 | t.join(); |
| 1252 | if (is_windows) { | 1280 | if (is_windows and t.wsa.status == .initialized) { |
| 1253 | if (t.wsa.status == .initialized) { | 1281 | if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); |
| 1254 | if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); | | |
| 1255 | } | | |
| 1256 | if (t.nul_handle) |handle| { | | |
| 1257 | windows.CloseHandle(handle); | | |
| 1258 | } | | |
| 1259 | } | 1282 | } |
| 1260 | if (posix.Sigaction != void and t.have_signal_handler) { | 1283 | if (posix.Sigaction != void and t.have_signal_handler) { |
| 1261 | if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); | 1284 | if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); |
| 1262 | if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); | 1285 | if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); |
| 1263 | } | 1286 | } |
| | 1287 | t.null_file.deinit(); |
| 1264 | t.* = undefined; | 1288 | t.* = undefined; |
| 1265 | } | 1289 | } |
| 1266 | | 1290 | |
| ... | @@ -1429,9 +1453,9 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1429,9 +1453,9 @@ pub fn io(t: *Threaded) Io { |
| 1429 | .unlockStderr = unlockStderr, | 1453 | .unlockStderr = unlockStderr, |
| 1430 | .processSetCurrentDir = processSetCurrentDir, | 1454 | .processSetCurrentDir = processSetCurrentDir, |
| 1431 | .processReplace = processReplace, | 1455 | .processReplace = processReplace, |
| 1432 | .processReplacePath = processReplacePath, // TODO audit for cancelation and unreachable | 1456 | .processReplacePath = processReplacePath, |
| 1433 | .processSpawn = processSpawn, // TODO audit for cancelation and unreachable | 1457 | .processSpawn = processSpawn, |
| 1434 | .processSpawnPath = processSpawnPath, // TODO audit for cancelation and unreachable | 1458 | .processSpawnPath = processSpawnPath, |
| 1435 | .childWait = childWait, // TODO audit for cancelation and unreachable | 1459 | .childWait = childWait, // TODO audit for cancelation and unreachable |
| 1436 | .childKill = childKill, // TODO audit for cancelation and unreachable | 1460 | .childKill = childKill, // TODO audit for cancelation and unreachable |
| 1437 | | 1461 | |
| ... | @@ -1656,6 +1680,7 @@ const have_wait4 = switch (native_os) { | ... | @@ -1656,6 +1680,7 @@ const have_wait4 = switch (native_os) { |
| 1656 | else => false, | 1680 | else => false, |
| 1657 | }; | 1681 | }; |
| 1658 | | 1682 | |
| | 1683 | const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open; |
| 1659 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; | 1684 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; |
| 1660 | const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; | 1685 | const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; |
| 1661 | const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; | 1686 | const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; |
| ... | @@ -12856,51 +12881,30 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp | ... | @@ -12856,51 +12881,30 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 12856 | // turns out, we `dup2` everything anyway, so there's no need! | 12881 | // turns out, we `dup2` everything anyway, so there's no need! |
| 12857 | const pipe_flags: posix.O = .{ .CLOEXEC = true }; | 12882 | const pipe_flags: posix.O = .{ .CLOEXEC = true }; |
| 12858 | | 12883 | |
| 12859 | const stdin_pipe = if (options.stdin == .pipe) try posix.pipe2(pipe_flags) else undefined; | 12884 | const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined; |
| 12860 | errdefer if (options.stdin == .pipe) { | 12885 | errdefer if (options.stdin == .pipe) { |
| 12861 | destroyPipe(stdin_pipe); | 12886 | destroyPipe(stdin_pipe); |
| 12862 | }; | 12887 | }; |
| 12863 | | 12888 | |
| 12864 | const stdout_pipe = if (options.stdout == .pipe) try posix.pipe2(pipe_flags) else undefined; | 12889 | const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined; |
| 12865 | errdefer if (options.stdout == .pipe) { | 12890 | errdefer if (options.stdout == .pipe) { |
| 12866 | destroyPipe(stdout_pipe); | 12891 | destroyPipe(stdout_pipe); |
| 12867 | }; | 12892 | }; |
| 12868 | | 12893 | |
| 12869 | const stderr_pipe = if (options.stderr == .pipe) try posix.pipe2(pipe_flags) else undefined; | 12894 | const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined; |
| 12870 | errdefer if (options.stderr == .pipe) { | 12895 | errdefer if (options.stderr == .pipe) { |
| 12871 | destroyPipe(stderr_pipe); | 12896 | destroyPipe(stderr_pipe); |
| 12872 | }; | 12897 | }; |
| 12873 | | 12898 | |
| 12874 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); | 12899 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); |
| 12875 | // TODO: cache file handle of /dev/null! | 12900 | const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; |
| 12876 | const dev_null_fd = if (any_ignore) | | |
| 12877 | posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { | | |
| 12878 | error.PathAlreadyExists => unreachable, | | |
| 12879 | error.NoSpaceLeft => unreachable, | | |
| 12880 | error.FileTooBig => unreachable, | | |
| 12881 | error.DeviceBusy => unreachable, | | |
| 12882 | error.FileLocksUnsupported => unreachable, | | |
| 12883 | error.BadPathName => unreachable, // Windows-only | | |
| 12884 | error.WouldBlock => unreachable, | | |
| 12885 | error.NetworkNotFound => unreachable, // Windows-only | | |
| 12886 | error.Canceled => unreachable, // temporarily in the posix error set | | |
| 12887 | error.SharingViolation => unreachable, // Windows-only | | |
| 12888 | error.PipeBusy => unreachable, // not a pipe | | |
| 12889 | error.AntivirusInterference => unreachable, // Windows-only | | |
| 12890 | else => |e| return e, | | |
| 12891 | } | | |
| 12892 | else | | |
| 12893 | undefined; | | |
| 12894 | defer { | | |
| 12895 | if (any_ignore) posix.close(dev_null_fd); | | |
| 12896 | } | | |
| 12897 | | 12901 | |
| 12898 | const prog_pipe: [2]posix.fd_t = p: { | 12902 | const prog_pipe: [2]posix.fd_t = p: { |
| 12899 | if (options.progress_node.index == .none) { | 12903 | if (options.progress_node.index == .none) { |
| 12900 | break :p .{ -1, -1 }; | 12904 | break :p .{ -1, -1 }; |
| 12901 | } else { | 12905 | } else { |
| 12902 | // We use CLOEXEC for the same reason as in `pipe_flags`. | 12906 | // We use CLOEXEC for the same reason as in `pipe_flags`. |
| 12903 | break :p try posix.pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); | 12907 | break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); |
| 12904 | } | 12908 | } |
| 12905 | }; | 12909 | }; |
| 12906 | errdefer destroyPipe(prog_pipe); | 12910 | errdefer destroyPipe(prog_pipe); |
| ... | @@ -12938,13 +12942,23 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp | ... | @@ -12938,13 +12942,23 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 12938 | | 12942 | |
| 12939 | // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. | 12943 | // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. |
| 12940 | // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. | 12944 | // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. |
| 12941 | const err_pipe: [2]posix.fd_t = try posix.pipe2(.{ .CLOEXEC = true }); | 12945 | const err_pipe: [2]posix.fd_t = try pipe2(.{ .CLOEXEC = true }); |
| 12942 | errdefer destroyPipe(err_pipe); | 12946 | errdefer destroyPipe(err_pipe); |
| 12943 | | 12947 | |
| 12944 | t.scanEnviron(); // for PATH | 12948 | t.scanEnviron(); // for PATH |
| 12945 | const PATH = t.environ.string.PATH orelse default_PATH; | 12949 | const PATH = t.environ.string.PATH orelse default_PATH; |
| 12946 | | 12950 | |
| 12947 | const pid_result = try posix.fork(); | 12951 | const pid_result: posix.pid_t = fork: { |
| | 12952 | const rc = posix.system.fork(); |
| | 12953 | switch (posix.errno(rc)) { |
| | 12954 | .SUCCESS => break :fork @intCast(rc), |
| | 12955 | .AGAIN => return error.SystemResources, |
| | 12956 | .NOMEM => return error.SystemResources, |
| | 12957 | .NOSYS => return error.OperationUnsupported, |
| | 12958 | else => |err| return posix.unexpectedErrno(err), |
| | 12959 | } |
| | 12960 | }; |
| | 12961 | |
| 12948 | if (pid_result == 0) { | 12962 | if (pid_result == 0) { |
| 12949 | // We are the child. | 12963 | // We are the child. |
| 12950 | if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; | 12964 | if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; |
| ... | @@ -13030,6 +13044,45 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp | ... | @@ -13030,6 +13044,45 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 13030 | }; | 13044 | }; |
| 13031 | } | 13045 | } |
| 13032 | | 13046 | |
| | 13047 | fn getDevNullFd(t: *Threaded) !posix.fd_t { |
| | 13048 | { |
| | 13049 | t.mutex.lock(); |
| | 13050 | defer t.mutex.unlock(); |
| | 13051 | if (t.null_file.fd != -1) return t.null_file.fd; |
| | 13052 | } |
| | 13053 | const syscall: Syscall = try .start(); |
| | 13054 | while (true) { |
| | 13055 | const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, 0); |
| | 13056 | switch (posix.errno(rc)) { |
| | 13057 | .SUCCESS => { |
| | 13058 | syscall.finish(); |
| | 13059 | const fresh_fd: posix.fd_t = @intCast(rc); |
| | 13060 | t.mutex.lock(); // Another thread might have won the race. |
| | 13061 | defer t.mutex.unlock(); |
| | 13062 | if (t.null_file.fd != -1) { |
| | 13063 | posix.close(fresh_fd); |
| | 13064 | return t.null_file.fd; |
| | 13065 | } else { |
| | 13066 | t.null_file.fd = fresh_fd; |
| | 13067 | return fresh_fd; |
| | 13068 | } |
| | 13069 | }, |
| | 13070 | .INTR => { |
| | 13071 | try syscall.checkCancel(); |
| | 13072 | continue; |
| | 13073 | }, |
| | 13074 | .ACCES => return syscall.fail(error.AccessDenied), |
| | 13075 | .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), |
| | 13076 | .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), |
| | 13077 | .NODEV => return syscall.fail(error.NoDevice), |
| | 13078 | .NOENT => return syscall.fail(error.FileNotFound), |
| | 13079 | .NOMEM => return syscall.fail(error.SystemResources), |
| | 13080 | .PERM => return syscall.fail(error.PermissionDenied), |
| | 13081 | else => |err| return syscall.unexpectedErrno(err), |
| | 13082 | } |
| | 13083 | } |
| | 13084 | } |
| | 13085 | |
| 13033 | fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { | 13086 | fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 13034 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 13087 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13035 | const spawned = try spawnPosix(t, options); | 13088 | const spawned = try spawnPosix(t, options); |
| ... | @@ -13639,7 +13692,7 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { | ... | @@ -13639,7 +13692,7 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 13639 | { | 13692 | { |
| 13640 | t.mutex.lock(); | 13693 | t.mutex.lock(); |
| 13641 | defer t.mutex.unlock(); | 13694 | defer t.mutex.unlock(); |
| 13642 | if (t.nul_handle) |handle| return handle; | 13695 | if (t.null_file.handle) |handle| return handle; |
| 13643 | } | 13696 | } |
| 13644 | | 13697 | |
| 13645 | const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; | 13698 | const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; |
| ... | @@ -13686,11 +13739,11 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { | ... | @@ -13686,11 +13739,11 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 13686 | syscall.finish(); | 13739 | syscall.finish(); |
| 13687 | t.mutex.lock(); // Another thread might have won the race. | 13740 | t.mutex.lock(); // Another thread might have won the race. |
| 13688 | defer t.mutex.unlock(); | 13741 | defer t.mutex.unlock(); |
| 13689 | if (t.nul_handle) |prev_handle| { | 13742 | if (t.null_file.handle) |prev_handle| { |
| 13690 | windows.CloseHandle(fresh_handle); | 13743 | windows.CloseHandle(fresh_handle); |
| 13691 | return prev_handle; | 13744 | return prev_handle; |
| 13692 | } else { | 13745 | } else { |
| 13693 | t.nul_handle = fresh_handle; | 13746 | t.null_file.handle = fresh_handle; |
| 13694 | return fresh_handle; | 13747 | return fresh_handle; |
| 13695 | } | 13748 | } |
| 13696 | }, | 13749 | }, |
| ... | @@ -15177,3 +15230,62 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { | ... | @@ -15177,3 +15230,62 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { |
| 15177 | else => comptime unreachable, | 15230 | else => comptime unreachable, |
| 15178 | } | 15231 | } |
| 15179 | } | 15232 | } |
| | 15233 | |
| | 15234 | pub const PipeError = error{ |
| | 15235 | SystemFdQuotaExceeded, |
| | 15236 | ProcessFdQuotaExceeded, |
| | 15237 | } || Io.UnexpectedError; |
| | 15238 | |
| | 15239 | pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t { |
| | 15240 | var fds: [2]posix.fd_t = undefined; |
| | 15241 | |
| | 15242 | if (@TypeOf(posix.system.pipe2) != void) { |
| | 15243 | switch (posix.errno(posix.system.pipe2(&fds, flags))) { |
| | 15244 | .SUCCESS => return fds, |
| | 15245 | .INVAL => |err| return errnoBug(err), // Invalid flags |
| | 15246 | .NFILE => return error.SystemFdQuotaExceeded, |
| | 15247 | .MFILE => return error.ProcessFdQuotaExceeded, |
| | 15248 | else => |err| return posix.unexpectedErrno(err), |
| | 15249 | } |
| | 15250 | } |
| | 15251 | |
| | 15252 | switch (posix.errno(posix.system.pipe(&fds))) { |
| | 15253 | .SUCCESS => {}, |
| | 15254 | .NFILE => return error.SystemFdQuotaExceeded, |
| | 15255 | .MFILE => return error.ProcessFdQuotaExceeded, |
| | 15256 | else => |err| return posix.unexpectedErrno(err), |
| | 15257 | } |
| | 15258 | errdefer { |
| | 15259 | posix.close(fds[0]); |
| | 15260 | posix.close(fds[1]); |
| | 15261 | } |
| | 15262 | |
| | 15263 | // https://github.com/ziglang/zig/issues/18882 |
| | 15264 | if (@as(u32, @bitCast(flags)) == 0) return fds; |
| | 15265 | |
| | 15266 | // CLOEXEC is special, it's a file descriptor flag and must be set using |
| | 15267 | // F.SETFD. |
| | 15268 | if (flags.CLOEXEC) for (fds) |fd| { |
| | 15269 | switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(u32, posix.FD_CLOEXEC)))) { |
| | 15270 | .SUCCESS => {}, |
| | 15271 | else => |err| return posix.unexpectedErrno(err), |
| | 15272 | } |
| | 15273 | }; |
| | 15274 | |
| | 15275 | const new_flags: u32 = f: { |
| | 15276 | var new_flags = flags; |
| | 15277 | new_flags.CLOEXEC = false; |
| | 15278 | break :f @bitCast(new_flags); |
| | 15279 | }; |
| | 15280 | |
| | 15281 | // Set every other flag affecting the file status using F.SETFL. |
| | 15282 | if (new_flags != 0) for (fds) |fd| { |
| | 15283 | switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, new_flags))) { |
| | 15284 | .SUCCESS => {}, |
| | 15285 | .INVAL => |err| return errnoBug(err), |
| | 15286 | else => |err| return posix.unexpectedErrno(err), |
| | 15287 | } |
| | 15288 | }; |
| | 15289 | |
| | 15290 | return fds; |
| | 15291 | } |