authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-12 13:30:02-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-13 13:26:23-07:00
logcdd6e545cc7e4c6cb32d1a87695f215ce9d5f753
treebfb9dc6d5a84eaf40b83ab0446e1be54505dbb24
parent59f14713067c59751dff4d6f73f81893a2284793

std.Io.Threaded: handle unsupported renamePreserveLinux

with link/unlink fallback. closes #31960

2 files changed, 18 insertions(+), 4 deletions(-)

lib/std/Io/Dir.zig+7-2
......@@ -1122,8 +1122,7 @@ pub const RenamePreserveError = error{
11221122 /// policy of the current thread or process disallows dataless directory materialization.
11231123 AccessDenied,
11241124 PathAlreadyExists,
1125 /// Operating system or file system does not support atomic nonreplacing
1126 /// rename.
1125 /// Operating system or file system does not support atomic nonreplacing rename.
11271126 OperationUnsupported,
11281127} || RenameError;
11291128
......@@ -1131,6 +1130,12 @@ pub const RenamePreserveError = error{
11311130///
11321131/// If `new_sub_path` already exists, `error.PathAlreadyExists` will be returned.
11331132///
1133/// If the operating system supports it, the entire operation will be done atomically. If unsupported, will
1134/// fall back to hard link followed by unlink, which is atomic with respect to `new_sub_path` but leaves a
1135/// window where the cleanup of `old_sub_path may fail. Although the operation is fairly portable, technically
1136/// this fallback, too, may be unsupported in some environments, in which case `error.OperationUnsupported` is
1137/// returned.
1138///
11341139/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
11351140/// * On WASI, both paths should be encoded as valid UTF-8.
11361141/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
lib/std/Io/Threaded.zig+11-2
......@@ -7756,7 +7756,14 @@ fn dirRenamePreserve(
77567756 const t: *Threaded = @ptrCast(@alignCast(userdata));
77577757 if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);
77587758 if (is_darwin) return dirRenamePreserveDarwin(old_dir, old_sub_path, new_dir, new_sub_path);
7759 if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);
7759 if (native_os == .linux) {
7760 if (dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path)) |_| {
7761 return;
7762 } else |err| switch (err) {
7763 else => |e| return e,
7764 error.OperationUnsupported => {}, // Fall back to link/unlink below.
7765 }
7766 }
77607767 // Make a hard link then delete the original.
77617768 try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });
77627769 const prev = swapCancelProtection(t, .blocked);
......@@ -8084,8 +8091,10 @@ fn dirRenamePreserveLinux(
80848091 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
80858092 .XDEV => return syscall.fail(error.CrossDevice),
80868093 .ILSEQ => return syscall.fail(error.BadPathName),
8094 // EINVAL when passing NOREPLACE=true observed on ext4, 7.0.0-38-generic #38~24.04.4-Ubuntu.
8095 // OPNOTSUPP handled defensively.
8096 .INVAL, .OPNOTSUPP => return syscall.fail(error.OperationUnsupported),
80878097 .FAULT => |err| return syscall.errnoBug(err),
8088 .INVAL => return syscall.fail(error.OperationUnsupported),
80898098 else => |err| return syscall.unexpectedErrno(err),
80908099 };
80918100}