authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-10-17 20:17:27-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-21 10:04:01-07:00
log07ea1c97ee891ea259020fe28fba317c20c3b03a
tree9b75c8b6e8f34729d412d4c0c64de786318bc077
parented136f830665c38af3d0d4beb1177584cb6d40d7

process: fix bsd `spawn`


3 files changed, 111 insertions(+), 70 deletions(-)

lib/std/Io/Threaded.zig+79-54
......@@ -7191,9 +7191,9 @@ fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
71917191 return n;
71927192 },
71937193 .linux, .serenity, .illumos => {
7194 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
7194 var proc_buf: [std.fmt.count("/proc/self/path/{d}\x00", .{std.math.minInt(posix.fd_t)})]u8 = undefined;
71957195 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
7196 const proc_path = std.mem.printSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable;
7196 const proc_path = std.mem.printSentinel(&proc_buf, template, .{fd}, 0) catch unreachable;
71977197 const syscall: Syscall = try .start();
71987198 while (true) {
71997199 const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len);
......@@ -7266,27 +7266,32 @@ fn fileHardLink(
72667266 new_sub_path: []const u8,
72677267 options: File.HardLinkOptions,
72687268) File.HardLinkError!void {
7269 _ = userdata;
7270 if (native_os != .linux) return error.OperationUnsupported;
7269 const t: *Threaded = @ptrCast(@alignCast(userdata));
7270 _ = t;
72717271
72727272 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
72737273 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
72747274
7275 const flags: u32 = if (options.follow_symlinks)
7276 posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH
7277 else
7278 posix.AT.EMPTY_PATH;
7275 switch (native_os) {
7276 else => return error.OperationUnsupported,
7277 .freebsd, .linux => {
7278 const flags: u32 = if (options.follow_symlinks)
7279 posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH
7280 else
7281 posix.AT.EMPTY_PATH;
72797282
7280 return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) {
7281 error.FileNotFound => {
7282 if (options.follow_symlinks) return error.FileNotFound;
7283 var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
7284 const proc_path = std.mem.printSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch
7285 unreachable;
7286 return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW);
7283 return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) {
7284 error.FileNotFound => {},
7285 else => |e| return e,
7286 };
72877287 },
7288 else => |e| return e,
7289 };
7288 .netbsd, .openbsd => {},
7289 }
7290
7291 if (options.follow_symlinks) return error.FileNotFound;
7292 var dev_buf: [std.fmt.count("/dev/fd/{d}\x00", .{std.math.minInt(posix.fd_t)})]u8 = undefined;
7293 const dev_path = std.mem.printSentinel(&dev_buf, "/dev/fd/{d}", .{file.handle}, 0) catch unreachable;
7294 return linkat(posix.AT.FDCWD, dev_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW);
72907295}
72917296
72927297fn linkat(
......@@ -8732,12 +8737,12 @@ fn fchmodatFallback(
87328737) Dir.SetFilePermissionsError!void {
87338738 comptime assert(native_os == .linux);
87348739
8735 // Fallback to changing permissions using procfs:
8740 // Fallback to changing permissions using devfs:
87368741 //
87378742 // 1. Open `path` as a `PATH` descriptor.
87388743 // 2. Stat the fd and check if it isn't a symbolic link.
8739 // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
8740 // 4. Pass the procfs path to `chmod` with the `mode`.
8744 // 3. Generate the devfs reference to the fd via `/dev/fd/{fd}`.
8745 // 4. Pass the devfs path to `chmod` with the `mode`.
87418746 const path_fd: posix.fd_t = fd: {
87428747 const syscall: Syscall = try .start();
87438748 while (true) {
......@@ -8808,11 +8813,11 @@ fn fchmodatFallback(
88088813 if ((path_mode & posix.S.IFMT) == posix.S.IFLNK)
88098814 return error.OperationUnsupported;
88108815
8811 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
8812 const proc_path = std.mem.printSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable;
8816 var dev_buf: [std.fmt.count("/dev/fd/{d}\x00", .{std.math.minInt(posix.fd_t)})]u8 = undefined;
8817 const dev_path = std.mem.printSentinel(&dev_buf, "/dev/fd/{d}", .{path_fd}, 0) catch unreachable;
88138818 const syscall: Syscall = try .start();
88148819 while (true) {
8815 switch (posix.errno(posix.system.chmod(proc_path, mode))) {
8820 switch (posix.errno(posix.system.chmod(dev_path, mode))) {
88168821 .SUCCESS => return syscall.finish(),
88178822 .INTR => {
88188823 try syscall.checkCancel();
......@@ -8821,7 +8826,7 @@ fn fchmodatFallback(
88218826 else => |e| {
88228827 syscall.finish();
88238828 switch (e) {
8824 .NOENT => return error.OperationUnsupported, // procfs not mounted.
8829 .NOENT => return error.OperationUnsupported, // devfs not mounted.
88258830 .BADF => |err| return errnoBug(err),
88268831 .FAULT => |err| return errnoBug(err),
88278832 .INVAL => |err| return errnoBug(err),
......@@ -17446,9 +17451,6 @@ pub fn posixExecveat(
1744617451) process.ReplaceError {
1744717452 try Thread.checkCancel();
1744817453 switch (err: {
17449 if (path) |p| if (dir == posix.AT.FDCWD or Dir.path.isAbsoluteZ(p)) {
17450 break :err posix.errno(posix.system.execve(p, argv, env_block.slice.ptr));
17451 };
1745217454 if (comptime native_os == .linux and (!builtin.link_libc or
1745317455 (builtin.target.isGnuLibC() and builtin.target.os.version_range.linux.glibc.order(
1745417456 .{ .major = 2, .minor = 34, .patch = 0 },
......@@ -17462,34 +17464,57 @@ pub fn posixExecveat(
1746217464 .NOSYS => {},
1746317465 }
1746417466 }
17465 var path_buffer: [posix.PATH_MAX]u8 = undefined;
17466 var path_len = realPathPosix(dir, &path_buffer) catch |err| switch (err) {
17467 else => |e| return e,
17468 error.InputOutput,
17469 error.FileTooBig,
17470 error.NoSpaceLeft,
17471 error.PathAlreadyExists,
17472 error.SymLinkLoop,
17473 error.NetworkNotFound,
17474 error.PipeBusy,
17475 error.AntivirusInterference,
17476 => return error.FileNotFound,
17477 error.DeviceBusy,
17478 error.NoDevice,
17479 error.UnrecognizedVolume,
17480 => return error.FileSystem,
17467 if (path) |p| if (dir == posix.AT.FDCWD or Dir.path.isAbsoluteZ(p)) {
17468 break :err posix.errno(posix.system.execve(p, argv, env_block.slice.ptr));
1748117469 };
17482 if (path) |p| {
17483 const path_slice = std.mem.span(p);
17484 path_buffer[path_len] = Dir.path.sep;
17485 path_len += 1;
17486 @memcpy(path_buffer[path_len..][0..path_slice.len], path_slice);
17487 path_len += path_slice.len;
17488 }
17489 path_buffer[path_len] = 0;
17490 break :err posix.errno(
17491 posix.system.execve(path_buffer[0..path_len :0], argv, env_block.slice.ptr),
17492 );
17470 const exe: posix.fd_t = if (path) |p| exe: {
17471 var flags: posix.O = .{};
17472 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
17473 if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
17474 if (@hasField(posix.O, "NOCTTY")) flags.NOCTTY = true;
17475 if (@hasField(posix.O, "PATH")) flags.PATH = true;
17476 if (@hasField(posix.O, "EXEC")) flags.EXEC = true;
17477 const rc = openat_sym(dir, p, flags, @as(posix.mode_t, 0));
17478 while (true) switch (posix.errno(rc)) {
17479 .SUCCESS => break :exe @intCast(rc),
17480 .INTR, .AGAIN => {},
17481 .FAULT => |err| return errnoBug(err),
17482 .INVAL => return error.BadPathName,
17483 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
17484 .ACCES => return error.AccessDenied,
17485 .FBIG => return error.InvalidExe,
17486 .OVERFLOW => return error.InvalidExe,
17487 .ISDIR => return error.IsDir,
17488 .LOOP => return error.InvalidExe,
17489 .MFILE => return error.ProcessFdQuotaExceeded,
17490 .NAMETOOLONG => return error.NameTooLong,
17491 .NFILE => return error.SystemFdQuotaExceeded,
17492 .NODEV => return error.FileSystem,
17493 .NOENT => return error.FileNotFound,
17494 .SRCH => return error.FileNotFound, // Linux when opening procfs files.
17495 .NOMEM => return error.SystemResources,
17496 .NOTDIR => return error.NotDir,
17497 .PERM => return error.PermissionDenied,
17498 .BUSY => return error.FileSystem,
17499 .TXTBSY => return error.FileBusy,
17500 .NXIO => return error.FileSystem,
17501 .ILSEQ => return error.BadPathName,
17502 else => |err| return posix.unexpectedErrno(err),
17503 };
17504 } else dir;
17505 defer closeFd(exe);
17506 if (builtin.link_libc and @TypeOf(posix.system.fexecve) != void) {
17507 break :err posix.errno(posix.system.fexecve(exe, argv, env_block.slice.ptr));
17508 }
17509 switch (native_os) {
17510 else => return error.OperationUnsupported,
17511 .linux, .serenity, .illumos => {
17512 var proc_buf: [std.fmt.count("/proc/self/path/{d}\x00", .{std.math.minInt(posix.fd_t)})]u8 = undefined;
17513 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
17514 const proc_path = std.mem.printSentinel(&proc_buf, template, .{exe}, 0) catch unreachable;
17515 break :err posix.errno(posix.system.execve(proc_path, argv, env_block.slice.ptr));
17516 },
17517 }
1749317518 }) {
1749417519 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1749517520 .@"2BIG" => return error.SystemResources,
lib/std/c.zig+26-11
......@@ -8387,6 +8387,8 @@ pub const AT = switch (native_os) {
83878387 /// to determine the target of relative file paths in the openat() and
83888388 /// similar syscalls.
83898389 pub const FDCWD = -100;
8390 /// Fail rename if target exists
8391 pub const RENAME_NOREPLACE = 0x0001;
83908392 /// Check access using effective user and group ID
83918393 pub const EACCESS = 0x0100;
83928394 /// Do not follow symbolic links
......@@ -8397,6 +8399,10 @@ pub const AT = switch (native_os) {
83978399 pub const REMOVEDIR = 0x0800;
83988400 /// Fail if not under dirfd
83998401 pub const BENEATH = 0x1000;
8402 /// Do not allow name resolution to walk out of dirfd
8403 pub const RESOLVE_BENEATH = 0x2000;
8404 /// Operate on dirfd if path is empty
8405 pub const EMPTY_PATH = 0x4000;
84008406 },
84018407 .netbsd => struct {
84028408 /// Magic value that specify the use of the current working directory
......@@ -8552,7 +8558,11 @@ pub const O = switch (native_os) {
85528558 DIRECTORY: bool = false,
85538559 CLOEXEC: bool = false,
85548560 SEARCH: bool = false,
8555 _: u8 = 0,
8561 NOSIGPIPE: bool = false,
8562 REGULAR: bool = false,
8563 EXEC: bool = false,
8564 CLOFORK: bool = false,
8565 _: u4 = 0,
85568566 },
85578567 .openbsd => packed struct(u32) {
85588568 ACCMODE: std.posix.ACCMODE = .RDONLY,
......@@ -10937,9 +10947,13 @@ pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: mode_t) c_int;
1093710947pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
1093810948pub extern "c" fn symlinkat(oldpath: [*:0]const u8, newdirfd: fd_t, newpath: [*:0]const u8) c_int;
1093910949pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;
10940pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, new: [*:0]const u8) c_int;
10950pub extern "c" fn renameat(olddirfd: fd_t, oldpath: [*:0]const u8, newdirfd: fd_t, newpath: [*:0]const u8) c_int;
1094110951pub extern "c" fn chdir(path: [*:0]const u8) c_int;
1094210952pub extern "c" fn fchdir(fd: fd_t) c_int;
10953pub const fexecve = switch (native_os) {
10954 else => {},
10955 .freebsd, .linux, .netbsd => private.fexecve,
10956};
1094310957pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
1094410958pub extern "c" fn execveat(dirfd: fd_t, path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, flags: EXECVEAT) c_int;
1094510959pub extern "c" fn dup(fd: fd_t) c_int;
......@@ -11791,34 +11805,35 @@ pub const setkeymap = serenity.setkeymap;
1179111805
1179211806/// External definitions shared by two or more operating systems.
1179311807const private = struct {
11794 pub extern "c" fn strdup(s: [*:0]const c_char) ?[*:0]c_char;
11795 pub extern "c" fn _strdup(s: [*:0]const c_char) ?[*:0]c_char;
11796 pub extern "c" fn wcsdup(s: [*:0]const wchar_t) ?[*:0]wchar_t;
11797 pub extern "c" fn _wcsdup(s: [*:0]const wchar_t) ?[*:0]wchar_t;
11808 extern "c" fn strdup(s: [*:0]const c_char) ?[*:0]c_char;
11809 extern "c" fn _strdup(s: [*:0]const c_char) ?[*:0]c_char;
11810 extern "c" fn wcsdup(s: [*:0]const wchar_t) ?[*:0]wchar_t;
11811 extern "c" fn _wcsdup(s: [*:0]const wchar_t) ?[*:0]wchar_t;
1179811812
11799 pub extern "c" fn swab(noalias from: *const anyopaque, noalias to: *anyopaque, n: isize) void;
11800 pub extern "c" fn _swab(noalias from: *const anyopaque, noalias to: *anyopaque, n: isize) void;
11813 extern "c" fn swab(noalias from: *const anyopaque, noalias to: *anyopaque, n: isize) void;
11814 extern "c" fn _swab(noalias from: *const anyopaque, noalias to: *anyopaque, n: isize) void;
1180111815
11802 extern "c" fn close(fd: fd_t) c_int;
1180311816 extern "c" fn clock_getres(clk_id: clockid_t, tp: *timespec) c_int;
1180411817 extern "c" fn clock_gettime(clk_id: clockid_t, tp: *timespec) c_int;
11818 extern "c" fn clock_nanosleep(clockid: clockid_t, flags: TIMER, t: *const timespec, remain: ?*timespec) c_int;
11819 extern "c" fn close(fd: fd_t) c_int;
1180511820 extern "c" fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: c_uint) isize;
11821 extern "c" fn fexecve(fd: fd_t, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
1180611822 extern "c" fn flock(fd: fd_t, operation: c_int) c_int;
1180711823 extern "c" fn fork() c_int;
1180811824 extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int;
1180911825 extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, buf: *Stat, flag: u32) c_int;
1181011826 extern "c" fn getdirentries(fd: fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize;
1181111827 extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) switch (native_os) {
11828 else => c_int,
1181211829 .freebsd => isize,
1181311830 .illumos => usize,
11814 else => c_int,
1181511831 };
1181611832 extern "c" fn getrusage(who: c_int, usage: *rusage) c_int;
1181711833 extern "c" fn gettimeofday(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int;
1181811834 extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int;
1181911835 extern "c" fn msync(addr: *align(page_size) const anyopaque, len: usize, flags: c_int) c_int;
1182011836 extern "c" fn nanosleep(rqtp: *const timespec, rmtp: ?*timespec) c_int;
11821 extern "c" fn clock_nanosleep(clockid: clockid_t, flags: TIMER, t: *const timespec, remain: ?*timespec) c_int;
1182211837 extern "c" fn pipe2(fds: *[2]fd_t, flags: O) c_int;
1182311838 extern "c" fn readdir(dir: *DIR) ?*dirent;
1182411839 extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8;
test/src/Runner.zig+6-5
......@@ -822,7 +822,7 @@ fn handleCommand(
822822 .pic = pic,
823823 };
824824 }) catch |err| switch (err) {
825 error.Canceled, error.OutOfMemory, error.AlreadyReported => |e| return e,
825 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
826826 error.ReadFailed => return error.ContentsReadFailed,
827827 },
828828 .write => {
......@@ -1006,7 +1006,7 @@ fn spawnCompiler(
10061006 output_mode: std.lang.OutputMode,
10071007 args_r: *std.Io.Reader,
10081008 target: Compiler.Target,
1009) (std.Io.Reader.ShortError || FailError)!void {
1009) (std.Io.Reader.ShortError || TestError)!void {
10101010 const gpa = runner.gpa;
10111011 const arena = runner.arena.allocator();
10121012 if (compiler.*) |*comp| {
......@@ -1118,6 +1118,7 @@ fn spawnCompiler(
11181118 .stderr = .pipe,
11191119 }) catch |err| switch (err) {
11201120 error.Canceled, error.OutOfMemory => |e| return e,
1121 error.OperationUnsupported => return error.SkipTest,
11211122 else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}),
11221123 };
11231124 compiler.* = .{
......@@ -1222,7 +1223,7 @@ const Compiler = struct {
12221223 expected: []const u8,
12231224 src_dir: std.Io.Dir,
12241225 digest: *const std.Build.Cache.BinDigest,
1225 ) FailError!void {
1226 ) TestError!void {
12261227 const runner = comp.runner;
12271228 const gpa = runner.gpa;
12281229 const arena = runner.arena.allocator();
......@@ -1560,7 +1561,7 @@ const Compiler = struct {
15601561 }
15611562 }
15621563
1563 fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) FailError![]const u8 {
1564 fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) TestError![]const u8 {
15641565 const runner = parent_comp.runner;
15651566 const arena = runner.arena.allocator();
15661567 var compiler: ?Compiler = null;
......@@ -1577,7 +1578,7 @@ const Compiler = struct {
15771578 .linker = .lld,
15781579 .pic = parent_comp.target.pic,
15791580 }) catch |err| switch (err) {
1580 error.Canceled, error.OutOfMemory, error.AlreadyReported => |e| return e,
1581 error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e,
15811582 error.ReadFailed => unreachable, // .fixed
15821583 };
15831584 const comp = &compiler.?;