authorgravatar for evyatar.shafran@gmail.comEJ <evyatar.shafran@gmail.com> 2026-06-12 23:30:44+02:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-06-12 23:30:44+02:00
logc11d6e716d651975d019ec85be40b56b109bf99e
tree0e5cf379f38446cd39f155bae6e1f037b7373f55
parent9e95d51e24a91bfea12754a036bff9ac6b8bebb7

std.Io.Dir: support `renamePreserve` on Darwin systems (#35358)

Related to #35340. Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35358

5 files changed, 74 insertions(+), 45 deletions(-)

lib/std/Io/Dir.zig+4
...@@ -1116,6 +1116,10 @@ pub const RenamePreserveError = error{...@@ -1116,6 +1116,10 @@ pub const RenamePreserveError = error{
1116 ///1116 ///
1117 /// On Windows, this error may be returned instead of PathAlreadyExists when1117 /// On Windows, this error may be returned instead of PathAlreadyExists when
1118 /// renaming a directory over an existing directory.1118 /// renaming a directory over an existing directory.
1119 ///
1120 /// On Darwin, this error may be returned when a component of either pathname
1121 /// refers to a "dataless" directory that requires materialization, and the I/O
1122 /// policy of the current thread or process disallows dataless directory materialization.
1119 AccessDenied,1123 AccessDenied,
1120 PathAlreadyExists,1124 PathAlreadyExists,
1121 /// Operating system or file system does not support atomic nonreplacing1125 /// Operating system or file system does not support atomic nonreplacing
lib/std/Io/Threaded.zig+53-43
...@@ -7505,6 +7505,7 @@ fn dirRenamePreserve(...@@ -7505,6 +7505,7 @@ fn dirRenamePreserve(
7505) Dir.RenamePreserveError!void {7505) Dir.RenamePreserveError!void {
7506 const t: *Threaded = @ptrCast(@alignCast(userdata));7506 const t: *Threaded = @ptrCast(@alignCast(userdata));
7507 if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);7507 if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);
7508 if (is_darwin) return dirRenamePreserveDarwin(old_dir, old_sub_path, new_dir, new_sub_path);
7508 if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);7509 if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);
7509 // Make a hard link then delete the original.7510 // Make a hard link then delete the original.
7510 try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });7511 try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });
...@@ -7696,6 +7697,58 @@ fn dirRenamePosix(...@@ -7696,6 +7697,58 @@ fn dirRenamePosix(
7696 return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix);7697 return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix);
7697}7698}
76987699
7700fn dirRenamePreserveDarwin(
7701 old_dir: Dir,
7702 old_sub_path: []const u8,
7703 new_dir: Dir,
7704 new_sub_path: []const u8,
7705) Dir.RenamePreserveError!void {
7706 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
7707 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
7708 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
7709 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
7710
7711 const syscall: Syscall = try .start();
7712 while (true) {
7713 switch (posix.errno(std.c.renameatx_np(
7714 old_dir.handle,
7715 old_sub_path_posix,
7716 new_dir.handle,
7717 new_sub_path_posix,
7718 .{ .EXCL = true },
7719 ))) {
7720 .SUCCESS => {
7721 syscall.finish();
7722 break;
7723 },
7724 .INTR => {
7725 try syscall.checkCancel();
7726 continue;
7727 },
7728 .INVAL => |err| return syscall.errnoBug(err),
7729 .FAULT => |err| return syscall.errnoBug(err),
7730 .BADF => |err| return syscall.errnoBug(err),
7731 .ISDIR => |err| return syscall.errnoBug(err),
7732 .NOTEMPTY => |err| return syscall.errnoBug(err),
7733 .OPNOTSUPP => return syscall.fail(error.OperationUnsupported),
7734 .IO => return syscall.fail(error.HardwareFailure),
7735 .DEADLK => return syscall.fail(error.AccessDenied),
7736 .ACCES => return syscall.fail(error.AccessDenied),
7737 .DQUOT => return syscall.fail(error.DiskQuota),
7738 .EXIST => return syscall.fail(error.PathAlreadyExists),
7739 .LOOP => return syscall.fail(error.LinkQuotaExceeded),
7740 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
7741 .NOENT => return syscall.fail(error.FileNotFound),
7742 .NOSPC => return syscall.fail(error.NoSpaceLeft),
7743 .NOTDIR => return syscall.fail(error.NotDir),
7744 .PERM => return syscall.fail(error.PermissionDenied),
7745 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
7746 .XDEV => return syscall.fail(error.CrossDevice),
7747 else => |err| return syscall.unexpectedErrno(err),
7748 }
7749 }
7750}
7751
7699fn dirRenamePreserveLinux(7752fn dirRenamePreserveLinux(
7700 old_dir: Dir,7753 old_dir: Dir,
7701 old_sub_path: []const u8,7754 old_sub_path: []const u8,
...@@ -7783,49 +7836,6 @@ fn renameat(...@@ -7783,49 +7836,6 @@ fn renameat(
7783 };7836 };
7784}7837}
77857838
7786fn renameatPreserve(
7787 old_dir: posix.fd_t,
7788 old_sub_path: [*:0]const u8,
7789 new_dir: posix.fd_t,
7790 new_sub_path: [*:0]const u8,
7791) Dir.RenameError!void {
7792 const syscall: Syscall = try .start();
7793 while (true) {
7794 switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
7795 .SUCCESS => return syscall.finish(),
7796 .INTR => {
7797 try syscall.checkCancel();
7798 continue;
7799 },
7800 else => |e| {
7801 syscall.finish();
7802 switch (e) {
7803 .ACCES => return error.AccessDenied,
7804 .PERM => return error.PermissionDenied,
7805 .BUSY => return error.FileBusy,
7806 .DQUOT => return error.DiskQuota,
7807 .FAULT => |err| return errnoBug(err),
7808 .INVAL => |err| return errnoBug(err),
7809 .ISDIR => return error.IsDir,
7810 .LOOP => return error.SymLinkLoop,
7811 .MLINK => return error.LinkQuotaExceeded,
7812 .NAMETOOLONG => return error.NameTooLong,
7813 .NOENT => return error.FileNotFound,
7814 .NOTDIR => return error.NotDir,
7815 .NOMEM => return error.SystemResources,
7816 .NOSPC => return error.NoSpaceLeft,
7817 .EXIST => return error.PathAlreadyExists,
7818 .NOTEMPTY => return error.PathAlreadyExists,
7819 .ROFS => return error.ReadOnlyFileSystem,
7820 .XDEV => return error.CrossDevice,
7821 .ILSEQ => return error.BadPathName,
7822 else => |err| return posix.unexpectedErrno(err),
7823 }
7824 },
7825 }
7826 }
7827}
7828
7829const dirSymLink = switch (native_os) {7839const dirSymLink = switch (native_os) {
7830 .windows => dirSymLinkWindows,7840 .windows => dirSymLinkWindows,
7831 .wasi => dirSymLinkWasi,7841 .wasi => dirSymLinkWasi,
lib/std/c.zig+1
...@@ -11389,6 +11389,7 @@ pub const clock_res_t = darwin.clock_res_t;...@@ -11389,6 +11389,7 @@ pub const clock_res_t = darwin.clock_res_t;
11389pub const @"close$NOCANCEL" = darwin.@"close$NOCANCEL";11389pub const @"close$NOCANCEL" = darwin.@"close$NOCANCEL";
11390pub const dispatch = darwin.dispatch;11390pub const dispatch = darwin.dispatch;
11391pub const fcopyfile = darwin.fcopyfile;11391pub const fcopyfile = darwin.fcopyfile;
11392pub const renameatx_np = darwin.renameatx_np;
11392pub const host_t = darwin.host_t;11393pub const host_t = darwin.host_t;
11393pub const integer_t = darwin.integer_t;11394pub const integer_t = darwin.integer_t;
11394pub const ipc_space_t = darwin.ipc_space_t;11395pub const ipc_space_t = darwin.ipc_space_t;
lib/std/c/darwin.zig+12
...@@ -380,6 +380,18 @@ pub const copyfile_state_t = *opaque {};...@@ -380,6 +380,18 @@ pub const copyfile_state_t = *opaque {};
380pub extern "c" fn fcopyfile(from: fd_t, to: fd_t, state: ?copyfile_state_t, flags: COPYFILE) c_int;380pub extern "c" fn fcopyfile(from: fd_t, to: fd_t, state: ?copyfile_state_t, flags: COPYFILE) c_int;
381pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize;381pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize;
382382
383pub const RENAME = packed struct(u32) {
384 SECLUDE: bool = false,
385 SWAP: bool = false,
386 EXCL: bool = false,
387 RESERVED1: bool = false,
388 NOFOLLOW_ANY: bool = false,
389 RESOLVE_BENEATH: bool = false,
390 _: u26 = 0,
391};
392
393pub extern "c" fn renameatx_np(fromfd: c_int, from: [*:0]const u8, tofd: c_int, to: [*:0]const u8, flags: RENAME) c_int;
394
383pub extern "c" fn mach_absolute_time() u64;395pub extern "c" fn mach_absolute_time() u64;
384pub extern "c" fn mach_continuous_time() u64;396pub extern "c" fn mach_continuous_time() u64;
385pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) kern_return_t;397pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) kern_return_t;
lib/std/fs/test.zig+4-2
...@@ -1096,8 +1096,10 @@ test "Dir.renamePreserve onto existing" {...@@ -1096,8 +1096,10 @@ test "Dir.renamePreserve onto existing" {
1096 // file -> dir1096 // file -> dir
1097 try expectError(error.PathAlreadyExists, ctx.dir.renamePreserve(test_file_path, ctx.dir, target_dir_path, io));1097 try expectError(error.PathAlreadyExists, ctx.dir.renamePreserve(test_file_path, ctx.dir, target_dir_path, io));
10981098
1099 // TODO: fix dir renaming on non-Linux, non-Windows systems, see https://codeberg.org/ziglang/zig/issues/353401099 // TODO: fix dir renaming on other systems, see https://codeberg.org/ziglang/zig/issues/35340
1100 if (native_os != .windows and native_os != .linux) return;1100 if (native_os != .windows and native_os != .linux and !native_os.isDarwin()) {
1101 return;
1102 }
11011103
1102 // dir -> file1104 // dir -> file
1103 try expectError(error.PathAlreadyExists, ctx.dir.renamePreserve(test_dir_path, ctx.dir, target_file_path, io));1105 try expectError(error.PathAlreadyExists, ctx.dir.renamePreserve(test_dir_path, ctx.dir, target_file_path, io));