diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index e3daa4da1713db..6e05878e0ea5b9 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -439,6 +439,11 @@ pub const StandaloneModuleGraph = struct { const cleanup = struct { pub fn toClean(name: [:0]const u8, fd: bun.FileDescriptor) void { + // Ensure we own the file + if (Environment.isPosix) { + // Make the file writable so we can delete it + _ = Syscall.fchmod(fd, 0o777); + } _ = Syscall.close(fd); _ = Syscall.unlink(name); } diff --git a/src/bun.js/api/bun/process.zig b/src/bun.js/api/bun/process.zig index fe982a202dd371..08cbbcf78b71d5 100644 --- a/src/bun.js/api/bun/process.zig +++ b/src/bun.js/api/bun/process.zig @@ -969,6 +969,7 @@ pub const PosixSpawnOptions = struct { windows: void = {}, argv0: ?[*:0]const u8 = null, stream: bool = true, + sync: bool = false, /// Apple Extension: If this bit is set, rather /// than returning to the caller, posix_spawn(2) @@ -998,6 +999,7 @@ pub const WindowsSpawnResult = struct { stderr: StdioResult = .unavailable, extra_pipes: std.ArrayList(StdioResult) = std.ArrayList(StdioResult).init(bun.default_allocator), stream: bool = true, + sync: bool = false, pub const StdioResult = union(enum) { /// inherit, ignore, path, pipe @@ -1240,8 +1242,7 @@ pub fn spawnProcessPosix( var to_set_cloexec = std.ArrayList(bun.FileDescriptor).init(allocator); defer { for (to_set_cloexec.items) |fd| { - const fcntl_flags = bun.sys.fcntl(fd, std.posix.F.GETFD, 0).unwrap() catch continue; - _ = bun.sys.fcntl(fd, std.posix.F.SETFD, bun.C.FD_CLOEXEC | fcntl_flags); + _ = bun.sys.setCloseOnExec(fd); } to_set_cloexec.clearAndFree(); @@ -1323,24 +1324,9 @@ pub fn spawnProcessPosix( } const fds: [2]bun.FileDescriptor = brk: { - var fds_: [2]std.c.fd_t = undefined; - const rc = std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds_); - if (rc != 0) { - return error.SystemResources; - } - - { - const before = std.c.fcntl(fds_[if (i == 0) 1 else 0], std.posix.F.GETFD); - _ = std.c.fcntl(fds_[if (i == 0) 1 else 0], std.posix.F.SETFD, before | std.posix.FD_CLOEXEC); - } + const pair = try bun.sys.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, .blocking).unwrap(); - if (comptime Environment.isMac) { - // SO_NOSIGPIPE - const before: i32 = 1; - _ = std.c.setsockopt(fds_[if (i == 0) 1 else 0], std.posix.SOL.SOCKET, std.posix.SO.NOSIGPIPE, &before, @sizeOf(c_int)); - } - - break :brk .{ bun.toFD(fds_[if (i == 0) 1 else 0]), bun.toFD(fds_[if (i == 0) 0 else 1]) }; + break :brk .{ bun.toFD(pair[if (i == 0) 1 else 0]), bun.toFD(pair[if (i == 0) 0 else 1]) }; }; if (i == 0) { @@ -1381,6 +1367,10 @@ pub fn spawnProcessPosix( try to_close_at_end.append(fds[1]); try to_close_on_error.append(fds[0]); + if (!options.sync) { + try bun.sys.setNonblocking(fds[0]).unwrap(); + } + try actions.dup2(fds[1], fileno); if (fds[1] != fileno) try actions.close(fds[1]); @@ -1414,25 +1404,12 @@ pub fn spawnProcessPosix( try actions.open(fileno, path, bun.O.RDWR | bun.O.CREAT, 0o664); }, .ipc, .buffer => { - const fds: [2]bun.FileDescriptor = brk: { - var fds_: [2]std.c.fd_t = undefined; - const rc = std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds_); - if (rc != 0) { - return error.SystemResources; - } - - // enable non-block - var before = std.c.fcntl(fds_[0], std.posix.F.GETFD); - - _ = std.c.fcntl(fds_[0], std.posix.F.SETFD, before | bun.C.FD_CLOEXEC); - - if (comptime Environment.isMac) { - // SO_NOSIGPIPE - _ = std.c.setsockopt(fds_[if (i == 0) 1 else 0], std.posix.SOL.SOCKET, std.posix.SO.NOSIGPIPE, &before, @sizeOf(c_int)); - } - - break :brk .{ bun.toFD(fds_[0]), bun.toFD(fds_[1]) }; - }; + const fds: [2]bun.FileDescriptor = try bun.sys.socketpair( + std.posix.AF.UNIX, + std.posix.SOCK.STREAM, + 0, + .nonblocking, + ).unwrap(); try to_close_at_end.append(fds[1]); try to_close_on_error.append(fds[0]); diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index ec729b3da6577f..38b89765205d95 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -3574,31 +3574,16 @@ pub const Interpreter = struct { pipe[0] = bun.FDImpl.fromUV(fds[0]).encode(); pipe[1] = bun.FDImpl.fromUV(fds[1]).encode(); } else { - const fds: [2]bun.FileDescriptor = brk: { - var fds_: [2]std.c.fd_t = undefined; - const rc = std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &fds_); - if (rc != 0) { - return bun.sys.Maybe(void).errno(bun.sys.getErrno(rc), .socketpair); - } - - var before = std.c.fcntl(fds_[0], std.posix.F.GETFL); - - const result = std.c.fcntl(fds_[0], std.posix.F.SETFL, before | bun.O.CLOEXEC); - if (result == -1) { - _ = bun.sys.close(bun.toFD(fds_[0])); - _ = bun.sys.close(bun.toFD(fds_[1])); - return Maybe(void).errno(bun.sys.getErrno(result), .fcntl); - } - - if (comptime bun.Environment.isMac) { - // SO_NOSIGPIPE - before = 1; - _ = std.c.setsockopt(fds_[0], std.posix.SOL.SOCKET, std.posix.SO.NOSIGPIPE, &before, @sizeOf(c_int)); - } - - break :brk .{ bun.toFD(fds_[0]), bun.toFD(fds_[1]) }; + const fds: [2]bun.FileDescriptor = switch (bun.sys.socketpair( + std.posix.AF.UNIX, + std.posix.SOCK.STREAM, + 0, + .blocking, + )) { + .result => |fds| .{ bun.toFD(fds[0]), bun.toFD(fds[1]) }, + .err => |err| return .{ .err = err }, }; - log("socketpair() = {{{}, {}}}", .{ fds[0], fds[1] }); + pipe.* = fds; } set_count.* += 1; diff --git a/src/sys.zig b/src/sys.zig index de27a70a0a0519..470111b7217a8b 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -249,6 +249,7 @@ pub const Tag = enum(u8) { pipe, try_write, socketpair, + setsockopt, uv_spawn, uv_pipe, @@ -751,9 +752,16 @@ pub fn mkdirOSPath(file_path: bun.OSPathSliceZ, flags: bun.Mode) Maybe(void) { const fnctl_int = if (Environment.isLinux) usize else c_int; pub fn fcntl(fd: bun.FileDescriptor, cmd: i32, arg: fnctl_int) Maybe(fnctl_int) { - const result = fcntl_symbol(fd.cast(), cmd, arg); - if (Maybe(fnctl_int).errnoSys(result, .fcntl)) |err| return err; - return .{ .result = @intCast(result) }; + while (true) { + const result = fcntl_symbol(fd.cast(), cmd, arg); + if (Maybe(fnctl_int).errnoSys(result, .fcntl)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + return .{ .result = @intCast(result) }; + } + + unreachable; } pub fn getErrno(rc: anytype) bun.C.E { @@ -2308,6 +2316,132 @@ pub fn mmapFile(path: [:0]const u8, flags: std.c.MAP, wanted_size: ?usize, offse return .{ .result = map }; } +pub fn setCloseOnExec(fd: bun.FileDescriptor) Maybe(void) { + switch (fcntl(fd, std.posix.F.GETFD, 0)) { + .result => |fl| { + switch (fcntl(fd, std.posix.F.SETFD, fl | std.posix.FD_CLOEXEC)) { + .result => {}, + .err => |err| return .{ .err = err }, + } + }, + .err => |err| return .{ .err = err }, + } + + return .{ .result = {} }; +} + +pub fn setsockopt(fd: bun.FileDescriptor, level: c_int, optname: u32, value: i32) Maybe(i32) { + while (true) { + const rc = syscall.setsockopt(fd.cast(), level, optname, &value, @sizeOf(i32)); + if (Maybe(i32).errnoSys(rc, .setsockopt)) |err| { + if (err.getErrno() == .INTR) continue; + log("setsockopt() = {d} {s}", .{ err.err.errno, err.err.name() }); + return err; + } + log("setsockopt({d}, {d}, {d}) = {d}", .{ fd.cast(), level, optname, rc }); + return .{ .result = @intCast(rc) }; + } + + unreachable; +} + +pub fn setNoSigpipe(fd: bun.FileDescriptor) Maybe(void) { + if (comptime Environment.isMac) { + return switch (setsockopt(fd, std.posix.SOL.SOCKET, std.posix.SO.NOSIGPIPE, 1)) { + .result => .{ .result = {} }, + .err => |err| .{ .err = err }, + }; + } + + return .{ .result = {} }; +} + +const socketpair_t = if (Environment.isLinux) i32 else c_uint; + +/// libc socketpair() except it defaults to: +/// - SOCK_CLOEXEC on Linux +/// - SO_NOSIGPIPE on macOS +/// +/// On POSIX it otherwise makes it do O_CLOEXEC. +pub fn socketpair(domain: socketpair_t, socktype: socketpair_t, protocol: socketpair_t, nonblocking_status: enum { blocking, nonblocking }) Maybe([2]bun.FileDescriptor) { + if (comptime !Environment.isPosix) @compileError("linux only!"); + + var fds_i: [2]syscall.fd_t = .{ 0, 0 }; + + if (comptime Environment.isLinux) { + while (true) { + const nonblock_flag: i32 = if (nonblocking_status == .nonblocking) linux.SOCK.NONBLOCK else 0; + const rc = std.os.linux.socketpair(domain, socktype | linux.SOCK.CLOEXEC | nonblock_flag, protocol, &fds_i); + if (Maybe(void).errnoSys(rc, .socketpair)) |err| { + if (err.getErrno() == .INTR) continue; + + log("socketpair() = {d} {s}", .{ err.err.errno, err.err.name() }); + return .{ .err = err }; + } + + break; + } + } else { + while (true) { + const err = libc.socketpair(domain, socktype, protocol, &fds_i); + + if (Maybe([2]bun.FileDescriptor).errnoSys(err, .socketpair)) |err2| { + if (err2.getErrno() == .INTR) continue; + log("socketpair() = {d} {s}", .{ err2.err.errno, err2.err.name() }); + return err2; + } + + break; + } + + const err: ?Syscall.Error = err: { + + // Set O_CLOEXEC first. + inline for (0..2) |i| { + switch (setCloseOnExec(bun.toFD(fds_i[i]))) { + .err => |err| break :err err, + .result => {}, + } + } + + if (comptime Environment.isMac) { + inline for (0..2) |i| { + switch (setNoSigpipe(bun.toFD(fds_i[i]))) { + .err => |err| break :err err, + else => {}, + } + } + } + + if (nonblocking_status == .nonblocking) { + inline for (0..2) |i| { + switch (setNonblocking(bun.toFD(fds_i[i]))) { + .err => |err| break :err err, + .result => {}, + } + } + } + + break :err null; + }; + + // On any error after socketpair(), we need to close it. + if (err) |errr| { + inline for (0..2) |i| { + _ = close(bun.toFD(fds_i[i])); + } + + log("socketpair() = {d} {s}", .{ errr.errno, errr.name() }); + + return .{ .err = errr }; + } + } + + log("socketpair() = [{d} {d}]", .{ fds_i[0], fds_i[1] }); + + return Maybe([2]bun.FileDescriptor){ .result = .{ bun.toFD(fds_i[0]), bun.toFD(fds_i[1]) } }; +} + pub fn munmap(memory: []align(mem.page_size) const u8) Maybe(void) { if (Maybe(void).errnoSys(syscall.munmap(memory.ptr, memory.len), .munmap)) |err| { return err;