1 #![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")]
2 
3 use std::{env, mem::MaybeUninit, process};
4 use test_programs::preview1::{assert_errno, open_scratch_directory};
5 
6 const CLOCK_ID: wasip1::Userdata = 0x0123_45678;
7 
poll_oneoff_impl( r#in: &[wasip1::Subscription], ) -> Result<Vec<wasip1::Event>, wasip1::Errno>8 unsafe fn poll_oneoff_impl(
9     r#in: &[wasip1::Subscription],
10 ) -> Result<Vec<wasip1::Event>, wasip1::Errno> {
11     let mut out: Vec<wasip1::Event> = Vec::new();
12     out.resize_with(r#in.len(), || {
13         MaybeUninit::<wasip1::Event>::zeroed().assume_init()
14     });
15     let size = wasip1::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())?;
16     out.truncate(size);
17     Ok(out)
18 }
19 
20 /// Repeatedly call `poll_oneoff` until all the subscriptions in `in` have
21 /// seen their events occur.
poll_oneoff_with_retry( r#in: &[wasip1::Subscription], ) -> Result<Vec<wasip1::Event>, wasip1::Errno>22 unsafe fn poll_oneoff_with_retry(
23     r#in: &[wasip1::Subscription],
24 ) -> Result<Vec<wasip1::Event>, wasip1::Errno> {
25     let mut subscriptions = r#in.to_vec();
26     let mut events = Vec::new();
27     while !subscriptions.is_empty() {
28         let mut out: Vec<wasip1::Event> = Vec::new();
29         out.resize_with(subscriptions.len(), || {
30             MaybeUninit::<wasip1::Event>::zeroed().assume_init()
31         });
32         let size = wasip1::poll_oneoff(
33             subscriptions.as_ptr(),
34             out.as_mut_ptr(),
35             subscriptions.len(),
36         )?;
37         out.truncate(size);
38 
39         // Append the events from this `poll` to the result.
40         events.extend_from_slice(&out);
41 
42         // Assuming userdata fields are unique, filter out any subscriptions
43         // whose event has occurred.
44         subscriptions.retain(|sub| !out.iter().any(|event| event.userdata == sub.userdata));
45     }
46     Ok(events)
47 }
48 
test_empty_poll()49 unsafe fn test_empty_poll() {
50     let r#in = [];
51     let mut out: Vec<wasip1::Event> = Vec::new();
52     assert_errno!(
53         wasip1::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
54             .expect_err("empty poll_oneoff should fail"),
55         wasip1::ERRNO_INVAL
56     );
57 }
58 
test_timeout()59 unsafe fn test_timeout() {
60     let timeout = 5_000_000u64; // 5 milliseconds
61     let clock = wasip1::SubscriptionClock {
62         id: wasip1::CLOCKID_MONOTONIC,
63         timeout,
64         precision: 0,
65         flags: 0,
66     };
67     let r#in = [wasip1::Subscription {
68         userdata: CLOCK_ID,
69         u: wasip1::SubscriptionU {
70             tag: wasip1::EVENTTYPE_CLOCK.raw(),
71             u: wasip1::SubscriptionUU { clock },
72         },
73     }];
74     let before = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
75     let out = poll_oneoff_impl(&r#in).unwrap();
76     let after = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
77     assert_eq!(out.len(), 1, "should return 1 event");
78     let event = &out[0];
79     assert_errno!(event.error, wasip1::ERRNO_SUCCESS);
80     assert_eq!(
81         event.type_,
82         wasip1::EVENTTYPE_CLOCK,
83         "the event.type should equal clock"
84     );
85     assert_eq!(
86         event.userdata, CLOCK_ID,
87         "the event.userdata should contain clock_id specified by the user"
88     );
89     assert!(
90         after - before >= timeout,
91         "poll_oneoff should sleep for the specified interval of {timeout}. before: {before}, after: {after}"
92     );
93 }
94 
95 // Like test_timeout, but uses `CLOCKID_REALTIME`, as WASI libc's sleep
96 // functions do.
test_sleep()97 unsafe fn test_sleep() {
98     let timeout = 5_000_000u64; // 5 milliseconds
99     let clock = wasip1::SubscriptionClock {
100         id: wasip1::CLOCKID_REALTIME,
101         timeout,
102         precision: 0,
103         flags: 0,
104     };
105     let r#in = [wasip1::Subscription {
106         userdata: CLOCK_ID,
107         u: wasip1::SubscriptionU {
108             tag: wasip1::EVENTTYPE_CLOCK.raw(),
109             u: wasip1::SubscriptionUU { clock },
110         },
111     }];
112     let before = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
113     let out = poll_oneoff_impl(&r#in).unwrap();
114     let after = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
115     assert_eq!(out.len(), 1, "should return 1 event");
116     let event = &out[0];
117     assert_errno!(event.error, wasip1::ERRNO_SUCCESS);
118     assert_eq!(
119         event.type_,
120         wasip1::EVENTTYPE_CLOCK,
121         "the event.type should equal clock"
122     );
123     assert_eq!(
124         event.userdata, CLOCK_ID,
125         "the event.userdata should contain clock_id specified by the user"
126     );
127     assert!(
128         after - before >= timeout,
129         "poll_oneoff should sleep for the specified interval of {timeout}. before: {before}, after: {after}"
130     );
131 }
132 
test_fd_readwrite( readable_fd: wasip1::Fd, writable_fd: wasip1::Fd, error_code: wasip1::Errno, )133 unsafe fn test_fd_readwrite(
134     readable_fd: wasip1::Fd,
135     writable_fd: wasip1::Fd,
136     error_code: wasip1::Errno,
137 ) {
138     let r#in = [
139         wasip1::Subscription {
140             userdata: 1,
141             u: wasip1::SubscriptionU {
142                 tag: wasip1::EVENTTYPE_FD_READ.raw(),
143                 u: wasip1::SubscriptionUU {
144                     fd_read: wasip1::SubscriptionFdReadwrite {
145                         file_descriptor: readable_fd,
146                     },
147                 },
148             },
149         },
150         wasip1::Subscription {
151             userdata: 2,
152             u: wasip1::SubscriptionU {
153                 tag: wasip1::EVENTTYPE_FD_WRITE.raw(),
154                 u: wasip1::SubscriptionUU {
155                     fd_write: wasip1::SubscriptionFdReadwrite {
156                         file_descriptor: writable_fd,
157                     },
158                 },
159             },
160         },
161     ];
162     let out = poll_oneoff_with_retry(&r#in).unwrap();
163     assert_eq!(out.len(), 2, "should return 2 events, got: {out:?}");
164 
165     let (read, write) = if out[0].userdata == 1 {
166         (&out[0], &out[1])
167     } else {
168         (&out[1], &out[0])
169     };
170     assert_eq!(
171         read.userdata, 1,
172         "the event.userdata should contain fd userdata specified by the user"
173     );
174     assert_errno!(read.error, error_code);
175     assert_eq!(
176         read.type_,
177         wasip1::EVENTTYPE_FD_READ,
178         "the event.type_ should equal FD_READ"
179     );
180     assert_eq!(
181         write.userdata, 2,
182         "the event.userdata should contain fd userdata specified by the user"
183     );
184     assert_errno!(write.error, error_code);
185     assert_eq!(
186         write.type_,
187         wasip1::EVENTTYPE_FD_WRITE,
188         "the event.type_ should equal FD_WRITE"
189     );
190 }
191 
test_fd_readwrite_valid_fd(dir_fd: wasip1::Fd)192 unsafe fn test_fd_readwrite_valid_fd(dir_fd: wasip1::Fd) {
193     // Create a file in the scratch directory.
194     let nonempty_file = wasip1::path_open(
195         dir_fd,
196         0,
197         "readable_file",
198         wasip1::OFLAGS_CREAT,
199         wasip1::RIGHTS_FD_WRITE,
200         0,
201         0,
202     )
203     .expect("create writable file");
204     // Write to file
205     let contents = &[1u8];
206     let ciovec = wasip1::Ciovec {
207         buf: contents.as_ptr() as *const _,
208         buf_len: contents.len(),
209     };
210     wasip1::fd_write(nonempty_file, &[ciovec]).expect("write");
211     wasip1::fd_close(nonempty_file).expect("close");
212 
213     // Now open the file for reading
214     let readable_fd =
215         wasip1::path_open(dir_fd, 0, "readable_file", 0, wasip1::RIGHTS_FD_READ, 0, 0)
216             .expect("opening a readable file");
217 
218     assert!(
219         readable_fd > libc::STDERR_FILENO as wasip1::Fd,
220         "file descriptor range check",
221     );
222     // Create a file in the scratch directory.
223     let writable_fd = wasip1::path_open(
224         dir_fd,
225         0,
226         "writable_file",
227         wasip1::OFLAGS_CREAT,
228         wasip1::RIGHTS_FD_WRITE,
229         0,
230         0,
231     )
232     .expect("opening a writable file");
233     assert!(
234         writable_fd > libc::STDERR_FILENO as wasip1::Fd,
235         "file descriptor range check",
236     );
237 
238     test_fd_readwrite(readable_fd, writable_fd, wasip1::ERRNO_SUCCESS);
239 
240     wasip1::fd_close(readable_fd).expect("closing readable_file");
241     wasip1::fd_close(writable_fd).expect("closing writable_file");
242     wasip1::path_unlink_file(dir_fd, "readable_file").expect("removing readable_file");
243     wasip1::path_unlink_file(dir_fd, "writable_file").expect("removing writable_file");
244 }
245 
test_fd_readwrite_invalid_fd()246 unsafe fn test_fd_readwrite_invalid_fd() {
247     let fd_readwrite = wasip1::SubscriptionFdReadwrite {
248         file_descriptor: wasip1::Fd::max_value(),
249     };
250     let r#in = [
251         wasip1::Subscription {
252             userdata: 1,
253             u: wasip1::SubscriptionU {
254                 tag: wasip1::EVENTTYPE_FD_READ.raw(),
255                 u: wasip1::SubscriptionUU {
256                     fd_read: fd_readwrite,
257                 },
258             },
259         },
260         wasip1::Subscription {
261             userdata: 2,
262             u: wasip1::SubscriptionU {
263                 tag: wasip1::EVENTTYPE_FD_WRITE.raw(),
264                 u: wasip1::SubscriptionUU {
265                     fd_write: fd_readwrite,
266                 },
267             },
268         },
269     ];
270     let err = poll_oneoff_impl(&r#in).unwrap_err();
271     assert_eq!(err, wasip1::ERRNO_BADF)
272 }
273 
test_poll_oneoff(dir_fd: wasip1::Fd)274 unsafe fn test_poll_oneoff(dir_fd: wasip1::Fd) {
275     test_timeout();
276     test_sleep();
277     test_empty_poll();
278     test_fd_readwrite_valid_fd(dir_fd);
279     test_fd_readwrite_invalid_fd();
280 }
main()281 fn main() {
282     let mut args = env::args();
283     let prog = args.next().unwrap();
284     let arg = if let Some(arg) = args.next() {
285         arg
286     } else {
287         eprintln!("usage: {prog} <scratch directory>");
288         process::exit(1);
289     };
290 
291     // Open scratch directory
292     let dir_fd = match open_scratch_directory(&arg) {
293         Ok(dir_fd) => dir_fd,
294         Err(err) => {
295             eprintln!("{err}");
296             process::exit(1)
297         }
298     };
299 
300     // Run the tests.
301     unsafe { test_poll_oneoff(dir_fd) }
302 }
303