| author | |
| committer | |
| log | ee574f665c4e3d1be4950b307ce3ff8324f13f46 |
| tree | 711757fd21992e3cc5297061b2fd9cf9d1f1d679 |
| parent | 8e1850e277c3112eda44b8bb3de95ef1791eccfa |
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 |
| 936 | pub const RenameError = error{ | 936 | pub const RenameError = error{ |
| 937 | /// In WASI, this error may occur when the file descriptor does | 937 | /// 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 slower | 962 | /// 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; |
| 968 | 967 | ||
| 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 existing | 972 | /// 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. |
| 979 | pub fn rename( | 978 | pub 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 | } |
| 995 | 994 | ||
| 995 | pub 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. | ||
| 1018 | pub 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 | |||
| 996 | pub const HardLinkOptions = File.HardLinkOptions; | 1028 | pub const HardLinkOptions = File.HardLinkOptions; |
| 997 | 1029 | ||
| 998 | pub const HardLinkError = File.HardLinkError; | 1030 | pub 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; |
| 732 | 732 |
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 | } |
| 39 | 39 | ||
| 40 | pub const LinkError = Dir.HardLinkError; | 40 | pub const LinkError = Dir.HardLinkError || Dir.RenamePreserveError; |
| 41 | 41 | ||
| 42 | /// Atomically materializes the file into place, failing with | 42 | /// 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`. | ||
| 44 | pub fn link(af: *Atomic, io: Io) LinkError!void { | 48 | pub 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 | } | ||
| 5698 | 5705 | ||
| 5706 | fn 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 | |||
| 5723 | fn 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; | ||
| 5704 | 5735 | ||
| 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); |
| 5879 | 5910 | ||
| 5911 | return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix); | ||
| 5912 | } | ||
| 5913 | |||
| 5914 | fn 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 | |||
| 5964 | fn 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 | |||
| 6001 | fn 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); |
| 1024 | 1024 | ||
| 1025 | // Rename should fail with PathAlreadyExists if target_dir is non-empty | 1025 | 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)); | ||
| 1027 | 1026 | ||
| 1028 | // Ensure the directory was not renamed | 1027 | // 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 | }; |
| 503 | 503 | ||
| 504 | pub 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`. |
| 505 | pub var elf_aux_maybe: ?[*]std.elf.Auxv = null; | 514 | pub var elf_aux_maybe: ?[*]std.elf.Auxv = null; |
| 506 | 515 | ||
| ... | @@ -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 | } |
| 1354 | 1376 | ||
| ... | @@ -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 | } |
| 1375 | 1397 | ||
| 1376 | pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: u32) usize { | 1398 | pub 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 | } |
| 1386 | 1408 |
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; |
| 3199 | 3199 | ||
| 3200 | pub fn RenameFile( | 3200 | pub 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; |
| 1600 | 1600 | ||
| ... | @@ -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 say | 1482 | // Garbage files leftover in zig-cache/tmp/ is, as they say |