| 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 | 21 | |
| 22 | 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 | 24 | pub const realpath = os.realpath; |
| 29 | 25 | pub const realpathZ = os.realpathZ; |
| 30 | 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 | 86 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); |
| 91 | 87 | |
| 92 | 88 | if (cwd().symLink(existing_path, tmp_path, .{})) { |
| 93 | return rename(tmp_path, new_path); | |
| 89 | return cwd().rename(tmp_path, new_path); | |
| 94 | 90 | } else |err| switch (err) { |
| 95 | 91 | error.PathAlreadyExists => continue, |
| 96 | 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 | 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 | 293 | pub const Dir = struct { |
| 259 | 294 | fd: os.fd_t, |
| 260 | 295 | |
| ... | ... | @@ -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 | 1397 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. |
| 1342 | 1398 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 1343 | 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 | 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 | 438 | test "openSelfExe" { |
| 278 | 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 | 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 | 1894 | /// In WASI, this error may occur when the file descriptor does |
| 1895 | 1895 | /// not hold the required rights to rename a resource by path relative to it. |
| 1896 | 1896 | AccessDenied, |
| ... | ... | @@ -2107,6 +2107,7 @@ pub fn renameatW( |
| 2107 | 2107 | .ACCESS_DENIED => return error.AccessDenied, |
| 2108 | 2108 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 2109 | 2109 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 2110 | .NOT_SAME_DEVICE => error.RenameAcrossMountPoints, | |
| 2110 | 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 | 828 | } |
| 829 | 829 | } |
| 830 | 830 | |
| 831 | pub const MoveFileError = error{Unexpected}; | |
| 831 | pub const MoveFileError = error{ FileNotFound, Unexpected }; | |
| 832 | 832 | |
| 833 | 833 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { |
| 834 | 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 | 839 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { |
| 840 | 840 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { |
| 841 | 841 | switch (kernel32.GetLastError()) { |
| 842 | .FILE_NOT_FOUND => return error.FileNotFound, | |
| 842 | 843 | else => |err| return unexpectedError(err), |
| 843 | 844 | } |
| 844 | 845 | } |