| author | |
| committer | |
| log | 5e3fa0e94f947c632aa584b9e13bfa2fe241fae1 |
| tree | d2361876d4a8bfe9e5ffab6fc7db81ad33d6ec73 |
| parent | 3672a187999c3db6eba35d9a9184e7cc066ed629 |
- Moves fs.rename functions to fs.renameAbsolute to match other functions outside of fs.Dir
- Adds fs.Dir.rename that takes two paths relative to the given Dir
- Adds fs.rename that takes two separate Dir's that the given paths are relative to (for renaming across directories without having to make the second path relative to a single directory)
- Fixes FileNotFound error return in std.os.windows.MoveFileExW
- Returns error.RenameAcrossMountPoints from renameatW
+ Matches the RenameAcrossMountPoints error return in renameatWasi/renameatZ4 files changed, 226 insertions(+), 7 deletions(-)
lib/std/fs.zig+61-5| ... | @@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig"); | ... | @@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig"); |
| 21 | 21 | ||
| 22 | // TODO audit these APIs with respect to Dir and absolute paths | 22 | // TODO audit these APIs with respect to Dir and absolute paths |
| 23 | 23 | ||
| 24 | pub const rename = os.rename; | ||
| 25 | pub const renameZ = os.renameZ; | ||
| 26 | pub const renameC = @compileError("deprecated: renamed to renameZ"); | ||
| 27 | pub const renameW = os.renameW; | ||
| 28 | pub const realpath = os.realpath; | 24 | pub const realpath = os.realpath; |
| 29 | pub const realpathZ = os.realpathZ; | 25 | pub const realpathZ = os.realpathZ; |
| 30 | pub const realpathC = @compileError("deprecated: renamed to realpathZ"); | 26 | pub const realpathC = @compileError("deprecated: renamed to realpathZ"); |
| ... | @@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: | ... | @@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 90 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); | 86 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); |
| 91 | 87 | ||
| 92 | if (cwd().symLink(existing_path, tmp_path, .{})) { | 88 | if (cwd().symLink(existing_path, tmp_path, .{})) { |
| 93 | return rename(tmp_path, new_path); | 89 | return cwd().rename(tmp_path, new_path); |
| 94 | } else |err| switch (err) { | 90 | } else |err| switch (err) { |
| 95 | error.PathAlreadyExists => continue, | 91 | error.PathAlreadyExists => continue, |
| 96 | else => return err, // TODO zig should know this set does not include PathAlreadyExists | 92 | else => return err, // TODO zig should know this set does not include PathAlreadyExists |
| ... | @@ -255,6 +251,45 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { | ... | @@ -255,6 +251,45 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { |
| 255 | return os.rmdirW(dir_path); | 251 | return os.rmdirW(dir_path); |
| 256 | } | 252 | } |
| 257 | 253 | ||
| 254 | pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ"); | ||
| 255 | |||
| 256 | /// Same as `Dir.rename` except the paths are absolute. | ||
| 257 | pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { | ||
| 258 | assert(path.isAbsolute(old_path)); | ||
| 259 | assert(path.isAbsolute(new_path)); | ||
| 260 | return os.rename(old_path, new_path); | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Same as `renameAbsolute` except the path parameters are null-terminated. | ||
| 264 | pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void { | ||
| 265 | assert(path.isAbsoluteZ(old_path)); | ||
| 266 | assert(path.isAbsoluteZ(new_path)); | ||
| 267 | return os.renameZ(old_path, new_path); | ||
| 268 | } | ||
| 269 | |||
| 270 | /// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows. | ||
| 271 | pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void { | ||
| 272 | assert(path.isAbsoluteWindowsW(old_path)); | ||
| 273 | assert(path.isAbsoluteWindowsW(new_path)); | ||
| 274 | return os.renameW(old_path, new_path); | ||
| 275 | } | ||
| 276 | |||
| 277 | /// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir` | ||
| 278 | pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void { | ||
| 279 | return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path); | ||
| 280 | } | ||
| 281 | |||
| 282 | /// Same as `rename` except the parameters are null-terminated. | ||
| 283 | pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void { | ||
| 284 | return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); | ||
| 285 | } | ||
| 286 | |||
| 287 | /// Same as `rename` except the parameters are UTF16LE, NT prefixed. | ||
| 288 | /// This function is Windows-only. | ||
| 289 | pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void { | ||
| 290 | return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w); | ||
| 291 | } | ||
| 292 | |||
| 258 | pub const Dir = struct { | 293 | pub const Dir = struct { |
| 259 | fd: os.fd_t, | 294 | fd: os.fd_t, |
| 260 | 295 | ||
| ... | @@ -1338,6 +1373,27 @@ pub const Dir = struct { | ... | @@ -1338,6 +1373,27 @@ pub const Dir = struct { |
| 1338 | }; | 1373 | }; |
| 1339 | } | 1374 | } |
| 1340 | 1375 | ||
| 1376 | pub const RenameError = os.RenameError; | ||
| 1377 | |||
| 1378 | /// Change the name or location of a file or directory. | ||
| 1379 | /// If new_sub_path already exists, it will be replaced. | ||
| 1380 | /// Renaming a file over an existing directory or a directory | ||
| 1381 | /// over an existing file will fail with `error.IsDir` or `error.NotDir` | ||
| 1382 | pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void { | ||
| 1383 | return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path); | ||
| 1384 | } | ||
| 1385 | |||
| 1386 | /// Same as `rename` except the parameters are null-terminated. | ||
| 1387 | pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void { | ||
| 1388 | return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z); | ||
| 1389 | } | ||
| 1390 | |||
| 1391 | /// Same as `rename` except the parameters are UTF16LE, NT prefixed. | ||
| 1392 | /// This function is Windows-only. | ||
| 1393 | pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void { | ||
| 1394 | return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w); | ||
| 1395 | } | ||
| 1396 | |||
| 1341 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. | 1397 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. |
| 1342 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent | 1398 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 1343 | /// one; the latter case is known as a dangling link. | 1399 | /// one; the latter case is known as a dangling link. |
lib/std/fs/test.zig+161| ... | @@ -274,6 +274,167 @@ test "file operations on directories" { | ... | @@ -274,6 +274,167 @@ test "file operations on directories" { |
| 274 | dir.close(); | 274 | dir.close(); |
| 275 | } | 275 | } |
| 276 | 276 | ||
| 277 | test "Dir.rename files" { | ||
| 278 | var tmp_dir = tmpDir(.{}); | ||
| 279 | defer tmp_dir.cleanup(); | ||
| 280 | |||
| 281 | testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else")); | ||
| 282 | |||
| 283 | // Renaming files | ||
| 284 | const test_file_name = "test_file"; | ||
| 285 | const renamed_test_file_name = "test_file_renamed"; | ||
| 286 | var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); | ||
| 287 | file.close(); | ||
| 288 | try tmp_dir.dir.rename(test_file_name, renamed_test_file_name); | ||
| 289 | |||
| 290 | // Ensure the file was renamed | ||
| 291 | testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); | ||
| 292 | file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); | ||
| 293 | file.close(); | ||
| 294 | |||
| 295 | // Rename to self succeeds | ||
| 296 | try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name); | ||
| 297 | |||
| 298 | // Rename to existing file succeeds | ||
| 299 | var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true }); | ||
| 300 | existing_file.close(); | ||
| 301 | try tmp_dir.dir.rename(renamed_test_file_name, "existing_file"); | ||
| 302 | |||
| 303 | testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{})); | ||
| 304 | file = try tmp_dir.dir.openFile("existing_file", .{}); | ||
| 305 | file.close(); | ||
| 306 | } | ||
| 307 | |||
| 308 | test "Dir.rename directories" { | ||
| 309 | // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 | ||
| 310 | if (builtin.os.tag == .windows) return error.SkipZigTest; | ||
| 311 | |||
| 312 | var tmp_dir = tmpDir(.{}); | ||
| 313 | defer tmp_dir.cleanup(); | ||
| 314 | |||
| 315 | // Renaming directories | ||
| 316 | try tmp_dir.dir.makeDir("test_dir"); | ||
| 317 | try tmp_dir.dir.rename("test_dir", "test_dir_renamed"); | ||
| 318 | |||
| 319 | // Ensure the directory was renamed | ||
| 320 | testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{})); | ||
| 321 | var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{}); | ||
| 322 | |||
| 323 | // Put a file in the directory | ||
| 324 | var file = try dir.createFile("test_file", .{ .read = true }); | ||
| 325 | file.close(); | ||
| 326 | dir.close(); | ||
| 327 | |||
| 328 | try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again"); | ||
| 329 | |||
| 330 | // Ensure the directory was renamed and the file still exists in it | ||
| 331 | testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{})); | ||
| 332 | dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); | ||
| 333 | file = try dir.openFile("test_file", .{}); | ||
| 334 | file.close(); | ||
| 335 | dir.close(); | ||
| 336 | |||
| 337 | // Try to rename to a non-empty directory now | ||
| 338 | var target_dir = try tmp_dir.dir.makeOpenPath("non_empty_target_dir", .{}); | ||
| 339 | file = try target_dir.createFile("filler", .{ .read = true }); | ||
| 340 | file.close(); | ||
| 341 | |||
| 342 | testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir_renamed_again", "non_empty_target_dir")); | ||
| 343 | |||
| 344 | // Ensure the directory was not renamed | ||
| 345 | dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); | ||
| 346 | file = try dir.openFile("test_file", .{}); | ||
| 347 | file.close(); | ||
| 348 | dir.close(); | ||
| 349 | } | ||
| 350 | |||
| 351 | test "Dir.rename file <-> dir" { | ||
| 352 | // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 | ||
| 353 | if (builtin.os.tag == .windows) return error.SkipZigTest; | ||
| 354 | |||
| 355 | var tmp_dir = tmpDir(.{}); | ||
| 356 | defer tmp_dir.cleanup(); | ||
| 357 | |||
| 358 | var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); | ||
| 359 | file.close(); | ||
| 360 | try tmp_dir.dir.makeDir("test_dir"); | ||
| 361 | testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir")); | ||
| 362 | testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file")); | ||
| 363 | } | ||
| 364 | |||
| 365 | test "rename" { | ||
| 366 | var tmp_dir1 = tmpDir(.{}); | ||
| 367 | defer tmp_dir1.cleanup(); | ||
| 368 | |||
| 369 | var tmp_dir2 = tmpDir(.{}); | ||
| 370 | defer tmp_dir2.cleanup(); | ||
| 371 | |||
| 372 | // Renaming files | ||
| 373 | const test_file_name = "test_file"; | ||
| 374 | const renamed_test_file_name = "test_file_renamed"; | ||
| 375 | var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); | ||
| 376 | file.close(); | ||
| 377 | try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); | ||
| 378 | |||
| 379 | // ensure the file was renamed | ||
| 380 | testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); | ||
| 381 | file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); | ||
| 382 | file.close(); | ||
| 383 | } | ||
| 384 | |||
| 385 | test "renameAbsolute" { | ||
| 386 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 387 | |||
| 388 | var tmp_dir = tmpDir(.{}); | ||
| 389 | defer tmp_dir.cleanup(); | ||
| 390 | |||
| 391 | // Get base abs path | ||
| 392 | var arena = ArenaAllocator.init(testing.allocator); | ||
| 393 | defer arena.deinit(); | ||
| 394 | const allocator = &arena.allocator; | ||
| 395 | |||
| 396 | const base_path = blk: { | ||
| 397 | const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] }); | ||
| 398 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); | ||
| 399 | }; | ||
| 400 | |||
| 401 | testing.expectError(error.FileNotFound, fs.renameAbsolute( | ||
| 402 | try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }), | ||
| 403 | try fs.path.join(allocator, &[_][]const u8{ base_path, "something_else" }), | ||
| 404 | )); | ||
| 405 | |||
| 406 | // Renaming files | ||
| 407 | const test_file_name = "test_file"; | ||
| 408 | const renamed_test_file_name = "test_file_renamed"; | ||
| 409 | var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); | ||
| 410 | file.close(); | ||
| 411 | try fs.renameAbsolute( | ||
| 412 | try fs.path.join(allocator, &[_][]const u8{ base_path, test_file_name }), | ||
| 413 | try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_file_name }), | ||
| 414 | ); | ||
| 415 | |||
| 416 | // ensure the file was renamed | ||
| 417 | testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); | ||
| 418 | file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); | ||
| 419 | const stat = try file.stat(); | ||
| 420 | testing.expect(stat.kind == .File); | ||
| 421 | file.close(); | ||
| 422 | |||
| 423 | // Renaming directories | ||
| 424 | const test_dir_name = "test_dir"; | ||
| 425 | const renamed_test_dir_name = "test_dir_renamed"; | ||
| 426 | try tmp_dir.dir.makeDir(test_dir_name); | ||
| 427 | try fs.renameAbsolute( | ||
| 428 | try fs.path.join(allocator, &[_][]const u8{ base_path, test_dir_name }), | ||
| 429 | try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_dir_name }), | ||
| 430 | ); | ||
| 431 | |||
| 432 | // ensure the directory was renamed | ||
| 433 | testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); | ||
| 434 | var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); | ||
| 435 | dir.close(); | ||
| 436 | } | ||
| 437 | |||
| 277 | test "openSelfExe" { | 438 | test "openSelfExe" { |
| 278 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 439 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 279 | 440 |
lib/std/os.zig+2-1| ... | @@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError | ... | @@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError |
| 1890 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); | 1890 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); |
| 1891 | } | 1891 | } |
| 1892 | 1892 | ||
| 1893 | const RenameError = error{ | 1893 | pub const RenameError = error{ |
| 1894 | /// In WASI, this error may occur when the file descriptor does | 1894 | /// In WASI, this error may occur when the file descriptor does |
| 1895 | /// not hold the required rights to rename a resource by path relative to it. | 1895 | /// not hold the required rights to rename a resource by path relative to it. |
| 1896 | AccessDenied, | 1896 | AccessDenied, |
| ... | @@ -2107,6 +2107,7 @@ pub fn renameatW( | ... | @@ -2107,6 +2107,7 @@ pub fn renameatW( |
| 2107 | .ACCESS_DENIED => return error.AccessDenied, | 2107 | .ACCESS_DENIED => return error.AccessDenied, |
| 2108 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 2108 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 2109 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | 2109 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 2110 | .NOT_SAME_DEVICE => error.RenameAcrossMountPoints, | ||
| 2110 | else => return windows.unexpectedStatus(rc), | 2111 | else => return windows.unexpectedStatus(rc), |
| 2111 | } | 2112 | } |
| 2112 | } | 2113 | } |
lib/std/os/windows.zig+2-1| ... | @@ -828,7 +828,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil | ... | @@ -828,7 +828,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil |
| 828 | } | 828 | } |
| 829 | } | 829 | } |
| 830 | 830 | ||
| 831 | pub const MoveFileError = error{Unexpected}; | 831 | pub const MoveFileError = error{ FileNotFound, Unexpected }; |
| 832 | 832 | ||
| 833 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { | 833 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { |
| 834 | const old_path_w = try sliceToPrefixedFileW(old_path); | 834 | const old_path_w = try sliceToPrefixedFileW(old_path); |
| ... | @@ -839,6 +839,7 @@ pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) Move | ... | @@ -839,6 +839,7 @@ pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) Move |
| 839 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { | 839 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { |
| 840 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { | 840 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { |
| 841 | switch (kernel32.GetLastError()) { | 841 | switch (kernel32.GetLastError()) { |
| 842 | .FILE_NOT_FOUND => return error.FileNotFound, | ||
| 842 | else => |err| return unexpectedError(err), | 843 | else => |err| return unexpectedError(err), |
| 843 | } | 844 | } |
| 844 | } | 845 | } |