authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-19 11:47:00+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-22 08:51:22+02:00
logfc7d87fef19964273eb65e8cee05eaffa7a9e72f
treefcba1d78951806d07f3cd02bd5fbdb35d36148a6
parentdd366d316d0b3555864145494b28fdb6e090a3f2

Move symlink to fs.symlinkAbsolute with SymlinkFlags

This way `std.fs.symlinkAbsolute` becomes cross-platform and we can legally include `SymlinkFlags` as an argument that's only used on Windows. Also, now `std.os.symlink` generates a compile error on Windows with a message to instead use `std.os.windows.CreateSymbolicLink`. Finally, this PR also reshuffles the tests between `std.os.test` and `std.fs.test`.

5 files changed, 148 insertions(+), 99 deletions(-)

lib/std/fs.zig+61-6
......@@ -16,9 +16,6 @@ pub const wasi = @import("fs/wasi.zig");
1616
1717// TODO audit these APIs with respect to Dir and absolute paths
1818
19pub const symLink = os.symlink;
20pub const symLinkZ = os.symlinkZ;
21pub const symLinkC = @compileError("deprecated: renamed to symlinkZ");
2219pub const rename = os.rename;
2320pub const renameZ = os.renameZ;
2421pub const renameC = @compileError("deprecated: renamed to renameZ");
......@@ -69,7 +66,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
6966
7067/// TODO remove the allocator requirement from this API
7168pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
72 if (symLink(existing_path, new_path, .{})) {
69 if (symLinkAbsolute(existing_path, new_path, .{})) {
7370 return;
7471 } else |err| switch (err) {
7572 error.PathAlreadyExists => {},
......@@ -87,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
8784 try crypto.randomBytes(rand_buf[0..]);
8885 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
8986
90 if (symLink(existing_path, tmp_path, .{})) {
87 if (symLinkAbsolute(existing_path, tmp_path, .{})) {
9188 return rename(tmp_path, new_path);
9289 } else |err| switch (err) {
9390 error.PathAlreadyExists => continue,
......@@ -1692,8 +1689,15 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8
16921689 return os.readlink(pathname, buffer);
16931690}
16941691
1692/// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16
1693/// encoded.
1694pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
1695 assert(path.isAbsoluteWindowsW(pathname_w));
1696 return os.readlinkW(pathname_w, buffer);
1697}
1698
16951699/// Same as `readLink`, except the path parameter is null-terminated.
1696pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
1700pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
16971701 assert(path.isAbsoluteZ(pathname_c));
16981702 return os.readlinkZ(pathname_c, buffer);
16991703}
......@@ -1701,6 +1705,57 @@ pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) !
17011705pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");
17021706pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");
17031707
1708/// Use with `symLinkAbsolute` to specify whether the symlink will point to a file
1709/// or a directory. This value is ignored on all hosts except Windows where
1710/// creating symlinks to different resource types, requires different flags.
1711/// By default, `symLinkAbsolute` is assumed to point to a file.
1712pub const SymlinkFlags = struct {
1713 is_directory: bool = false,
1714};
1715
1716/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1717/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1718/// one; the latter case is known as a dangling link.
1719/// If `sym_link_path` exists, it will not be overwritten.
1720/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
1721pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymlinkFlags) !void {
1722 if (builtin.os.tag == .wasi) {
1723 @compileError("symLink is not supported in WASI");
1724 }
1725 assert(path.isAbsolute(target_path));
1726 assert(path.isAbsolute(sym_link_path));
1727 if (builtin.os.tag == .windows) {
1728 return os.windows.CreateSymbolicLink(target_path, sym_link_path, flags.is_directory);
1729 }
1730 return os.symlink(target_path, sym_link_path);
1731}
1732
1733/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded.
1734/// Note that this function will by default try creating a symbolic link to a file. If you would
1735/// like to create a symbolic link to a directory, specify this with `SymlinkFlags{ .is_directory = true }`.
1736/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
1737pub fn symLinkAbsoluteW(target_path_w: [*:0]const u16, sym_link_path_w: [*:0]const u16, flags: SymlinkFlags) !void {
1738 assert(path.isAbsoluteWindowsW(target_path_w));
1739 assert(path.isAbsoluteWindowsW(sym_link_path_w));
1740 return os.windows.CreateSymbolicLinkW(target_path_w, sym_link_path_w, flags.is_directory);
1741}
1742
1743/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
1744/// See also `symLinkAbsolute`.
1745pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymlinkFlags) !void {
1746 assert(path.isAbsoluteZ(target_path_c));
1747 assert(path.isAbsoluteZ(sym_link_path_c));
1748 if (builtin.os.tag == .windows) {
1749 const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
1750 const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
1751 return os.windows.CreateSymbolicLinkW(target_path_w.span().ptr, sym_link_path_w.span().ptr, flags.is_directory);
1752 }
1753 return os.symlinkZ(target_path_c, sym_link_path_c);
1754}
1755
1756pub const symLink = @compileError("deprecated: use symLinkAbsolute");
1757pub const symLinkC = @compileError("deprecated: use symLinkAbsoluteC");
1758
17041759pub const Walker = struct {
17051760 stack: std.ArrayList(StackItem),
17061761 name_buffer: std.ArrayList(u8),
lib/std/fs/test.zig+44
......@@ -10,6 +10,50 @@ const Dir = std.fs.Dir;
1010const File = std.fs.File;
1111const tmpDir = testing.tmpDir;
1212
13test "readLinkAbsolute" {
14 if (builtin.os.tag == .wasi) return error.SkipZigTest;
15
16 var tmp = tmpDir(.{});
17 defer tmp.cleanup();
18
19 // Create some targets
20 try tmp.dir.writeFile("file.txt", "nonsense");
21 try tmp.dir.makeDir("subdir");
22
23 // Get base abs path
24 var arena = ArenaAllocator.init(testing.allocator);
25 defer arena.deinit();
26
27 const base_path = blk: {
28 const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
29 break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
30 };
31 const allocator = &arena.allocator;
32
33 {
34 const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "file.txt" });
35 const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink1" });
36
37 // Create symbolic link by path
38 try fs.symLinkAbsolute(target_path, symlink_path, .{});
39 try testReadlinkAbsolute(target_path, symlink_path);
40 }
41 {
42 const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" });
43 const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink2" });
44
45 // Create symbolic link by path
46 try fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
47 try testReadlinkAbsolute(target_path, symlink_path);
48 }
49}
50
51fn testReadlinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
52 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
53 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
54 testing.expect(mem.eql(u8, target_path, given));
55}
56
1357test "Dir.Iterator" {
1458 var tmp_dir = tmpDir(.{ .iterate = true });
1559 defer tmp_dir.cleanup();
lib/std/os.zig+6-25
......@@ -1520,14 +1520,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
15201520 }
15211521}
15221522
1523/// Use with `symlink` to specify whether the symlink will point to a file
1524/// or a directory. This value is ignored on all hosts except Windows where
1525/// creating symlinks to different resource types, requires different flags.
1526/// By default, symlink is assumed to point to a file.
1527pub const SymlinkFlags = struct {
1528 is_directory: bool = false,
1529};
1530
15311523pub const SymLinkError = error{
15321524 /// In WASI, this error may occur when the file descriptor does
15331525 /// not hold the required rights to create a new symbolic link relative to it.
......@@ -1550,37 +1542,26 @@ pub const SymLinkError = error{
15501542/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
15511543/// one; the latter case is known as a dangling link.
15521544/// If `sym_link_path` exists, it will not be overwritten.
1553/// See also `symlinkC` and `symlinkW`.
1554pub fn symlink(target_path: []const u8, sym_link_path: []const u8, flags: SymlinkFlags) SymLinkError!void {
1545/// See also `symlinkZ.
1546pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
15551547 if (builtin.os.tag == .wasi) {
15561548 @compileError("symlink is not supported in WASI; use symlinkat instead");
15571549 }
15581550 if (builtin.os.tag == .windows) {
1559 const target_path_w = try windows.sliceToWin32PrefixedFileW(target_path);
1560 const sym_link_path_w = try windows.sliceToWin32PrefixedFileW(sym_link_path);
1561 return symlinkW(target_path_w.span().ptr, sym_link_path_w.span().ptr, flags);
1551 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
15621552 }
15631553 const target_path_c = try toPosixPath(target_path);
15641554 const sym_link_path_c = try toPosixPath(sym_link_path);
1565 return symlinkZ(&target_path_c, &sym_link_path_c, flags);
1555 return symlinkZ(&target_path_c, &sym_link_path_c);
15661556}
15671557
15681558pub const symlinkC = @compileError("deprecated: renamed to symlinkZ");
15691559
1570/// Windows-only. Same as `symlink` except the parameters are null-terminated, WTF16 encoded.
1571/// Note that this function will by default try creating a symbolic link to a file. If you would
1572/// like to create a symbolic link to a directory, specify this with `SymlinkFlags{ .is_directory = true }`.
1573pub fn symlinkW(target_path: [*:0]const u16, sym_link_path: [*:0]const u16, flags: SymlinkFlags) SymLinkError!void {
1574 return windows.CreateSymbolicLinkW(sym_link_path, target_path, flags.is_directory);
1575}
1576
15771560/// This is the same as `symlink` except the parameters are null-terminated pointers.
15781561/// See also `symlink`.
1579pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8, flags: SymlinkFlags) SymLinkError!void {
1562pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
15801563 if (builtin.os.tag == .windows) {
1581 const target_path_w = try windows.cStrToWin32PrefixedFileW(target_path);
1582 const sym_link_path_w = try windows.cStrToWin32PrefixedFileW(sym_link_path);
1583 return symlinkW(target_path_w.span().ptr, sym_link_path_w.span().ptr);
1564 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
15841565 }
15851566 switch (errno(system.symlink(target_path, sym_link_path))) {
15861567 0 => return,
lib/std/os/test.zig+35-66
......@@ -19,6 +19,41 @@ const tmpDir = std.testing.tmpDir;
1919const Dir = std.fs.Dir;
2020const ArenaAllocator = std.heap.ArenaAllocator;
2121
22test "symlink with relative paths" {
23 if (builtin.os.tag == .wasi) return error.SkipZigTest;
24
25 // First, try relative paths in cwd
26 var cwd = fs.cwd();
27 try cwd.writeFile("file.txt", "nonsense");
28
29 if (builtin.os.tag == .windows) {
30 try os.windows.CreateSymbolicLink("file.txt", "symlinked", false);
31 } else {
32 try os.symlink("file.txt", "symlinked");
33 }
34
35 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
36 const given = try os.readlink("symlinked", buffer[0..]);
37 expect(mem.eql(u8, "file.txt", given));
38
39 try cwd.deleteFile("file.txt");
40 try cwd.deleteFile("symlinked");
41}
42
43test "readlink on Windows" {
44 if (builtin.os.tag != .windows) return error.SkipZigTest;
45
46 try testReadlink("C:\\ProgramData", "C:\\Users\\All Users");
47 try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User");
48 try testReadlink("C:\\Users", "C:\\Documents and Settings");
49}
50
51fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
52 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
53 const given = try os.readlink(symlink_path, buffer[0..]);
54 expect(mem.eql(u8, target_path, given));
55}
56
2257test "fstatat" {
2358 // enable when `fstat` and `fstatat` are implemented on Windows
2459 if (builtin.os.tag == .windows) return error.SkipZigTest;
......@@ -41,72 +76,6 @@ test "fstatat" {
4176 expectEqual(stat, statat);
4277}
4378
44test "readlink" {
45 if (builtin.os.tag == .wasi) return error.SkipZigTest;
46
47 // First, try relative paths in cwd
48 {
49 var cwd = fs.cwd();
50 try cwd.writeFile("file.txt", "nonsense");
51 try os.symlink("file.txt", "symlinked", .{});
52
53 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
54 const given = try os.readlink("symlinked", buffer[0..]);
55 expect(mem.eql(u8, "file.txt", given));
56
57 try cwd.deleteFile("file.txt");
58 try cwd.deleteFile("symlinked");
59 }
60
61 // Now, let's use tempdir
62 var tmp = tmpDir(.{});
63 // defer tmp.cleanup();
64
65 // Create some targets
66 try tmp.dir.writeFile("file.txt", "nonsense");
67 try tmp.dir.makeDir("subdir");
68
69 // Get base abs path
70 var arena = ArenaAllocator.init(testing.allocator);
71 defer arena.deinit();
72
73
74 const base_path = blk: {
75 const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
76 break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
77 };
78 const allocator = &arena.allocator;
79
80 {
81 const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "file.txt" });
82 const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink1" });
83
84 // Create symbolic link by path
85 try os.symlink(target_path, symlink_path, .{ .is_directory = false });
86 try testReadlink(target_path, symlink_path);
87 }
88 {
89 const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" });
90 const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink2" });
91
92 // Create symbolic link by path
93 try os.symlink(target_path, symlink_path, .{ .is_directory = true });
94 try testReadlink(target_path, symlink_path);
95 }
96
97 if (builtin.os.tag == .windows) {
98 try testReadlink("C:\\ProgramData", "C:\\Users\\All Users");
99 try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User");
100 try testReadlink("C:\\Users", "C:\\Documents and Settings");
101 }
102}
103
104fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
105 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
106 const given = try os.readlink(symlink_path, buffer[0..]);
107 expect(mem.eql(u8, target_path, given));
108}
109
11079test "readlinkat" {
11180 // enable when `readlinkat` and `symlinkat` are implemented on Windows
11281 if (builtin.os.tag == .windows) return error.SkipZigTest;
lib/std/os/windows.zig+2-2
......@@ -609,8 +609,8 @@ pub fn CreateSymbolicLink(
609609 target_path: []const u8,
610610 is_directory: bool,
611611) CreateSymbolicLinkError!void {
612 const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
613 const target_path_w = try sliceToPrefixedFileW(target_path);
612 const sym_link_path_w = try sliceToWin32PrefixedFileW(sym_link_path);
613 const target_path_w = try sliceToWin32PrefixedFileW(target_path);
614614 return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, is_directory);
615615}
616616