Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wasi-preview1-component-adapter/src/lib.rs
3109 views
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
121
pub fn maybe_link_cabi_realloc() {}
122
}
123
}
124
125
#[unsafe(export_name = "wasi:cli/[email protected]#run")]
126
#[cfg(feature = "command")]
127
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> {
151
fn trapping_unwrap(self) -> T;
152
}
153
154
impl<T> TrappingUnwrap<T> for Option<T> {
155
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> {
164
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)]
179
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)]
188
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)]
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)]
203
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!
318
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 {
445
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
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/[email protected]")]
473
unsafe extern "C" {
474
#[link_name = "get-arguments"]
475
fn wasi_cli_get_arguments(rval: *mut WasmStrList);
476
#[link_name = "get-environment"]
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)]
483
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)]
520
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)]
561
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)]
601
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)]
657
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)]
681
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)]
709
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)]
737
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)]
752
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)]
774
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)]
788
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)]
918
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)]
947
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)]
963
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)]
1013
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"))]
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)]
1043
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)]
1072
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)]
1116
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)]
1163
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)]
1192
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)]
1239
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
1307
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")]
1328
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"))]
1340
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)]
1588
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)]
1595
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)]
1640
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)]
1654
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)]
1668
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)]
1742
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)]
1762
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)]
1797
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)]
1833
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)]
1865
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)]
1923
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)]
1977
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)]
1997
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)]
2022
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)]
2046
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 {
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 {
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)]
2092
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/[email protected]")]
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)]
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)]
2342
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)]
2349
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)]
2362
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)]
2387
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)]
2395
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)]
2410
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)]
2423
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"))]
2433
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"))]
2442
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"))]
2451
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"))]
2469
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.
2492
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 {
2539
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
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
}
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 {
2659
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
2757
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" {
2796
fn get_state_ptr() -> *mut State;
2797
fn set_state_ptr(state: *mut State);
2798
fn get_allocation_state() -> AllocationState;
2799
fn set_allocation_state(state: AllocationState);
2800
}
2801
2802
impl State {
2803
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
2814
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]
2826
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]
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
2891
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
2903
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
2914
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.
2925
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.
2934
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.
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
2954