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
1692 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 wasi::*;
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
wasi::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(wasi::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(wasi::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 = wasi::RIGHTS_PATH_CREATE_DIRECTORY
803
| wasi::RIGHTS_PATH_CREATE_FILE
804
| wasi::RIGHTS_PATH_LINK_SOURCE
805
| wasi::RIGHTS_PATH_LINK_TARGET
806
| wasi::RIGHTS_PATH_OPEN
807
| wasi::RIGHTS_FD_READDIR
808
| wasi::RIGHTS_PATH_READLINK
809
| wasi::RIGHTS_PATH_RENAME_SOURCE
810
| wasi::RIGHTS_PATH_RENAME_TARGET
811
| wasi::RIGHTS_PATH_SYMLINK
812
| wasi::RIGHTS_PATH_REMOVE_DIRECTORY
813
| wasi::RIGHTS_PATH_UNLINK_FILE
814
| wasi::RIGHTS_PATH_FILESTAT_GET
815
| wasi::RIGHTS_PATH_FILESTAT_SET_TIMES
816
| wasi::RIGHTS_FD_FILESTAT_GET
817
| wasi::RIGHTS_FD_FILESTAT_SET_TIMES;
818
819
let fs_rights_inheriting = fs_rights_base
820
| wasi::RIGHTS_FD_DATASYNC
821
| wasi::RIGHTS_FD_READ
822
| wasi::RIGHTS_FD_SEEK
823
| wasi::RIGHTS_FD_FDSTAT_SET_FLAGS
824
| wasi::RIGHTS_FD_SYNC
825
| wasi::RIGHTS_FD_TELL
826
| wasi::RIGHTS_FD_WRITE
827
| wasi::RIGHTS_FD_ADVISE
828
| wasi::RIGHTS_FD_ALLOCATE
829
| wasi::RIGHTS_FD_FILESTAT_GET
830
| wasi::RIGHTS_FD_FILESTAT_SET_SIZE
831
| wasi::RIGHTS_FD_FILESTAT_SET_TIMES
832
| wasi::RIGHTS_POLL_FD_READWRITE;
833
834
unsafe {
835
stat.write(Fdstat {
836
fs_filetype: wasi::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 wasi::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(wasi::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 [`wasi::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(wasi::ERRNO_NOTSUP),
956
Descriptor::Closed(..) | Descriptor::Bad => Err(wasi::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(wasi::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(wasi::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
wasi::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: wasi::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 wasi::DIRCOOKIE_START..cookie {
1406
match iter.next() {
1407
Some(Ok(_)) => {}
1408
Some(Err(e)) => return Err(e),
1409
None => return Ok(()),
1410
}
1411
}
1412
}
1413
};
1414
1415
while buf.len() > 0 {
1416
let (dirent, name) = match iter.next() {
1417
Some(Ok(pair)) => pair,
1418
Some(Err(e)) => return Err(e),
1419
None => break,
1420
};
1421
1422
// Copy a `dirent` describing this entry into the destination `buf`,
1423
// truncating it if it doesn't fit entirely.
1424
let bytes = unsafe {
1425
slice::from_raw_parts(
1426
(&dirent as *const wasi::Dirent).cast::<u8>(),
1427
size_of::<Dirent>(),
1428
)
1429
};
1430
let dirent_bytes_to_copy = buf.len().min(bytes.len());
1431
buf[..dirent_bytes_to_copy].copy_from_slice(&bytes[..dirent_bytes_to_copy]);
1432
buf = &mut buf[dirent_bytes_to_copy..];
1433
1434
// Copy the name bytes into the output `buf`, truncating it if it
1435
// doesn't fit.
1436
//
1437
// Note that this might be a 0-byte copy if the `dirent` was
1438
// truncated or fit entirely into the destination.
1439
let name_bytes_to_copy = buf.len().min(name.len());
1440
unsafe {
1441
ptr::copy_nonoverlapping(
1442
name.as_ptr().cast(),
1443
buf.as_mut_ptr(),
1444
name_bytes_to_copy,
1445
);
1446
}
1447
1448
buf = &mut buf[name_bytes_to_copy..];
1449
1450
// If the buffer is empty then that means the value may be
1451
// truncated, so save the state of the iterator in our dirent cache
1452
// and return.
1453
//
1454
// Note that `cookie - 1` is stored here since `iter.cookie` stores
1455
// the address of the next item, and we're rewinding one item since
1456
// the current item is truncated and will want to resume from that
1457
// in the future.
1458
//
1459
// Additionally note that this caching step is skipped if the name
1460
// to store doesn't actually fit in the dirent cache's path storage.
1461
// In that case there's not much we can do and let the next call to
1462
// `fd_readdir` start from scratch.
1463
if buf.len() == 0 && name.len() <= DIRENT_CACHE {
1464
let DirectoryEntryIterator { stream, cookie, .. } = iter;
1465
state.dirent_cache.stream.set(Some(stream));
1466
state.dirent_cache.for_fd.set(fd);
1467
state.dirent_cache.cookie.set(cookie - 1);
1468
state.dirent_cache.cached_dirent.set(dirent);
1469
unsafe {
1470
ptr::copy(
1471
name.as_ptr().cast::<u8>(),
1472
(*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8,
1473
name.len(),
1474
);
1475
}
1476
break;
1477
}
1478
}
1479
1480
*bufused = buf_len - buf.len();
1481
Ok(())
1482
});
1483
1484
struct DirectoryEntryIterator<'a> {
1485
state: &'a State,
1486
use_cache: bool,
1487
cookie: Dircookie,
1488
stream: DirectoryEntryStream,
1489
dir_descriptor: &'a filesystem::Descriptor,
1490
}
1491
1492
impl<'a> Iterator for DirectoryEntryIterator<'a> {
1493
// Note the usage of `UnsafeCell<u8>` here to indicate that the data can
1494
// alias the storage within `state`.
1495
type Item = Result<(wasi::Dirent, &'a [UnsafeCell<u8>]), Errno>;
1496
1497
fn next(&mut self) -> Option<Self::Item> {
1498
let current_cookie = self.cookie;
1499
1500
self.cookie += 1;
1501
1502
// Preview1 programs expect to see `.` and `..` in the traversal, but
1503
// Preview2 excludes them, so re-add them.
1504
match current_cookie {
1505
0 => {
1506
let metadata_hash = match self.dir_descriptor.metadata_hash() {
1507
Ok(h) => h,
1508
Err(e) => return Some(Err(e.into())),
1509
};
1510
let dirent = wasi::Dirent {
1511
d_next: self.cookie,
1512
d_ino: metadata_hash.lower,
1513
d_type: wasi::FILETYPE_DIRECTORY,
1514
d_namlen: 1,
1515
};
1516
return Some(Ok((dirent, &self.state.dotdot[..1])));
1517
}
1518
1 => {
1519
let dirent = wasi::Dirent {
1520
d_next: self.cookie,
1521
d_ino: 0,
1522
d_type: wasi::FILETYPE_DIRECTORY,
1523
d_namlen: 2,
1524
};
1525
return Some(Ok((dirent, &self.state.dotdot[..])));
1526
}
1527
_ => {}
1528
}
1529
1530
if self.use_cache {
1531
self.use_cache = false;
1532
return Some(unsafe {
1533
let dirent = self.state.dirent_cache.cached_dirent.as_ptr().read();
1534
let ptr = (*(*self.state.dirent_cache.path_data.get()).as_ptr())
1535
.as_ptr()
1536
.cast();
1537
let buffer = slice::from_raw_parts(ptr, dirent.d_namlen as usize);
1538
Ok((dirent, buffer))
1539
});
1540
}
1541
let entry = self
1542
.state
1543
.with_one_temporary_alloc(|| self.stream.0.read_directory_entry());
1544
let entry = match entry {
1545
Ok(Some(entry)) => entry,
1546
Ok(None) => return None,
1547
Err(e) => return Some(Err(e.into())),
1548
};
1549
1550
let filesystem::DirectoryEntry { type_, name } = entry;
1551
let d_ino = self
1552
.dir_descriptor
1553
.metadata_hash_at(filesystem::PathFlags::empty(), &name)
1554
.map(|h| h.lower)
1555
.unwrap_or(0);
1556
let name = ManuallyDrop::new(name);
1557
let dirent = wasi::Dirent {
1558
d_next: self.cookie,
1559
d_ino,
1560
d_namlen: u32::try_from(name.len()).trapping_unwrap(),
1561
d_type: type_.into(),
1562
};
1563
// Extend the lifetime of `name` to the `self.state` lifetime for
1564
// this iterator since the data for the name lives within state.
1565
let name = unsafe {
1566
assert_eq!(name.as_ptr(), self.state.temporary_data.get().cast());
1567
slice::from_raw_parts(name.as_ptr().cast(), name.len())
1568
};
1569
Some(Ok((dirent, name)))
1570
}
1571
}
1572
}
1573
1574
/// Atomically replace a file descriptor by renumbering another file descriptor.
1575
/// Due to the strong focus on thread safety, this environment does not provide
1576
/// a mechanism to duplicate or renumber a file descriptor to an arbitrary
1577
/// number, like `dup2()`. This would be prone to race conditions, as an actual
1578
/// file descriptor with the same number could be allocated by a different
1579
/// thread at the same time.
1580
/// This function provides a way to atomically renumber file descriptors, which
1581
/// would disappear if `dup2()` were to be removed entirely.
1582
#[unsafe(no_mangle)]
1583
pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno {
1584
State::with(|state| state.descriptors_mut().renumber(fd, to))
1585
}
1586
1587
/// Move the offset of a file descriptor.
1588
/// Note: This is similar to `lseek` in POSIX.
1589
#[unsafe(no_mangle)]
1590
pub unsafe extern "C" fn fd_seek(
1591
fd: Fd,
1592
offset: Filedelta,
1593
whence: Whence,
1594
newoffset: &mut Filesize,
1595
) -> Errno {
1596
cfg_filesystem_available! {
1597
State::with(|state| {
1598
let mut ds = state.descriptors_mut();
1599
let stream = ds.get_seekable_stream_mut(fd)?;
1600
1601
// Seeking only works on files.
1602
if let StreamType::File(file) = &mut stream.type_ {
1603
if let filesystem::DescriptorType::Directory = file.descriptor_type {
1604
// This isn't really the "right" errno, but it is consistient with wasmtime's
1605
// preview 1 tests.
1606
return Err(ERRNO_BADF);
1607
}
1608
let from = match whence {
1609
WHENCE_SET if offset >= 0 => offset,
1610
WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) {
1611
Some(pos) if pos >= 0 => pos,
1612
_ => return Err(ERRNO_INVAL),
1613
},
1614
WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) {
1615
Some(pos) if pos >= 0 => pos,
1616
_ => return Err(ERRNO_INVAL),
1617
},
1618
_ => return Err(ERRNO_INVAL),
1619
};
1620
drop(stream.input.take());
1621
drop(stream.output.take());
1622
file.position.set(from as filesystem::Filesize);
1623
*newoffset = from as filesystem::Filesize;
1624
Ok(())
1625
} else {
1626
Err(ERRNO_SPIPE)
1627
}
1628
})
1629
}
1630
}
1631
1632
/// Synchronize the data and metadata of a file to disk.
1633
/// Note: This is similar to `fsync` in POSIX.
1634
#[unsafe(no_mangle)]
1635
pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno {
1636
cfg_filesystem_available! {
1637
State::with(|state| {
1638
let ds = state.descriptors();
1639
let file = ds.get_file(fd)?;
1640
file.fd.sync()?;
1641
Ok(())
1642
})
1643
}
1644
}
1645
1646
/// Return the current offset of a file descriptor.
1647
/// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
1648
#[unsafe(no_mangle)]
1649
pub unsafe extern "C" fn fd_tell(fd: Fd, offset: &mut Filesize) -> Errno {
1650
cfg_filesystem_available! {
1651
State::with(|state| {
1652
let ds = state.descriptors();
1653
let file = ds.get_seekable_file(fd)?;
1654
*offset = file.position.get();
1655
Ok(())
1656
})
1657
}
1658
}
1659
1660
/// Write to a file descriptor.
1661
/// Note: This is similar to `writev` in POSIX.
1662
#[unsafe(no_mangle)]
1663
pub unsafe extern "C" fn fd_write(
1664
fd: Fd,
1665
mut iovs_ptr: *const Ciovec,
1666
mut iovs_len: usize,
1667
nwritten: &mut Size,
1668
) -> Errno {
1669
if !matches!(
1670
unsafe { get_allocation_state() },
1671
AllocationState::StackAllocated | AllocationState::StateAllocated
1672
) {
1673
*nwritten = 0;
1674
return ERRNO_IO;
1675
}
1676
1677
let bytes = unsafe {
1678
// Skip leading empty buffers.
1679
while iovs_len > 1 && (*iovs_ptr).buf_len == 0 {
1680
iovs_ptr = iovs_ptr.add(1);
1681
iovs_len -= 1;
1682
}
1683
if iovs_len == 0 {
1684
*nwritten = 0;
1685
return ERRNO_SUCCESS;
1686
}
1687
let ptr = (*iovs_ptr).buf;
1688
let len = (*iovs_ptr).buf_len;
1689
slice::from_raw_parts(ptr, len)
1690
};
1691
1692
State::with(|state| {
1693
let ds = state.descriptors();
1694
match ds.get(fd)? {
1695
Descriptor::Streams(streams) => {
1696
let wasi_stream = streams.get_write_stream()?;
1697
1698
#[cfg(not(feature = "proxy"))]
1699
let nbytes = if let StreamType::File(file) = &streams.type_ {
1700
file.blocking_mode.write(wasi_stream, bytes)?
1701
} else {
1702
// Use blocking writes on non-file streams (stdout, stderr, as sockets
1703
// aren't currently used).
1704
BlockingMode::Blocking.write(wasi_stream, bytes)?
1705
};
1706
#[cfg(feature = "proxy")]
1707
let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?;
1708
1709
// If this is a file, keep the current-position pointer up
1710
// to date. Note that for files that perform appending
1711
// writes this function will always update the current
1712
// position to the end of the file.
1713
//
1714
// NB: this isn't "atomic" as it doesn't necessarily account
1715
// for concurrent writes, but there's not much that can be
1716
// done about that.
1717
#[cfg(not(feature = "proxy"))]
1718
if let StreamType::File(file) = &streams.type_ {
1719
if file.append {
1720
file.position.set(file.fd.stat()?.size);
1721
} else {
1722
file.position.set(file.position.get() + nbytes as u64);
1723
}
1724
}
1725
1726
*nwritten = nbytes;
1727
Ok(())
1728
}
1729
Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),
1730
}
1731
})
1732
}
1733
1734
/// Create a directory.
1735
/// Note: This is similar to `mkdirat` in POSIX.
1736
#[unsafe(no_mangle)]
1737
pub unsafe extern "C" fn path_create_directory(
1738
fd: Fd,
1739
path_ptr: *const u8,
1740
path_len: usize,
1741
) -> Errno {
1742
cfg_filesystem_available! {
1743
let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1744
1745
State::with(|state| {
1746
let ds = state.descriptors();
1747
let file = ds.get_dir(fd)?;
1748
file.fd.create_directory_at(path)?;
1749
Ok(())
1750
})
1751
}
1752
}
1753
1754
/// Return the attributes of a file or directory.
1755
/// Note: This is similar to `stat` in POSIX.
1756
#[unsafe(no_mangle)]
1757
pub unsafe extern "C" fn path_filestat_get(
1758
fd: Fd,
1759
flags: Lookupflags,
1760
path_ptr: *const u8,
1761
path_len: usize,
1762
buf: &mut Filestat,
1763
) -> Errno {
1764
cfg_filesystem_available! {
1765
let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1766
let at_flags = at_flags_from_lookupflags(flags);
1767
1768
State::with(|state| {
1769
let ds = state.descriptors();
1770
let file = ds.get_dir(fd)?;
1771
let stat = file.fd.stat_at(at_flags, path)?;
1772
let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?;
1773
let filetype = stat.type_.into();
1774
*buf = Filestat {
1775
dev: 1,
1776
ino: metadata_hash.lower,
1777
filetype,
1778
nlink: stat.link_count,
1779
size: stat.size,
1780
atim: datetime_to_timestamp(stat.data_access_timestamp),
1781
mtim: datetime_to_timestamp(stat.data_modification_timestamp),
1782
ctim: datetime_to_timestamp(stat.status_change_timestamp),
1783
};
1784
Ok(())
1785
})
1786
}
1787
}
1788
1789
/// Adjust the timestamps of a file or directory.
1790
/// Note: This is similar to `utimensat` in POSIX.
1791
#[unsafe(no_mangle)]
1792
pub unsafe extern "C" fn path_filestat_set_times(
1793
fd: Fd,
1794
flags: Lookupflags,
1795
path_ptr: *const u8,
1796
path_len: usize,
1797
atim: Timestamp,
1798
mtim: Timestamp,
1799
fst_flags: Fstflags,
1800
) -> Errno {
1801
cfg_filesystem_available! {
1802
let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1803
let at_flags = at_flags_from_lookupflags(flags);
1804
1805
State::with(|state| {
1806
let atim = systimespec(
1807
fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM,
1808
atim,
1809
fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW,
1810
)?;
1811
let mtim = systimespec(
1812
fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM,
1813
mtim,
1814
fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW,
1815
)?;
1816
1817
let ds = state.descriptors();
1818
let file = ds.get_dir(fd)?;
1819
file.fd.set_times_at(at_flags, path, atim, mtim)?;
1820
Ok(())
1821
})
1822
}
1823
}
1824
1825
/// Create a hard link.
1826
/// Note: This is similar to `linkat` in POSIX.
1827
#[unsafe(no_mangle)]
1828
pub unsafe extern "C" fn path_link(
1829
old_fd: Fd,
1830
old_flags: Lookupflags,
1831
old_path_ptr: *const u8,
1832
old_path_len: usize,
1833
new_fd: Fd,
1834
new_path_ptr: *const u8,
1835
new_path_len: usize,
1836
) -> Errno {
1837
cfg_filesystem_available! {
1838
let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };
1839
let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };
1840
let at_flags = at_flags_from_lookupflags(old_flags);
1841
1842
State::with(|state| {
1843
let ds = state.descriptors();
1844
let old = &ds.get_dir(old_fd)?.fd;
1845
let new = &ds.get_dir(new_fd)?.fd;
1846
old.link_at(at_flags, old_path, new, new_path)?;
1847
Ok(())
1848
})
1849
}
1850
}
1851
1852
/// Open a file or directory.
1853
/// The returned file descriptor is not guaranteed to be the lowest-numbered
1854
/// file descriptor not currently open; it is randomized to prevent
1855
/// applications from depending on making assumptions about indexes, since this
1856
/// is error-prone in multi-threaded contexts. The returned file descriptor is
1857
/// guaranteed to be less than 2**31.
1858
/// Note: This is similar to `openat` in POSIX.
1859
#[unsafe(no_mangle)]
1860
pub unsafe extern "C" fn path_open(
1861
fd: Fd,
1862
dirflags: Lookupflags,
1863
path_ptr: *const u8,
1864
path_len: usize,
1865
oflags: Oflags,
1866
fs_rights_base: Rights,
1867
fs_rights_inheriting: Rights,
1868
fdflags: Fdflags,
1869
opened_fd: &mut Fd,
1870
) -> Errno {
1871
cfg_filesystem_available! {
1872
let _ = fs_rights_inheriting;
1873
1874
let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1875
let at_flags = at_flags_from_lookupflags(dirflags);
1876
let o_flags = o_flags_from_oflags(oflags);
1877
let flags = descriptor_flags_from_flags(fs_rights_base, fdflags);
1878
let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND;
1879
1880
#[cfg(feature = "proxy")]
1881
return wasi::ERRNO_NOTSUP;
1882
1883
#[cfg(not(feature = "proxy"))]
1884
State::with(|state| {
1885
let result = state
1886
.descriptors()
1887
.get_dir(fd)?
1888
.fd
1889
.open_at(at_flags, path, o_flags, flags)?;
1890
let descriptor_type = result.get_type()?;
1891
let desc = Descriptor::Streams(Streams {
1892
input: OnceCell::new(),
1893
output: OnceCell::new(),
1894
type_: StreamType::File(File {
1895
fd: result,
1896
descriptor_type,
1897
position: Cell::new(0),
1898
append,
1899
blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 {
1900
BlockingMode::Blocking
1901
} else {
1902
BlockingMode::NonBlocking
1903
},
1904
preopen_name_len: None,
1905
}),
1906
});
1907
1908
let fd = state.descriptors_mut().open(desc)?;
1909
*opened_fd = fd;
1910
Ok(())
1911
})
1912
}
1913
}
1914
1915
/// Read the contents of a symbolic link.
1916
/// Note: This is similar to `readlinkat` in POSIX.
1917
#[unsafe(no_mangle)]
1918
pub unsafe extern "C" fn path_readlink(
1919
fd: Fd,
1920
path_ptr: *const u8,
1921
path_len: usize,
1922
buf: *mut u8,
1923
buf_len: Size,
1924
bufused: &mut Size,
1925
) -> Errno {
1926
cfg_filesystem_available! {
1927
let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1928
1929
State::with(|state| {
1930
// If the user gave us a buffer shorter than `PATH_MAX`, it may not be
1931
// long enough to accept the actual path. `cabi_realloc` can't fail,
1932
// so instead we handle this case specially.
1933
let use_state_buf = buf_len < PATH_MAX;
1934
1935
let ds = state.descriptors();
1936
let file = ds.get_dir(fd)?;
1937
let path = if use_state_buf {
1938
state
1939
.with_one_temporary_alloc( || {
1940
file.fd.readlink_at(path)
1941
})?
1942
} else {
1943
state
1944
.with_one_import_alloc(buf, buf_len, || file.fd.readlink_at(path))?
1945
};
1946
1947
if use_state_buf {
1948
// Preview1 follows POSIX in truncating the returned path if it
1949
// doesn't fit.
1950
let len = min(path.len(), buf_len);
1951
unsafe {
1952
ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len);
1953
}
1954
*bufused = len;
1955
} else {
1956
*bufused = path.len();
1957
}
1958
1959
// The returned string's memory was allocated in `buf`, so don't separately
1960
// free it.
1961
forget(path);
1962
1963
Ok(())
1964
})
1965
}
1966
}
1967
1968
/// Remove a directory.
1969
/// Return `errno::notempty` if the directory is not empty.
1970
/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
1971
#[unsafe(no_mangle)]
1972
pub unsafe extern "C" fn path_remove_directory(
1973
fd: Fd,
1974
path_ptr: *const u8,
1975
path_len: usize,
1976
) -> Errno {
1977
cfg_filesystem_available! {
1978
let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
1979
1980
State::with(|state| {
1981
let ds = state.descriptors();
1982
let file = ds.get_dir(fd)?;
1983
file.fd.remove_directory_at(path)?;
1984
Ok(())
1985
})
1986
}
1987
}
1988
1989
/// Rename a file or directory.
1990
/// Note: This is similar to `renameat` in POSIX.
1991
#[unsafe(no_mangle)]
1992
pub unsafe extern "C" fn path_rename(
1993
old_fd: Fd,
1994
old_path_ptr: *const u8,
1995
old_path_len: usize,
1996
new_fd: Fd,
1997
new_path_ptr: *const u8,
1998
new_path_len: usize,
1999
) -> Errno {
2000
cfg_filesystem_available! {
2001
let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };
2002
let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };
2003
2004
State::with(|state| {
2005
let ds = state.descriptors();
2006
let old = &ds.get_dir(old_fd)?.fd;
2007
let new = &ds.get_dir(new_fd)?.fd;
2008
old.rename_at(old_path, new, new_path)?;
2009
Ok(())
2010
})
2011
}
2012
}
2013
2014
/// Create a symbolic link.
2015
/// Note: This is similar to `symlinkat` in POSIX.
2016
#[unsafe(no_mangle)]
2017
pub unsafe extern "C" fn path_symlink(
2018
old_path_ptr: *const u8,
2019
old_path_len: usize,
2020
fd: Fd,
2021
new_path_ptr: *const u8,
2022
new_path_len: usize,
2023
) -> Errno {
2024
cfg_filesystem_available! {
2025
let old_path = unsafe { slice::from_raw_parts(old_path_ptr, old_path_len) };
2026
let new_path = unsafe { slice::from_raw_parts(new_path_ptr, new_path_len) };
2027
2028
State::with(|state| {
2029
let ds = state.descriptors();
2030
let file = ds.get_dir(fd)?;
2031
file.fd.symlink_at(old_path, new_path)?;
2032
Ok(())
2033
})
2034
}
2035
}
2036
2037
/// Unlink a file.
2038
/// Return `errno::isdir` if the path refers to a directory.
2039
/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
2040
#[unsafe(no_mangle)]
2041
pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno {
2042
cfg_filesystem_available! {
2043
let path = unsafe { slice::from_raw_parts(path_ptr, path_len) };
2044
2045
State::with(|state| {
2046
let ds = state.descriptors();
2047
let file = ds.get_dir(fd)?;
2048
file.fd.unlink_file_at(path)?;
2049
Ok(())
2050
})
2051
}
2052
}
2053
2054
struct Pollables {
2055
pointer: *mut Pollable,
2056
index: usize,
2057
length: usize,
2058
}
2059
2060
impl Pollables {
2061
unsafe fn push(&mut self, pollable: Pollable) {
2062
assert!(self.index < self.length);
2063
// Use `ptr::write` instead of `*... = pollable` because `ptr::write`
2064
// doesn't call drop on the old memory.
2065
unsafe {
2066
self.pointer.add(self.index).write(pollable);
2067
}
2068
self.index += 1;
2069
}
2070
}
2071
2072
// We create new pollable handles for each `poll_oneoff` call, so drop them all
2073
// after the call.
2074
impl Drop for Pollables {
2075
fn drop(&mut self) {
2076
while self.index != 0 {
2077
self.index -= 1;
2078
unsafe {
2079
core::ptr::drop_in_place(self.pointer.add(self.index));
2080
}
2081
}
2082
}
2083
}
2084
2085
/// Concurrently poll for the occurrence of a set of events.
2086
#[unsafe(no_mangle)]
2087
pub unsafe extern "C" fn poll_oneoff(
2088
r#in: *const Subscription,
2089
out: *mut Event,
2090
nsubscriptions: Size,
2091
nevents: &mut Size,
2092
) -> Errno {
2093
*nevents = 0;
2094
2095
let subscriptions = unsafe { slice::from_raw_parts(r#in, nsubscriptions) };
2096
2097
// We're going to split the `nevents` buffer into two non-overlapping
2098
// buffers: one to store the pollable handles, and the other to store
2099
// the bool results.
2100
//
2101
// First, we assert that this is possible:
2102
assert!(align_of::<Event>() >= align_of::<Pollable>());
2103
assert!(align_of::<Pollable>() >= align_of::<u32>());
2104
assert!(
2105
nsubscriptions
2106
.checked_mul(size_of::<Event>())
2107
.trapping_unwrap()
2108
>= nsubscriptions
2109
.checked_mul(size_of::<Pollable>())
2110
.trapping_unwrap()
2111
.checked_add(
2112
nsubscriptions
2113
.checked_mul(size_of::<u32>())
2114
.trapping_unwrap()
2115
)
2116
.trapping_unwrap()
2117
);
2118
// Store the pollable handles at the beginning, and the bool results at the
2119
// end, so that we don't clobber the bool results when writing the events.
2120
let pollables = out as *mut c_void as *mut Pollable;
2121
let results = unsafe { out.add(nsubscriptions).cast::<u32>().sub(nsubscriptions) };
2122
2123
// Indefinite sleeping is not supported in preview1.
2124
if nsubscriptions == 0 {
2125
return ERRNO_INVAL;
2126
}
2127
2128
State::with(|state| {
2129
const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw();
2130
const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw();
2131
const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw();
2132
2133
let mut pollables = Pollables {
2134
pointer: pollables,
2135
index: 0,
2136
length: nsubscriptions,
2137
};
2138
2139
for subscription in subscriptions {
2140
let pollable = match subscription.u.tag {
2141
EVENTTYPE_CLOCK => {
2142
let clock = unsafe { &subscription.u.u.clock };
2143
let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME)
2144
== SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME;
2145
match clock.id {
2146
CLOCKID_REALTIME => {
2147
let timeout = if absolute {
2148
// Convert `clock.timeout` to `Datetime`.
2149
let mut datetime = wall_clock::Datetime {
2150
seconds: clock.timeout / 1_000_000_000,
2151
nanoseconds: (clock.timeout % 1_000_000_000) as _,
2152
};
2153
2154
// Subtract `now`.
2155
let now = wall_clock::now();
2156
datetime.seconds -= now.seconds;
2157
if datetime.nanoseconds < now.nanoseconds {
2158
datetime.seconds -= 1;
2159
datetime.nanoseconds += 1_000_000_000;
2160
}
2161
datetime.nanoseconds -= now.nanoseconds;
2162
2163
// Convert to nanoseconds.
2164
let nanos = datetime
2165
.seconds
2166
.checked_mul(1_000_000_000)
2167
.ok_or(ERRNO_OVERFLOW)?;
2168
nanos
2169
.checked_add(datetime.nanoseconds.into())
2170
.ok_or(ERRNO_OVERFLOW)?
2171
} else {
2172
clock.timeout
2173
};
2174
2175
monotonic_clock::subscribe_duration(timeout)
2176
}
2177
2178
CLOCKID_MONOTONIC => {
2179
if absolute {
2180
monotonic_clock::subscribe_instant(clock.timeout)
2181
} else {
2182
monotonic_clock::subscribe_duration(clock.timeout)
2183
}
2184
}
2185
2186
_ => return Err(ERRNO_INVAL),
2187
}
2188
}
2189
2190
EVENTTYPE_FD_READ => state
2191
.descriptors()
2192
.get_read_stream(unsafe { subscription.u.u.fd_read.file_descriptor })
2193
.map(|stream| stream.subscribe())?,
2194
2195
EVENTTYPE_FD_WRITE => state
2196
.descriptors()
2197
.get_write_stream(unsafe { subscription.u.u.fd_write.file_descriptor })
2198
.map(|stream| stream.subscribe())?,
2199
2200
_ => return Err(ERRNO_INVAL),
2201
};
2202
unsafe {
2203
pollables.push(pollable);
2204
}
2205
}
2206
2207
#[link(wasm_import_module = "wasi:io/[email protected]")]
2208
unsafe extern "C" {
2209
#[link_name = "poll"]
2210
fn poll_import(pollables: *const Pollable, len: usize, rval: *mut ReadyList);
2211
}
2212
let mut ready_list = ReadyList {
2213
base: std::ptr::null(),
2214
len: 0,
2215
};
2216
2217
state.with_one_import_alloc(
2218
results.cast(),
2219
nsubscriptions
2220
.checked_mul(size_of::<u32>())
2221
.trapping_unwrap(),
2222
|| unsafe {
2223
poll_import(
2224
pollables.pointer,
2225
pollables.length,
2226
&mut ready_list as *mut _,
2227
);
2228
},
2229
);
2230
2231
assert!(ready_list.len <= nsubscriptions);
2232
assert_eq!(ready_list.base, results as *const u32);
2233
2234
drop(pollables);
2235
2236
let ready = unsafe { std::slice::from_raw_parts(ready_list.base, ready_list.len) };
2237
2238
let mut count = 0;
2239
2240
for subscription in ready {
2241
let subscription = unsafe { *subscriptions.as_ptr().add(*subscription as usize) };
2242
2243
let type_;
2244
2245
let (error, nbytes, flags) = match subscription.u.tag {
2246
EVENTTYPE_CLOCK => {
2247
type_ = wasi::EVENTTYPE_CLOCK;
2248
(ERRNO_SUCCESS, 0, 0)
2249
}
2250
2251
EVENTTYPE_FD_READ => {
2252
type_ = wasi::EVENTTYPE_FD_READ;
2253
let ds = state.descriptors();
2254
let desc = ds
2255
.get(unsafe { subscription.u.u.fd_read.file_descriptor })
2256
.trapping_unwrap();
2257
match desc {
2258
Descriptor::Streams(streams) => match &streams.type_ {
2259
#[cfg(not(feature = "proxy"))]
2260
StreamType::File(file) => match file.fd.stat() {
2261
Ok(stat) => {
2262
let nbytes = stat.size.saturating_sub(file.position.get());
2263
(
2264
ERRNO_SUCCESS,
2265
nbytes,
2266
if nbytes == 0 {
2267
EVENTRWFLAGS_FD_READWRITE_HANGUP
2268
} else {
2269
0
2270
},
2271
)
2272
}
2273
Err(e) => (e.into(), 1, 0),
2274
},
2275
StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
2276
},
2277
_ => unreachable!(),
2278
}
2279
}
2280
EVENTTYPE_FD_WRITE => {
2281
type_ = wasi::EVENTTYPE_FD_WRITE;
2282
let ds = state.descriptors();
2283
let desc = ds
2284
.get(unsafe { subscription.u.u.fd_write.file_descriptor })
2285
.trapping_unwrap();
2286
match desc {
2287
Descriptor::Streams(streams) => match &streams.type_ {
2288
#[cfg(not(feature = "proxy"))]
2289
StreamType::File(_) => (ERRNO_SUCCESS, 1, 0),
2290
StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0),
2291
},
2292
_ => unreachable!(),
2293
}
2294
}
2295
2296
_ => unreachable!(),
2297
};
2298
2299
unsafe {
2300
*out.add(count) = Event {
2301
userdata: subscription.userdata,
2302
error,
2303
type_,
2304
fd_readwrite: EventFdReadwrite { nbytes, flags },
2305
};
2306
}
2307
2308
count += 1;
2309
}
2310
2311
*nevents = count;
2312
2313
Ok(())
2314
})
2315
}
2316
2317
/// Terminate the process normally. An exit code of 0 indicates successful
2318
/// termination of the program. The meanings of other values is dependent on
2319
/// the environment.
2320
#[unsafe(no_mangle)]
2321
pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! {
2322
#[cfg(feature = "proxy")]
2323
{
2324
unreachable!("no other implementation available in proxy world");
2325
}
2326
#[cfg(not(feature = "proxy"))]
2327
{
2328
let status = if rval == 0 { Ok(()) } else { Err(()) };
2329
crate::bindings::wasi::cli::exit::exit(status); // does not return
2330
unreachable!("host exit implementation didn't exit!") // actually unreachable
2331
}
2332
}
2333
2334
/// Send a signal to the process of the calling thread.
2335
/// Note: This is similar to `raise` in POSIX.
2336
#[unsafe(no_mangle)]
2337
pub unsafe extern "C" fn proc_raise(_sig: Signal) -> Errno {
2338
unreachable!()
2339
}
2340
2341
/// Temporarily yield execution of the calling thread.
2342
/// Note: This is similar to `sched_yield` in POSIX.
2343
#[unsafe(no_mangle)]
2344
pub unsafe extern "C" fn sched_yield() -> Errno {
2345
// TODO: This is not yet covered in Preview2.
2346
2347
ERRNO_SUCCESS
2348
}
2349
2350
/// Write high-quality random data into a buffer.
2351
/// This function blocks when the implementation is unable to immediately
2352
/// provide sufficient high-quality random data.
2353
/// This function may execute slowly, so when large mounts of random data are
2354
/// required, it's advisable to use this function to seed a pseudo-random
2355
/// number generator, rather than to provide the random data directly.
2356
#[unsafe(no_mangle)]
2357
pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno {
2358
if matches!(
2359
unsafe { get_allocation_state() },
2360
AllocationState::StackAllocated | AllocationState::StateAllocated
2361
) {
2362
State::with(|state| {
2363
assert_eq!(buf_len as u32 as Size, buf_len);
2364
let result = state
2365
.with_one_import_alloc(buf, buf_len, || random::get_random_bytes(buf_len as u64));
2366
assert_eq!(result.as_ptr(), buf);
2367
2368
// The returned buffer's memory was allocated in `buf`, so don't separately
2369
// free it.
2370
forget(result);
2371
2372
Ok(())
2373
})
2374
} else {
2375
ERRNO_SUCCESS
2376
}
2377
}
2378
2379
/// Accept a new incoming connection.
2380
/// Note: This is similar to `accept` in POSIX.
2381
#[unsafe(no_mangle)]
2382
pub unsafe extern "C" fn sock_accept(_fd: Fd, _flags: Fdflags, _connection: *mut Fd) -> Errno {
2383
unreachable!()
2384
}
2385
2386
/// Receive a message from a socket.
2387
/// Note: This is similar to `recv` in POSIX, though it also supports reading
2388
/// the data into multiple buffers in the manner of `readv`.
2389
#[unsafe(no_mangle)]
2390
pub unsafe extern "C" fn sock_recv(
2391
_fd: Fd,
2392
_ri_data_ptr: *const Iovec,
2393
_ri_data_len: usize,
2394
_ri_flags: Riflags,
2395
_ro_datalen: *mut Size,
2396
_ro_flags: *mut Roflags,
2397
) -> Errno {
2398
unreachable!()
2399
}
2400
2401
/// Send a message on a socket.
2402
/// Note: This is similar to `send` in POSIX, though it also supports writing
2403
/// the data from multiple buffers in the manner of `writev`.
2404
#[unsafe(no_mangle)]
2405
pub unsafe extern "C" fn sock_send(
2406
_fd: Fd,
2407
_si_data_ptr: *const Ciovec,
2408
_si_data_len: usize,
2409
_si_flags: Siflags,
2410
_so_datalen: *mut Size,
2411
) -> Errno {
2412
unreachable!()
2413
}
2414
2415
/// Shut down socket send and receive channels.
2416
/// Note: This is similar to `shutdown` in POSIX.
2417
#[unsafe(no_mangle)]
2418
pub unsafe extern "C" fn sock_shutdown(fd: Fd, _how: Sdflags) -> Errno {
2419
State::with(|state| {
2420
state
2421
.descriptors_mut()
2422
.get_stream_with_error_mut(fd, wasi::ERRNO_BADF)?;
2423
Err(wasi::ERRNO_NOTSOCK)
2424
})
2425
}
2426
2427
#[cfg(not(feature = "proxy"))]
2428
fn datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> Timestamp {
2429
match datetime {
2430
Some(datetime) => u64::from(datetime.nanoseconds)
2431
.saturating_add(datetime.seconds.saturating_mul(1_000_000_000)),
2432
None => 0,
2433
}
2434
}
2435
2436
#[cfg(not(feature = "proxy"))]
2437
fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags {
2438
if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW {
2439
filesystem::PathFlags::SYMLINK_FOLLOW
2440
} else {
2441
filesystem::PathFlags::empty()
2442
}
2443
}
2444
2445
#[cfg(not(feature = "proxy"))]
2446
fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags {
2447
let mut o_flags = filesystem::OpenFlags::empty();
2448
if flags & OFLAGS_CREAT == OFLAGS_CREAT {
2449
o_flags |= filesystem::OpenFlags::CREATE;
2450
}
2451
if flags & OFLAGS_DIRECTORY == OFLAGS_DIRECTORY {
2452
o_flags |= filesystem::OpenFlags::DIRECTORY;
2453
}
2454
if flags & OFLAGS_EXCL == OFLAGS_EXCL {
2455
o_flags |= filesystem::OpenFlags::EXCLUSIVE;
2456
}
2457
if flags & OFLAGS_TRUNC == OFLAGS_TRUNC {
2458
o_flags |= filesystem::OpenFlags::TRUNCATE;
2459
}
2460
o_flags
2461
}
2462
2463
#[cfg(not(feature = "proxy"))]
2464
fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags {
2465
let mut flags = filesystem::DescriptorFlags::empty();
2466
if rights & wasi::RIGHTS_FD_READ == wasi::RIGHTS_FD_READ {
2467
flags |= filesystem::DescriptorFlags::READ;
2468
}
2469
if rights & wasi::RIGHTS_FD_WRITE == wasi::RIGHTS_FD_WRITE {
2470
flags |= filesystem::DescriptorFlags::WRITE;
2471
}
2472
if fdflags & wasi::FDFLAGS_SYNC == wasi::FDFLAGS_SYNC {
2473
flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC;
2474
}
2475
if fdflags & wasi::FDFLAGS_DSYNC == wasi::FDFLAGS_DSYNC {
2476
flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC;
2477
}
2478
if fdflags & wasi::FDFLAGS_RSYNC == wasi::FDFLAGS_RSYNC {
2479
flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC;
2480
}
2481
flags
2482
}
2483
2484
#[cfg(not(feature = "proxy"))]
2485
impl From<filesystem::ErrorCode> for Errno {
2486
#[inline(never)] // Disable inlining as this is bulky and relatively cold.
2487
fn from(err: filesystem::ErrorCode) -> Errno {
2488
match err {
2489
// Use a black box to prevent the optimizer from generating a
2490
// lookup table, which would require a static initializer.
2491
filesystem::ErrorCode::Access => black_box(ERRNO_ACCES),
2492
filesystem::ErrorCode::WouldBlock => ERRNO_AGAIN,
2493
filesystem::ErrorCode::Already => ERRNO_ALREADY,
2494
filesystem::ErrorCode::BadDescriptor => ERRNO_BADF,
2495
filesystem::ErrorCode::Busy => ERRNO_BUSY,
2496
filesystem::ErrorCode::Deadlock => ERRNO_DEADLK,
2497
filesystem::ErrorCode::Quota => ERRNO_DQUOT,
2498
filesystem::ErrorCode::Exist => ERRNO_EXIST,
2499
filesystem::ErrorCode::FileTooLarge => ERRNO_FBIG,
2500
filesystem::ErrorCode::IllegalByteSequence => ERRNO_ILSEQ,
2501
filesystem::ErrorCode::InProgress => ERRNO_INPROGRESS,
2502
filesystem::ErrorCode::Interrupted => ERRNO_INTR,
2503
filesystem::ErrorCode::Invalid => ERRNO_INVAL,
2504
filesystem::ErrorCode::Io => ERRNO_IO,
2505
filesystem::ErrorCode::IsDirectory => ERRNO_ISDIR,
2506
filesystem::ErrorCode::Loop => ERRNO_LOOP,
2507
filesystem::ErrorCode::TooManyLinks => ERRNO_MLINK,
2508
filesystem::ErrorCode::MessageSize => ERRNO_MSGSIZE,
2509
filesystem::ErrorCode::NameTooLong => ERRNO_NAMETOOLONG,
2510
filesystem::ErrorCode::NoDevice => ERRNO_NODEV,
2511
filesystem::ErrorCode::NoEntry => ERRNO_NOENT,
2512
filesystem::ErrorCode::NoLock => ERRNO_NOLCK,
2513
filesystem::ErrorCode::InsufficientMemory => ERRNO_NOMEM,
2514
filesystem::ErrorCode::InsufficientSpace => ERRNO_NOSPC,
2515
filesystem::ErrorCode::Unsupported => ERRNO_NOTSUP,
2516
filesystem::ErrorCode::NotDirectory => ERRNO_NOTDIR,
2517
filesystem::ErrorCode::NotEmpty => ERRNO_NOTEMPTY,
2518
filesystem::ErrorCode::NotRecoverable => ERRNO_NOTRECOVERABLE,
2519
filesystem::ErrorCode::NoTty => ERRNO_NOTTY,
2520
filesystem::ErrorCode::NoSuchDevice => ERRNO_NXIO,
2521
filesystem::ErrorCode::Overflow => ERRNO_OVERFLOW,
2522
filesystem::ErrorCode::NotPermitted => ERRNO_PERM,
2523
filesystem::ErrorCode::Pipe => ERRNO_PIPE,
2524
filesystem::ErrorCode::ReadOnly => ERRNO_ROFS,
2525
filesystem::ErrorCode::InvalidSeek => ERRNO_SPIPE,
2526
filesystem::ErrorCode::TextFileBusy => ERRNO_TXTBSY,
2527
filesystem::ErrorCode::CrossDevice => ERRNO_XDEV,
2528
}
2529
}
2530
}
2531
2532
#[cfg(not(feature = "proxy"))]
2533
impl From<filesystem::DescriptorType> for wasi::Filetype {
2534
fn from(ty: filesystem::DescriptorType) -> wasi::Filetype {
2535
match ty {
2536
filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE,
2537
filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY,
2538
filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE,
2539
filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE,
2540
// preview1 never had a FIFO code.
2541
filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN,
2542
// TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and
2543
// FILETYPE_SOCKET_DGRAM.
2544
filesystem::DescriptorType::Socket => unreachable!(),
2545
filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK,
2546
filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN,
2547
}
2548
}
2549
}
2550
2551
#[derive(Clone, Copy)]
2552
pub enum BlockingMode {
2553
NonBlocking,
2554
Blocking,
2555
}
2556
2557
impl BlockingMode {
2558
// note: these methods must take self, not &self, to avoid rustc creating a constant
2559
// out of a BlockingMode literal that it places in .romem, creating a data section and
2560
// breaking our fragile linking scheme
2561
fn read(
2562
self,
2563
input_stream: &streams::InputStream,
2564
read_len: u64,
2565
) -> Result<Vec<u8>, streams::StreamError> {
2566
match self {
2567
BlockingMode::NonBlocking => input_stream.read(read_len),
2568
BlockingMode::Blocking => input_stream.blocking_read(read_len),
2569
}
2570
}
2571
fn write(
2572
self,
2573
output_stream: &streams::OutputStream,
2574
mut bytes: &[u8],
2575
) -> Result<usize, Errno> {
2576
match self {
2577
BlockingMode::Blocking => {
2578
let total = bytes.len();
2579
loop {
2580
let len = bytes.len().min(4096);
2581
let (chunk, rest) = bytes.split_at(len);
2582
bytes = rest;
2583
match output_stream.blocking_write_and_flush(chunk) {
2584
Ok(()) if bytes.is_empty() => break,
2585
Ok(()) => {}
2586
Err(streams::StreamError::Closed) => return Err(ERRNO_IO),
2587
Err(streams::StreamError::LastOperationFailed(e)) => {
2588
return Err(stream_error_to_errno(e));
2589
}
2590
}
2591
}
2592
Ok(total)
2593
}
2594
2595
BlockingMode::NonBlocking => {
2596
let permit = match output_stream.check_write() {
2597
Ok(n) => n,
2598
Err(streams::StreamError::Closed) => 0,
2599
Err(streams::StreamError::LastOperationFailed(e)) => {
2600
return Err(stream_error_to_errno(e));
2601
}
2602
};
2603
2604
let len = bytes.len().min(permit as usize);
2605
2606
match output_stream.write(&bytes[..len]) {
2607
Ok(_) => {}
2608
Err(streams::StreamError::Closed) => return Ok(0),
2609
Err(streams::StreamError::LastOperationFailed(e)) => {
2610
return Err(stream_error_to_errno(e));
2611
}
2612
}
2613
2614
match output_stream.blocking_flush() {
2615
Ok(_) => {}
2616
Err(streams::StreamError::Closed) => return Ok(0),
2617
Err(streams::StreamError::LastOperationFailed(e)) => {
2618
return Err(stream_error_to_errno(e));
2619
}
2620
}
2621
2622
Ok(len)
2623
}
2624
}
2625
}
2626
}
2627
2628
#[repr(C)]
2629
#[cfg(not(feature = "proxy"))]
2630
pub struct File {
2631
/// The handle to the preview2 descriptor that this file is referencing.
2632
fd: filesystem::Descriptor,
2633
2634
/// The descriptor type, as supplied by filesystem::get_type at opening
2635
descriptor_type: filesystem::DescriptorType,
2636
2637
/// The current-position pointer.
2638
position: Cell<filesystem::Filesize>,
2639
2640
/// In append mode, all writes append to the file.
2641
append: bool,
2642
2643
/// In blocking mode, read and write calls dispatch to blocking_read and
2644
/// blocking_check_write on the underlying streams. When false, read and write
2645
/// dispatch to stream's plain read and check_write.
2646
blocking_mode: BlockingMode,
2647
2648
/// TODO
2649
preopen_name_len: Option<NonZeroUsize>,
2650
}
2651
2652
#[cfg(not(feature = "proxy"))]
2653
impl File {
2654
fn is_dir(&self) -> bool {
2655
match self.descriptor_type {
2656
filesystem::DescriptorType::Directory => true,
2657
_ => false,
2658
}
2659
}
2660
}
2661
2662
const PAGE_SIZE: usize = 65536;
2663
2664
/// The maximum path length. WASI doesn't explicitly guarantee this, but all
2665
/// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this
2666
/// polyfill.
2667
const PATH_MAX: usize = 4096;
2668
2669
/// Maximum number of bytes to cache for a `wasi::Dirent` plus its path name.
2670
const DIRENT_CACHE: usize = 256;
2671
2672
/// A canary value to detect memory corruption within `State`.
2673
const MAGIC: u32 = u32::from_le_bytes(*b"ugh!");
2674
2675
#[repr(C)] // used for now to keep magic1 and magic2 at the start and end
2676
struct State {
2677
/// A canary constant value located at the beginning of this structure to
2678
/// try to catch memory corruption coming from the bottom.
2679
magic1: u32,
2680
2681
/// Used to coordinate allocations of `cabi_import_realloc`
2682
import_alloc: Cell<ImportAlloc>,
2683
2684
/// Storage of mapping from preview1 file descriptors to preview2 file
2685
/// descriptors.
2686
///
2687
/// Do not use this member directly - use State::descriptors() to ensure
2688
/// lazy initialization happens.
2689
descriptors: RefCell<Option<Descriptors>>,
2690
2691
/// TODO
2692
temporary_data: UnsafeCell<MaybeUninit<[u8; temporary_data_size()]>>,
2693
2694
/// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path
2695
/// name that didn't fit into the caller's buffer.
2696
#[cfg(not(feature = "proxy"))]
2697
dirent_cache: DirentCache,
2698
2699
/// The string `..` for use by the directory iterator.
2700
#[cfg(not(feature = "proxy"))]
2701
dotdot: [UnsafeCell<u8>; 2],
2702
2703
/// Another canary constant located at the end of the structure to catch
2704
/// memory corruption coming from the bottom.
2705
magic2: u32,
2706
}
2707
2708
#[cfg(not(feature = "proxy"))]
2709
struct DirentCache {
2710
stream: Cell<Option<DirectoryEntryStream>>,
2711
for_fd: Cell<wasi::Fd>,
2712
cookie: Cell<wasi::Dircookie>,
2713
cached_dirent: Cell<wasi::Dirent>,
2714
path_data: UnsafeCell<MaybeUninit<[u8; DIRENT_CACHE]>>,
2715
}
2716
2717
#[cfg(not(feature = "proxy"))]
2718
struct DirectoryEntryStream(filesystem::DirectoryEntryStream);
2719
2720
#[repr(C)]
2721
pub struct WasmStr {
2722
ptr: *const u8,
2723
len: usize,
2724
}
2725
2726
#[repr(C)]
2727
pub struct WasmStrList {
2728
base: *const WasmStr,
2729
len: usize,
2730
}
2731
2732
#[repr(C)]
2733
pub struct StrTuple {
2734
key: WasmStr,
2735
value: WasmStr,
2736
}
2737
2738
#[derive(Copy, Clone)]
2739
#[repr(C)]
2740
pub struct StrTupleList {
2741
base: *const StrTuple,
2742
len: usize,
2743
}
2744
2745
#[derive(Copy, Clone)]
2746
#[repr(C)]
2747
pub struct ReadyList {
2748
base: *const u32,
2749
len: usize,
2750
}
2751
2752
const fn temporary_data_size() -> usize {
2753
// The total size of the struct should be a page, so start there
2754
let mut start = PAGE_SIZE;
2755
2756
// Remove big chunks of the struct for its various fields.
2757
start -= size_of::<Descriptors>();
2758
#[cfg(not(feature = "proxy"))]
2759
{
2760
start -= size_of::<DirentCache>();
2761
}
2762
2763
// Remove miscellaneous metadata also stored in state.
2764
let misc = if cfg!(feature = "proxy") { 8 } else { 10 };
2765
start -= misc * size_of::<usize>();
2766
2767
// Everything else is the `command_data` allocation.
2768
start
2769
}
2770
2771
// Statically assert that the `State` structure is the size of a wasm page. This
2772
// mostly guarantees that it's not larger than one page which is relied upon
2773
// below.
2774
#[cfg(target_arch = "wasm32")]
2775
const _: () = {
2776
let _size_assert: [(); PAGE_SIZE] = [(); size_of::<State>()];
2777
};
2778
2779
#[expect(unused, reason = "not used in all configurations")]
2780
#[repr(i32)]
2781
enum AllocationState {
2782
StackUnallocated,
2783
StackAllocating,
2784
StackAllocated,
2785
StateAllocating,
2786
StateAllocated,
2787
}
2788
2789
#[expect(improper_ctypes, reason = "types behind pointers")]
2790
unsafe extern "C" {
2791
fn get_state_ptr() -> *mut State;
2792
fn set_state_ptr(state: *mut State);
2793
fn get_allocation_state() -> AllocationState;
2794
fn set_allocation_state(state: AllocationState);
2795
}
2796
2797
impl State {
2798
fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno {
2799
let state_ref = State::ptr();
2800
assert_eq!(state_ref.magic1, MAGIC);
2801
assert_eq!(state_ref.magic2, MAGIC);
2802
let ret = f(state_ref);
2803
match ret {
2804
Ok(()) => ERRNO_SUCCESS,
2805
Err(err) => err,
2806
}
2807
}
2808
2809
fn ptr() -> &'static State {
2810
unsafe {
2811
let mut ptr = get_state_ptr();
2812
if ptr.is_null() {
2813
ptr = State::new();
2814
set_state_ptr(ptr);
2815
}
2816
&*ptr
2817
}
2818
}
2819
2820
#[cold]
2821
fn new() -> *mut State {
2822
#[link(wasm_import_module = "__main_module__")]
2823
unsafe extern "C" {
2824
fn cabi_realloc(
2825
old_ptr: *mut u8,
2826
old_len: usize,
2827
align: usize,
2828
new_len: usize,
2829
) -> *mut u8;
2830
}
2831
2832
assert!(matches!(
2833
unsafe { get_allocation_state() },
2834
AllocationState::StackAllocated
2835
));
2836
2837
unsafe { set_allocation_state(AllocationState::StateAllocating) };
2838
2839
let ret = unsafe {
2840
cabi_realloc(
2841
ptr::null_mut(),
2842
0,
2843
mem::align_of::<UnsafeCell<State>>(),
2844
mem::size_of::<UnsafeCell<State>>(),
2845
) as *mut State
2846
};
2847
2848
unsafe { set_allocation_state(AllocationState::StateAllocated) };
2849
2850
unsafe {
2851
Self::init(ret);
2852
}
2853
2854
ret
2855
}
2856
2857
#[cold]
2858
unsafe fn init(state: *mut State) {
2859
unsafe {
2860
state.write(State {
2861
magic1: MAGIC,
2862
magic2: MAGIC,
2863
import_alloc: Cell::new(ImportAlloc::None),
2864
descriptors: RefCell::new(None),
2865
temporary_data: UnsafeCell::new(MaybeUninit::uninit()),
2866
#[cfg(not(feature = "proxy"))]
2867
dirent_cache: DirentCache {
2868
stream: Cell::new(None),
2869
for_fd: Cell::new(0),
2870
cookie: Cell::new(wasi::DIRCOOKIE_START),
2871
cached_dirent: Cell::new(wasi::Dirent {
2872
d_next: 0,
2873
d_ino: 0,
2874
d_type: FILETYPE_UNKNOWN,
2875
d_namlen: 0,
2876
}),
2877
path_data: UnsafeCell::new(MaybeUninit::uninit()),
2878
},
2879
#[cfg(not(feature = "proxy"))]
2880
dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')],
2881
});
2882
}
2883
}
2884
2885
/// Accessor for the descriptors member that ensures it is properly initialized
2886
fn descriptors<'a>(&'a self) -> impl Deref<Target = Descriptors> + 'a {
2887
let mut d = self
2888
.descriptors
2889
.try_borrow_mut()
2890
.unwrap_or_else(|_| unreachable!());
2891
if d.is_none() {
2892
*d = Some(Descriptors::new(self));
2893
}
2894
RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))
2895
}
2896
2897
/// Mut accessor for the descriptors member that ensures it is properly initialized
2898
fn descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref<Target = Descriptors> + 'a {
2899
let mut d = self
2900
.descriptors
2901
.try_borrow_mut()
2902
.unwrap_or_else(|_| unreachable!());
2903
if d.is_none() {
2904
*d = Some(Descriptors::new(self));
2905
}
2906
RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))
2907
}
2908
2909
unsafe fn temporary_alloc(&self) -> BumpAlloc {
2910
BumpAlloc {
2911
base: self.temporary_data.get().cast(),
2912
len: mem::size_of_val(&self.temporary_data),
2913
}
2914
}
2915
2916
/// Configure that `cabi_import_realloc` will allocate once from
2917
/// `self.temporary_data` for the duration of the closure `f`.
2918
///
2919
/// Panics if the import allocator is already configured.
2920
fn with_one_temporary_alloc<T>(&self, f: impl FnOnce() -> T) -> T {
2921
let alloc = unsafe { self.temporary_alloc() };
2922
self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).0
2923
}
2924
2925
/// Configure that `cabi_import_realloc` will allocate once from
2926
/// `base` with at most `len` bytes for the duration of `f`.
2927
///
2928
/// Panics if the import allocator is already configured.
2929
fn with_one_import_alloc<T>(&self, base: *mut u8, len: usize, f: impl FnOnce() -> T) -> T {
2930
let alloc = BumpAlloc { base, len };
2931
self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).0
2932
}
2933
2934
/// Configures the `alloc` specified to be the allocator for
2935
/// `cabi_import_realloc` for the duration of `f`.
2936
///
2937
/// Panics if the import allocator is already configured.
2938
fn with_import_alloc<T>(&self, alloc: ImportAlloc, f: impl FnOnce() -> T) -> (T, ImportAlloc) {
2939
match self.import_alloc.replace(alloc) {
2940
ImportAlloc::None => {}
2941
_ => unreachable!("import allocator already set"),
2942
}
2943
let r = f();
2944
let alloc = self.import_alloc.replace(ImportAlloc::None);
2945
(r, alloc)
2946
}
2947
}
2948
2949