| ... | ... | @@ -64,7 +64,7 @@ stderr_writer_initialized: bool = false, |
| 64 | 64 | argv0: Argv0, |
| 65 | 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 | 69 | pub const Argv0 = switch (native_os) { |
| 70 | 70 | .openbsd, .haiku => 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 | 154 | pub const Pid = if (native_os == .linux) enum(posix.pid_t) { |
| 127 | 155 | unknown = 0, |
| 128 | 156 | _, |
| ... | ... | @@ -1249,18 +1277,14 @@ pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { |
| 1249 | 1277 | |
| 1250 | 1278 | pub fn deinit(t: *Threaded) void { |
| 1251 | 1279 | t.join(); |
| 1252 | | if (is_windows) { |
| 1253 | | if (t.wsa.status == .initialized) { |
| 1254 | | if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); |
| 1255 | | } |
| 1256 | | if (t.nul_handle) |handle| { |
| 1257 | | windows.CloseHandle(handle); |
| 1258 | | } |
| 1280 | if (is_windows and t.wsa.status == .initialized) { |
| 1281 | if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); |
| 1259 | 1282 | } |
| 1260 | 1283 | if (posix.Sigaction != void and t.have_signal_handler) { |
| 1261 | 1284 | if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); |
| 1262 | 1285 | if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); |
| 1263 | 1286 | } |
| 1287 | t.null_file.deinit(); |
| 1264 | 1288 | t.* = undefined; |
| 1265 | 1289 | } |
| 1266 | 1290 | |
| ... | ... | @@ -1429,9 +1453,9 @@ pub fn io(t: *Threaded) Io { |
| 1429 | 1453 | .unlockStderr = unlockStderr, |
| 1430 | 1454 | .processSetCurrentDir = processSetCurrentDir, |
| 1431 | 1455 | .processReplace = processReplace, |
| 1432 | | .processReplacePath = processReplacePath, // TODO audit for cancelation and unreachable |
| 1433 | | .processSpawn = processSpawn, // TODO audit for cancelation and unreachable |
| 1434 | | .processSpawnPath = processSpawnPath, // TODO audit for cancelation and unreachable |
| 1456 | .processReplacePath = processReplacePath, |
| 1457 | .processSpawn = processSpawn, |
| 1458 | .processSpawnPath = processSpawnPath, |
| 1435 | 1459 | .childWait = childWait, // TODO audit for cancelation and unreachable |
| 1436 | 1460 | .childKill = childKill, // TODO audit for cancelation and unreachable |
| 1437 | 1461 | |
| ... | ... | @@ -1656,6 +1680,7 @@ const have_wait4 = switch (native_os) { |
| 1656 | 1680 | else => false, |
| 1657 | 1681 | }; |
| 1658 | 1682 | |
| 1683 | const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open; |
| 1659 | 1684 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; |
| 1660 | 1685 | const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; |
| 1661 | 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 | 12881 | // turns out, we `dup2` everything anyway, so there's no need! |
| 12857 | 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 | 12885 | errdefer if (options.stdin == .pipe) { |
| 12861 | 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 | 12890 | errdefer if (options.stdout == .pipe) { |
| 12866 | 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 | 12895 | errdefer if (options.stderr == .pipe) { |
| 12871 | 12896 | destroyPipe(stderr_pipe); |
| 12872 | 12897 | }; |
| 12873 | 12898 | |
| 12874 | 12899 | const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); |
| 12875 | | // TODO: cache file handle of /dev/null! |
| 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 | | } |
| 12900 | const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; |
| 12897 | 12901 | |
| 12898 | 12902 | const prog_pipe: [2]posix.fd_t = p: { |
| 12899 | 12903 | if (options.progress_node.index == .none) { |
| 12900 | 12904 | break :p .{ -1, -1 }; |
| 12901 | 12905 | } else { |
| 12902 | 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 | 12910 | errdefer destroyPipe(prog_pipe); |
| ... | ... | @@ -12938,13 +12942,23 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 12938 | 12942 | |
| 12939 | 12943 | // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. |
| 12940 | 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 | 12946 | errdefer destroyPipe(err_pipe); |
| 12943 | 12947 | |
| 12944 | 12948 | t.scanEnviron(); // for PATH |
| 12945 | 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 | 12962 | if (pid_result == 0) { |
| 12949 | 12963 | // We are the child. |
| 12950 | 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 | 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 | 13086 | fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { |
| 13034 | 13087 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 13035 | 13088 | const spawned = try spawnPosix(t, options); |
| ... | ... | @@ -13639,7 +13692,7 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { |
| 13639 | 13692 | { |
| 13640 | 13693 | t.mutex.lock(); |
| 13641 | 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 | 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 | 13739 | syscall.finish(); |
| 13687 | 13740 | t.mutex.lock(); // Another thread might have won the race. |
| 13688 | 13741 | defer t.mutex.unlock(); |
| 13689 | | if (t.nul_handle) |prev_handle| { |
| 13742 | if (t.null_file.handle) |prev_handle| { |
| 13690 | 13743 | windows.CloseHandle(fresh_handle); |
| 13691 | 13744 | return prev_handle; |
| 13692 | 13745 | } else { |
| 13693 | | t.nul_handle = fresh_handle; |
| 13746 | t.null_file.handle = fresh_handle; |
| 13694 | 13747 | return fresh_handle; |
| 13695 | 13748 | } |
| 13696 | 13749 | }, |
| ... | ... | @@ -15177,3 +15230,62 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { |
| 15177 | 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 | } |