1 #![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")]
2 
3 use std::{env, process};
4 use test_programs::preview1::{assert_errno, config, create_file, open_scratch_directory};
5 
6 // These are all macro-rules so the panic line number shows us where
7 // things went wrong.
8 
9 macro_rules! filestats_assert_eq {
10     ($left:ident, $right:ident) => {
11         assert_eq!($left.dev, $right.dev, "dev should be equal");
12         assert_eq!($left.ino, $right.ino, "ino should be equal");
13         assert_eq!($left.atim, $right.atim, "atim should be equal");
14         assert_eq!($left.ctim, $right.ctim, "ctim should be equal");
15         assert_eq!($left.mtim, $right.mtim, "mtim should be equal");
16         assert_eq!($left.size, $right.size, "size should be equal");
17         assert_eq!($left.nlink, $right.nlink, "nlink should be equal");
18         assert_eq!($left.filetype, $right.filetype, "filetype should be equal");
19     };
20 }
21 
22 macro_rules! fdstats_assert_eq {
23     ($left:ident, $right:ident) => {
24         assert_eq!($left.fs_flags, $right.fs_flags, "fs_flags should be equal");
25         assert_eq!(
26             $left.fs_filetype, $right.fs_filetype,
27             "fs_filetype should be equal"
28         );
29         assert_eq!(
30             $left.fs_rights_base, $right.fs_rights_base,
31             "fs_rights_base should be equal"
32         );
33         assert_eq!(
34             $left.fs_rights_inheriting, $right.fs_rights_inheriting,
35             "fs_rights_inheriting should be equal"
36         );
37     };
38 }
39 
40 macro_rules! check_rights {
41     ($orig_fd:ident, $link_fd:ident) => {
42         let orig_filestat =
43             wasip1::fd_filestat_get($orig_fd).expect("reading filestat of the source");
44         let link_filestat =
45             wasip1::fd_filestat_get($link_fd).expect("reading filestat of the link");
46         filestats_assert_eq!(orig_filestat, link_filestat);
47 
48         // Compare Fdstats
49         let orig_fdstat = wasip1::fd_fdstat_get($orig_fd).expect("reading fdstat of the source");
50         let link_fdstat = wasip1::fd_fdstat_get($link_fd).expect("reading fdstat of the link");
51         fdstats_assert_eq!(orig_fdstat, link_fdstat);
52     };
53 }
54 // Extra calls of fd_close are needed for Windows, which will not remove
55 // the directory until all handles are closed.
test_path_link(dir_fd: wasip1::Fd)56 unsafe fn test_path_link(dir_fd: wasip1::Fd) {
57     // Create a file
58     let create_fd =
59         wasip1::path_open(dir_fd, 0, "file", wasip1::OFLAGS_CREAT, 0, 0, 0).expect("create file");
60     wasip1::fd_close(create_fd).unwrap();
61 
62     // Open a fresh descriptor to the file. We won't have a write right that was implied by OFLAGS_CREAT
63     // above.
64     let file_fd = wasip1::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("open file");
65 
66     // Create a link in the same directory and compare rights
67     wasip1::path_link(dir_fd, 0, "file", dir_fd, "link")
68         .expect("creating a link in the same directory");
69 
70     let link_fd = wasip1::path_open(dir_fd, 0, "link", 0, 0, 0, 0).expect("open link");
71 
72     check_rights!(file_fd, link_fd);
73     wasip1::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
74     wasip1::path_unlink_file(dir_fd, "link").expect("removing a link");
75 
76     // Create a link in a different directory and compare rights
77     wasip1::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
78     let subdir_fd = wasip1::path_open(dir_fd, 0, "subdir", wasip1::OFLAGS_DIRECTORY, 0, 0, 0)
79         .expect("open subdir directory");
80     wasip1::path_link(dir_fd, 0, "file", subdir_fd, "link")
81         .expect("creating a link in subdirectory");
82     let link_fd = wasip1::path_open(subdir_fd, 0, "link", 0, 0, 0, 0).expect("open link in subdir");
83     check_rights!(file_fd, link_fd);
84     wasip1::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows
85     wasip1::path_unlink_file(subdir_fd, "link").expect("removing a link");
86     wasip1::fd_close(subdir_fd).expect("Closing subdir_fd"); // needed for Windows
87     wasip1::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory");
88 
89     // Create a link to a path that already exists
90     create_file(dir_fd, "link");
91 
92     assert_errno!(
93         wasip1::path_link(dir_fd, 0, "file", dir_fd, "link")
94             .expect_err("creating a link to existing path should fail"),
95         wasip1::ERRNO_EXIST
96     );
97     wasip1::path_unlink_file(dir_fd, "link").expect("removing a file");
98 
99     // Create a link to itself
100     assert_errno!(
101         wasip1::path_link(dir_fd, 0, "file", dir_fd, "file")
102             .expect_err("creating a link to itself should fail"),
103         wasip1::ERRNO_EXIST
104     );
105 
106     // Create a link where target is a directory
107     wasip1::path_create_directory(dir_fd, "link").expect("creating a dir");
108 
109     assert_errno!(
110         wasip1::path_link(dir_fd, 0, "file", dir_fd, "link")
111             .expect_err("creating a link where target is a directory should fail"),
112         wasip1::ERRNO_EXIST
113     );
114     wasip1::path_remove_directory(dir_fd, "link").expect("removing a dir");
115 
116     // Create a link to a directory
117     wasip1::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory");
118     let subdir_fd = wasip1::path_open(dir_fd, 0, "subdir", wasip1::OFLAGS_DIRECTORY, 0, 0, 0)
119         .expect("open new descriptor to subdir");
120 
121     assert_errno!(
122         wasip1::path_link(dir_fd, 0, "subdir", dir_fd, "link")
123             .expect_err("creating a link to a directory should fail"),
124         wasip1::ERRNO_PERM,
125         wasip1::ERRNO_ACCES
126     );
127     wasip1::fd_close(subdir_fd).expect("close subdir before deleting it");
128     wasip1::path_remove_directory(dir_fd, "subdir").expect("removing a subdirectory");
129 
130     // Create a link to a file with trailing slash
131     assert_errno!(
132         wasip1::path_link(dir_fd, 0, "file", dir_fd, "link/")
133             .expect_err("creating a link to a file with trailing slash should fail"),
134         wasip1::ERRNO_NOENT
135     );
136 
137     if config().support_dangling_filesystem() {
138         // Create a link to a dangling symlink
139         wasip1::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
140 
141         // This should succeed, because we're not following symlinks
142         wasip1::path_link(dir_fd, 0, "symlink", dir_fd, "link")
143             .expect("creating a link to a dangling symlink should succeed");
144         wasip1::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
145         wasip1::path_unlink_file(dir_fd, "link").expect("removing a hardlink");
146 
147         // Create a link to a symlink loop
148         wasip1::path_symlink("symlink", dir_fd, "symlink").expect("creating a symlink loop");
149 
150         wasip1::path_link(dir_fd, 0, "symlink", dir_fd, "link")
151             .expect("creating a link to a symlink loop should succeed");
152         wasip1::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
153         wasip1::path_unlink_file(dir_fd, "link").expect("removing a hardlink");
154 
155         // Create a link where target is a dangling symlink
156         wasip1::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
157 
158         assert_errno!(
159             wasip1::path_link(dir_fd, 0, "file", dir_fd, "symlink")
160                 .expect_err("creating a link where target is a dangling symlink"),
161             wasip1::ERRNO_EXIST
162         );
163         wasip1::path_unlink_file(dir_fd, "symlink").expect("removing a symlink");
164 
165         // Create a link where target is a dangling symlink following symlinks
166         wasip1::path_symlink("target", dir_fd, "symlink").expect("creating a dangling symlink");
167 
168         // Symlink following with path_link is rejected
169         assert_errno!(
170             wasip1::path_link(
171                 dir_fd,
172                 wasip1::LOOKUPFLAGS_SYMLINK_FOLLOW,
173                 "symlink",
174                 dir_fd,
175                 "link",
176             )
177             .expect_err("calling path_link with LOOKUPFLAGS_SYMLINK_FOLLOW should fail"),
178             wasip1::ERRNO_INVAL
179         );
180 
181         // Clean up.
182         wasip1::path_unlink_file(dir_fd, "file").expect("removing a file");
183     }
184 }
185 
main()186 fn main() {
187     let mut args = env::args();
188     let prog = args.next().unwrap();
189     let arg = if let Some(arg) = args.next() {
190         arg
191     } else {
192         eprintln!("usage: {prog} <scratch directory>");
193         process::exit(1);
194     };
195 
196     // Open scratch directory
197     let dir_fd = match open_scratch_directory(&arg) {
198         Ok(dir_fd) => dir_fd,
199         Err(err) => {
200             eprintln!("{err}");
201             process::exit(1)
202         }
203     };
204 
205     // Run the tests.
206     unsafe { test_path_link(dir_fd) }
207 }
208