1 // The proxy world has no filesystem which most of this file is concerned with,
2 // so disable many warnings to avoid having to contort code too much for the
3 // proxy world.
4 #![cfg_attr(
5 feature = "proxy",
6 expect(
7 unused_mut,
8 unused_variables,
9 dead_code,
10 unused_imports,
11 unreachable_code,
12 reason = "stripped down in proxy build",
13 )
14 )]
15
16 use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock};
17 use crate::bindings::wasi::io::poll;
18 use crate::bindings::wasi::io::streams;
19 use crate::bindings::wasi::random::random;
20 use core::cell::OnceCell;
21 use core::cell::{Cell, RefCell, RefMut, UnsafeCell};
22 use core::cmp::min;
23 use core::ffi::c_void;
24 use core::hint::black_box;
25 use core::mem::{self, ManuallyDrop, MaybeUninit, align_of, forget, size_of};
26 use core::num::NonZeroUsize;
27 use core::ops::{Deref, DerefMut};
28 use core::ptr::{self, null_mut};
29 use core::slice;
30 use poll::Pollable;
31 use wasip1::*;
32
33 #[cfg(not(feature = "proxy"))]
34 use crate::bindings::wasi::filesystem::types as filesystem;
35
36 #[cfg(any(
37 all(feature = "command", feature = "reactor"),
38 all(feature = "reactor", feature = "proxy"),
39 all(feature = "command", feature = "proxy"),
40 ))]
41 compile_error!(
42 "only one of the `command`, `reactor` or `proxy` features may be selected at a time"
43 );
44
45 #[macro_use]
46 mod macros;
47
48 mod descriptors;
49 use crate::descriptors::{Descriptor, Descriptors, StreamType, Streams};
50
51 pub mod bindings {
52 #[cfg(feature = "command")]
53 wit_bindgen_rust_macro::generate!({
54 path: "../wasi/src/p2/wit",
55 world: "wasi:cli/command",
56 raw_strings,
57 runtime_path: "crate::bindings::wit_bindgen_rt_shim",
58 // Automatically generated bindings for these functions will allocate
59 // Vecs, which in turn pulls in the panic machinery from std, which
60 // creates vtables that end up in the wasm elem section, which we
61 // can't support in these special core-wasm adapters.
62 // Instead, we manually define the bindings for these functions in
63 // terms of raw pointers.
64 skip: [
65 "run",
66 "get-environment",
67 "poll",
68 "[method]outgoing-datagram-stream.send",
69 ],
70 generate_all,
71 disable_custom_section_link_helpers: true,
72 });
73
74 #[cfg(feature = "reactor")]
75 wit_bindgen_rust_macro::generate!({
76 path: "../wasi/src/p2/wit",
77 world: "wasi:cli/imports",
78 raw_strings,
79 runtime_path: "crate::bindings::wit_bindgen_rt_shim",
80 // Automatically generated bindings for these functions will allocate
81 // Vecs, which in turn pulls in the panic machinery from std, which
82 // creates vtables that end up in the wasm elem section, which we
83 // can't support in these special core-wasm adapters.
84 // Instead, we manually define the bindings for these functions in
85 // terms of raw pointers.
86 skip: [
87 "get-environment",
88 "poll",
89 "[method]outgoing-datagram-stream.send",
90 ],
91 generate_all,
92 disable_custom_section_link_helpers: true,
93 });
94
95 #[cfg(feature = "proxy")]
96 wit_bindgen_rust_macro::generate!({
97 path: "../wasi-http/wit",
98 inline: r#"
99 package wasmtime:adapter;
100
101 world adapter {
102 import wasi:clocks/[email protected];
103 import wasi:clocks/[email protected];
104 import wasi:random/[email protected];
105 import wasi:cli/[email protected];
106 import wasi:cli/[email protected];
107 import wasi:cli/[email protected];
108 }
109 "#,
110 world: "wasmtime:adapter/adapter",
111 raw_strings,
112 runtime_path: "crate::bindings::wit_bindgen_rt_shim",
113 skip: ["poll", "[method]outgoing-datagram-stream.send"],
114 generate_all,
115 disable_custom_section_link_helpers: true,
116 });
117
118 pub mod wit_bindgen_rt_shim {
119 pub use bitflags;
120
maybe_link_cabi_realloc()121 pub fn maybe_link_cabi_realloc() {}
122 }
123 }
124
125 #[unsafe(export_name = "wasi:cli/run@0.2.6#run")]
126 #[cfg(feature = "command")]
run() -> u32127 pub extern "C" fn run() -> u32 {
128 #[link(wasm_import_module = "__main_module__")]
129 unsafe extern "C" {
130 safe fn _start();
131 }
132 _start();
133 0
134 }
135
136 #[cfg(feature = "proxy")]
137 macro_rules! cfg_filesystem_available {
138 ($($t:tt)*) => {
139 wasip1::ERRNO_NOTSUP
140 };
141 }
142 #[cfg(not(feature = "proxy"))]
143 macro_rules! cfg_filesystem_available {
144 ($($t:tt)*) => ($($t)*);
145 }
146
147 // The unwrap/expect methods in std pull panic when they fail, which pulls
148 // in unwinding machinery that we can't use in the adapter. Instead, use this
149 // extension trait to get postfixed upwrap on Option and Result.
150 trait TrappingUnwrap<T> {
trapping_unwrap(self) -> T151 fn trapping_unwrap(self) -> T;
152 }
153
154 impl<T> TrappingUnwrap<T> for Option<T> {
trapping_unwrap(self) -> T155 fn trapping_unwrap(self) -> T {
156 match self {
157 Some(t) => t,
158 None => unreachable!(),
159 }
160 }
161 }
162
163 impl<T, E> TrappingUnwrap<T> for Result<T, E> {
trapping_unwrap(self) -> T164 fn trapping_unwrap(self) -> T {
165 match self {
166 Ok(t) => t,
167 Err(_) => unreachable!(),
168 }
169 }
170 }
171
172 /// Allocate a file descriptor which will generate an `ERRNO_BADF` if passed to
173 /// any WASI Preview 1 function implemented by this adapter.
174 ///
175 /// This is intended for use by `wasi-libc` during its incremental transition
176 /// from WASI Preview 1 to Preview 2. It will use this function to reserve
177 /// descriptors for its own use, valid only for use with libc functions.
178 #[unsafe(no_mangle)]
adapter_open_badfd(fd: &mut u32) -> Errno179 pub unsafe extern "C" fn adapter_open_badfd(fd: &mut u32) -> Errno {
180 State::with(|state| {
181 *fd = state.descriptors_mut().open(Descriptor::Bad)?;
182 Ok(())
183 })
184 }
185
186 /// Close a descriptor previously opened using `adapter_open_badfd`.
187 #[unsafe(no_mangle)]
adapter_close_badfd(fd: u32) -> Errno188 pub unsafe extern "C" fn adapter_close_badfd(fd: u32) -> Errno {
189 State::with(|state| state.descriptors_mut().close(fd))
190 }
191
192 #[unsafe(no_mangle)]
reset_adapter_state()193 pub unsafe extern "C" fn reset_adapter_state() {
194 unsafe {
195 let state = get_state_ptr();
196 if !state.is_null() {
197 State::init(state)
198 }
199 }
200 }
201
202 #[unsafe(no_mangle)]
cabi_import_realloc( old_ptr: *mut u8, old_size: usize, align: usize, new_size: usize, ) -> *mut u8203 pub unsafe extern "C" fn cabi_import_realloc(
204 old_ptr: *mut u8,
205 old_size: usize,
206 align: usize,
207 new_size: usize,
208 ) -> *mut u8 {
209 let mut ptr = null_mut::<u8>();
210 State::with(|state| {
211 let mut alloc = state.import_alloc.replace(ImportAlloc::None);
212 ptr = unsafe { alloc.alloc(old_ptr, old_size, align, new_size) };
213 state.import_alloc.set(alloc);
214 Ok(())
215 });
216 ptr
217 }
218
219 /// Different ways that calling imports can allocate memory.
220 ///
221 /// This behavior is used to customize the behavior of `cabi_import_realloc`.
222 /// This is configured within `State` whenever an import is called that may
223 /// invoke `cabi_import_realloc`.
224 ///
225 /// The general idea behind these various behaviors of import allocation is
226 /// that we're limited for space in the adapter here to 1 page of memory but
227 /// that may not fit the total sum of arguments, environment variables, and
228 /// preopens. WASIp1 APIs all provide user-provided buffers as well for these
229 /// allocations so we technically don't need to store them in the adapter
230 /// itself. Instead what this does is it tries to copy strings and such directly
231 /// into their destination pointers where possible.
232 ///
233 /// The types requiring allocation in the WASIp2 APIs that the WASIp1 APIs call
234 /// are relatively simple. They all look like `list<T>` where `T` only has
235 /// indirections in the form of `String`. This means that we can apply a
236 /// "clever" hack where the alignment of an allocation is used to disambiguate
237 /// whether we're allocating a string or allocating the `list<T>` allocation.
238 /// This signal with alignment means that we can configure where everything
239 /// goes.
240 ///
241 /// For example consider `args_sizes_get` and `args_get`. When `args_sizes_get`
242 /// is called the `list<T>` allocation happens first with alignment 4. This
243 /// must be valid for the rest of the strings since the canonical ABI will fill
244 /// it in, so it's allocated from `State::temporary_data`. Next all other
245 /// arguments will be `string` type with alignment 1. These are also allocated
246 /// within `State::temporary_data` but they're "allocated on top of one
247 /// another" meaning that internal allocator state isn't updated after a string
248 /// is allocated. While these strings are discarded their sizes are all summed
249 /// up and returned from `args_sizes_get`.
250 ///
251 /// Later though when `args_get` is called it's a similar allocation strategy
252 /// except that strings are instead redirected to the allocation provided to
253 /// `args_get` itself. This enables strings to be directly allocated into their
254 /// destinations.
255 ///
256 /// Overall this means that we're limiting the maximum number of arguments plus
257 /// the size of the largest string, but otherwise we're not limiting the total
258 /// size of all arguments (or env vars, preopens, etc).
259 enum ImportAlloc {
260 /// A single allocation from the provided `BumpAlloc` is supported. After
261 /// the single allocation is performed all future allocations will fail.
262 OneAlloc(BumpAlloc),
263
264 /// An allocator intended for `list<T>` where `T` has string types but no
265 /// other indirections. String allocations are discarded but counted for
266 /// size.
267 ///
268 /// This allocator will use `alloc` for all allocations. Any string-related
269 /// allocation, detected via an alignment of 1, is considered "temporary"
270 /// and doesn't affect the internal state of the allocator. The allocation
271 /// is assumed to not be used after the import call returns.
272 ///
273 /// The total sum of all string sizes, however, is accumulated within
274 /// `strings_size`.
275 CountAndDiscardStrings {
276 strings_size: usize,
277 alloc: BumpAlloc,
278 },
279
280 /// An allocator intended for `list<T>` where `T` has string types but no
281 /// other indirections. String allocations go into `strings` and the
282 /// `list<..>` allocation goes into `pointers`.
283 ///
284 /// This allocator enables placing strings within a caller-supplied buffer
285 /// configured with `strings`. The `pointers` allocation is
286 /// `State::temporary_data`.
287 ///
288 /// This will additionally over-allocate strings with one extra byte to be
289 /// nul-terminated or `=`-terminated in the case of env vars.
290 SeparateStringsAndPointers {
291 strings: BumpAlloc,
292 pointers: BumpAlloc,
293 },
294
295 /// An allocator specifically for getting the nth string allocation used
296 /// for preopens.
297 ///
298 /// This will allocate everything into `alloc`. All strings other than the
299 /// `nth` string, however, will be discarded (the allocator's state is reset
300 /// after the allocation). This means that the pointer returned for the
301 /// `nth` string will be retained in `alloc` while all others will be
302 /// discarded.
303 ///
304 /// The `cur` count starts at 0 and counts up per-string.
305 GetPreopenPath {
306 cur: u32,
307 nth: u32,
308 alloc: BumpAlloc,
309 },
310
311 /// No import allocator is configured and if an allocation happens then
312 /// this will abort.
313 None,
314 }
315
316 impl ImportAlloc {
317 /// To be used by cabi_import_realloc only!
alloc( &mut self, old_ptr: *mut u8, old_size: usize, align: usize, size: usize, ) -> *mut u8318 unsafe fn alloc(
319 &mut self,
320 old_ptr: *mut u8,
321 old_size: usize,
322 align: usize,
323 size: usize,
324 ) -> *mut u8 {
325 // This is ... a hack. This is a hack in subtle ways that is quite
326 // brittle and may break over time. There's only one case for the
327 // `realloc`-like-behavior in the canonical ABI and that's when the host
328 // is transferring a string to the guest and the host has a different
329 // string encoding. For example JS uses utf-16 (ish) and Rust/WASIp1 use
330 // utf-8. That means that when this adapter is used with a JS host
331 // realloc behavior may be triggered in which case `old_ptr` may not be
332 // null.
333 //
334 // In the case that `old_ptr` may not be null we come to the first
335 // brittle assumption: it's assumed that this is shrinking memory. In
336 // the canonical ABI overlarge allocations are made originally and then
337 // shrunk afterwards once encoding is finished. This means that the
338 // first allocation is too big and the `realloc` call is shrinking
339 // memory. This assumption may be violated in the future if the
340 // canonical ABI is updated to handle growing strings in addition to
341 // shrinking strings. (e.g. starting with an assume-ascii path and then
342 // falling back to an ok-needs-more-space path for larger unicode code
343 // points).
344 //
345 // This comes to the second brittle assumption, nothing happens here
346 // when a shrink happens. This is brittle for each of the cases below,
347 // enumerated here:
348 //
349 // * For `OneAlloc` this isn't the end of the world. That's already
350 // asserting that only a single string is allocated. Returning the
351 // original pointer keeps the pointer the same and the host will keep
352 // track of the appropriate length. In this case the final length is
353 // read out of the return value of a function, meaning that everything
354 // actually works out here.
355 //
356 // * For `CountAndDiscardStrings` we're relying on the fact that
357 // this is only used for `environ_sizes_get` and `args_sizes_get`. In
358 // both situations we're actually going to return an "overlarge"
359 // return value for the size of arguments and return values. By
360 // assuming memory shrinks after the first allocation the return value
361 // of `environ_sizes_get` and `args_sizes_get` will be the overlong
362 // approximation for all strings. That means that the final exact size
363 // won't be what's returned. This ends up being ok because technically
364 // nothing about WASI says that those blocks have to be exact-sized.
365 // In our case we're (ab)using that to force the caller to make an
366 // overlarge return area which we'll allocate into. All-in-all we
367 // don't track the shrink request and ignore the size.
368 //
369 // * For `SeparateStringsAndPointers` it's similar to the previous case
370 // except the weird part is that the caller is providing the
371 // argument/env space buffer to write into. It's over-large because of
372 // the case of `CountAndDiscardStrings` above, but we'll exploit that
373 // here and end up having space between all the arguments. Technically
374 // WASI doesn't say all the strings have to be adjacent, so this
375 // should work out in practice.
376 //
377 // * Finally for `GetPreopenPath` this works out only insofar that the
378 // `State::temporary_alloc` space is used to store the path. The
379 // WASI-provided buffer is precisely sized, not overly large, meaning
380 // that we're forced to copy from `temporary_alloc` into the
381 // destination buffer for this WASI call.
382 //
383 // Basically it's a case-by-case basis here that enables ignoring
384 // shrinking return calls here. Not robust.
385 if !old_ptr.is_null() {
386 assert!(old_size > size);
387 assert_eq!(align, 1);
388 return old_ptr;
389 }
390 match self {
391 ImportAlloc::OneAlloc(alloc) => unsafe {
392 let ret = alloc.alloc(align, size);
393 *self = ImportAlloc::None;
394 ret
395 },
396 ImportAlloc::SeparateStringsAndPointers { strings, pointers } => unsafe {
397 if align == 1 {
398 strings.alloc(align, size + 1)
399 } else {
400 pointers.alloc(align, size)
401 }
402 },
403 ImportAlloc::CountAndDiscardStrings {
404 strings_size,
405 alloc,
406 } => unsafe {
407 if align == 1 {
408 *strings_size += size;
409 alloc.clone().alloc(align, size)
410 } else {
411 alloc.alloc(align, size)
412 }
413 },
414 ImportAlloc::GetPreopenPath { cur, nth, alloc } => unsafe {
415 if align == 1 {
416 let real_alloc = *nth == *cur;
417 *cur += 1;
418 if real_alloc {
419 alloc.alloc(align, size)
420 } else {
421 alloc.clone().alloc(align, size)
422 }
423 } else {
424 alloc.alloc(align, size)
425 }
426 },
427 ImportAlloc::None => {
428 unreachable!("no allocator configured")
429 }
430 }
431 }
432 }
433
434 /// Helper type to manage allocations from a `base`/`len` combo.
435 ///
436 /// This isn't really used much in an arena-style per se but it's used in
437 /// combination with the `ImportAlloc` flavors above.
438 #[derive(Clone)]
439 struct BumpAlloc {
440 base: *mut u8,
441 len: usize,
442 }
443
444 impl BumpAlloc {
alloc(&mut self, align: usize, size: usize) -> *mut u8445 unsafe fn alloc(&mut self, align: usize, size: usize) -> *mut u8 {
446 unsafe {
447 self.align_to(align);
448 }
449 if size > self.len {
450 unreachable!("allocation size is too large")
451 }
452 self.len -= size;
453 let ret = self.base;
454 self.base = unsafe { ret.add(size) };
455 ret
456 }
457
align_to(&mut self, align: usize)458 unsafe fn align_to(&mut self, align: usize) {
459 if !align.is_power_of_two() {
460 unreachable!("invalid alignment");
461 }
462 let align_offset = self.base.align_offset(align);
463 if align_offset > self.len {
464 unreachable!("failed to allocate")
465 }
466 self.len -= align_offset;
467 self.base = unsafe { self.base.add(align_offset) };
468 }
469 }
470
471 #[cfg(not(feature = "proxy"))]
472 #[link(wasm_import_module = "wasi:cli/environment@0.2.6")]
473 unsafe extern "C" {
474 #[link_name = "get-arguments"]
wasi_cli_get_arguments(rval: *mut WasmStrList)475 fn wasi_cli_get_arguments(rval: *mut WasmStrList);
476 #[link_name = "get-environment"]
wasi_cli_get_environment(rval: *mut StrTupleList)477 fn wasi_cli_get_environment(rval: *mut StrTupleList);
478 }
479
480 /// Read command-line argument data.
481 /// The size of the array should match that returned by `args_sizes_get`
482 #[unsafe(no_mangle)]
args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno483 pub unsafe extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno {
484 State::with(|state| {
485 #[cfg(not(feature = "proxy"))]
486 {
487 let alloc = ImportAlloc::SeparateStringsAndPointers {
488 strings: BumpAlloc {
489 base: argv_buf,
490 len: usize::MAX,
491 },
492 pointers: unsafe { state.temporary_alloc() },
493 };
494 let (list, _) = state.with_import_alloc(alloc, || unsafe {
495 let mut list = WasmStrList {
496 base: std::ptr::null(),
497 len: 0,
498 };
499 wasi_cli_get_arguments(&mut list);
500 list
501 });
502
503 // Fill in `argv` by walking over the returned `list` and then
504 // additionally apply the nul-termination for each argument itself
505 // here.
506 unsafe {
507 for i in 0..list.len {
508 let s = list.base.add(i).read();
509 *argv.add(i) = s.ptr.cast_mut();
510 *s.ptr.add(s.len).cast_mut() = 0;
511 }
512 }
513 }
514 Ok(())
515 })
516 }
517
518 /// Return command-line argument data sizes.
519 #[unsafe(no_mangle)]
args_sizes_get(argc: &mut Size, argv_buf_size: &mut Size) -> Errno520 pub unsafe extern "C" fn args_sizes_get(argc: &mut Size, argv_buf_size: &mut Size) -> Errno {
521 State::with(|state| {
522 #[cfg(feature = "proxy")]
523 {
524 *argc = 0;
525 *argv_buf_size = 0;
526 }
527 #[cfg(not(feature = "proxy"))]
528 {
529 let alloc = ImportAlloc::CountAndDiscardStrings {
530 strings_size: 0,
531 alloc: unsafe { state.temporary_alloc() },
532 };
533 let (len, alloc) = state.with_import_alloc(alloc, || unsafe {
534 let mut list = WasmStrList {
535 base: std::ptr::null(),
536 len: 0,
537 };
538 wasi_cli_get_arguments(&mut list);
539 list.len
540 });
541 match alloc {
542 ImportAlloc::CountAndDiscardStrings {
543 strings_size,
544 alloc: _,
545 } => {
546 *argc = len;
547 // add in bytes needed for a 0-byte at the end of each
548 // argument.
549 *argv_buf_size = strings_size + len;
550 }
551 _ => unreachable!(),
552 }
553 }
554 Ok(())
555 })
556 }
557
558 /// Read environment variable data.
559 /// The sizes of the buffers should match that returned by `environ_sizes_get`.
560 #[unsafe(no_mangle)]
environ_get(environ: *mut *const u8, environ_buf: *mut u8) -> Errno561 pub unsafe extern "C" fn environ_get(environ: *mut *const u8, environ_buf: *mut u8) -> Errno {
562 State::with(|state| {
563 #[cfg(not(feature = "proxy"))]
564 {
565 let alloc = ImportAlloc::SeparateStringsAndPointers {
566 strings: BumpAlloc {
567 base: environ_buf,
568 len: usize::MAX,
569 },
570 pointers: unsafe { state.temporary_alloc() },
571 };
572 let (list, _) = state.with_import_alloc(alloc, || unsafe {
573 let mut list = StrTupleList {
574 base: std::ptr::null(),
575 len: 0,
576 };
577 wasi_cli_get_environment(&mut list);
578 list
579 });
580
581 // Fill in `environ` by walking over the returned `list`. Strings
582 // are guaranteed to be allocated next to each other with one
583 // extra byte at the end, so also insert the `=` between keys and
584 // the `\0` at the end of the env var.
585 unsafe {
586 for i in 0..list.len {
587 let s = list.base.add(i).read();
588 *environ.add(i) = s.key.ptr;
589 *s.key.ptr.add(s.key.len).cast_mut() = b'=';
590 *s.value.ptr.add(s.value.len).cast_mut() = 0;
591 }
592 }
593 }
594
595 Ok(())
596 })
597 }
598
599 /// Return environment variable data sizes.
600 #[unsafe(no_mangle)]
environ_sizes_get( environc: &mut Size, environ_buf_size: &mut Size, ) -> Errno601 pub unsafe extern "C" fn environ_sizes_get(
602 environc: &mut Size,
603 environ_buf_size: &mut Size,
604 ) -> Errno {
605 if !matches!(
606 unsafe { get_allocation_state() },
607 AllocationState::StackAllocated | AllocationState::StateAllocated
608 ) {
609 *environc = 0;
610 *environ_buf_size = 0;
611 return ERRNO_SUCCESS;
612 }
613
614 State::with(|state| {
615 #[cfg(feature = "proxy")]
616 {
617 *environc = 0;
618 *environ_buf_size = 0;
619 }
620 #[cfg(not(feature = "proxy"))]
621 {
622 let alloc = ImportAlloc::CountAndDiscardStrings {
623 strings_size: 0,
624 alloc: unsafe { state.temporary_alloc() },
625 };
626 let (len, alloc) = state.with_import_alloc(alloc, || unsafe {
627 let mut list = StrTupleList {
628 base: std::ptr::null(),
629 len: 0,
630 };
631 wasi_cli_get_environment(&mut list);
632 list.len
633 });
634 match alloc {
635 ImportAlloc::CountAndDiscardStrings {
636 strings_size,
637 alloc: _,
638 } => {
639 *environc = len;
640 // Account for `=` between keys and a 0-byte at the end of
641 // each key.
642 *environ_buf_size = strings_size + 2 * len;
643 }
644 _ => unreachable!(),
645 }
646 }
647
648 Ok(())
649 })
650 }
651
652 /// Return the resolution of a clock.
653 /// Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks,
654 /// return `errno::inval`.
655 /// Note: This is similar to `clock_getres` in POSIX.
656 #[unsafe(no_mangle)]
clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errno657 pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errno {
658 match id {
659 CLOCKID_MONOTONIC => {
660 *resolution = monotonic_clock::resolution();
661 ERRNO_SUCCESS
662 }
663 CLOCKID_REALTIME => {
664 let res = wall_clock::resolution();
665 *resolution = match Timestamp::from(res.seconds)
666 .checked_mul(1_000_000_000)
667 .and_then(|ns| ns.checked_add(res.nanoseconds.into()))
668 {
669 Some(ns) => ns,
670 None => return ERRNO_OVERFLOW,
671 };
672 ERRNO_SUCCESS
673 }
674 _ => ERRNO_BADF,
675 }
676 }
677
678 /// Return the time value of a clock.
679 /// Note: This is similar to `clock_gettime` in POSIX.
680 #[unsafe(no_mangle)]
clock_time_get( id: Clockid, _precision: Timestamp, time: &mut Timestamp, ) -> Errno681 pub unsafe extern "C" fn clock_time_get(
682 id: Clockid,
683 _precision: Timestamp,
684 time: &mut Timestamp,
685 ) -> Errno {
686 match id {
687 CLOCKID_MONOTONIC => {
688 *time = monotonic_clock::now();
689 ERRNO_SUCCESS
690 }
691 CLOCKID_REALTIME => {
692 let res = wall_clock::now();
693 *time = match Timestamp::from(res.seconds)
694 .checked_mul(1_000_000_000)
695 .and_then(|ns| ns.checked_add(res.nanoseconds.into()))
696 {
697 Some(ns) => ns,
698 None => return ERRNO_OVERFLOW,
699 };
700 ERRNO_SUCCESS
701 }
702 _ => ERRNO_BADF,
703 }
704 }
705
706 /// Provide file advisory information on a file descriptor.
707 /// Note: This is similar to `posix_fadvise` in POSIX.
708 #[unsafe(no_mangle)]
fd_advise( fd: Fd, offset: Filesize, len: Filesize, advice: Advice, ) -> Errno709 pub unsafe extern "C" fn fd_advise(
710 fd: Fd,
711 offset: Filesize,
712 len: Filesize,
713 advice: Advice,
714 ) -> Errno {
715 cfg_filesystem_available! {
716 let advice = match advice {
717 ADVICE_NORMAL => filesystem::Advice::Normal,
718 ADVICE_SEQUENTIAL => filesystem::Advice::Sequential,
719 ADVICE_RANDOM => filesystem::Advice::Random,
720 ADVICE_WILLNEED => filesystem::Advice::WillNeed,
721 ADVICE_DONTNEED => filesystem::Advice::DontNeed,
722 ADVICE_NOREUSE => filesystem::Advice::NoReuse,
723 _ => return ERRNO_INVAL,
724 };
725 State::with(|state| {
726 let ds = state.descriptors();
727 let file = ds.get_seekable_file(fd)?;
728 file.fd.advise(offset, len, advice)?;
729 Ok(())
730 })
731 }
732 }
733
734 /// Force the allocation of space in a file.
735 /// Note: This is similar to `posix_fallocate` in POSIX.
736 #[unsafe(no_mangle)]
fd_allocate(fd: Fd, _offset: Filesize, _len: Filesize) -> Errno737 pub unsafe extern "C" fn fd_allocate(fd: Fd, _offset: Filesize, _len: Filesize) -> Errno {
738 cfg_filesystem_available! {
739 State::with(|state| {
740 let ds = state.descriptors();
741 // For not-files, fail with BADF
742 ds.get_file(fd)?;
743 // For all files, fail with NOTSUP, because this call does not exist in preview 2.
744 Err(wasip1::ERRNO_NOTSUP)
745 })
746 }
747 }
748
749 /// Close a file descriptor.
750 /// Note: This is similar to `close` in POSIX.
751 #[unsafe(no_mangle)]
fd_close(fd: Fd) -> Errno752 pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno {
753 State::with(|state| {
754 if let Descriptor::Bad = state.descriptors().get(fd)? {
755 return Err(wasip1::ERRNO_BADF);
756 }
757
758 // If there's a dirent cache entry for this file descriptor then drop
759 // it since the descriptor is being closed and future calls to
760 // `fd_readdir` should return an error.
761 #[cfg(not(feature = "proxy"))]
762 if fd == state.dirent_cache.for_fd.get() {
763 drop(state.dirent_cache.stream.replace(None));
764 }
765
766 state.descriptors_mut().close(fd)?;
767 Ok(())
768 })
769 }
770
771 /// Synchronize the data of a file to disk.
772 /// Note: This is similar to `fdatasync` in POSIX.
773 #[unsafe(no_mangle)]
fd_datasync(fd: Fd) -> Errno774 pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno {
775 cfg_filesystem_available! {
776 State::with(|state| {
777 let ds = state.descriptors();
778 let file = ds.get_file(fd)?;
779 file.fd.sync_data()?;
780 Ok(())
781 })
782 }
783 }
784
785 /// Get the attributes of a file descriptor.
786 /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.
787 #[unsafe(no_mangle)]
fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno788 pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno {
789 cfg_filesystem_available! {
790 State::with(|state| {
791 let ds = state.descriptors();
792 match ds.get(fd)? {
793 Descriptor::Streams(Streams {
794 type_: StreamType::File(file),
795 ..
796 }) => {
797 let flags = file.fd.get_flags()?;
798 let type_ = file.fd.get_type()?;
799 match type_ {
800 filesystem::DescriptorType::Directory => {
801 // Hard-coded set of rights expected by many userlands:
802 let fs_rights_base = wasip1::RIGHTS_PATH_CREATE_DIRECTORY
803 | wasip1::RIGHTS_PATH_CREATE_FILE
804 | wasip1::RIGHTS_PATH_LINK_SOURCE
805 | wasip1::RIGHTS_PATH_LINK_TARGET
806 | wasip1::RIGHTS_PATH_OPEN
807 | wasip1::RIGHTS_FD_READDIR
808 | wasip1::RIGHTS_PATH_READLINK
809 | wasip1::RIGHTS_PATH_RENAME_SOURCE
810 | wasip1::RIGHTS_PATH_RENAME_TARGET
811 | wasip1::RIGHTS_PATH_SYMLINK
812 | wasip1::RIGHTS_PATH_REMOVE_DIRECTORY
813 | wasip1::RIGHTS_PATH_UNLINK_FILE
814 | wasip1::RIGHTS_PATH_FILESTAT_GET
815 | wasip1::RIGHTS_PATH_FILESTAT_SET_TIMES
816 | wasip1::RIGHTS_FD_FILESTAT_GET
817 | wasip1::RIGHTS_FD_FILESTAT_SET_TIMES;
818
819 let fs_rights_inheriting = fs_rights_base
820 | wasip1::RIGHTS_FD_DATASYNC
821 | wasip1::RIGHTS_FD_READ
822 | wasip1::RIGHTS_FD_SEEK
823 | wasip1::RIGHTS_FD_FDSTAT_SET_FLAGS
824 | wasip1::RIGHTS_FD_SYNC
825 | wasip1::RIGHTS_FD_TELL
826 | wasip1::RIGHTS_FD_WRITE
827 | wasip1::RIGHTS_FD_ADVISE
828 | wasip1::RIGHTS_FD_ALLOCATE
829 | wasip1::RIGHTS_FD_FILESTAT_GET
830 | wasip1::RIGHTS_FD_FILESTAT_SET_SIZE
831 | wasip1::RIGHTS_FD_FILESTAT_SET_TIMES
832 | wasip1::RIGHTS_POLL_FD_READWRITE;
833
834 unsafe {
835 stat.write(Fdstat {
836 fs_filetype: wasip1::FILETYPE_DIRECTORY,
837 fs_flags: 0,
838 fs_rights_base,
839 fs_rights_inheriting,
840 });
841 }
842 Ok(())
843 }
844 _ => {
845 let fs_filetype = type_.into();
846
847 let mut fs_flags = 0;
848 let mut fs_rights_base = !0;
849 if !flags.contains(filesystem::DescriptorFlags::READ) {
850 fs_rights_base &= !RIGHTS_FD_READ;
851 fs_rights_base &= !RIGHTS_FD_READDIR;
852 }
853 if !flags.contains(filesystem::DescriptorFlags::WRITE) {
854 fs_rights_base &= !RIGHTS_FD_WRITE;
855 }
856 if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
857 fs_flags |= FDFLAGS_DSYNC;
858 }
859 if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
860 fs_flags |= FDFLAGS_RSYNC;
861 }
862 if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
863 fs_flags |= FDFLAGS_SYNC;
864 }
865 if file.append {
866 fs_flags |= FDFLAGS_APPEND;
867 }
868 if matches!(file.blocking_mode, BlockingMode::NonBlocking) {
869 fs_flags |= FDFLAGS_NONBLOCK;
870 }
871 let fs_rights_inheriting = fs_rights_base;
872
873 unsafe {
874 stat.write(Fdstat {
875 fs_filetype,
876 fs_flags,
877 fs_rights_base,
878 fs_rights_inheriting,
879 });
880 }
881 Ok(())
882 }
883 }
884 }
885 Descriptor::Streams(Streams {
886 input,
887 output,
888 type_: StreamType::Stdio(stdio),
889 }) => {
890 let fs_flags = 0;
891 let mut fs_rights_base = 0;
892 if input.get().is_some() {
893 fs_rights_base |= RIGHTS_FD_READ;
894 }
895 if output.get().is_some() {
896 fs_rights_base |= RIGHTS_FD_WRITE;
897 }
898 let fs_rights_inheriting = fs_rights_base;
899 unsafe {
900 stat.write(Fdstat {
901 fs_filetype: stdio.filetype(),
902 fs_flags,
903 fs_rights_base,
904 fs_rights_inheriting,
905 });
906 }
907 Ok(())
908 }
909 Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),
910 }
911 })
912 }
913 }
914
915 /// Adjust the flags associated with a file descriptor.
916 /// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.
917 #[unsafe(no_mangle)]
fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno918 pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno {
919 // Only support changing the NONBLOCK or APPEND flags.
920 if flags & !(FDFLAGS_NONBLOCK | FDFLAGS_APPEND) != 0 {
921 return wasip1::ERRNO_INVAL;
922 }
923
924 cfg_filesystem_available! {
925 State::with(|state| {
926 let mut ds = state.descriptors_mut();
927 let file = match ds.get_mut(fd)? {
928 Descriptor::Streams(Streams {
929 type_: StreamType::File(file),
930 ..
931 }) if !file.is_dir() => file,
932 _ => Err(wasip1::ERRNO_BADF)?,
933 };
934 file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND;
935 file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK {
936 BlockingMode::NonBlocking
937 } else {
938 BlockingMode::Blocking
939 };
940 Ok(())
941 })
942 }
943 }
944
945 /// Does not do anything if `fd` corresponds to a valid descriptor and returns [`wasip1::ERRNO_BADF`] otherwise.
946 #[unsafe(no_mangle)]
fd_fdstat_set_rights( fd: Fd, _fs_rights_base: Rights, _fs_rights_inheriting: Rights, ) -> Errno947 pub unsafe extern "C" fn fd_fdstat_set_rights(
948 fd: Fd,
949 _fs_rights_base: Rights,
950 _fs_rights_inheriting: Rights,
951 ) -> Errno {
952 State::with(|state| {
953 let ds = state.descriptors();
954 match ds.get(fd)? {
955 Descriptor::Streams(..) => Err(wasip1::ERRNO_NOTSUP),
956 Descriptor::Closed(..) | Descriptor::Bad => Err(wasip1::ERRNO_BADF),
957 }
958 })
959 }
960
961 /// Return the attributes of an open file.
962 #[unsafe(no_mangle)]
fd_filestat_get(fd: Fd, buf: &mut Filestat) -> Errno963 pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: &mut Filestat) -> Errno {
964 cfg_filesystem_available! {
965 State::with(|state| {
966 let ds = state.descriptors();
967 match ds.get(fd)? {
968 Descriptor::Streams(Streams {
969 type_: StreamType::File(file),
970 ..
971 }) => {
972 let stat = file.fd.stat()?;
973 let metadata_hash = file.fd.metadata_hash()?;
974 let filetype = stat.type_.into();
975 *buf = Filestat {
976 dev: 1,
977 ino: metadata_hash.lower,
978 filetype,
979 nlink: stat.link_count,
980 size: stat.size,
981 atim: datetime_to_timestamp(stat.data_access_timestamp),
982 mtim: datetime_to_timestamp(stat.data_modification_timestamp),
983 ctim: datetime_to_timestamp(stat.status_change_timestamp),
984 };
985 Ok(())
986 }
987 // Stdio is all zero fields, except for filetype character device
988 Descriptor::Streams(Streams {
989 type_: StreamType::Stdio(stdio),
990 ..
991 }) => {
992 *buf = Filestat {
993 dev: 0,
994 ino: 0,
995 filetype: stdio.filetype(),
996 nlink: 0,
997 size: 0,
998 atim: 0,
999 mtim: 0,
1000 ctim: 0,
1001 };
1002 Ok(())
1003 }
1004 _ => Err(wasip1::ERRNO_BADF),
1005 }
1006 })
1007 }
1008 }
1009
1010 /// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
1011 /// Note: This is similar to `ftruncate` in POSIX.
1012 #[unsafe(no_mangle)]
fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno1013 pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno {
1014 cfg_filesystem_available! {
1015 State::with(|state| {
1016 let ds = state.descriptors();
1017 let file = ds.get_file(fd)?;
1018 file.fd.set_size(size)?;
1019 Ok(())
1020 })
1021 }
1022 }
1023
1024 #[cfg(not(feature = "proxy"))]
systimespec(set: bool, ts: Timestamp, now: bool) -> Result<filesystem::NewTimestamp, Errno>1025 fn systimespec(set: bool, ts: Timestamp, now: bool) -> Result<filesystem::NewTimestamp, Errno> {
1026 if set && now {
1027 Err(wasip1::ERRNO_INVAL)
1028 } else if set {
1029 Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime {
1030 seconds: ts / 1_000_000_000,
1031 nanoseconds: (ts % 1_000_000_000) as _,
1032 }))
1033 } else if now {
1034 Ok(filesystem::NewTimestamp::Now)
1035 } else {
1036 Ok(filesystem::NewTimestamp::NoChange)
1037 }
1038 }
1039
1040 /// Adjust the timestamps of an open file or directory.
1041 /// Note: This is similar to `futimens` in POSIX.
1042 #[unsafe(no_mangle)]
fd_filestat_set_times( fd: Fd, atim: Timestamp, mtim: Timestamp, fst_flags: Fstflags, ) -> Errno1043 pub unsafe extern "C" fn fd_filestat_set_times(
1044 fd: Fd,
1045 atim: Timestamp,
1046 mtim: Timestamp,
1047 fst_flags: Fstflags,
1048 ) -> Errno {
1049 cfg_filesystem_available! {
1050 State::with(|state| {
1051 let atim = systimespec(
1052 fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,
1053 atim,
1054 fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,
1055 )?;
1056 let mtim = systimespec(
1057 fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,
1058 mtim,
1059 fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,
1060 )?;
1061 let ds = state.descriptors();
1062 let file = ds.get_file(fd)?;
1063 file.fd.set_times(atim, mtim)?;
1064 Ok(())
1065 })
1066 }
1067 }
1068
1069 /// Read from a file descriptor, without using and updating the file descriptor's offset.
1070 /// Note: This is similar to `preadv` in POSIX.
1071 #[unsafe(no_mangle)]
fd_pread( fd: Fd, mut iovs_ptr: *const Iovec, mut iovs_len: usize, offset: Filesize, nread: &mut Size, ) -> Errno1072 pub unsafe extern "C" fn fd_pread(
1073 fd: Fd,
1074 mut iovs_ptr: *const Iovec,
1075 mut iovs_len: usize,
1076 offset: Filesize,
1077 nread: &mut Size,
1078 ) -> Errno {
1079 cfg_filesystem_available! {
1080 let (ptr, len) = unsafe {
1081 // Skip leading non-empty buffers.
1082 while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {
1083 iovs_ptr = iovs_ptr.add(1);
1084 iovs_len -= 1;
1085 }
1086 if iovs_len == 0 {
1087 *nread = 0;
1088 return ERRNO_SUCCESS;
1089 }
1090 ((*iovs_ptr).buf, (*iovs_ptr).buf_len)
1091 };
1092
1093 State::with(|state| {
1094
1095 let ds = state.descriptors();
1096 let file = ds.get_file(fd)?;
1097 let (data, end) = state
1098 .with_one_import_alloc(ptr, len, || file.fd.read(len as u64, offset))?;
1099 assert_eq!(data.as_ptr(), ptr);
1100 assert!(data.len() <= len);
1101
1102 let len = data.len();
1103 forget(data);
1104 if !end && len == 0 {
1105 Err(ERRNO_INTR)
1106 } else {
1107 *nread = len;
1108 Ok(())
1109 }
1110 })
1111 }
1112 }
1113
1114 /// Return a description of the given preopened file descriptor.
1115 #[unsafe(no_mangle)]
fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno1116 pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno {
1117 if !matches!(
1118 unsafe { get_allocation_state() },
1119 AllocationState::StackAllocated | AllocationState::StateAllocated
1120 ) {
1121 return ERRNO_BADF;
1122 }
1123
1124 // For the proxy adapter don't return `ERRNO_NOTSUP` through below, instead
1125 // always return `ERRNO_BADF` which is the indicator that prestats aren't
1126 // available.
1127 if cfg!(feature = "proxy") {
1128 return ERRNO_BADF;
1129 }
1130
1131 cfg_filesystem_available! {
1132 State::with(|state| {
1133 let ds = state.descriptors();
1134 match ds.get(fd)? {
1135 Descriptor::Streams(Streams {
1136 type_: StreamType::File(File {
1137 preopen_name_len: Some(len),
1138 ..
1139 }),
1140 ..
1141 }) => {
1142 unsafe {
1143 buf.write(Prestat {
1144 tag: 0,
1145 u: PrestatU {
1146 dir: PrestatDir {
1147 pr_name_len: len.get(),
1148 },
1149 },
1150 });
1151 }
1152
1153 Ok(())
1154 }
1155 _ => Err(ERRNO_BADF),
1156 }
1157 })
1158 }
1159 }
1160
1161 /// Return a description of the given preopened file descriptor.
1162 #[unsafe(no_mangle)]
fd_prestat_dir_name(fd: Fd, path: *mut u8, path_max_len: Size) -> Errno1163 pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_max_len: Size) -> Errno {
1164 cfg_filesystem_available! {
1165 State::with(|state| {
1166 let ds = state.descriptors();
1167 let preopen_len = match ds.get(fd)? {
1168 Descriptor::Streams(Streams {
1169 type_: StreamType::File(File {
1170 preopen_name_len: Some(len),
1171 ..
1172 }),
1173 ..
1174 }) => len.get(),
1175 _ => return Err(ERRNO_BADF),
1176 };
1177 if preopen_len > path_max_len {
1178 return Err(ERRNO_NAMETOOLONG)
1179 }
1180
1181 unsafe {
1182 ds.get_preopen_path(state, fd, path, path_max_len);
1183 }
1184 Ok(())
1185 })
1186 }
1187 }
1188
1189 /// Write to a file descriptor, without using and updating the file descriptor's offset.
1190 /// Note: This is similar to `pwritev` in POSIX.
1191 #[unsafe(no_mangle)]
fd_pwrite( fd: Fd, mut iovs_ptr: *const Ciovec, mut iovs_len: usize, offset: Filesize, nwritten: &mut Size, ) -> Errno1192 pub unsafe extern "C" fn fd_pwrite(
1193 fd: Fd,
1194 mut iovs_ptr: *const Ciovec,
1195 mut iovs_len: usize,
1196 offset: Filesize,
1197 nwritten: &mut Size,
1198 ) -> Errno {
1199 cfg_filesystem_available! {
1200 let bytes = unsafe {
1201 // Skip leading non-empty buffers.
1202 while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {
1203 iovs_ptr = iovs_ptr.add(1);
1204 iovs_len -= 1;
1205 }
1206 if iovs_len == 0 {
1207 *nwritten = 0;
1208 return ERRNO_SUCCESS;
1209 }
1210
1211 let ptr = (*iovs_ptr).buf;
1212 let len = (*iovs_ptr).buf_len;
1213 slice::from_raw_parts(ptr, len)
1214 };
1215
1216 State::with(|state| {
1217 let ds = state.descriptors();
1218 let file = ds.get_seekable_file(fd)?;
1219 let bytes = if file.append {
1220 match file.fd.append_via_stream()?.blocking_write_and_flush(bytes) {
1221 Ok(()) => bytes.len(),
1222 Err(streams::StreamError::Closed) => 0,
1223 Err(streams::StreamError::LastOperationFailed(e)) => {
1224 return Err(stream_error_to_errno(e))
1225 }
1226 }
1227 } else {
1228 file.fd.write(bytes, offset)? as usize
1229 };
1230 *nwritten = bytes;
1231 Ok(())
1232 })
1233 }
1234 }
1235
1236 /// Read from a file descriptor.
1237 /// Note: This is similar to `readv` in POSIX.
1238 #[unsafe(no_mangle)]
fd_read( fd: Fd, mut iovs_ptr: *const Iovec, mut iovs_len: usize, nread: &mut Size, ) -> Errno1239 pub unsafe extern "C" fn fd_read(
1240 fd: Fd,
1241 mut iovs_ptr: *const Iovec,
1242 mut iovs_len: usize,
1243 nread: &mut Size,
1244 ) -> Errno {
1245 let (ptr, len) = unsafe {
1246 // Skip leading non-empty buffers.
1247 while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {
1248 iovs_ptr = iovs_ptr.add(1);
1249 iovs_len -= 1;
1250 }
1251 if iovs_len == 0 {
1252 *nread = 0;
1253 return ERRNO_SUCCESS;
1254 }
1255
1256 ((*iovs_ptr).buf, (*iovs_ptr).buf_len)
1257 };
1258
1259 State::with(|state| {
1260 let ds = state.descriptors();
1261 match ds.get(fd)? {
1262 Descriptor::Streams(streams) => {
1263 #[cfg(not(feature = "proxy"))]
1264 let blocking_mode = if let StreamType::File(file) = &streams.type_ {
1265 file.blocking_mode
1266 } else {
1267 BlockingMode::Blocking
1268 };
1269 #[cfg(feature = "proxy")]
1270 let blocking_mode = BlockingMode::Blocking;
1271
1272 let read_len = u64::try_from(len).trapping_unwrap();
1273 let wasi_stream = streams.get_read_stream()?;
1274 let data = match state
1275 .with_one_import_alloc(ptr, len, || blocking_mode.read(wasi_stream, read_len))
1276 {
1277 Ok(data) => data,
1278 Err(streams::StreamError::Closed) => {
1279 *nread = 0;
1280 return Ok(());
1281 }
1282 Err(streams::StreamError::LastOperationFailed(e)) => {
1283 Err(stream_error_to_errno(e))?
1284 }
1285 };
1286
1287 assert_eq!(data.as_ptr(), ptr);
1288 assert!(data.len() <= len);
1289
1290 // If this is a file, keep the current-position pointer up to date.
1291 #[cfg(not(feature = "proxy"))]
1292 if let StreamType::File(file) = &streams.type_ {
1293 file.position
1294 .set(file.position.get() + data.len() as filesystem::Filesize);
1295 }
1296
1297 let len = data.len();
1298 *nread = len;
1299 forget(data);
1300 Ok(())
1301 }
1302 Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),
1303 }
1304 })
1305 }
1306
stream_error_to_errno(err: streams::Error) -> Errno1307 fn stream_error_to_errno(err: streams::Error) -> Errno {
1308 #[cfg(feature = "proxy")]
1309 return ERRNO_IO;
1310 #[cfg(not(feature = "proxy"))]
1311 match filesystem::filesystem_error_code(&err) {
1312 Some(code) => code.into(),
1313 None => ERRNO_IO,
1314 }
1315 }
1316
1317 /// Read directory entries from a directory.
1318 /// When successful, the contents of the output buffer consist of a sequence of
1319 /// directory entries. Each directory entry consists of a `dirent` object,
1320 /// followed by `dirent::d_namlen` bytes holding the name of the directory
1321 /// entry.
1322 /// This function fills the output buffer as much as possible, potentially
1323 /// truncating the last directory entry. This allows the caller to grow its
1324 /// read buffer size in case it's too small to fit a single large directory
1325 /// entry, or skip the oversized directory entry.
1326 #[unsafe(no_mangle)]
1327 #[cfg(feature = "proxy")]
fd_readdir( fd: Fd, buf: *mut u8, buf_len: Size, cookie: Dircookie, bufused: *mut Size, ) -> Errno1328 pub unsafe extern "C" fn fd_readdir(
1329 fd: Fd,
1330 buf: *mut u8,
1331 buf_len: Size,
1332 cookie: Dircookie,
1333 bufused: *mut Size,
1334 ) -> Errno {
1335 wasip1::ERRNO_NOTSUP
1336 }
1337
1338 #[unsafe(no_mangle)]
1339 #[cfg(not(feature = "proxy"))]
fd_readdir( fd: Fd, buf: *mut u8, buf_len: Size, cookie: Dircookie, bufused: &mut Size, ) -> Errno1340 pub unsafe extern "C" fn fd_readdir(
1341 fd: Fd,
1342 buf: *mut u8,
1343 buf_len: Size,
1344 cookie: Dircookie,
1345 bufused: &mut Size,
1346 ) -> Errno {
1347 let mut buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };
1348 return State::with(|state| {
1349 // First determine if there's an entry in the dirent cache to use. This
1350 // is done to optimize the use case where a large directory is being
1351 // used with a fixed-sized buffer to avoid re-invoking the `readdir`
1352 // function and continuing to use the same iterator.
1353 //
1354 // This is a bit tricky since the requested state in this function call
1355 // must match the prior state of the dirent stream, if any, so that's
1356 // all validated here as well.
1357 //
1358 // Note that for the duration of this function the `cookie` specifier is
1359 // the `n`th iteration of the `readdir` stream return value.
1360 let prev_stream = state.dirent_cache.stream.replace(None);
1361 let stream =
1362 if state.dirent_cache.for_fd.get() == fd && state.dirent_cache.cookie.get() == cookie {
1363 prev_stream
1364 } else {
1365 None
1366 };
1367
1368 // Compute the inode of `.` so that the iterator can produce an entry
1369 // for it.
1370 let ds = state.descriptors();
1371 let dir = ds.get_dir(fd)?;
1372
1373 let mut iter;
1374 match stream {
1375 // All our checks passed and a dirent cache was available with a
1376 // prior stream. Construct an iterator which will yield its first
1377 // entry from cache and is additionally resuming at the `cookie`
1378 // specified.
1379 Some(stream) => {
1380 iter = DirectoryEntryIterator {
1381 stream,
1382 state,
1383 cookie,
1384 use_cache: true,
1385 dir_descriptor: &dir.fd,
1386 }
1387 }
1388
1389 // Either a dirent stream wasn't previously available, a different
1390 // cookie was requested, or a brand new directory is now being read.
1391 // In these situations fall back to resuming reading the directory
1392 // from scratch, and the `cookie` value indicates how many items
1393 // need skipping.
1394 None => {
1395 iter = DirectoryEntryIterator {
1396 state,
1397 cookie: wasip1::DIRCOOKIE_START,
1398 use_cache: false,
1399 stream: DirectoryEntryStream(dir.fd.read_directory()?),
1400 dir_descriptor: &dir.fd,
1401 };
1402
1403 // Skip to the entry that is requested by the `cookie`
1404 // parameter.
1405 for _ in wasip1::DIRCOOKIE_START..cookie {
1406 match iter.next() {
1407 Some(Ok(_)) => {}
1408 Some(Err(e)) => return Err(e),
1409 None => {
1410 *bufused = 0;
1411 return Ok(());
1412 }
1413 }
1414 }
1415 }
1416 };
1417
1418 while buf.len() > 0 {
1419 let (dirent, name) = match iter.next() {
1420 Some(Ok(pair)) => pair,
1421 Some(Err(e)) => return Err(e),
1422 None => break,
1423 };
1424
1425 // Copy a `dirent` describing this entry into the destination `buf`,
1426 // truncating it if it doesn't fit entirely.
1427 let bytes = unsafe {
1428 slice::from_raw_parts(
1429 (&dirent as *const wasip1::Dirent).cast::<u8>(),
1430 size_of::<Dirent>(),
1431 )
1432 };
1433 let dirent_bytes_to_copy = buf.len().min(bytes.len());
1434 unsafe {
1435 ptr::copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr(), dirent_bytes_to_copy);
1436 }
1437 buf = &mut buf[dirent_bytes_to_copy..];
1438
1439 // Copy the name bytes into the output `buf`, truncating it if it
1440 // doesn't fit.
1441 //
1442 // Note that this might be a 0-byte copy if the `dirent` was
1443 // truncated or fit entirely into the destination.
1444 let name_bytes_to_copy = buf.len().min(name.len());
1445 unsafe {
1446 ptr::copy_nonoverlapping(
1447 name.as_ptr().cast(),
1448 buf.as_mut_ptr(),
1449 name_bytes_to_copy,
1450 );
1451 }
1452
1453 buf = &mut buf[name_bytes_to_copy..];
1454
1455 // If the buffer is empty then that means the value may be
1456 // truncated, so save the state of the iterator in our dirent cache
1457 // and return.
1458 //
1459 // Note that `cookie - 1` is stored here since `iter.cookie` stores
1460 // the address of the next item, and we're rewinding one item since
1461 // the current item is truncated and will want to resume from that
1462 // in the future.
1463 //
1464 // Additionally note that this caching step is skipped if the name
1465 // to store doesn't actually fit in the dirent cache's path storage.
1466 // In that case there's not much we can do and let the next call to
1467 // `fd_readdir` start from scratch.
1468 if buf.len() == 0 && name.len() <= DIRENT_CACHE {
1469 let DirectoryEntryIterator { stream, cookie, .. } = iter;
1470 state.dirent_cache.stream.set(Some(stream));
1471 state.dirent_cache.for_fd.set(fd);
1472 state.dirent_cache.cookie.set(cookie - 1);
1473 state.dirent_cache.cached_dirent.set(dirent);
1474 unsafe {
1475 ptr::copy(
1476 name.as_ptr().cast::<u8>(),
1477 (*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8,
1478 name.len(),
1479 );
1480 }
1481 break;
1482 }
1483 }
1484
1485 *bufused = buf_len - buf.len();
1486 Ok(())
1487 });
1488
1489 struct DirectoryEntryIterator<'a> {
1490 state: &'a State,
1491 use_cache: bool,
1492 cookie: Dircookie,
1493 stream: DirectoryEntryStream,
1494 dir_descriptor: &'a filesystem::Descriptor,
1495 }
1496
1497 impl<'a> Iterator for DirectoryEntryIterator<'a> {
1498 // Note the usage of `UnsafeCell<u8>` here to indicate that the data can
1499 // alias the storage within `state`.
1500 type Item = Result<(wasip1::Dirent, &'a [UnsafeCell<u8>]), Errno>;
1501
1502 fn next(&mut self) -> Option<Self::Item> {
1503 let current_cookie = self.cookie;
1504
1505 self.cookie += 1;
1506
1507 // Preview1 programs expect to see `.` and `..` in the traversal, but
1508 // Preview2 excludes them, so re-add them.
1509 match current_cookie {
1510 0 => {
1511 let metadata_hash = match self.dir_descriptor.metadata_hash() {
1512 Ok(h) => h,
1513 Err(e) => return Some(Err(e.into())),
1514 };
1515 let dirent = wasip1::Dirent {
1516 d_next: self.cookie,
1517 d_ino: metadata_hash.lower,
1518 d_type: wasip1::FILETYPE_DIRECTORY,
1519 d_namlen: 1,
1520 };
1521 return Some(Ok((dirent, &self.state.dotdot[..1])));
1522 }
1523 1 => {
1524 let dirent = wasip1::Dirent {
1525 d_next: self.cookie,
1526 d_ino: 0,
1527 d_type: wasip1::FILETYPE_DIRECTORY,
1528 d_namlen: 2,
1529 };
1530 return Some(Ok((dirent, &self.state.dotdot[..])));
1531 }
1532 _ => {}
1533 }
1534
1535 if self.use_cache {
1536 self.use_cache = false;
1537 return Some(unsafe {
1538 let dirent = self.state.dirent_cache.cached_dirent.as_ptr().read();
1539 let ptr = (*(*self.state.dirent_cache.path_data.get()).as_ptr())
1540 .as_ptr()
1541 .cast();
1542 let buffer = slice::from_raw_parts(ptr, dirent.d_namlen as usize);
1543 Ok((dirent, buffer))
1544 });
1545 }
1546 let entry = self
1547 .state
1548 .with_one_temporary_alloc(|| self.stream.0.read_directory_entry());
1549 let entry = match entry {
1550 Ok(Some(entry)) => entry,
1551 Ok(None) => return None,
1552 Err(e) => return Some(Err(e.into())),
1553 };
1554
1555 let filesystem::DirectoryEntry { type_, name } = entry;
1556 let d_ino = self
1557 .dir_descriptor
1558 .metadata_hash_at(filesystem::PathFlags::empty(), &name)
1559 .map(|h| h.lower)
1560 .unwrap_or(0);
1561 let name = ManuallyDrop::new(name);
1562 let dirent = wasip1::Dirent {
1563 d_next: self.cookie,
1564 d_ino,
1565 d_namlen: u32::try_from(name.len()).trapping_unwrap(),
1566 d_type: type_.into(),
1567 };
1568 // Extend the lifetime of `name` to the `self.state` lifetime for
1569 // this iterator since the data for the name lives within state.
1570 let name = unsafe {
1571 assert_eq!(name.as_ptr(), self.state.temporary_data.get().cast());
1572 slice::from_raw_parts(name.as_ptr().cast(), name.len())
1573 };
1574 Some(Ok((dirent, name)))
1575 }
1576 }
1577 }
1578
1579 /// Atomically replace a file descriptor by renumbering another file descriptor.
1580 /// Due to the strong focus on thread safety, this environment does not provide
1581 /// a mechanism to duplicate or renumber a file descriptor to an arbitrary
1582 /// number, like `dup2()`. This would be prone to race conditions, as an actual
1583 /// file descriptor with the same number could be allocated by a different
1584 /// thread at the same time.
1585 /// This function provides a way to atomically renumber file descriptors, which
1586 /// would disappear if `dup2()` were to be removed entirely.
1587 #[unsafe(no_mangle)]
fd_renumber(fd: Fd, to: Fd) -> Errno1588 pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno {
1589 State::with(|state| state.descriptors_mut().renumber(fd, to))
1590 }
1591
1592 /// Move the offset of a file descriptor.
1593 /// Note: This is similar to `lseek` in POSIX.
1594 #[unsafe(no_mangle)]
fd_seek( fd: Fd, offset: Filedelta, whence: Whence, newoffset: &mut Filesize, ) -> Errno1595 pub unsafe extern "C" fn fd_seek(
1596 fd: Fd,
1597 offset: Filedelta,
1598 whence: Whence,
1599 newoffset: &mut Filesize,
1600 ) -> Errno {
1601 cfg_filesystem_available! {
1602 State::with(|state| {
1603 let mut ds = state.descriptors_mut();
1604 let stream = ds.get_seekable_stream_mut(fd)?;
1605
1606 // Seeking only works on files.
1607 if let StreamType::File(file) = &mut stream.type_ {
1608 if let filesystem::DescriptorType::Directory = file.descriptor_type {
1609 // This isn't really the "right" errno, but it is consistient with wasmtime's
1610 // preview 1 tests.
1611 return Err(ERRNO_BADF);
1612 }
1613 let from = match whence {
1614 WHENCE_SET if offset >= 0 => offset,
1615 WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) {
1616 Some(pos) if pos >= 0 => pos,
1617 _ => return Err(ERRNO_INVAL),
1618 },
1619 WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) {
1620 Some(pos) if pos >= 0 => pos,
1621 _ => return Err(ERRNO_INVAL),
1622 },
1623 _ => return Err(ERRNO_INVAL),
1624 };
1625 drop(stream.input.take());
1626 drop(stream.output.take());
1627 file.position.set(from as filesystem::Filesize);
1628 *newoffset = from as filesystem::Filesize;
1629 Ok(())
1630 } else {
1631 Err(ERRNO_SPIPE)
1632 }
1633 })
1634 }
1635 }
1636
1637 /// Synchronize the data and metadata of a file to disk.
1638 /// Note: This is similar to `fsync` in POSIX.
1639 #[unsafe(no_mangle)]
fd_sync(fd: Fd) -> Errno1640 pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno {
1641 cfg_filesystem_available! {
1642 State::with(|state| {
1643 let ds = state.descriptors();
1644 let file = ds.get_file(fd)?;
1645 file.fd.sync()?;
1646 Ok(())
1647 })
1648 }
1649 }
1650
1651 /// Return the current offset of a file descriptor.
1652 /// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
1653 #[unsafe(no_mangle)]
fd_tell(fd: Fd, offset: &mut Filesize) -> Errno1654 pub unsafe extern "C" fn fd_tell(fd: Fd, offset: &mut Filesize) -> Errno {
1655 cfg_filesystem_available! {
1656 State::with(|state| {
1657 let ds = state.descriptors();
1658 let file = ds.get_seekable_file(fd)?;
1659 *offset = file.position.get();
1660 Ok(())
1661 })
1662 }
1663 }
1664
1665 /// Write to a file descriptor.
1666 /// Note: This is similar to `writev` in POSIX.
1667 #[unsafe(no_mangle)]
fd_write( fd: Fd, mut iovs_ptr: *const Ciovec, mut iovs_len: usize, nwritten: &mut Size, ) -> Errno1668 pub unsafe extern "C" fn fd_write(
1669 fd: Fd,
1670 mut iovs_ptr: *const Ciovec,
1671 mut iovs_len: usize,
1672 nwritten: &mut Size,
1673 ) -> Errno {
1674 if !matches!(
1675 unsafe { get_allocation_state() },
1676 AllocationState::StackAllocated | AllocationState::StateAllocated
1677 ) {
1678 *nwritten = 0;
1679 return ERRNO_IO;
1680 }
1681
1682 let bytes = unsafe {
1683 // Skip leading empty buffers.
1684 while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {
1685 iovs_ptr = iovs_ptr.add(1);
1686 iovs_len -= 1;
1687 }
1688 if iovs_len == 0 {
1689 *nwritten = 0;
1690 return ERRNO_SUCCESS;
1691 }
1692 let ptr = (*iovs_ptr).buf;
1693 let len = (*iovs_ptr).buf_len;
1694 slice::from_raw_parts(ptr, len)
1695 };
1696
1697 State::with(|state| {
1698 let ds = state.descriptors();
1699 match ds.get(fd)? {
1700 Descriptor::Streams(streams) => {
1701 let wasi_stream = streams.get_write_stream()?;
1702
1703 #[cfg(not(feature = "proxy"))]
1704 let nbytes = if let StreamType::File(file) = &streams.type_ {
1705 file.blocking_mode.write(wasi_stream, bytes)?
1706 } else {
1707 // Use blocking writes on non-file streams (stdout, stderr, as sockets
1708 // aren't currently used).
1709 BlockingMode::Blocking.write(wasi_stream, bytes)?
1710 };
1711 #[cfg(feature = "proxy")]
1712 let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?;
1713
1714 // If this is a file, keep the current-position pointer up
1715 // to date. Note that for files that perform appending
1716 // writes this function will always update the current
1717 // position to the end of the file.
1718 //
1719 // NB: this isn't "atomic" as it doesn't necessarily account
1720 // for concurrent writes, but there's not much that can be
1721 // done about that.
1722 #[cfg(not(feature = "proxy"))]
1723 if let StreamType::File(file) = &streams.type_ {
1724 if file.append {
1725 file.position.set(file.fd.stat()?.size);
1726 } else {
1727 file.position.set(file.position.get() + nbytes as u64);
1728 }
1729 }
1730
1731 *nwritten = nbytes;
1732 Ok(())
1733 }
1734 Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),
1735 }
1736 })
1737 }
1738
1739 /// Create a directory.
1740 /// Note: This is similar to `mkdirat` in POSIX.
1741 #[unsafe(no_mangle)]
path_create_directory( fd: Fd, path_ptr: *const u8, path_len: usize, ) -> Errno1742 pub unsafe extern "C" fn path_create_directory(
1743 fd: Fd,
1744 path_ptr: *const u8,
1745 path_len: usize,
1746 ) -> Errno {
1747 cfg_filesystem_available! {
1748 let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1749
1750 State::with(|state| {
1751 let ds = state.descriptors();
1752 let file = ds.get_dir(fd)?;
1753 file.fd.create_directory_at(path)?;
1754 Ok(())
1755 })
1756 }
1757 }
1758
1759 /// Return the attributes of a file or directory.
1760 /// Note: This is similar to `stat` in POSIX.
1761 #[unsafe(no_mangle)]
path_filestat_get( fd: Fd, flags: Lookupflags, path_ptr: *const u8, path_len: usize, buf: &mut Filestat, ) -> Errno1762 pub unsafe extern "C" fn path_filestat_get(
1763 fd: Fd,
1764 flags: Lookupflags,
1765 path_ptr: *const u8,
1766 path_len: usize,
1767 buf: &mut Filestat,
1768 ) -> Errno {
1769 cfg_filesystem_available! {
1770 let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1771 let at_flags = at_flags_from_lookupflags(flags);
1772
1773 State::with(|state| {
1774 let ds = state.descriptors();
1775 let file = ds.get_dir(fd)?;
1776 let stat = file.fd.stat_at(at_flags, path)?;
1777 let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?;
1778 let filetype = stat.type_.into();
1779 *buf = Filestat {
1780 dev: 1,
1781 ino: metadata_hash.lower,
1782 filetype,
1783 nlink: stat.link_count,
1784 size: stat.size,
1785 atim: datetime_to_timestamp(stat.data_access_timestamp),
1786 mtim: datetime_to_timestamp(stat.data_modification_timestamp),
1787 ctim: datetime_to_timestamp(stat.status_change_timestamp),
1788 };
1789 Ok(())
1790 })
1791 }
1792 }
1793
1794 /// Adjust the timestamps of a file or directory.
1795 /// Note: This is similar to `utimensat` in POSIX.
1796 #[unsafe(no_mangle)]
path_filestat_set_times( fd: Fd, flags: Lookupflags, path_ptr: *const u8, path_len: usize, atim: Timestamp, mtim: Timestamp, fst_flags: Fstflags, ) -> Errno1797 pub unsafe extern "C" fn path_filestat_set_times(
1798 fd: Fd,
1799 flags: Lookupflags,
1800 path_ptr: *const u8,
1801 path_len: usize,
1802 atim: Timestamp,
1803 mtim: Timestamp,
1804 fst_flags: Fstflags,
1805 ) -> Errno {
1806 cfg_filesystem_available! {
1807 let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1808 let at_flags = at_flags_from_lookupflags(flags);
1809
1810 State::with(|state| {
1811 let atim = systimespec(
1812 fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,
1813 atim,
1814 fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,
1815 )?;
1816 let mtim = systimespec(
1817 fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,
1818 mtim,
1819 fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,
1820 )?;
1821
1822 let ds = state.descriptors();
1823 let file = ds.get_dir(fd)?;
1824 file.fd.set_times_at(at_flags, path, atim, mtim)?;
1825 Ok(())
1826 })
1827 }
1828 }
1829
1830 /// Create a hard link.
1831 /// Note: This is similar to `linkat` in POSIX.
1832 #[unsafe(no_mangle)]
path_link( old_fd: Fd, old_flags: Lookupflags, old_path_ptr: *const u8, old_path_len: usize, new_fd: Fd, new_path_ptr: *const u8, new_path_len: usize, ) -> Errno1833 pub unsafe extern "C" fn path_link(
1834 old_fd: Fd,
1835 old_flags: Lookupflags,
1836 old_path_ptr: *const u8,
1837 old_path_len: usize,
1838 new_fd: Fd,
1839 new_path_ptr: *const u8,
1840 new_path_len: usize,
1841 ) -> Errno {
1842 cfg_filesystem_available! {
1843 let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };
1844 let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };
1845 let at_flags = at_flags_from_lookupflags(old_flags);
1846
1847 State::with(|state| {
1848 let ds = state.descriptors();
1849 let old = &ds.get_dir(old_fd)?.fd;
1850 let new = &ds.get_dir(new_fd)?.fd;
1851 old.link_at(at_flags, old_path, new, new_path)?;
1852 Ok(())
1853 })
1854 }
1855 }
1856
1857 /// Open a file or directory.
1858 /// The returned file descriptor is not guaranteed to be the lowest-numbered
1859 /// file descriptor not currently open; it is randomized to prevent
1860 /// applications from depending on making assumptions about indexes, since this
1861 /// is error-prone in multi-threaded contexts. The returned file descriptor is
1862 /// guaranteed to be less than 2**31.
1863 /// Note: This is similar to `openat` in POSIX.
1864 #[unsafe(no_mangle)]
path_open( fd: Fd, dirflags: Lookupflags, path_ptr: *const u8, path_len: usize, oflags: Oflags, fs_rights_base: Rights, fs_rights_inheriting: Rights, fdflags: Fdflags, opened_fd: &mut Fd, ) -> Errno1865 pub unsafe extern "C" fn path_open(
1866 fd: Fd,
1867 dirflags: Lookupflags,
1868 path_ptr: *const u8,
1869 path_len: usize,
1870 oflags: Oflags,
1871 fs_rights_base: Rights,
1872 fs_rights_inheriting: Rights,
1873 fdflags: Fdflags,
1874 opened_fd: &mut Fd,
1875 ) -> Errno {
1876 cfg_filesystem_available! {
1877 let _ = fs_rights_inheriting;
1878
1879 let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1880 let at_flags = at_flags_from_lookupflags(dirflags);
1881 let o_flags = o_flags_from_oflags(oflags);
1882 let flags = descriptor_flags_from_flags(fs_rights_base, fdflags);
1883 let append = fdflags & wasip1::FDFLAGS_APPEND == wasip1::FDFLAGS_APPEND;
1884
1885 #[cfg(feature = "proxy")]
1886 return wasip1::ERRNO_NOTSUP;
1887
1888 #[cfg(not(feature = "proxy"))]
1889 State::with(|state| {
1890 let result = state
1891 .descriptors()
1892 .get_dir(fd)?
1893 .fd
1894 .open_at(at_flags, path, o_flags, flags)?;
1895 let descriptor_type = result.get_type()?;
1896 let desc = Descriptor::Streams(Streams {
1897 input: OnceCell::new(),
1898 output: OnceCell::new(),
1899 type_: StreamType::File(File {
1900 fd: result,
1901 descriptor_type,
1902 position: Cell::new(0),
1903 append,
1904 blocking_mode: if fdflags & wasip1::FDFLAGS_NONBLOCK == 0 {
1905 BlockingMode::Blocking
1906 } else {
1907 BlockingMode::NonBlocking
1908 },
1909 preopen_name_len: None,
1910 }),
1911 });
1912
1913 let fd = state.descriptors_mut().open(desc)?;
1914 *opened_fd = fd;
1915 Ok(())
1916 })
1917 }
1918 }
1919
1920 /// Read the contents of a symbolic link.
1921 /// Note: This is similar to `readlinkat` in POSIX.
1922 #[unsafe(no_mangle)]
path_readlink( fd: Fd, path_ptr: *const u8, path_len: usize, buf: *mut u8, buf_len: Size, bufused: &mut Size, ) -> Errno1923 pub unsafe extern "C" fn path_readlink(
1924 fd: Fd,
1925 path_ptr: *const u8,
1926 path_len: usize,
1927 buf: *mut u8,
1928 buf_len: Size,
1929 bufused: &mut Size,
1930 ) -> Errno {
1931 cfg_filesystem_available! {
1932 let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1933
1934 State::with(|state| {
1935 // If the user gave us a buffer shorter than `PATH_MAX`, it may not be
1936 // long enough to accept the actual path. `cabi_realloc` can't fail,
1937 // so instead we handle this case specially.
1938 let use_state_buf = buf_len < PATH_MAX;
1939
1940 let ds = state.descriptors();
1941 let file = ds.get_dir(fd)?;
1942 let path = if use_state_buf {
1943 state
1944 .with_one_temporary_alloc( || {
1945 file.fd.readlink_at(path)
1946 })?
1947 } else {
1948 state
1949 .with_one_import_alloc(buf, buf_len, || file.fd.readlink_at(path))?
1950 };
1951
1952 if use_state_buf {
1953 // Preview1 follows POSIX in truncating the returned path if it
1954 // doesn't fit.
1955 let len = min(path.len(), buf_len);
1956 unsafe {
1957 ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len);
1958 }
1959 *bufused = len;
1960 } else {
1961 *bufused = path.len();
1962 }
1963
1964 // The returned string's memory was allocated in `buf`, so don't separately
1965 // free it.
1966 forget(path);
1967
1968 Ok(())
1969 })
1970 }
1971 }
1972
1973 /// Remove a directory.
1974 /// Return `errno::notempty` if the directory is not empty.
1975 /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
1976 #[unsafe(no_mangle)]
path_remove_directory( fd: Fd, path_ptr: *const u8, path_len: usize, ) -> Errno1977 pub unsafe extern "C" fn path_remove_directory(
1978 fd: Fd,
1979 path_ptr: *const u8,
1980 path_len: usize,
1981 ) -> Errno {
1982 cfg_filesystem_available! {
1983 let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1984
1985 State::with(|state| {
1986 let ds = state.descriptors();
1987 let file = ds.get_dir(fd)?;
1988 file.fd.remove_directory_at(path)?;
1989 Ok(())
1990 })
1991 }
1992 }
1993
1994 /// Rename a file or directory.
1995 /// Note: This is similar to `renameat` in POSIX.
1996 #[unsafe(no_mangle)]
path_rename( old_fd: Fd, old_path_ptr: *const u8, old_path_len: usize, new_fd: Fd, new_path_ptr: *const u8, new_path_len: usize, ) -> Errno1997 pub unsafe extern "C" fn path_rename(
1998 old_fd: Fd,
1999 old_path_ptr: *const u8,
2000 old_path_len: usize,
2001 new_fd: Fd,
2002 new_path_ptr: *const u8,
2003 new_path_len: usize,
2004 ) -> Errno {
2005 cfg_filesystem_available! {
2006 let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };
2007 let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };
2008
2009 State::with(|state| {
2010 let ds = state.descriptors();
2011 let old = &ds.get_dir(old_fd)?.fd;
2012 let new = &ds.get_dir(new_fd)?.fd;
2013 old.rename_at(old_path, new, new_path)?;
2014 Ok(())
2015 })
2016 }
2017 }
2018
2019 /// Create a symbolic link.
2020 /// Note: This is similar to `symlinkat` in POSIX.
2021 #[unsafe(no_mangle)]
path_symlink( old_path_ptr: *const u8, old_path_len: usize, fd: Fd, new_path_ptr: *const u8, new_path_len: usize, ) -> Errno2022 pub unsafe extern "C" fn path_symlink(
2023 old_path_ptr: *const u8,
2024 old_path_len: usize,
2025 fd: Fd,
2026 new_path_ptr: *const u8,
2027 new_path_len: usize,
2028 ) -> Errno {
2029 cfg_filesystem_available! {
2030 let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };
2031 let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };
2032
2033 State::with(|state| {
2034 let ds = state.descriptors();
2035 let file = ds.get_dir(fd)?;
2036 file.fd.symlink_at(old_path, new_path)?;
2037 Ok(())
2038 })
2039 }
2040 }
2041
2042 /// Unlink a file.
2043 /// Return `errno::isdir` if the path refers to a directory.
2044 /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
2045 #[unsafe(no_mangle)]
path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno2046 pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno {
2047 cfg_filesystem_available! {
2048 let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
2049
2050 State::with(|state| {
2051 let ds = state.descriptors();
2052 let file = ds.get_dir(fd)?;
2053 file.fd.unlink_file_at(path)?;
2054 Ok(())
2055 })
2056 }
2057 }
2058
2059 struct Pollables {
2060 pointer: *mut Pollable,
2061 index: usize,
2062 length: usize,
2063 }
2064
2065 impl Pollables {
push(&mut self, pollable: Pollable)2066 unsafe fn push(&mut self, pollable: Pollable) {
2067 assert!(self.index < self.length);
2068 // Use `ptr::write` instead of `*... = pollable` because `ptr::write`
2069 // doesn't call drop on the old memory.
2070 unsafe {
2071 self.pointer.add(self.index).write(pollable);
2072 }
2073 self.index += 1;
2074 }
2075 }
2076
2077 // We create new pollable handles for each `poll_oneoff` call, so drop them all
2078 // after the call.
2079 impl Drop for Pollables {
drop(&mut self)2080 fn drop(&mut self) {
2081 while self.index != 0 {
2082 self.index -= 1;
2083 unsafe {
2084 core::ptr::drop_in_place(self.pointer.add(self.index));
2085 }
2086 }
2087 }
2088 }
2089
2090 /// Concurrently poll for the occurrence of a set of events.
2091 #[unsafe(no_mangle)]
poll_oneoff( r#in: *const Subscription, out: *mut Event, nsubscriptions: Size, nevents: &mut Size, ) -> Errno2092 pub unsafe extern "C" fn poll_oneoff(
2093 r#in: *const Subscription,
2094 out: *mut Event,
2095 nsubscriptions: Size,
2096 nevents: &mut Size,
2097 ) -> Errno {
2098 *nevents = 0;
2099
2100 let subscriptions = unsafe { slice::from_raw_parts(r#in, nsubscriptions) };
2101
2102 // We're going to split the `nevents` buffer into two non-overlapping
2103 // buffers: one to store the pollable handles, and the other to store
2104 // the bool results.
2105 //
2106 // First, we assert that this is possible:
2107 assert!(align_of::<Event>() >= align_of::<Pollable>());
2108 assert!(align_of::<Pollable>() >= align_of::<u32>());
2109 assert!(
2110 nsubscriptions
2111 .checked_mul(size_of::<Event>())
2112 .trapping_unwrap()
2113 >= nsubscriptions
2114 .checked_mul(size_of::<Pollable>())
2115 .trapping_unwrap()
2116 .checked_add(
2117 nsubscriptions
2118 .checked_mul(size_of::<u32>())
2119 .trapping_unwrap()
2120 )
2121 .trapping_unwrap()
2122 );
2123 // Store the pollable handles at the beginning, and the bool results at the
2124 // end, so that we don't clobber the bool results when writing the events.
2125 let pollables = out as *mut c_void as *mut Pollable;
2126 let results = unsafe { out.add(nsubscriptions).cast::<u32>().sub(nsubscriptions) };
2127
2128 // Indefinite sleeping is not supported in preview1.
2129 if nsubscriptions == 0 {
2130 return ERRNO_INVAL;
2131 }
2132
2133 State::with(|state| {
2134 const EVENTTYPE_CLOCK: u8 = wasip1::EVENTTYPE_CLOCK.raw();
2135 const EVENTTYPE_FD_READ: u8 = wasip1::EVENTTYPE_FD_READ.raw();
2136 const EVENTTYPE_FD_WRITE: u8 = wasip1::EVENTTYPE_FD_WRITE.raw();
2137
2138 let mut pollables = Pollables {
2139 pointer: pollables,
2140 index: 0,
2141 length: nsubscriptions,
2142 };
2143
2144 for subscription in subscriptions {
2145 let pollable = match subscription.u.tag {
2146 EVENTTYPE_CLOCK => {
2147 let clock = unsafe { &subscription.u.u.clock };
2148 let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME)
2149 == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME;
2150 match clock.id {
2151 CLOCKID_REALTIME => {
2152 let timeout = if absolute {
2153 // Convert `clock.timeout` to `Datetime`.
2154 let mut datetime = wall_clock::Datetime {
2155 seconds: clock.timeout / 1_000_000_000,
2156 nanoseconds: (clock.timeout % 1_000_000_000) as _,
2157 };
2158
2159 // Subtract `now`.
2160 let now = wall_clock::now();
2161 datetime.seconds -= now.seconds;
2162 if datetime.nanoseconds < now.nanoseconds {
2163 datetime.seconds -= 1;
2164 datetime.nanoseconds += 1_000_000_000;
2165 }
2166 datetime.nanoseconds -= now.nanoseconds;
2167
2168 // Convert to nanoseconds.
2169 let nanos = datetime
2170 .seconds
2171 .checked_mul(1_000_000_000)
2172 .ok_or(ERRNO_OVERFLOW)?;
2173 nanos
2174 .checked_add(datetime.nanoseconds.into())
2175 .ok_or(ERRNO_OVERFLOW)?
2176 } else {
2177 clock.timeout
2178 };
2179
2180 monotonic_clock::subscribe_duration(timeout)
2181 }
2182
2183 CLOCKID_MONOTONIC => {
2184 if absolute {
2185 monotonic_clock::subscribe_instant(clock.timeout)
2186 } else {
2187 monotonic_clock::subscribe_duration(clock.timeout)
2188 }
2189 }
2190
2191 _ => return Err(ERRNO_INVAL),
2192 }
2193 }
2194
2195 EVENTTYPE_FD_READ => state
2196 .descriptors()
2197 .get_read_stream(unsafe { subscription.u.u.fd_read.file_descriptor })
2198 .map(|stream| stream.subscribe())?,
2199
2200 EVENTTYPE_FD_WRITE => state
2201 .descriptors()
2202 .get_write_stream(unsafe { subscription.u.u.fd_write.file_descriptor })
2203 .map(|stream| stream.subscribe())?,
2204
2205 _ => return Err(ERRNO_INVAL),
2206 };
2207 unsafe {
2208 pollables.push(pollable);
2209 }
2210 }
2211
2212 #[link(wasm_import_module = "wasi:io/poll@0.2.6")]
2213 unsafe extern "C" {
2214 #[link_name = "poll"]
2215 fn poll_import(pollables: *const Pollable, len: usize, rval: *mut ReadyList);
2216 }
2217 let mut ready_list = ReadyList {
2218 base: std::ptr::null(),
2219 len: 0,
2220 };
2221
2222 state.with_one_import_alloc(
2223 results.cast(),
2224 nsubscriptions
2225 .checked_mul(size_of::<u32>())
2226 .trapping_unwrap(),
2227 || unsafe {
2228 poll_import(
2229 pollables.pointer,
2230 pollables.length,
2231 &mut ready_list as *mut _,
2232 );
2233 },
2234 );
2235
2236 assert!(ready_list.len <= nsubscriptions);
2237 assert_eq!(ready_list.base, results as *const u32);
2238
2239 drop(pollables);
2240
2241 let ready = unsafe { std::slice::from_raw_parts(ready_list.base, ready_list.len) };
2242
2243 let mut count = 0;
2244
2245 for subscription in ready {
2246 let subscription = unsafe { *subscriptions.as_ptr().add(*subscription as usize) };
2247
2248 let type_;
2249
2250 let (error, nbytes, flags) = match subscription.u.tag {
2251 EVENTTYPE_CLOCK => {
2252 type_ = wasip1::EVENTTYPE_CLOCK;
2253 (ERRNO_SUCCESS, 0, 0)
2254 }
2255
2256 EVENTTYPE_FD_READ => {
2257 type_ = wasip1::EVENTTYPE_FD_READ;
2258 let ds = state.descriptors();
2259 let desc = ds
2260 .get(unsafe { subscription.u.u.fd_read.file_descriptor })
2261 .trapping_unwrap();
2262 match desc {
2263 Descriptor::Streams(streams) => match &streams.type_ {
2264 #[cfg(not(feature = "proxy"))]
2265 StreamType::File(file) => match file.fd.stat() {
2266 Ok(stat) => {
2267 let nbytes = stat.size.saturating_sub(file.position.get());
2268 (
2269 ERRNO_SUCCESS,
2270 nbytes,
2271 if nbytes == 0 {
2272 EVENTRWFLAGS_FD_READWRITE_HANGUP
2273 } else {
2274 0
2275 },
2276 )
2277 }
2278 Err(e) => (e.into(), 1, 0),
2279 },
2280 StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
2281 },
2282 _ => unreachable!(),
2283 }
2284 }
2285 EVENTTYPE_FD_WRITE => {
2286 type_ = wasip1::EVENTTYPE_FD_WRITE;
2287 let ds = state.descriptors();
2288 let desc = ds
2289 .get(unsafe { subscription.u.u.fd_write.file_descriptor })
2290 .trapping_unwrap();
2291 match desc {
2292 Descriptor::Streams(streams) => match &streams.type_ {
2293 #[cfg(not(feature = "proxy"))]
2294 StreamType::File(_) => (ERRNO_SUCCESS, 1, 0),
2295 StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
2296 },
2297 _ => unreachable!(),
2298 }
2299 }
2300
2301 _ => unreachable!(),
2302 };
2303
2304 unsafe {
2305 *out.add(count) = Event {
2306 userdata: subscription.userdata,
2307 error,
2308 type_,
2309 fd_readwrite: EventFdReadwrite { nbytes, flags },
2310 };
2311 }
2312
2313 count += 1;
2314 }
2315
2316 *nevents = count;
2317
2318 Ok(())
2319 })
2320 }
2321
2322 /// Terminate the process normally. An exit code of 0 indicates successful
2323 /// termination of the program. The meanings of other values is dependent on
2324 /// the environment.
2325 #[unsafe(no_mangle)]
proc_exit(rval: Exitcode) -> !2326 pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! {
2327 #[cfg(feature = "proxy")]
2328 {
2329 unreachable!("no other implementation available in proxy world");
2330 }
2331 #[cfg(not(feature = "proxy"))]
2332 {
2333 let status = if rval == 0 { Ok(()) } else { Err(()) };
2334 crate::bindings::wasi::cli::exit::exit(status); // does not return
2335 unreachable!("host exit implementation didn't exit!") // actually unreachable
2336 }
2337 }
2338
2339 /// Send a signal to the process of the calling thread.
2340 /// Note: This is similar to `raise` in POSIX.
2341 #[unsafe(no_mangle)]
proc_raise(_sig: Signal) -> Errno2342 pub unsafe extern "C" fn proc_raise(_sig: Signal) -> Errno {
2343 unreachable!()
2344 }
2345
2346 /// Temporarily yield execution of the calling thread.
2347 /// Note: This is similar to `sched_yield` in POSIX.
2348 #[unsafe(no_mangle)]
sched_yield() -> Errno2349 pub unsafe extern "C" fn sched_yield() -> Errno {
2350 // TODO: This is not yet covered in Preview2.
2351
2352 ERRNO_SUCCESS
2353 }
2354
2355 /// Write high-quality random data into a buffer.
2356 /// This function blocks when the implementation is unable to immediately
2357 /// provide sufficient high-quality random data.
2358 /// This function may execute slowly, so when large mounts of random data are
2359 /// required, it's advisable to use this function to seed a pseudo-random
2360 /// number generator, rather than to provide the random data directly.
2361 #[unsafe(no_mangle)]
random_get(buf: *mut u8, buf_len: Size) -> Errno2362 pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno {
2363 if matches!(
2364 unsafe { get_allocation_state() },
2365 AllocationState::StackAllocated | AllocationState::StateAllocated
2366 ) {
2367 State::with(|state| {
2368 assert_eq!(buf_len as u32 as Size, buf_len);
2369 let result = state
2370 .with_one_import_alloc(buf, buf_len, || random::get_random_bytes(buf_len as u64));
2371 assert_eq!(result.as_ptr(), buf);
2372
2373 // The returned buffer's memory was allocated in `buf`, so don't separately
2374 // free it.
2375 forget(result);
2376
2377 Ok(())
2378 })
2379 } else {
2380 ERRNO_SUCCESS
2381 }
2382 }
2383
2384 /// Accept a new incoming connection.
2385 /// Note: This is similar to `accept` in POSIX.
2386 #[unsafe(no_mangle)]
sock_accept(_fd: Fd, _flags: Fdflags, _connection: *mut Fd) -> Errno2387 pub unsafe extern "C" fn sock_accept(_fd: Fd, _flags: Fdflags, _connection: *mut Fd) -> Errno {
2388 unreachable!()
2389 }
2390
2391 /// Receive a message from a socket.
2392 /// Note: This is similar to `recv` in POSIX, though it also supports reading
2393 /// the data into multiple buffers in the manner of `readv`.
2394 #[unsafe(no_mangle)]
sock_recv( _fd: Fd, _ri_data_ptr: *const Iovec, _ri_data_len: usize, _ri_flags: Riflags, _ro_datalen: *mut Size, _ro_flags: *mut Roflags, ) -> Errno2395 pub unsafe extern "C" fn sock_recv(
2396 _fd: Fd,
2397 _ri_data_ptr: *const Iovec,
2398 _ri_data_len: usize,
2399 _ri_flags: Riflags,
2400 _ro_datalen: *mut Size,
2401 _ro_flags: *mut Roflags,
2402 ) -> Errno {
2403 unreachable!()
2404 }
2405
2406 /// Send a message on a socket.
2407 /// Note: This is similar to `send` in POSIX, though it also supports writing
2408 /// the data from multiple buffers in the manner of `writev`.
2409 #[unsafe(no_mangle)]
sock_send( _fd: Fd, _si_data_ptr: *const Ciovec, _si_data_len: usize, _si_flags: Siflags, _so_datalen: *mut Size, ) -> Errno2410 pub unsafe extern "C" fn sock_send(
2411 _fd: Fd,
2412 _si_data_ptr: *const Ciovec,
2413 _si_data_len: usize,
2414 _si_flags: Siflags,
2415 _so_datalen: *mut Size,
2416 ) -> Errno {
2417 unreachable!()
2418 }
2419
2420 /// Shut down socket send and receive channels.
2421 /// Note: This is similar to `shutdown` in POSIX.
2422 #[unsafe(no_mangle)]
sock_shutdown(fd: Fd, _how: Sdflags) -> Errno2423 pub unsafe extern "C" fn sock_shutdown(fd: Fd, _how: Sdflags) -> Errno {
2424 State::with(|state| {
2425 state
2426 .descriptors_mut()
2427 .get_stream_with_error_mut(fd, wasip1::ERRNO_BADF)?;
2428 Err(wasip1::ERRNO_NOTSOCK)
2429 })
2430 }
2431
2432 #[cfg(not(feature = "proxy"))]
datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> Timestamp2433 fn datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> Timestamp {
2434 match datetime {
2435 Some(datetime) => u64::from(datetime.nanoseconds)
2436 .saturating_add(datetime.seconds.saturating_mul(1_000_000_000)),
2437 None => 0,
2438 }
2439 }
2440
2441 #[cfg(not(feature = "proxy"))]
at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags2442 fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags {
2443 if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW {
2444 filesystem::PathFlags::SYMLINK_FOLLOW
2445 } else {
2446 filesystem::PathFlags::empty()
2447 }
2448 }
2449
2450 #[cfg(not(feature = "proxy"))]
o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags2451 fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags {
2452 let mut o_flags = filesystem::OpenFlags::empty();
2453 if flags & OFLAGS_CREAT == OFLAGS_CREAT {
2454 o_flags |= filesystem::OpenFlags::CREATE;
2455 }
2456 if flags & OFLAGS_DIRECTORY == OFLAGS_DIRECTORY {
2457 o_flags |= filesystem::OpenFlags::DIRECTORY;
2458 }
2459 if flags & OFLAGS_EXCL == OFLAGS_EXCL {
2460 o_flags |= filesystem::OpenFlags::EXCLUSIVE;
2461 }
2462 if flags & OFLAGS_TRUNC == OFLAGS_TRUNC {
2463 o_flags |= filesystem::OpenFlags::TRUNCATE;
2464 }
2465 o_flags
2466 }
2467
2468 #[cfg(not(feature = "proxy"))]
descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags2469 fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags {
2470 let mut flags = filesystem::DescriptorFlags::empty();
2471 if rights & wasip1::RIGHTS_FD_READ == wasip1::RIGHTS_FD_READ {
2472 flags |= filesystem::DescriptorFlags::READ;
2473 }
2474 if rights & wasip1::RIGHTS_FD_WRITE == wasip1::RIGHTS_FD_WRITE {
2475 flags |= filesystem::DescriptorFlags::WRITE;
2476 }
2477 if fdflags & wasip1::FDFLAGS_SYNC == wasip1::FDFLAGS_SYNC {
2478 flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC;
2479 }
2480 if fdflags & wasip1::FDFLAGS_DSYNC == wasip1::FDFLAGS_DSYNC {
2481 flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC;
2482 }
2483 if fdflags & wasip1::FDFLAGS_RSYNC == wasip1::FDFLAGS_RSYNC {
2484 flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC;
2485 }
2486 flags
2487 }
2488
2489 #[cfg(not(feature = "proxy"))]
2490 impl From<filesystem::ErrorCode> for Errno {
2491 #[inline(never)] // Disable inlining as this is bulky and relatively cold.
from(err: filesystem::ErrorCode) -> Errno2492 fn from(err: filesystem::ErrorCode) -> Errno {
2493 match err {
2494 // Use a black box to prevent the optimizer from generating a
2495 // lookup table, which would require a static initializer.
2496 filesystem::ErrorCode::Access => black_box(ERRNO_ACCES),
2497 filesystem::ErrorCode::WouldBlock => ERRNO_AGAIN,
2498 filesystem::ErrorCode::Already => ERRNO_ALREADY,
2499 filesystem::ErrorCode::BadDescriptor => ERRNO_BADF,
2500 filesystem::ErrorCode::Busy => ERRNO_BUSY,
2501 filesystem::ErrorCode::Deadlock => ERRNO_DEADLK,
2502 filesystem::ErrorCode::Quota => ERRNO_DQUOT,
2503 filesystem::ErrorCode::Exist => ERRNO_EXIST,
2504 filesystem::ErrorCode::FileTooLarge => ERRNO_FBIG,
2505 filesystem::ErrorCode::IllegalByteSequence => ERRNO_ILSEQ,
2506 filesystem::ErrorCode::InProgress => ERRNO_INPROGRESS,
2507 filesystem::ErrorCode::Interrupted => ERRNO_INTR,
2508 filesystem::ErrorCode::Invalid => ERRNO_INVAL,
2509 filesystem::ErrorCode::Io => ERRNO_IO,
2510 filesystem::ErrorCode::IsDirectory => ERRNO_ISDIR,
2511 filesystem::ErrorCode::Loop => ERRNO_LOOP,
2512 filesystem::ErrorCode::TooManyLinks => ERRNO_MLINK,
2513 filesystem::ErrorCode::MessageSize => ERRNO_MSGSIZE,
2514 filesystem::ErrorCode::NameTooLong => ERRNO_NAMETOOLONG,
2515 filesystem::ErrorCode::NoDevice => ERRNO_NODEV,
2516 filesystem::ErrorCode::NoEntry => ERRNO_NOENT,
2517 filesystem::ErrorCode::NoLock => ERRNO_NOLCK,
2518 filesystem::ErrorCode::InsufficientMemory => ERRNO_NOMEM,
2519 filesystem::ErrorCode::InsufficientSpace => ERRNO_NOSPC,
2520 filesystem::ErrorCode::Unsupported => ERRNO_NOTSUP,
2521 filesystem::ErrorCode::NotDirectory => ERRNO_NOTDIR,
2522 filesystem::ErrorCode::NotEmpty => ERRNO_NOTEMPTY,
2523 filesystem::ErrorCode::NotRecoverable => ERRNO_NOTRECOVERABLE,
2524 filesystem::ErrorCode::NoTty => ERRNO_NOTTY,
2525 filesystem::ErrorCode::NoSuchDevice => ERRNO_NXIO,
2526 filesystem::ErrorCode::Overflow => ERRNO_OVERFLOW,
2527 filesystem::ErrorCode::NotPermitted => ERRNO_PERM,
2528 filesystem::ErrorCode::Pipe => ERRNO_PIPE,
2529 filesystem::ErrorCode::ReadOnly => ERRNO_ROFS,
2530 filesystem::ErrorCode::InvalidSeek => ERRNO_SPIPE,
2531 filesystem::ErrorCode::TextFileBusy => ERRNO_TXTBSY,
2532 filesystem::ErrorCode::CrossDevice => ERRNO_XDEV,
2533 }
2534 }
2535 }
2536
2537 #[cfg(not(feature = "proxy"))]
2538 impl From<filesystem::DescriptorType> for wasip1::Filetype {
from(ty: filesystem::DescriptorType) -> wasip1::Filetype2539 fn from(ty: filesystem::DescriptorType) -> wasip1::Filetype {
2540 match ty {
2541 filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE,
2542 filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY,
2543 filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE,
2544 filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE,
2545 // preview1 never had a FIFO code.
2546 filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN,
2547 // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and
2548 // FILETYPE_SOCKET_DGRAM.
2549 filesystem::DescriptorType::Socket => unreachable!(),
2550 filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK,
2551 filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN,
2552 }
2553 }
2554 }
2555
2556 #[derive(Clone, Copy)]
2557 pub enum BlockingMode {
2558 NonBlocking,
2559 Blocking,
2560 }
2561
2562 impl BlockingMode {
2563 // note: these methods must take self, not &self, to avoid rustc creating a constant
2564 // out of a BlockingMode literal that it places in .romem, creating a data section and
2565 // breaking our fragile linking scheme
read( self, input_stream: &streams::InputStream, read_len: u64, ) -> Result<Vec<u8>, streams::StreamError>2566 fn read(
2567 self,
2568 input_stream: &streams::InputStream,
2569 read_len: u64,
2570 ) -> Result<Vec<u8>, streams::StreamError> {
2571 match self {
2572 BlockingMode::NonBlocking => input_stream.read(read_len),
2573 BlockingMode::Blocking => input_stream.blocking_read(read_len),
2574 }
2575 }
write( self, output_stream: &streams::OutputStream, mut bytes: &[u8], ) -> Result<usize, Errno>2576 fn write(
2577 self,
2578 output_stream: &streams::OutputStream,
2579 mut bytes: &[u8],
2580 ) -> Result<usize, Errno> {
2581 match self {
2582 BlockingMode::Blocking => {
2583 let total = bytes.len();
2584 loop {
2585 let len = bytes.len().min(4096);
2586 let (chunk, rest) = bytes.split_at(len);
2587 bytes = rest;
2588 match output_stream.blocking_write_and_flush(chunk) {
2589 Ok(()) if bytes.is_empty() => break,
2590 Ok(()) => {}
2591 Err(streams::StreamError::Closed) => return Err(ERRNO_IO),
2592 Err(streams::StreamError::LastOperationFailed(e)) => {
2593 return Err(stream_error_to_errno(e));
2594 }
2595 }
2596 }
2597 Ok(total)
2598 }
2599
2600 BlockingMode::NonBlocking => {
2601 let permit = match output_stream.check_write() {
2602 Ok(n) => n,
2603 Err(streams::StreamError::Closed) => 0,
2604 Err(streams::StreamError::LastOperationFailed(e)) => {
2605 return Err(stream_error_to_errno(e));
2606 }
2607 };
2608
2609 let len = bytes.len().min(permit as usize);
2610
2611 match output_stream.write(&bytes[..len]) {
2612 Ok(_) => {}
2613 Err(streams::StreamError::Closed) => return Ok(0),
2614 Err(streams::StreamError::LastOperationFailed(e)) => {
2615 return Err(stream_error_to_errno(e));
2616 }
2617 }
2618
2619 match output_stream.blocking_flush() {
2620 Ok(_) => {}
2621 Err(streams::StreamError::Closed) => return Ok(0),
2622 Err(streams::StreamError::LastOperationFailed(e)) => {
2623 return Err(stream_error_to_errno(e));
2624 }
2625 }
2626
2627 Ok(len)
2628 }
2629 }
2630 }
2631 }
2632
2633 #[repr(C)]
2634 #[cfg(not(feature = "proxy"))]
2635 pub struct File {
2636 /// The handle to the preview2 descriptor that this file is referencing.
2637 fd: filesystem::Descriptor,
2638
2639 /// The descriptor type, as supplied by filesystem::get_type at opening
2640 descriptor_type: filesystem::DescriptorType,
2641
2642 /// The current-position pointer.
2643 position: Cell<filesystem::Filesize>,
2644
2645 /// In append mode, all writes append to the file.
2646 append: bool,
2647
2648 /// In blocking mode, read and write calls dispatch to blocking_read and
2649 /// blocking_check_write on the underlying streams. When false, read and write
2650 /// dispatch to stream's plain read and check_write.
2651 blocking_mode: BlockingMode,
2652
2653 /// TODO
2654 preopen_name_len: Option<NonZeroUsize>,
2655 }
2656
2657 #[cfg(not(feature = "proxy"))]
2658 impl File {
is_dir(&self) -> bool2659 fn is_dir(&self) -> bool {
2660 match self.descriptor_type {
2661 filesystem::DescriptorType::Directory => true,
2662 _ => false,
2663 }
2664 }
2665 }
2666
2667 const PAGE_SIZE: usize = 65536;
2668
2669 /// The maximum path length. WASI doesn't explicitly guarantee this, but all
2670 /// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this
2671 /// polyfill.
2672 const PATH_MAX: usize = 4096;
2673
2674 /// Maximum number of bytes to cache for a `wasip1::Dirent` plus its path name.
2675 const DIRENT_CACHE: usize = 256;
2676
2677 /// A canary value to detect memory corruption within `State`.
2678 const MAGIC: u32 = u32::from_le_bytes(*b"ugh!");
2679
2680 #[repr(C)] // used for now to keep magic1 and magic2 at the start and end
2681 struct State {
2682 /// A canary constant value located at the beginning of this structure to
2683 /// try to catch memory corruption coming from the bottom.
2684 magic1: u32,
2685
2686 /// Used to coordinate allocations of `cabi_import_realloc`
2687 import_alloc: Cell<ImportAlloc>,
2688
2689 /// Storage of mapping from preview1 file descriptors to preview2 file
2690 /// descriptors.
2691 ///
2692 /// Do not use this member directly - use State::descriptors() to ensure
2693 /// lazy initialization happens.
2694 descriptors: RefCell<Option<Descriptors>>,
2695
2696 /// TODO
2697 temporary_data: UnsafeCell<MaybeUninit<[u8; temporary_data_size()]>>,
2698
2699 /// Cache for the `fd_readdir` call for a final `wasip1::Dirent` plus path
2700 /// name that didn't fit into the caller's buffer.
2701 #[cfg(not(feature = "proxy"))]
2702 dirent_cache: DirentCache,
2703
2704 /// The string `..` for use by the directory iterator.
2705 #[cfg(not(feature = "proxy"))]
2706 dotdot: [UnsafeCell<u8>; 2],
2707
2708 /// Another canary constant located at the end of the structure to catch
2709 /// memory corruption coming from the bottom.
2710 magic2: u32,
2711 }
2712
2713 #[cfg(not(feature = "proxy"))]
2714 struct DirentCache {
2715 stream: Cell<Option<DirectoryEntryStream>>,
2716 for_fd: Cell<wasip1::Fd>,
2717 cookie: Cell<wasip1::Dircookie>,
2718 cached_dirent: Cell<wasip1::Dirent>,
2719 path_data: UnsafeCell<MaybeUninit<[u8; DIRENT_CACHE]>>,
2720 }
2721
2722 #[cfg(not(feature = "proxy"))]
2723 struct DirectoryEntryStream(filesystem::DirectoryEntryStream);
2724
2725 #[repr(C)]
2726 pub struct WasmStr {
2727 ptr: *const u8,
2728 len: usize,
2729 }
2730
2731 #[repr(C)]
2732 pub struct WasmStrList {
2733 base: *const WasmStr,
2734 len: usize,
2735 }
2736
2737 #[repr(C)]
2738 pub struct StrTuple {
2739 key: WasmStr,
2740 value: WasmStr,
2741 }
2742
2743 #[derive(Copy, Clone)]
2744 #[repr(C)]
2745 pub struct StrTupleList {
2746 base: *const StrTuple,
2747 len: usize,
2748 }
2749
2750 #[derive(Copy, Clone)]
2751 #[repr(C)]
2752 pub struct ReadyList {
2753 base: *const u32,
2754 len: usize,
2755 }
2756
temporary_data_size() -> usize2757 const fn temporary_data_size() -> usize {
2758 // The total size of the struct should be a page, so start there
2759 let mut start = PAGE_SIZE;
2760
2761 // Remove big chunks of the struct for its various fields.
2762 start -= size_of::<Descriptors>();
2763 #[cfg(not(feature = "proxy"))]
2764 {
2765 start -= size_of::<DirentCache>();
2766 }
2767
2768 // Remove miscellaneous metadata also stored in state.
2769 let misc = if cfg!(feature = "proxy") { 8 } else { 10 };
2770 start -= misc * size_of::<usize>();
2771
2772 // Everything else is the `command_data` allocation.
2773 start
2774 }
2775
2776 // Statically assert that the `State` structure is the size of a wasm page. This
2777 // mostly guarantees that it's not larger than one page which is relied upon
2778 // below.
2779 #[cfg(target_arch = "wasm32")]
2780 const _: () = {
2781 let _size_assert: [(); PAGE_SIZE] = [(); size_of::<State>()];
2782 };
2783
2784 #[expect(unused, reason = "not used in all configurations")]
2785 #[repr(i32)]
2786 enum AllocationState {
2787 StackUnallocated,
2788 StackAllocating,
2789 StackAllocated,
2790 StateAllocating,
2791 StateAllocated,
2792 }
2793
2794 #[expect(improper_ctypes, reason = "types behind pointers")]
2795 unsafe extern "C" {
get_state_ptr() -> *mut State2796 fn get_state_ptr() -> *mut State;
set_state_ptr(state: *mut State)2797 fn set_state_ptr(state: *mut State);
get_allocation_state() -> AllocationState2798 fn get_allocation_state() -> AllocationState;
set_allocation_state(state: AllocationState)2799 fn set_allocation_state(state: AllocationState);
2800 }
2801
2802 impl State {
with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno2803 fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno {
2804 let state_ref = State::ptr();
2805 assert_eq!(state_ref.magic1, MAGIC);
2806 assert_eq!(state_ref.magic2, MAGIC);
2807 let ret = f(state_ref);
2808 match ret {
2809 Ok(()) => ERRNO_SUCCESS,
2810 Err(err) => err,
2811 }
2812 }
2813
ptr() -> &'static State2814 fn ptr() -> &'static State {
2815 unsafe {
2816 let mut ptr = get_state_ptr();
2817 if ptr.is_null() {
2818 ptr = State::new();
2819 set_state_ptr(ptr);
2820 }
2821 &*ptr
2822 }
2823 }
2824
2825 #[cold]
new() -> *mut State2826 fn new() -> *mut State {
2827 #[link(wasm_import_module = "__main_module__")]
2828 unsafe extern "C" {
2829 fn cabi_realloc(
2830 old_ptr: *mut u8,
2831 old_len: usize,
2832 align: usize,
2833 new_len: usize,
2834 ) -> *mut u8;
2835 }
2836
2837 assert!(matches!(
2838 unsafe { get_allocation_state() },
2839 AllocationState::StackAllocated
2840 ));
2841
2842 unsafe { set_allocation_state(AllocationState::StateAllocating) };
2843
2844 let ret = unsafe {
2845 cabi_realloc(
2846 ptr::null_mut(),
2847 0,
2848 mem::align_of::<UnsafeCell<State>>(),
2849 mem::size_of::<UnsafeCell<State>>(),
2850 ) as *mut State
2851 };
2852
2853 unsafe { set_allocation_state(AllocationState::StateAllocated) };
2854
2855 unsafe {
2856 Self::init(ret);
2857 }
2858
2859 ret
2860 }
2861
2862 #[cold]
init(state: *mut State)2863 unsafe fn init(state: *mut State) {
2864 unsafe {
2865 state.write(State {
2866 magic1: MAGIC,
2867 magic2: MAGIC,
2868 import_alloc: Cell::new(ImportAlloc::None),
2869 descriptors: RefCell::new(None),
2870 temporary_data: UnsafeCell::new(MaybeUninit::uninit()),
2871 #[cfg(not(feature = "proxy"))]
2872 dirent_cache: DirentCache {
2873 stream: Cell::new(None),
2874 for_fd: Cell::new(0),
2875 cookie: Cell::new(wasip1::DIRCOOKIE_START),
2876 cached_dirent: Cell::new(wasip1::Dirent {
2877 d_next: 0,
2878 d_ino: 0,
2879 d_type: FILETYPE_UNKNOWN,
2880 d_namlen: 0,
2881 }),
2882 path_data: UnsafeCell::new(MaybeUninit::uninit()),
2883 },
2884 #[cfg(not(feature = "proxy"))]
2885 dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')],
2886 });
2887 }
2888 }
2889
2890 /// Accessor for the descriptors member that ensures it is properly initialized
descriptors<'a>(&'a self) -> impl Deref<Target = Descriptors> + 'a2891 fn descriptors<'a>(&'a self) -> impl Deref<Target = Descriptors> + 'a {
2892 let mut d = self
2893 .descriptors
2894 .try_borrow_mut()
2895 .unwrap_or_else(|_| unreachable!());
2896 if d.is_none() {
2897 *d = Some(Descriptors::new(self));
2898 }
2899 RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))
2900 }
2901
2902 /// Mut accessor for the descriptors member that ensures it is properly initialized
descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref<Target = Descriptors> + 'a2903 fn descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref<Target = Descriptors> + 'a {
2904 let mut d = self
2905 .descriptors
2906 .try_borrow_mut()
2907 .unwrap_or_else(|_| unreachable!());
2908 if d.is_none() {
2909 *d = Some(Descriptors::new(self));
2910 }
2911 RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))
2912 }
2913
temporary_alloc(&self) -> BumpAlloc2914 unsafe fn temporary_alloc(&self) -> BumpAlloc {
2915 BumpAlloc {
2916 base: self.temporary_data.get().cast(),
2917 len: mem::size_of_val(&self.temporary_data),
2918 }
2919 }
2920
2921 /// Configure that `cabi_import_realloc` will allocate once from
2922 /// `self.temporary_data` for the duration of the closure `f`.
2923 ///
2924 /// Panics if the import allocator is already configured.
with_one_temporary_alloc<T>(&self, f: impl FnOnce() -> T) -> T2925 fn with_one_temporary_alloc<T>(&self, f: impl FnOnce() -> T) -> T {
2926 let alloc = unsafe { self.temporary_alloc() };
2927 self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).0
2928 }
2929
2930 /// Configure that `cabi_import_realloc` will allocate once from
2931 /// `base` with at most `len` bytes for the duration of `f`.
2932 ///
2933 /// Panics if the import allocator is already configured.
with_one_import_alloc<T>(&self, base: *mut u8, len: usize, f: impl FnOnce() -> T) -> T2934 fn with_one_import_alloc<T>(&self, base: *mut u8, len: usize, f: impl FnOnce() -> T) -> T {
2935 let alloc = BumpAlloc { base, len };
2936 self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).0
2937 }
2938
2939 /// Configures the `alloc` specified to be the allocator for
2940 /// `cabi_import_realloc` for the duration of `f`.
2941 ///
2942 /// Panics if the import allocator is already configured.
with_import_alloc<T>(&self, alloc: ImportAlloc, f: impl FnOnce() -> T) -> (T, ImportAlloc)2943 fn with_import_alloc<T>(&self, alloc: ImportAlloc, f: impl FnOnce() -> T) -> (T, ImportAlloc) {
2944 match self.import_alloc.replace(alloc) {
2945 ImportAlloc::None => {}
2946 _ => unreachable!("import allocator already set"),
2947 }
2948 let r = f();
2949 let alloc = self.import_alloc.replace(ImportAlloc::None);
2950 (r, alloc)
2951 }
2952 }
2953