Skip to content

Commit e8f1be1

Browse files
committed
rename: Add error.CircularLoop
Adds error.CircularLoop to rename functions. For POSIX systems: according to the man page for rename, "EINVAL The old pathname names an ancestor directory of the new pathname, or either pathname argument contains a final component that is dot or dot-dot." The later is always programmer error, so returning error.CircularLoop should always be correct. For WASI systems: the error should be the same as POSIX. The documentation for INVAL says "Invalid argument, similar to `EINVAL` in POSIX." This holds true for wasmtime 33.0.0 on my system, however the zig CI returns PERM, so the test is skipped on WASI. For Windows systems: based on empirical testing, SHARING_VIOLATION is returned on Windows 10 to indicate a circular loop. At some point on Windows 11, INVALID_PARAMATER started being used.
1 parent 4d79806 commit e8f1be1

File tree

4 files changed

+44
-5
lines changed

4 files changed

+44
-5
lines changed

lib/std/fs/test.zig

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,33 @@ test "Dir.rename file <-> dir" {
991991
}.impl);
992992
}
993993

994+
test "Dir.rename circular loop" {
995+
// TODO: The CI fails with error.PermissionDenied
996+
if (native_os == .wasi) return error.SkipZigTest;
997+
998+
try testWithAllSupportedPathTypes(struct {
999+
fn impl(ctx: *TestContext) !void {
1000+
const parent_path = try ctx.transformPath("parent");
1001+
const subdir_path = try ctx.transformPath("parent/subdir");
1002+
try ctx.dir.makeDir(parent_path);
1003+
try testing.expectError(error.CircularLoop, ctx.dir.rename(parent_path, subdir_path));
1004+
1005+
if (ctx.path_type == .relative) {
1006+
var parent = try ctx.dir.openDir("parent", .{});
1007+
defer parent.close();
1008+
try testing.expectError(error.CircularLoop, parent.rename("../parent", "subdir"));
1009+
}
1010+
1011+
if (ctx.path_type == .absolute) {
1012+
try testing.expectError(error.CircularLoop, std.fs.renameAbsolute(
1013+
parent_path,
1014+
subdir_path,
1015+
));
1016+
}
1017+
}
1018+
}.impl);
1019+
}
1020+
9941021
test "rename" {
9951022
var tmp_dir1 = tmpDir(.{});
9961023
defer tmp_dir1.cleanup();

lib/std/os/windows.zig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,12 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
11211121
}
11221122
}
11231123

1124-
pub const MoveFileError = error{ FileNotFound, AccessDenied, Unexpected };
1124+
pub const MoveFileError = error{
1125+
FileNotFound,
1126+
AccessDenied,
1127+
CircularLoop,
1128+
Unexpected,
1129+
};
11251130

11261131
pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) (MoveFileError || Wtf8ToPrefixedFileWError)!void {
11271132
const old_path_w = try sliceToPrefixedFileW(null, old_path);
@@ -1134,6 +1139,9 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
11341139
switch (GetLastError()) {
11351140
.FILE_NOT_FOUND => return error.FileNotFound,
11361141
.ACCESS_DENIED => return error.AccessDenied,
1142+
.INVALID_PARAMETER,
1143+
.SHARING_VIOLATION, // This is returned before Windows 11
1144+
=> return error.CircularLoop,
11371145
else => |err| return unexpectedError(err),
11381146
}
11391147
}

lib/std/posix.zig

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2599,6 +2599,7 @@ pub const RenameError = error{
25992599
FileBusy,
26002600
DiskQuota,
26012601
IsDir,
2602+
CircularLoop,
26022603
SymLinkLoop,
26032604
LinkQuotaExceeded,
26042605
NameTooLong,
@@ -2662,7 +2663,7 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
26622663
.BUSY => return error.FileBusy,
26632664
.DQUOT => return error.DiskQuota,
26642665
.FAULT => unreachable,
2665-
.INVAL => unreachable,
2666+
.INVAL => return error.CircularLoop,
26662667
.ISDIR => return error.IsDir,
26672668
.LOOP => return error.SymLinkLoop,
26682669
.MLINK => return error.LinkQuotaExceeded,
@@ -2725,7 +2726,7 @@ fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
27252726
.BUSY => return error.FileBusy,
27262727
.DQUOT => return error.DiskQuota,
27272728
.FAULT => unreachable,
2728-
.INVAL => unreachable,
2729+
.INVAL => return error.CircularLoop,
27292730
.ISDIR => return error.IsDir,
27302731
.LOOP => return error.SymLinkLoop,
27312732
.MLINK => return error.LinkQuotaExceeded,
@@ -2777,7 +2778,7 @@ pub fn renameatZ(
27772778
.BUSY => return error.FileBusy,
27782779
.DQUOT => return error.DiskQuota,
27792780
.FAULT => unreachable,
2780-
.INVAL => unreachable,
2781+
.INVAL => return error.CircularLoop,
27812782
.ISDIR => return error.IsDir,
27822783
.LOOP => return error.SymLinkLoop,
27832784
.MLINK => return error.LinkQuotaExceeded,
@@ -2891,7 +2892,6 @@ pub fn renameatW(
28912892
switch (rc) {
28922893
.SUCCESS => {},
28932894
.INVALID_HANDLE => unreachable,
2894-
.INVALID_PARAMETER => unreachable,
28952895
.OBJECT_PATH_SYNTAX_BAD => unreachable,
28962896
.ACCESS_DENIED => return error.AccessDenied,
28972897
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
@@ -2901,6 +2901,9 @@ pub fn renameatW(
29012901
.DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
29022902
.FILE_IS_A_DIRECTORY => return error.IsDir,
29032903
.NOT_A_DIRECTORY => return error.NotDir,
2904+
.INVALID_PARAMETER,
2905+
.SHARING_VIOLATION, // This is returned before Windows 11
2906+
=> return error.CircularLoop,
29042907
else => return windows.unexpectedStatus(rc),
29052908
}
29062909
}

src/fmt.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ const FmtError = error{
222222
NetNameDeleted,
223223
InvalidArgument,
224224
ProcessNotFound,
225+
CircularLoop,
225226
} || fs.File.OpenError;
226227

227228
fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void {

0 commit comments

Comments
 (0)