master
 1// Test relative paths through POSIX APIS.  These tests have to change the cwd, so
 2// they shouldn't be Zig unit tests.
 3
 4const std = @import("std");
 5const builtin = @import("builtin");
 6
 7pub fn main() !void {
 8    if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir
 9
10    var Allocator = std.heap.DebugAllocator(.{}){};
11    const a = Allocator.allocator();
12    defer std.debug.assert(Allocator.deinit() == .ok);
13
14    var tmp = std.testing.tmpDir(.{});
15    defer tmp.cleanup();
16
17    // Want to test relative paths, so cd into the tmpdir for these tests
18    try tmp.dir.setAsCwd();
19
20    try test_symlink(a, tmp);
21    try test_link(tmp);
22}
23
24fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void {
25    const target_name = "symlink-target";
26    const symlink_name = "symlinker";
27
28    // Create the target file
29    try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" });
30
31    if (builtin.target.os.tag == .windows) {
32        const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name);
33        const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name);
34        defer a.free(wtarget_name);
35        defer a.free(wsymlink_name);
36
37        std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) {
38            // Symlink requires admin privileges on windows, so this test can legitimately fail.
39            error.AccessDenied => return,
40            else => return err,
41        };
42    } else {
43        try std.posix.symlink(target_name, symlink_name);
44    }
45
46    var buffer: [std.fs.max_path_bytes]u8 = undefined;
47    const given = try std.posix.readlink(symlink_name, buffer[0..]);
48    try std.testing.expectEqualStrings(target_name, given);
49}
50
51fn test_link(tmp: std.testing.TmpDir) !void {
52    switch (builtin.target.os.tag) {
53        .linux, .illumos => {},
54        else => return,
55    }
56
57    if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.target.os.tag == .linux and !builtin.link_libc) {
58        return; // No `fstat()`.
59    }
60
61    if (builtin.cpu.arch.isMIPS64()) {
62        return; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
63    }
64
65    const target_name = "link-target";
66    const link_name = "newlink";
67
68    try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
69
70    // Test 1: create the relative link from inside tmp
71    try std.posix.link(target_name, link_name);
72
73    // Verify
74    const efd = try tmp.dir.openFile(target_name, .{});
75    defer efd.close();
76
77    const nfd = try tmp.dir.openFile(link_name, .{});
78    defer nfd.close();
79
80    {
81        const estat = try std.posix.fstat(efd.handle);
82        const nstat = try std.posix.fstat(nfd.handle);
83        try std.testing.expectEqual(estat.ino, nstat.ino);
84        try std.testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
85    }
86
87    // Test 2: Remove the link and see the stats update
88    try std.posix.unlink(link_name);
89
90    {
91        const estat = try std.posix.fstat(efd.handle);
92        try std.testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
93    }
94}