diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index eda18e2579525f5299fda9a357bb89edb67adbd5..414e63c99cea565c4176dfa2dc0a327fe2ebec63 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -1116,6 +1116,10 @@ pub const RenamePreserveError = error{ /// /// On Windows, this error may be returned instead of PathAlreadyExists when /// renaming a directory over an existing directory. + /// + /// On Darwin, this error may be returned when a component of either pathname + /// refers to a "dataless" directory that requires materialization, and the I/O + /// policy of the current thread or process disallows dataless directory materialization. AccessDenied, PathAlreadyExists, /// Operating system or file system does not support atomic nonreplacing diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0c012fc8cc3aa9a0707c2e12aeeb580768de2431..b14d00bb9b11580828ccb08f5125d80a9ccbc7e3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7505,6 +7505,7 @@ fn dirRenamePreserve( ) Dir.RenamePreserveError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false); + if (is_darwin) return dirRenamePreserveDarwin(old_dir, old_sub_path, new_dir, new_sub_path); if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path); // Make a hard link then delete the original. try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false }); @@ -7696,6 +7697,58 @@ fn dirRenamePosix( return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix); } +fn dirRenamePreserveDarwin( + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, +) Dir.RenamePreserveError!void { + var old_path_buffer: [posix.PATH_MAX]u8 = undefined; + var new_path_buffer: [posix.PATH_MAX]u8 = undefined; + const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); + const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); + + const syscall: Syscall = try .start(); + while (true) { + switch (posix.errno(std.c.renameatx_np( + old_dir.handle, + old_sub_path_posix, + new_dir.handle, + new_sub_path_posix, + .{ .EXCL = true }, + ))) { + .SUCCESS => { + syscall.finish(); + break; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .INVAL => |err| return syscall.errnoBug(err), + .FAULT => |err| return syscall.errnoBug(err), + .BADF => |err| return syscall.errnoBug(err), + .ISDIR => |err| return syscall.errnoBug(err), + .NOTEMPTY => |err| return syscall.errnoBug(err), + .OPNOTSUPP => return syscall.fail(error.OperationUnsupported), + .IO => return syscall.fail(error.HardwareFailure), + .DEADLK => return syscall.fail(error.AccessDenied), + .ACCES => return syscall.fail(error.AccessDenied), + .DQUOT => return syscall.fail(error.DiskQuota), + .EXIST => return syscall.fail(error.PathAlreadyExists), + .LOOP => return syscall.fail(error.LinkQuotaExceeded), + .NAMETOOLONG => return syscall.fail(error.NameTooLong), + .NOENT => return syscall.fail(error.FileNotFound), + .NOSPC => return syscall.fail(error.NoSpaceLeft), + .NOTDIR => return syscall.fail(error.NotDir), + .PERM => return syscall.fail(error.PermissionDenied), + .ROFS => return syscall.fail(error.ReadOnlyFileSystem), + .XDEV => return syscall.fail(error.CrossDevice), + else => |err| return syscall.unexpectedErrno(err), + } + } +} + fn dirRenamePreserveLinux( old_dir: Dir, old_sub_path: []const u8, @@ -7783,49 +7836,6 @@ fn renameat( }; } -fn renameatPreserve( - old_dir: posix.fd_t, - old_sub_path: [*:0]const u8, - new_dir: posix.fd_t, - new_sub_path: [*:0]const u8, -) Dir.RenameError!void { - const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) { - .SUCCESS => return syscall.finish(), - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |e| { - syscall.finish(); - switch (e) { - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BUSY => return error.FileBusy, - .DQUOT => return error.DiskQuota, - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, - .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.CrossDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } -} - const dirSymLink = switch (native_os) { .windows => dirSymLinkWindows, .wasi => dirSymLinkWasi, diff --git a/lib/std/c.zig b/lib/std/c.zig index 5c61cbb676d9e73309af48f0d898cb2e9ddab48d..13a2b7eb4e6607ba9ea1fba0fe6a19feec998890 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -11389,6 +11389,7 @@ pub const clock_res_t = darwin.clock_res_t; pub const @"close$NOCANCEL" = darwin.@"close$NOCANCEL"; pub const dispatch = darwin.dispatch; pub const fcopyfile = darwin.fcopyfile; +pub const renameatx_np = darwin.renameatx_np; pub const host_t = darwin.host_t; pub const integer_t = darwin.integer_t; pub const ipc_space_t = darwin.ipc_space_t; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 7e475a057b87df7e68bbce4c5c681510405bd780..1a24cb11331fef306846d1686119f41b0be74010 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -380,6 +380,18 @@ pub const copyfile_state_t = *opaque {}; pub extern "c" fn fcopyfile(from: fd_t, to: fd_t, state: ?copyfile_state_t, flags: COPYFILE) c_int; pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize; +pub const RENAME = packed struct(u32) { + SECLUDE: bool = false, + SWAP: bool = false, + EXCL: bool = false, + RESERVED1: bool = false, + NOFOLLOW_ANY: bool = false, + RESOLVE_BENEATH: bool = false, + _: u26 = 0, +}; + +pub extern "c" fn renameatx_np(fromfd: c_int, from: [*:0]const u8, tofd: c_int, to: [*:0]const u8, flags: RENAME) c_int; + pub extern "c" fn mach_absolute_time() u64; pub extern "c" fn mach_continuous_time() u64; pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) kern_return_t; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index d9d8f011e381cd9c345af49878530968e02cf871..bf16a7c216b4f9087195c7a98160e6b765687960 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1096,8 +1096,10 @@ test "Dir.renamePreserve onto existing" { // file -> dir try expectError(error.PathAlreadyExists, ctx.dir.renamePreserve(test_file_path, ctx.dir, target_dir_path, io)); - // TODO: fix dir renaming on non-Linux, non-Windows systems, see https://codeberg.org/ziglang/zig/issues/35340 - if (native_os != .windows and native_os != .linux) return; + // TODO: fix dir renaming on other systems, see https://codeberg.org/ziglang/zig/issues/35340 + if (native_os != .windows and native_os != .linux and !native_os.isDarwin()) { + return; + } // dir -> file try expectError(error.PathAlreadyExists, ctx.dir.renamePreserve(test_dir_path, ctx.dir, target_file_path, io));