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