authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-06 19:56:49-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 11:03:37-08:00
logee574f665c4e3d1be4950b307ce3ff8324f13f46
tree711757fd21992e3cc5297061b2fd9cf9d1f1d679
parent8e1850e277c3112eda44b8bb3de95ef1791eccfa

std.Io.Dir: introduce renamePreserve and use it in File.Atomic.link

breaking change: the error for renaming over a non-empty directory now returns error.DirNotEmpty rather than error.PathAlreadyExists.

11 files changed, 219 insertions(+), 35 deletions(-)

lib/std/Io.zig+1
...@@ -676,6 +676,7 @@ pub const VTable = struct {...@@ -676,6 +676,7 @@ pub const VTable = struct {
676 dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,676 dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
677 dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,677 dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
678 dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,678 dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
679 dirRenamePreserve: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenamePreserveError!void,
679 dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void,680 dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void,
680 dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,681 dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
681 dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,682 dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
lib/std/Io/Dir.zig+40-8
...@@ -936,10 +936,9 @@ pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void...@@ -936,10 +936,9 @@ pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void
936pub const RenameError = error{936pub const RenameError = error{
937 /// In WASI, this error may occur when the file descriptor does937 /// In WASI, this error may occur when the file descriptor does
938 /// not hold the required rights to rename a resource by path relative to it.938 /// not hold the required rights to rename a resource by path relative to it.
939 ///
940 /// On Windows, this error may be returned instead of PathAlreadyExists when
941 /// renaming a directory over an existing directory.
942 AccessDenied,939 AccessDenied,
940 /// Attempted to replace a nonempty directory.
941 DirNotEmpty,
943 PermissionDenied,942 PermissionDenied,
944 FileBusy,943 FileBusy,
945 DiskQuota,944 DiskQuota,
...@@ -950,9 +949,8 @@ pub const RenameError = error{...@@ -950,9 +949,8 @@ pub const RenameError = error{
950 NotDir,949 NotDir,
951 SystemResources,950 SystemResources,
952 NoSpaceLeft,951 NoSpaceLeft,
953 PathAlreadyExists,
954 ReadOnlyFileSystem,952 ReadOnlyFileSystem,
955 RenameAcrossMountPoints,953 CrossDevice,
956 NoDevice,954 NoDevice,
957 SharingViolation,955 SharingViolation,
958 PipeBusy,956 PipeBusy,
...@@ -964,6 +962,7 @@ pub const RenameError = error{...@@ -964,6 +962,7 @@ pub const RenameError = error{
964 /// intercepts file system operations and makes them significantly slower962 /// intercepts file system operations and makes them significantly slower
965 /// in addition to possibly failing with this error code.963 /// in addition to possibly failing with this error code.
966 AntivirusInterference,964 AntivirusInterference,
965 HardwareFailure,
967} || PathNameError || Io.Cancelable || Io.UnexpectedError;966} || PathNameError || Io.Cancelable || Io.UnexpectedError;
968967
969/// Change the name or location of a file or directory.968/// Change the name or location of a file or directory.
...@@ -973,9 +972,9 @@ pub const RenameError = error{...@@ -973,9 +972,9 @@ pub const RenameError = error{
973/// Renaming a file over an existing directory or a directory over an existing972/// Renaming a file over an existing directory or a directory over an existing
974/// file will fail with `error.IsDir` or `error.NotDir`973/// file will fail with `error.IsDir` or `error.NotDir`
975///974///
976/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).975/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
977/// On WASI, both paths should be encoded as valid UTF-8.976/// * On WASI, both paths should be encoded as valid UTF-8.
978/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.977/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
979pub fn rename(978pub fn rename(
980 old_dir: Dir,979 old_dir: Dir,
981 old_sub_path: []const u8,980 old_sub_path: []const u8,
...@@ -993,6 +992,39 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename...@@ -993,6 +992,39 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
993 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);992 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
994}993}
995994
995pub const RenamePreserveError = error{
996 /// In WASI, this error may occur when the file descriptor does
997 /// not hold the required rights to rename a resource by path relative to it.
998 ///
999 /// On Windows, this error may be returned instead of PathAlreadyExists when
1000 /// renaming a directory over an existing directory.
1001 AccessDenied,
1002 PathAlreadyExists,
1003 /// Operating system or file system does not support atomic nonreplacing
1004 /// rename.
1005 OperationUnsupported,
1006} || RenameError;
1007
1008/// Change the name or location of a file or directory.
1009///
1010/// If `new_sub_path` already exists, `error.PathAlreadyExists` will be returned.
1011///
1012/// Renaming a file over an existing directory or a directory over an existing
1013/// file will fail with `error.IsDir` or `error.NotDir`
1014///
1015/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1016/// * On WASI, both paths should be encoded as valid UTF-8.
1017/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1018pub fn renamePreserve(
1019 old_dir: Dir,
1020 old_sub_path: []const u8,
1021 new_dir: Dir,
1022 new_sub_path: []const u8,
1023 io: Io,
1024) RenamePreserveError!void {
1025 return io.vtable.dirRenamePreserve(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
1026}
1027
996pub const HardLinkOptions = File.HardLinkOptions;1028pub const HardLinkOptions = File.HardLinkOptions;
9971029
998pub const HardLinkError = File.HardLinkError;1030pub const HardLinkError = File.HardLinkError;
lib/std/Io/File.zig+1-1
...@@ -726,7 +726,7 @@ pub const HardLinkError = error{...@@ -726,7 +726,7 @@ pub const HardLinkError = error{
726 SystemResources,726 SystemResources,
727 NoSpaceLeft,727 NoSpaceLeft,
728 ReadOnlyFileSystem,728 ReadOnlyFileSystem,
729 NotSameFileSystem,729 CrossDevice,
730 NotDir,730 NotDir,
731} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;731} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;
732732
lib/std/Io/File/Atomic.zig+6-3
...@@ -37,10 +37,14 @@ pub fn deinit(af: *Atomic, io: Io) void {...@@ -37,10 +37,14 @@ pub fn deinit(af: *Atomic, io: Io) void {
37 af.* = undefined;37 af.* = undefined;
38}38}
3939
40pub const LinkError = Dir.HardLinkError;40pub const LinkError = Dir.HardLinkError || Dir.RenamePreserveError;
4141
42/// Atomically materializes the file into place, failing with42/// Atomically materializes the file into place, failing with
43/// `error.PathAlreadyExists` if something already exists there.43/// `error.PathAlreadyExists` if something already exists there.
44///
45/// If this operation could not be done with an unnamed temporary file, the
46/// named temporary file will be deleted in a following operation, which may
47/// independently fail. The result of that operation is stored in `delete_err`.
44pub fn link(af: *Atomic, io: Io) LinkError!void {48pub fn link(af: *Atomic, io: Io) LinkError!void {
45 if (af.file_exists) {49 if (af.file_exists) {
46 if (af.file_open) {50 if (af.file_open) {
...@@ -48,8 +52,7 @@ pub fn link(af: *Atomic, io: Io) LinkError!void {...@@ -48,8 +52,7 @@ pub fn link(af: *Atomic, io: Io) LinkError!void {
48 af.file_open = false;52 af.file_open = false;
49 }53 }
50 const tmp_sub_path = std.fmt.hex(af.file_basename_hex);54 const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
51 try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{});55 try af.dir.renamePreserve(&tmp_sub_path, af.dir, af.dest_sub_path, io);
52 af.dir.deleteFile(io, &tmp_sub_path) catch {};
53 af.file_exists = false;56 af.file_exists = false;
54 } else {57 } else {
55 assert(af.file_open);58 assert(af.file_open);
lib/std/Io/Threaded.zig+138-11
...@@ -1442,6 +1442,7 @@ pub fn io(t: *Threaded) Io {...@@ -1442,6 +1442,7 @@ pub fn io(t: *Threaded) Io {
1442 .dirDeleteFile = dirDeleteFile,1442 .dirDeleteFile = dirDeleteFile,
1443 .dirDeleteDir = dirDeleteDir,1443 .dirDeleteDir = dirDeleteDir,
1444 .dirRename = dirRename,1444 .dirRename = dirRename,
1445 .dirRenamePreserve = dirRenamePreserve,
1445 .dirSymLink = dirSymLink,1446 .dirSymLink = dirSymLink,
1446 .dirReadLink = dirReadLink,1447 .dirReadLink = dirReadLink,
1447 .dirSetOwner = dirSetOwner,1448 .dirSetOwner = dirSetOwner,
...@@ -1593,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1593,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {
1593 .dirDeleteFile = dirDeleteFile,1594 .dirDeleteFile = dirDeleteFile,
1594 .dirDeleteDir = dirDeleteDir,1595 .dirDeleteDir = dirDeleteDir,
1595 .dirRename = dirRename,1596 .dirRename = dirRename,
1597 .dirRenamePreserve = dirRenamePreserve,
1596 .dirSymLink = dirSymLink,1598 .dirSymLink = dirSymLink,
1597 .dirReadLink = dirReadLink,1599 .dirReadLink = dirReadLink,
1598 .dirSetOwner = dirSetOwner,1600 .dirSetOwner = dirSetOwner,
...@@ -5283,7 +5285,7 @@ fn linkat(...@@ -5283,7 +5285,7 @@ fn linkat(
5283 .NOTDIR => return syscall.fail(error.NotDir),5285 .NOTDIR => return syscall.fail(error.NotDir),
5284 .PERM => return syscall.fail(error.PermissionDenied),5286 .PERM => return syscall.fail(error.PermissionDenied),
5285 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),5287 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
5286 .XDEV => return syscall.fail(error.NotSameFileSystem),5288 .XDEV => return syscall.fail(error.CrossDevice),
5287 .ILSEQ => return syscall.fail(error.BadPathName),5289 .ILSEQ => return syscall.fail(error.BadPathName),
5288 .FAULT => |err| return syscall.errnoBug(err),5290 .FAULT => |err| return syscall.errnoBug(err),
5289 .INVAL => |err| return syscall.errnoBug(err),5291 .INVAL => |err| return syscall.errnoBug(err),
...@@ -5692,15 +5694,44 @@ fn dirRenameWindows(...@@ -5692,15 +5694,44 @@ fn dirRenameWindows(
5692 new_dir: Dir,5694 new_dir: Dir,
5693 new_sub_path: []const u8,5695 new_sub_path: []const u8,
5694) Dir.RenameError!void {5696) Dir.RenameError!void {
5695 const w = windows;
5696 const t: *Threaded = @ptrCast(@alignCast(userdata));5697 const t: *Threaded = @ptrCast(@alignCast(userdata));
5697 _ = t;5698 _ = t;
5699 return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) {
5700 error.PathAlreadyExists => return error.Unexpected,
5701 error.OperationUnsupported => return error.Unexpected,
5702 else => |e| return e,
5703 };
5704}
56985705
5706fn dirRenamePreserve(
5707 userdata: ?*anyopaque,
5708 old_dir: Dir,
5709 old_sub_path: []const u8,
5710 new_dir: Dir,
5711 new_sub_path: []const u8,
5712) Dir.RenamePreserveError!void {
5713 const t: *Threaded = @ptrCast(@alignCast(userdata));
5714 if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);
5715 if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);
5716 // Make a hard link then delete the original.
5717 try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });
5718 const prev = swapCancelProtection(t, .blocked);
5719 defer _ = swapCancelProtection(t, prev);
5720 dirDeleteFile(t, old_dir, old_sub_path) catch {};
5721}
5722
5723fn dirRenameWindowsInner(
5724 old_dir: Dir,
5725 old_sub_path: []const u8,
5726 new_dir: Dir,
5727 new_sub_path: []const u8,
5728 replace_if_exists: bool,
5729) Dir.RenamePreserveError!void {
5730 const w = windows;
5699 const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);5731 const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
5700 const old_path_w = old_path_w_buf.span();5732 const old_path_w = old_path_w_buf.span();
5701 const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);5733 const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);
5702 const new_path_w = new_path_w_buf.span();5734 const new_path_w = new_path_w_buf.span();
5703 const replace_if_exists = true;
57045735
5705 const src_fd = src_fd: {5736 const src_fd = src_fd: {
5706 const syscall: Syscall = try .start();5737 const syscall: Syscall = try .start();
...@@ -5802,9 +5833,9 @@ fn dirRenameWindows(...@@ -5802,9 +5833,9 @@ fn dirRenameWindows(
5802 .ACCESS_DENIED => return error.AccessDenied,5833 .ACCESS_DENIED => return error.AccessDenied,
5803 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,5834 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
5804 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,5835 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
5805 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,5836 .NOT_SAME_DEVICE => return error.CrossDevice,
5806 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,5837 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
5807 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,5838 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
5808 .FILE_IS_A_DIRECTORY => return error.IsDir,5839 .FILE_IS_A_DIRECTORY => return error.IsDir,
5809 .NOT_A_DIRECTORY => return error.NotDir,5840 .NOT_A_DIRECTORY => return error.NotDir,
5810 else => return w.unexpectedStatus(rc),5841 else => return w.unexpectedStatus(rc),
...@@ -5848,10 +5879,10 @@ fn dirRenameWasi(...@@ -5848,10 +5879,10 @@ fn dirRenameWasi(
5848 .NOTDIR => return error.NotDir,5879 .NOTDIR => return error.NotDir,
5849 .NOMEM => return error.SystemResources,5880 .NOMEM => return error.SystemResources,
5850 .NOSPC => return error.NoSpaceLeft,5881 .NOSPC => return error.NoSpaceLeft,
5851 .EXIST => return error.PathAlreadyExists,5882 .EXIST => return error.DirNotEmpty,
5852 .NOTEMPTY => return error.PathAlreadyExists,5883 .NOTEMPTY => return error.DirNotEmpty,
5853 .ROFS => return error.ReadOnlyFileSystem,5884 .ROFS => return error.ReadOnlyFileSystem,
5854 .XDEV => return error.RenameAcrossMountPoints,5885 .XDEV => return error.CrossDevice,
5855 .NOTCAPABLE => return error.AccessDenied,5886 .NOTCAPABLE => return error.AccessDenied,
5856 .ILSEQ => return error.BadPathName,5887 .ILSEQ => return error.BadPathName,
5857 else => |err| return posix.unexpectedErrno(err),5888 else => |err| return posix.unexpectedErrno(err),
...@@ -5877,9 +5908,105 @@ fn dirRenamePosix(...@@ -5877,9 +5908,105 @@ fn dirRenamePosix(
5877 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);5908 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
5878 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);5909 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
58795910
5911 return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix);
5912}
5913
5914fn dirRenamePreserveLinux(
5915 old_dir: Dir,
5916 old_sub_path: []const u8,
5917 new_dir: Dir,
5918 new_sub_path: []const u8,
5919) Dir.RenamePreserveError!void {
5920 const linux = std.os.linux;
5921
5922 var old_path_buffer: [linux.PATH_MAX]u8 = undefined;
5923 var new_path_buffer: [linux.PATH_MAX]u8 = undefined;
5924
5925 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
5926 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
5927
5928 const syscall: Syscall = try .start();
5929 while (true) switch (linux.errno(linux.renameat2(
5930 old_dir.handle,
5931 old_sub_path_posix,
5932 new_dir.handle,
5933 new_sub_path_posix,
5934 .{ .NOREPLACE = true },
5935 ))) {
5936 .SUCCESS => return syscall.finish(),
5937 .INTR => {
5938 try syscall.checkCancel();
5939 continue;
5940 },
5941 .ACCES => return syscall.fail(error.AccessDenied),
5942 .PERM => return syscall.fail(error.PermissionDenied),
5943 .BUSY => return syscall.fail(error.FileBusy),
5944 .DQUOT => return syscall.fail(error.DiskQuota),
5945 .ISDIR => return syscall.fail(error.IsDir),
5946 .LOOP => return syscall.fail(error.SymLinkLoop),
5947 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
5948 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
5949 .NOENT => return syscall.fail(error.FileNotFound),
5950 .NOTDIR => return syscall.fail(error.NotDir),
5951 .NOMEM => return syscall.fail(error.SystemResources),
5952 .NOSPC => return syscall.fail(error.NoSpaceLeft),
5953 .EXIST => return syscall.fail(error.PathAlreadyExists),
5954 .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
5955 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
5956 .XDEV => return syscall.fail(error.CrossDevice),
5957 .ILSEQ => return syscall.fail(error.BadPathName),
5958 .FAULT => |err| return syscall.errnoBug(err),
5959 .INVAL => |err| return syscall.errnoBug(err),
5960 else => |err| return syscall.unexpectedErrno(err),
5961 };
5962}
5963
5964fn renameat(
5965 old_dir: posix.fd_t,
5966 old_sub_path: [*:0]const u8,
5967 new_dir: posix.fd_t,
5968 new_sub_path: [*:0]const u8,
5969) Dir.RenameError!void {
5970 const syscall: Syscall = try .start();
5971 while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
5972 .SUCCESS => return syscall.finish(),
5973 .INTR => {
5974 try syscall.checkCancel();
5975 continue;
5976 },
5977 .ACCES => return syscall.fail(error.AccessDenied),
5978 .PERM => return syscall.fail(error.PermissionDenied),
5979 .BUSY => return syscall.fail(error.FileBusy),
5980 .DQUOT => return syscall.fail(error.DiskQuota),
5981 .ISDIR => return syscall.fail(error.IsDir),
5982 .IO => return syscall.fail(error.HardwareFailure),
5983 .LOOP => return syscall.fail(error.SymLinkLoop),
5984 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
5985 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
5986 .NOENT => return syscall.fail(error.FileNotFound),
5987 .NOTDIR => return syscall.fail(error.NotDir),
5988 .NOMEM => return syscall.fail(error.SystemResources),
5989 .NOSPC => return syscall.fail(error.NoSpaceLeft),
5990 .EXIST => return syscall.fail(error.DirNotEmpty),
5991 .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
5992 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
5993 .XDEV => return syscall.fail(error.CrossDevice),
5994 .ILSEQ => return syscall.fail(error.BadPathName),
5995 .FAULT => |err| return syscall.errnoBug(err),
5996 .INVAL => |err| return syscall.errnoBug(err),
5997 else => |err| return syscall.unexpectedErrno(err),
5998 };
5999}
6000
6001fn renameatPreserve(
6002 old_dir: posix.fd_t,
6003 old_sub_path: [*:0]const u8,
6004 new_dir: posix.fd_t,
6005 new_sub_path: [*:0]const u8,
6006) Dir.RenameError!void {
5880 const syscall: Syscall = try .start();6007 const syscall: Syscall = try .start();
5881 while (true) {6008 while (true) {
5882 switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {6009 switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
5883 .SUCCESS => return syscall.finish(),6010 .SUCCESS => return syscall.finish(),
5884 .INTR => {6011 .INTR => {
5885 try syscall.checkCancel();6012 try syscall.checkCancel();
...@@ -5905,7 +6032,7 @@ fn dirRenamePosix(...@@ -5905,7 +6032,7 @@ fn dirRenamePosix(
5905 .EXIST => return error.PathAlreadyExists,6032 .EXIST => return error.PathAlreadyExists,
5906 .NOTEMPTY => return error.PathAlreadyExists,6033 .NOTEMPTY => return error.PathAlreadyExists,
5907 .ROFS => return error.ReadOnlyFileSystem,6034 .ROFS => return error.ReadOnlyFileSystem,
5908 .XDEV => return error.RenameAcrossMountPoints,6035 .XDEV => return error.CrossDevice,
5909 .ILSEQ => return error.BadPathName,6036 .ILSEQ => return error.BadPathName,
5910 else => |err| return posix.unexpectedErrno(err),6037 else => |err| return posix.unexpectedErrno(err),
5911 }6038 }
...@@ -7686,7 +7813,7 @@ fn dirHardLink(...@@ -7686,7 +7813,7 @@ fn dirHardLink(
7686 .NOTDIR => return error.NotDir,7813 .NOTDIR => return error.NotDir,
7687 .PERM => return error.PermissionDenied,7814 .PERM => return error.PermissionDenied,
7688 .ROFS => return error.ReadOnlyFileSystem,7815 .ROFS => return error.ReadOnlyFileSystem,
7689 .XDEV => return error.NotSameFileSystem,7816 .XDEV => return error.CrossDevice,
7690 .INVAL => |err| return errnoBug(err),7817 .INVAL => |err| return errnoBug(err),
7691 .ILSEQ => return error.BadPathName,7818 .ILSEQ => return error.BadPathName,
7692 else => |err| return posix.unexpectedErrno(err),7819 else => |err| return posix.unexpectedErrno(err),
lib/std/fs/test.zig+1-2
...@@ -1022,8 +1022,7 @@ test "Dir.rename directory onto non-empty dir" {...@@ -1022,8 +1022,7 @@ test "Dir.rename directory onto non-empty dir" {
1022 file.close(io);1022 file.close(io);
1023 target_dir.close(io);1023 target_dir.close(io);
10241024
1025 // Rename should fail with PathAlreadyExists if target_dir is non-empty1025 try expectError(error.DirNotEmpty, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
1026 try expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
10271026
1028 // Ensure the directory was not renamed1027 // Ensure the directory was not renamed
1029 var dir = try ctx.dir.openDir(io, test_dir_path, .{});1028 var dir = try ctx.dir.openDir(io, test_dir_path, .{});
lib/std/os/linux.zig+26-4
...@@ -501,6 +501,15 @@ pub const O = switch (native_arch) {...@@ -501,6 +501,15 @@ pub const O = switch (native_arch) {
501 else => @compileError("missing std.os.linux.O constants for this architecture"),501 else => @compileError("missing std.os.linux.O constants for this architecture"),
502};502};
503503
504pub const RENAME = packed struct(u32) {
505 /// Cannot be set together with `EXCHANGE`.
506 NOREPLACE: bool = false,
507 /// Cannot be set together with `NOREPLACE`.
508 EXCHANGE: bool = false,
509 WHITEOUT: bool = false,
510 _: u29 = 0,
511};
512
504/// Set by startup code, used by `getauxval`.513/// Set by startup code, used by `getauxval`.
505pub var elf_aux_maybe: ?[*]std.elf.Auxv = null;514pub var elf_aux_maybe: ?[*]std.elf.Auxv = null;
506515
...@@ -1346,9 +1355,22 @@ pub fn rename(old: [*:0]const u8, new: [*:0]const u8) usize {...@@ -1346,9 +1355,22 @@ pub fn rename(old: [*:0]const u8, new: [*:0]const u8) usize {
1346 if (@hasField(SYS, "rename")) {1355 if (@hasField(SYS, "rename")) {
1347 return syscall2(.rename, @intFromPtr(old), @intFromPtr(new));1356 return syscall2(.rename, @intFromPtr(old), @intFromPtr(new));
1348 } else if (@hasField(SYS, "renameat")) {1357 } else if (@hasField(SYS, "renameat")) {
1349 return syscall4(.renameat, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new));1358 return syscall4(
1359 .renameat,
1360 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1361 @intFromPtr(old),
1362 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1363 @intFromPtr(new),
1364 );
1350 } else {1365 } else {
1351 return syscall5(.renameat2, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new), 0);1366 return syscall5(
1367 .renameat2,
1368 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1369 @intFromPtr(old),
1370 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1371 @intFromPtr(new),
1372 0,
1373 );
1352 }1374 }
1353}1375}
13541376
...@@ -1373,14 +1395,14 @@ pub fn renameat(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]co...@@ -1373,14 +1395,14 @@ pub fn renameat(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]co
1373 }1395 }
1374}1396}
13751397
1376pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: u32) usize {1398pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: RENAME) usize {
1377 return syscall5(1399 return syscall5(
1378 .renameat2,1400 .renameat2,
1379 @as(usize, @bitCast(@as(isize, oldfd))),1401 @as(usize, @bitCast(@as(isize, oldfd))),
1380 @intFromPtr(oldpath),1402 @intFromPtr(oldpath),
1381 @as(usize, @bitCast(@as(isize, newfd))),1403 @as(usize, @bitCast(@as(isize, newfd))),
1382 @intFromPtr(newpath),1404 @intFromPtr(newpath),
1383 flags,1405 @as(u32, @bitCast(flags)),
1384 );1406 );
1385}1407}
13861408
lib/std/os/windows.zig+2-2
...@@ -3194,7 +3194,7 @@ pub const RenameError = error{...@@ -3194,7 +3194,7 @@ pub const RenameError = error{
3194 NetworkNotFound,3194 NetworkNotFound,
3195 AntivirusInterference,3195 AntivirusInterference,
3196 BadPathName,3196 BadPathName,
3197 RenameAcrossMountPoints,3197 CrossDevice,
3198} || UnexpectedError;3198} || UnexpectedError;
31993199
3200pub fn RenameFile(3200pub fn RenameFile(
...@@ -3295,7 +3295,7 @@ pub fn RenameFile(...@@ -3295,7 +3295,7 @@ pub fn RenameFile(
3295 .ACCESS_DENIED => return error.AccessDenied,3295 .ACCESS_DENIED => return error.AccessDenied,
3296 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,3296 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3297 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,3297 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3298 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,3298 .NOT_SAME_DEVICE => return error.CrossDevice,
3299 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,3299 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
3300 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,3300 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
3301 .FILE_IS_A_DIRECTORY => return error.IsDir,3301 .FILE_IS_A_DIRECTORY => return error.IsDir,
lib/std/posix.zig+2-2
...@@ -1594,7 +1594,7 @@ pub const FanotifyMarkError = error{...@@ -1594,7 +1594,7 @@ pub const FanotifyMarkError = error{
1594 NotDir,1594 NotDir,
1595 OperationUnsupported,1595 OperationUnsupported,
1596 PermissionDenied,1596 PermissionDenied,
1597 NotSameFileSystem,1597 CrossDevice,
1598 NameTooLong,1598 NameTooLong,
1599} || UnexpectedError;1599} || UnexpectedError;
16001600
...@@ -1634,7 +1634,7 @@ pub fn fanotify_markZ(...@@ -1634,7 +1634,7 @@ pub fn fanotify_markZ(
1634 .NOTDIR => return error.NotDir,1634 .NOTDIR => return error.NotDir,
1635 .OPNOTSUPP => return error.OperationUnsupported,1635 .OPNOTSUPP => return error.OperationUnsupported,
1636 .PERM => return error.PermissionDenied,1636 .PERM => return error.PermissionDenied,
1637 .XDEV => return error.NotSameFileSystem,1637 .XDEV => return error.CrossDevice,
1638 else => |err| return unexpectedErrno(err),1638 else => |err| return unexpectedErrno(err),
1639 }1639 }
1640}1640}
src/Compilation.zig+1-1
...@@ -3460,7 +3460,7 @@ fn renameTmpIntoCache(...@@ -3460,7 +3460,7 @@ fn renameTmpIntoCache(
3460 },3460 },
3461 else => return error.AccessDenied,3461 else => return error.AccessDenied,
3462 },3462 },
3463 error.PathAlreadyExists => {3463 error.DirNotEmpty => {
3464 try cache_directory.handle.deleteTree(io, o_sub_path);3464 try cache_directory.handle.deleteTree(io, o_sub_path);
3465 continue;3465 continue;
3466 },3466 },
src/Package/Fetch.zig+1-1
...@@ -1476,7 +1476,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u...@@ -1476,7 +1476,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u
1476 };1476 };
1477 continue;1477 continue;
1478 },1478 },
1479 error.PathAlreadyExists, error.AccessDenied => {1479 error.DirNotEmpty, error.AccessDenied => {
1480 // Package has been already downloaded and may already be in use on the system.1480 // Package has been already downloaded and may already be in use on the system.
1481 cache_dir.deleteTree(io, tmp_dir_sub_path) catch {1481 cache_dir.deleteTree(io, tmp_dir_sub_path) catch {
1482 // Garbage files leftover in zig-cache/tmp/ is, as they say1482 // Garbage files leftover in zig-cache/tmp/ is, as they say