authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2020-09-16 20:59:45-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-17 17:22:26-04:00
log5e3fa0e94f947c632aa584b9e13bfa2fe241fae1
treed2361876d4a8bfe9e5ffab6fc7db81ad33d6ec73
parent3672a187999c3db6eba35d9a9184e7cc066ed629

Add rename to std.fs API

- 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/renameatZ

4 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");
2121
22// TODO audit these APIs with respect to Dir and absolute paths22// TODO audit these APIs with respect to Dir and absolute paths
2323
24pub const rename = os.rename;
25pub const renameZ = os.renameZ;
26pub const renameC = @compileError("deprecated: renamed to renameZ");
27pub const renameW = os.renameW;
28pub const realpath = os.realpath;24pub const realpath = os.realpath;
29pub const realpathZ = os.realpathZ;25pub const realpathZ = os.realpathZ;
30pub const realpathC = @compileError("deprecated: renamed to realpathZ");26pub 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);
9187
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 PathAlreadyExists92 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}
257253
254pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ");
255
256/// Same as `Dir.rename` except the paths are absolute.
257pub 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.
264pub 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.
271pub 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`
278pub 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.
283pub 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.
289pub 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
258pub const Dir = struct {293pub const Dir = struct {
259 fd: os.fd_t,294 fd: os.fd_t,
260295
...@@ -1338,6 +1373,27 @@ pub const Dir = struct {...@@ -1338,6 +1373,27 @@ pub const Dir = struct {
1338 };1373 };
1339 }1374 }
13401375
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 nonexistent1398 /// 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}
276276
277test "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
308test "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
351test "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
365test "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
385test "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
277test "openSelfExe" {438test "openSelfExe" {
278 if (builtin.os.tag == .wasi) return error.SkipZigTest;439 if (builtin.os.tag == .wasi) return error.SkipZigTest;
279440
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}
18921892
1893const RenameError = error{1893pub const RenameError = error{
1894 /// In WASI, this error may occur when the file descriptor does1894 /// 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}
830830
831pub const MoveFileError = error{Unexpected};831pub const MoveFileError = error{ FileNotFound, Unexpected };
832832
833pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {833pub 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
839pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {839pub 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 }