| author | |
| committer | |
| log | fc7d87fef19964273eb65e8cee05eaffa7a9e72f |
| tree | fcba1d78951806d07f3cd02bd5fbdb35d36148a6 |
| parent | dd366d316d0b3555864145494b28fdb6e090a3f2 |
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"); |
| 16 | 16 | |
| 17 | 17 | // TODO audit these APIs with respect to Dir and absolute paths |
| 18 | 18 | |
| 19 | pub const symLink = os.symlink; | |
| 20 | pub const symLinkZ = os.symlinkZ; | |
| 21 | pub const symLinkC = @compileError("deprecated: renamed to symlinkZ"); | |
| 22 | 19 | pub const rename = os.rename; |
| 23 | 20 | pub const renameZ = os.renameZ; |
| 24 | 21 | pub 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) { |
| 69 | 66 | |
| 70 | 67 | /// TODO remove the allocator requirement from this API |
| 71 | 68 | pub 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, .{})) { | |
| 73 | 70 | return; |
| 74 | 71 | } else |err| switch (err) { |
| 75 | 72 | error.PathAlreadyExists => {}, |
| ... | ... | @@ -87,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 87 | 84 | try crypto.randomBytes(rand_buf[0..]); |
| 88 | 85 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); |
| 89 | 86 | |
| 90 | if (symLink(existing_path, tmp_path, .{})) { | |
| 87 | if (symLinkAbsolute(existing_path, tmp_path, .{})) { | |
| 91 | 88 | return rename(tmp_path, new_path); |
| 92 | 89 | } else |err| switch (err) { |
| 93 | 90 | error.PathAlreadyExists => continue, |
| ... | ... | @@ -1692,8 +1689,15 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 |
| 1692 | 1689 | return os.readlink(pathname, buffer); |
| 1693 | 1690 | } |
| 1694 | 1691 | |
| 1692 | /// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16 | |
| 1693 | /// encoded. | |
| 1694 | pub 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 | ||
| 1695 | 1699 | /// Same as `readLink`, except the path parameter is null-terminated. |
| 1696 | pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { | |
| 1700 | pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { | |
| 1697 | 1701 | assert(path.isAbsoluteZ(pathname_c)); |
| 1698 | 1702 | return os.readlinkZ(pathname_c, buffer); |
| 1699 | 1703 | } |
| ... | ... | @@ -1701,6 +1705,57 @@ pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ! |
| 1701 | 1705 | pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute"); |
| 1702 | 1706 | pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ"); |
| 1703 | 1707 | |
| 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. | |
| 1712 | pub 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`. | |
| 1721 | pub 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`. | |
| 1737 | pub 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`. | |
| 1745 | pub 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 | ||
| 1756 | pub const symLink = @compileError("deprecated: use symLinkAbsolute"); | |
| 1757 | pub const symLinkC = @compileError("deprecated: use symLinkAbsoluteC"); | |
| 1758 | ||
| 1704 | 1759 | pub const Walker = struct { |
| 1705 | 1760 | stack: std.ArrayList(StackItem), |
| 1706 | 1761 | name_buffer: std.ArrayList(u8), |
lib/std/fs/test.zig+44| ... | ... | @@ -10,6 +10,50 @@ const Dir = std.fs.Dir; |
| 10 | 10 | const File = std.fs.File; |
| 11 | 11 | const tmpDir = testing.tmpDir; |
| 12 | 12 | |
| 13 | test "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 | ||
| 51 | fn 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 | ||
| 13 | 57 | test "Dir.Iterator" { |
| 14 | 58 | var tmp_dir = tmpDir(.{ .iterate = true }); |
| 15 | 59 | defer tmp_dir.cleanup(); |
lib/std/os.zig+6-25| ... | ... | @@ -1520,14 +1520,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { |
| 1520 | 1520 | } |
| 1521 | 1521 | } |
| 1522 | 1522 | |
| 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. | |
| 1527 | pub const SymlinkFlags = struct { | |
| 1528 | is_directory: bool = false, | |
| 1529 | }; | |
| 1530 | ||
| 1531 | 1523 | pub const SymLinkError = error{ |
| 1532 | 1524 | /// In WASI, this error may occur when the file descriptor does |
| 1533 | 1525 | /// not hold the required rights to create a new symbolic link relative to it. |
| ... | ... | @@ -1550,37 +1542,26 @@ pub const SymLinkError = error{ |
| 1550 | 1542 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 1551 | 1543 | /// one; the latter case is known as a dangling link. |
| 1552 | 1544 | /// If `sym_link_path` exists, it will not be overwritten. |
| 1553 | /// See also `symlinkC` and `symlinkW`. | |
| 1554 | pub fn symlink(target_path: []const u8, sym_link_path: []const u8, flags: SymlinkFlags) SymLinkError!void { | |
| 1545 | /// See also `symlinkZ. | |
| 1546 | pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { | |
| 1555 | 1547 | if (builtin.os.tag == .wasi) { |
| 1556 | 1548 | @compileError("symlink is not supported in WASI; use symlinkat instead"); |
| 1557 | 1549 | } |
| 1558 | 1550 | 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"); | |
| 1562 | 1552 | } |
| 1563 | 1553 | const target_path_c = try toPosixPath(target_path); |
| 1564 | 1554 | 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); | |
| 1566 | 1556 | } |
| 1567 | 1557 | |
| 1568 | 1558 | pub const symlinkC = @compileError("deprecated: renamed to symlinkZ"); |
| 1569 | 1559 | |
| 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 }`. | |
| 1573 | pub 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 | ||
| 1577 | 1560 | /// This is the same as `symlink` except the parameters are null-terminated pointers. |
| 1578 | 1561 | /// See also `symlink`. |
| 1579 | pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8, flags: SymlinkFlags) SymLinkError!void { | |
| 1562 | pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { | |
| 1580 | 1563 | 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"); | |
| 1584 | 1565 | } |
| 1585 | 1566 | switch (errno(system.symlink(target_path, sym_link_path))) { |
| 1586 | 1567 | 0 => return, |
lib/std/os/test.zig+35-66| ... | ... | @@ -19,6 +19,41 @@ const tmpDir = std.testing.tmpDir; |
| 19 | 19 | const Dir = std.fs.Dir; |
| 20 | 20 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 21 | 21 | |
| 22 | test "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 | ||
| 43 | test "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 | ||
| 51 | fn 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 | ||
| 22 | 57 | test "fstatat" { |
| 23 | 58 | // enable when `fstat` and `fstatat` are implemented on Windows |
| 24 | 59 | if (builtin.os.tag == .windows) return error.SkipZigTest; |
| ... | ... | @@ -41,72 +76,6 @@ test "fstatat" { |
| 41 | 76 | expectEqual(stat, statat); |
| 42 | 77 | } |
| 43 | 78 | |
| 44 | test "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 | ||
| 104 | fn 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 | ||
| 110 | 79 | test "readlinkat" { |
| 111 | 80 | // enable when `readlinkat` and `symlinkat` are implemented on Windows |
| 112 | 81 | if (builtin.os.tag == .windows) return error.SkipZigTest; |
lib/std/os/windows.zig+2-2| ... | ... | @@ -609,8 +609,8 @@ pub fn CreateSymbolicLink( |
| 609 | 609 | target_path: []const u8, |
| 610 | 610 | is_directory: bool, |
| 611 | 611 | ) 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); | |
| 614 | 614 | return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, is_directory); |
| 615 | 615 | } |
| 616 | 616 |