Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wasi/src/p1.rs
3084 views
1
//! Bindings for WASIp1 aka Preview 1 aka `wasi_snapshot_preview1`.
2
//!
3
//! This module contains runtime support for configuring and executing
4
//! WASIp1-using core WebAssembly modules. Support for WASIp1 is built on top of
5
//! support for WASIp2 available at [the crate root](crate), but that's just an
6
//! internal implementation detail.
7
//!
8
//! Unlike the crate root, support for WASIp1 centers around two APIs:
9
//!
10
//! * [`WasiP1Ctx`]
11
//! * [`add_to_linker_sync`] (or [`add_to_linker_async`])
12
//!
13
//! First a [`WasiCtxBuilder`] will be used and finalized with the [`build_p1`]
14
//! method to create a [`WasiCtx`]. Next a [`wasmtime::Linker`] is configured
15
//! with WASI imports by using the `add_to_linker_*` desired (sync or async).
16
//!
17
//! Note that WASIp1 is not as extensible or configurable as WASIp2 so the
18
//! support in this module is enough to run wasm modules but any customization
19
//! beyond that [`WasiCtxBuilder`] already supports is not possible yet.
20
//!
21
//! [`WasiCtxBuilder`]: crate::WasiCtxBuilder
22
//! [`build_p1`]: crate::WasiCtxBuilder::build_p1
23
//!
24
//! # Components vs Modules
25
//!
26
//! Note that WASIp1 does not work for components at this time, only core wasm
27
//! modules. That means this module is only for users of [`wasmtime::Module`]
28
//! and [`wasmtime::Linker`], for example. If you're using
29
//! [`wasmtime::component::Component`] or [`wasmtime::component::Linker`] you'll
30
//! want the WASIp2 [support this crate has](crate) instead.
31
//!
32
//! # Examples
33
//!
34
//! ```no_run
35
//! use wasmtime::{Result, Engine, Linker, Module, Store};
36
//! use wasmtime_wasi::p1::{self, WasiP1Ctx};
37
//! use wasmtime_wasi::WasiCtxBuilder;
38
//!
39
//! // An example of executing a WASIp1 "command"
40
//! fn main() -> Result<()> {
41
//! let args = std::env::args().skip(1).collect::<Vec<_>>();
42
//! let engine = Engine::default();
43
//! let module = Module::from_file(&engine, &args[0])?;
44
//!
45
//! let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
46
//! p1::add_to_linker_async(&mut linker, |t| t)?;
47
//! let pre = linker.instantiate_pre(&module)?;
48
//!
49
//! let wasi_ctx = WasiCtxBuilder::new()
50
//! .inherit_stdio()
51
//! .inherit_env()
52
//! .args(&args)
53
//! .build_p1();
54
//!
55
//! let mut store = Store::new(&engine, wasi_ctx);
56
//! let instance = pre.instantiate(&mut store)?;
57
//! let func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
58
//! func.call(&mut store, ())?;
59
//!
60
//! Ok(())
61
//! }
62
//! ```
63
64
use crate::cli::WasiCliView as _;
65
use crate::clocks::WasiClocksView as _;
66
use crate::filesystem::WasiFilesystemView as _;
67
use crate::p2::bindings::{
68
cli::{
69
stderr::Host as _, stdin::Host as _, stdout::Host as _, terminal_input, terminal_output,
70
terminal_stderr::Host as _, terminal_stdin::Host as _, terminal_stdout::Host as _,
71
},
72
clocks::{monotonic_clock, wall_clock},
73
filesystem::types as filesystem,
74
};
75
use crate::p2::{FsError, IsATTY};
76
use crate::{ResourceTable, WasiCtx, WasiCtxView, WasiView};
77
use std::collections::{BTreeMap, BTreeSet, HashSet, btree_map};
78
use std::mem::{self, size_of, size_of_val};
79
use std::slice;
80
use std::sync::Arc;
81
use std::sync::atomic::{AtomicU64, Ordering};
82
use system_interface::fs::FileIoExt;
83
use wasmtime::component::Resource;
84
use wasmtime::{bail, error::Context as _};
85
use wasmtime_wasi_io::{
86
bindings::wasi::io::streams,
87
streams::{StreamError, StreamResult},
88
};
89
use wiggle::tracing::instrument;
90
use wiggle::{GuestError, GuestMemory, GuestPtr, GuestType};
91
92
// Bring all WASI traits in scope that this implementation builds on.
93
use crate::p2::bindings::cli::environment::Host as _;
94
use crate::p2::bindings::filesystem::types::HostDescriptor as _;
95
use crate::p2::bindings::random::random::Host as _;
96
use wasmtime_wasi_io::bindings::wasi::io::poll::Host as _;
97
98
/// Structure containing state for WASIp1.
99
///
100
/// This structure is created through [`WasiCtxBuilder::build_p1`] and is
101
/// configured through the various methods of [`WasiCtxBuilder`]. This structure
102
/// itself implements generated traits for WASIp1 as well as [`WasiView`] to
103
/// have access to WASIp2.
104
///
105
/// Instances of [`WasiP1Ctx`] are typically stored within the `T` of
106
/// [`Store<T>`](wasmtime::Store).
107
///
108
/// [`WasiCtxBuilder::build_p1`]: crate::WasiCtxBuilder::build_p1
109
/// [`WasiCtxBuilder`]: crate::WasiCtxBuilder
110
///
111
/// # Examples
112
///
113
/// ```no_run
114
/// use wasmtime::{Result, Linker};
115
/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
116
/// use wasmtime_wasi::WasiCtxBuilder;
117
///
118
/// struct MyState {
119
/// // ... custom state as necessary ...
120
///
121
/// wasi: WasiP1Ctx,
122
/// }
123
///
124
/// impl MyState {
125
/// fn new() -> MyState {
126
/// MyState {
127
/// // .. initialize custom state if needed ..
128
///
129
/// wasi: WasiCtxBuilder::new()
130
/// .arg("./foo.wasm")
131
/// // .. more customization if necessary ..
132
/// .build_p1(),
133
/// }
134
/// }
135
/// }
136
///
137
/// fn add_to_linker(linker: &mut Linker<MyState>) -> Result<()> {
138
/// p1::add_to_linker_sync(linker, |my_state| &mut my_state.wasi)?;
139
/// Ok(())
140
/// }
141
/// ```
142
pub struct WasiP1Ctx {
143
table: ResourceTable,
144
wasi: WasiCtx,
145
adapter: WasiP1Adapter,
146
}
147
148
impl WasiP1Ctx {
149
pub(crate) fn new(wasi: WasiCtx) -> Self {
150
Self {
151
table: ResourceTable::new(),
152
wasi,
153
adapter: WasiP1Adapter::new(),
154
}
155
}
156
}
157
158
impl WasiView for WasiP1Ctx {
159
fn ctx(&mut self) -> WasiCtxView<'_> {
160
WasiCtxView {
161
ctx: &mut self.wasi,
162
table: &mut self.table,
163
}
164
}
165
}
166
167
#[derive(Debug)]
168
struct File {
169
/// The handle to the preview2 descriptor of type [`crate::filesystem::Descriptor::File`].
170
fd: Resource<filesystem::Descriptor>,
171
172
/// The current-position pointer.
173
position: Arc<AtomicU64>,
174
175
/// In append mode, all writes append to the file.
176
append: bool,
177
178
/// When blocking, read and write calls dispatch to blocking_read and
179
/// blocking_check_write on the underlying streams. When false, read and write
180
/// dispatch to stream's plain read and check_write.
181
blocking_mode: BlockingMode,
182
}
183
184
/// NB: p1 files always use blocking writes regardless of what
185
/// they're configured to use since OSes don't have nonblocking
186
/// reads/writes anyway. This behavior originated in the first
187
/// implementation of WASIp1 where flags were propagated to the
188
/// OS and the OS ignored the nonblocking flag for files
189
/// generally.
190
#[derive(Clone, Copy, Debug)]
191
enum BlockingMode {
192
Blocking,
193
NonBlocking,
194
}
195
impl BlockingMode {
196
fn from_fdflags(flags: &types::Fdflags) -> Self {
197
if flags.contains(types::Fdflags::NONBLOCK) {
198
BlockingMode::NonBlocking
199
} else {
200
BlockingMode::Blocking
201
}
202
}
203
async fn read(
204
&self,
205
host: &mut impl streams::HostInputStream,
206
input_stream: Resource<streams::InputStream>,
207
max_size: usize,
208
) -> Result<Vec<u8>, types::Error> {
209
let max_size = max_size.try_into().unwrap_or(u64::MAX);
210
match streams::HostInputStream::blocking_read(host, input_stream, max_size).await {
211
Ok(r) if r.is_empty() => Err(types::Errno::Intr.into()),
212
Ok(r) => Ok(r),
213
Err(StreamError::Closed) => Ok(Vec::new()),
214
Err(e) => Err(e.into()),
215
}
216
}
217
async fn write(
218
&self,
219
memory: &mut GuestMemory<'_>,
220
host: &mut impl streams::HostOutputStream,
221
output_stream: Resource<streams::OutputStream>,
222
bytes: GuestPtr<[u8]>,
223
) -> StreamResult<usize> {
224
use streams::HostOutputStream as Streams;
225
226
let bytes = memory
227
.as_cow(bytes)
228
.map_err(|e| StreamError::Trap(e.into()))?;
229
let mut bytes = &bytes[..];
230
231
let total = bytes.len();
232
while !bytes.is_empty() {
233
// NOTE: blocking_write_and_flush takes at most one 4k buffer.
234
let len = bytes.len().min(4096);
235
let (chunk, rest) = bytes.split_at(len);
236
bytes = rest;
237
238
Streams::blocking_write_and_flush(host, output_stream.borrowed(), Vec::from(chunk))
239
.await?
240
}
241
242
Ok(total)
243
}
244
}
245
246
#[derive(Debug)]
247
enum Descriptor {
248
Stdin {
249
stream: Resource<streams::InputStream>,
250
isatty: IsATTY,
251
},
252
Stdout {
253
stream: Resource<streams::OutputStream>,
254
isatty: IsATTY,
255
},
256
Stderr {
257
stream: Resource<streams::OutputStream>,
258
isatty: IsATTY,
259
},
260
/// A fd of type [`crate::filesystem::Descriptor::Dir`]
261
Directory {
262
fd: Resource<filesystem::Descriptor>,
263
/// The path this directory was preopened as.
264
/// `None` means this directory was opened using `open-at`.
265
preopen_path: Option<String>,
266
},
267
/// A fd of type [`crate::filesystem::Descriptor::File`]
268
File(File),
269
}
270
271
#[derive(Debug, Default)]
272
struct WasiP1Adapter {
273
descriptors: Option<Descriptors>,
274
}
275
276
#[derive(Debug, Default)]
277
struct Descriptors {
278
used: BTreeMap<u32, Descriptor>,
279
free: BTreeSet<u32>,
280
}
281
282
impl Descriptors {
283
/// Initializes [Self] using `preopens`
284
fn new(host: &mut WasiP1Ctx) -> Result<Self, types::Error> {
285
let mut descriptors = Self::default();
286
descriptors.push(Descriptor::Stdin {
287
stream: host
288
.cli()
289
.get_stdin()
290
.context("failed to call `get-stdin`")
291
.map_err(types::Error::trap)?,
292
isatty: if let Some(term_in) = host
293
.cli()
294
.get_terminal_stdin()
295
.context("failed to call `get-terminal-stdin`")
296
.map_err(types::Error::trap)?
297
{
298
terminal_input::HostTerminalInput::drop(&mut host.cli(), term_in)
299
.context("failed to call `drop-terminal-input`")
300
.map_err(types::Error::trap)?;
301
IsATTY::Yes
302
} else {
303
IsATTY::No
304
},
305
})?;
306
descriptors.push(Descriptor::Stdout {
307
stream: host
308
.cli()
309
.get_stdout()
310
.context("failed to call `get-stdout`")
311
.map_err(types::Error::trap)?,
312
isatty: if let Some(term_out) = host
313
.cli()
314
.get_terminal_stdout()
315
.context("failed to call `get-terminal-stdout`")
316
.map_err(types::Error::trap)?
317
{
318
terminal_output::HostTerminalOutput::drop(&mut host.cli(), term_out)
319
.context("failed to call `drop-terminal-output`")
320
.map_err(types::Error::trap)?;
321
IsATTY::Yes
322
} else {
323
IsATTY::No
324
},
325
})?;
326
descriptors.push(Descriptor::Stderr {
327
stream: host
328
.cli()
329
.get_stderr()
330
.context("failed to call `get-stderr`")
331
.map_err(types::Error::trap)?,
332
isatty: if let Some(term_out) = host
333
.cli()
334
.get_terminal_stderr()
335
.context("failed to call `get-terminal-stderr`")
336
.map_err(types::Error::trap)?
337
{
338
terminal_output::HostTerminalOutput::drop(&mut host.cli(), term_out)
339
.context("failed to call `drop-terminal-output`")
340
.map_err(types::Error::trap)?;
341
IsATTY::Yes
342
} else {
343
IsATTY::No
344
},
345
})?;
346
347
for dir in host
348
.filesystem()
349
.get_directories()
350
.context("failed to call `get-directories`")
351
.map_err(types::Error::trap)?
352
{
353
descriptors.push(Descriptor::Directory {
354
fd: dir.0,
355
preopen_path: Some(dir.1),
356
})?;
357
}
358
Ok(descriptors)
359
}
360
361
/// Returns next descriptor number, which was never assigned
362
fn unused(&self) -> Result<u32> {
363
match self.used.last_key_value() {
364
Some((fd, _)) => {
365
if let Some(fd) = fd.checked_add(1) {
366
return Ok(fd);
367
}
368
if self.used.len() == u32::MAX as usize {
369
return Err(types::Errno::Loop.into());
370
}
371
// TODO: Optimize
372
Ok((0..u32::MAX)
373
.rev()
374
.find(|fd| !self.used.contains_key(fd))
375
.expect("failed to find an unused file descriptor"))
376
}
377
None => Ok(0),
378
}
379
}
380
381
/// Pushes the [Descriptor] returning corresponding number.
382
/// This operation will try to reuse numbers previously removed via [`Self::remove`]
383
/// and rely on [`Self::unused`] if no free numbers are recorded
384
fn push(&mut self, desc: Descriptor) -> Result<u32> {
385
let fd = if let Some(fd) = self.free.pop_last() {
386
fd
387
} else {
388
self.unused()?
389
};
390
assert!(self.used.insert(fd, desc).is_none());
391
Ok(fd)
392
}
393
}
394
395
impl WasiP1Adapter {
396
fn new() -> Self {
397
Self::default()
398
}
399
}
400
401
/// A mutably-borrowed `WasiP1Ctx`, which provides access to the stored
402
/// state. It can be thought of as an in-flight [`WasiP1Adapter`] transaction, all
403
/// changes will be recorded in the underlying [`WasiP1Adapter`] returned by
404
/// [`WasiPreview1View::adapter_mut`] on [`Drop`] of this struct.
405
// NOTE: This exists for the most part just due to the fact that `bindgen` generates methods with
406
// `&mut self` receivers and so this struct lets us extend the lifetime of the `&mut self` borrow
407
// of the [`WasiPreview1View`] to provide means to return mutably and immutably borrowed [`Descriptors`]
408
// without having to rely on something like `Arc<Mutex<Descriptors>>`, while also being able to
409
// call methods like [`Descriptor::is_file`] and hiding complexity from p1 method implementations.
410
struct Transaction<'a> {
411
view: &'a mut WasiP1Ctx,
412
descriptors: Descriptors,
413
}
414
415
impl Drop for Transaction<'_> {
416
/// Record changes in the [`WasiP1Adapter`] .
417
fn drop(&mut self) {
418
let descriptors = mem::take(&mut self.descriptors);
419
self.view.adapter.descriptors = Some(descriptors);
420
}
421
}
422
423
impl Transaction<'_> {
424
/// Borrows [`Descriptor`] corresponding to `fd`.
425
///
426
/// # Errors
427
///
428
/// Returns [`types::Errno::Badf`] if no [`Descriptor`] is found
429
fn get_descriptor(&self, fd: types::Fd) -> Result<&Descriptor> {
430
let fd = fd.into();
431
let desc = self.descriptors.used.get(&fd).ok_or(types::Errno::Badf)?;
432
Ok(desc)
433
}
434
435
/// Borrows [`File`] corresponding to `fd`
436
/// if it describes a [`Descriptor::File`]
437
fn get_file(&self, fd: types::Fd) -> Result<&File> {
438
let fd = fd.into();
439
match self.descriptors.used.get(&fd) {
440
Some(Descriptor::File(file)) => Ok(file),
441
_ => Err(types::Errno::Badf.into()),
442
}
443
}
444
445
/// Mutably borrows [`File`] corresponding to `fd`
446
/// if it describes a [`Descriptor::File`]
447
fn get_file_mut(&mut self, fd: types::Fd) -> Result<&mut File> {
448
let fd = fd.into();
449
match self.descriptors.used.get_mut(&fd) {
450
Some(Descriptor::File(file)) => Ok(file),
451
_ => Err(types::Errno::Badf.into()),
452
}
453
}
454
455
/// Borrows [`File`] corresponding to `fd`
456
/// if it describes a [`Descriptor::File`]
457
///
458
/// # Errors
459
///
460
/// Returns [`types::Errno::Spipe`] if the descriptor corresponds to stdio
461
fn get_seekable(&self, fd: types::Fd) -> Result<&File> {
462
let fd = fd.into();
463
match self.descriptors.used.get(&fd) {
464
Some(Descriptor::File(file)) => Ok(file),
465
Some(
466
Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. },
467
) => {
468
// NOTE: legacy implementation returns SPIPE here
469
Err(types::Errno::Spipe.into())
470
}
471
_ => Err(types::Errno::Badf.into()),
472
}
473
}
474
475
/// Returns [`filesystem::Descriptor`] corresponding to `fd`
476
fn get_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
477
match self.get_descriptor(fd)? {
478
Descriptor::File(File { fd, .. }) => Ok(fd.borrowed()),
479
Descriptor::Directory { fd, .. } => Ok(fd.borrowed()),
480
Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => {
481
Err(types::Errno::Badf.into())
482
}
483
}
484
}
485
486
/// Returns [`filesystem::Descriptor`] corresponding to `fd`
487
/// if it describes a [`Descriptor::File`]
488
fn get_file_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
489
self.get_file(fd).map(|File { fd, .. }| fd.borrowed())
490
}
491
492
/// Returns [`filesystem::Descriptor`] corresponding to `fd`
493
/// if it describes a [`Descriptor::Directory`]
494
fn get_dir_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
495
let fd = fd.into();
496
match self.descriptors.used.get(&fd) {
497
Some(Descriptor::Directory { fd, .. }) => Ok(fd.borrowed()),
498
_ => Err(types::Errno::Badf.into()),
499
}
500
}
501
}
502
503
impl WasiP1Ctx {
504
/// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
505
/// and returns [`Transaction`] on success
506
fn transact(&mut self) -> Result<Transaction<'_>, types::Error> {
507
let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() {
508
descriptors
509
} else {
510
Descriptors::new(self)?
511
};
512
Ok(Transaction {
513
view: self,
514
descriptors,
515
})
516
}
517
518
/// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
519
/// and returns [`filesystem::Descriptor`] corresponding to `fd`
520
fn get_fd(&mut self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>, types::Error> {
521
let st = self.transact()?;
522
let fd = st.get_fd(fd)?;
523
Ok(fd)
524
}
525
526
/// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
527
/// and returns [`filesystem::Descriptor`] corresponding to `fd`
528
/// if it describes a [`Descriptor::File`] of [`crate::p2::filesystem::File`] type
529
fn get_file_fd(
530
&mut self,
531
fd: types::Fd,
532
) -> Result<Resource<filesystem::Descriptor>, types::Error> {
533
let st = self.transact()?;
534
let fd = st.get_file_fd(fd)?;
535
Ok(fd)
536
}
537
538
/// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`]
539
/// and returns [`filesystem::Descriptor`] corresponding to `fd`
540
/// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`]
541
/// of [`crate::p2::filesystem::Dir`] type
542
fn get_dir_fd(
543
&mut self,
544
fd: types::Fd,
545
) -> Result<Resource<filesystem::Descriptor>, types::Error> {
546
let st = self.transact()?;
547
let fd = st.get_dir_fd(fd)?;
548
Ok(fd)
549
}
550
551
/// Shared implementation of `fd_write` and `fd_pwrite`.
552
async fn fd_write_impl(
553
&mut self,
554
memory: &mut GuestMemory<'_>,
555
fd: types::Fd,
556
ciovs: types::CiovecArray,
557
write: FdWrite,
558
) -> Result<types::Size, types::Error> {
559
let t = self.transact()?;
560
let desc = t.get_descriptor(fd)?;
561
match desc {
562
Descriptor::File(File {
563
fd,
564
append,
565
position,
566
// NB: files always use blocking writes regardless of what
567
// they're configured to use since OSes don't have nonblocking
568
// reads/writes anyway. This behavior originated in the first
569
// implementation of WASIp1 where flags were propagated to the
570
// OS and the OS ignored the nonblocking flag for files
571
// generally.
572
blocking_mode: _,
573
}) => {
574
let fd = fd.borrowed();
575
let position = position.clone();
576
let pos = position.load(Ordering::Relaxed);
577
let append = *append;
578
drop(t);
579
let f = self.table.get(&fd)?.file()?;
580
let buf = first_non_empty_ciovec(memory, ciovs)?;
581
582
let do_write = move |f: &cap_std::fs::File, buf: &[u8]| match (append, write) {
583
// Note that this is implementing Linux semantics of
584
// `pwrite` where the offset is ignored if the file was
585
// opened in append mode.
586
(true, _) => f.append(&buf),
587
(false, FdWrite::At(pos)) => f.write_at(&buf, pos),
588
(false, FdWrite::AtCur) => f.write_at(&buf, pos),
589
};
590
591
let nwritten = match f.as_blocking_file() {
592
// If we can block then skip the copy out of wasm memory and
593
// write directly to `f`.
594
Some(f) => do_write(f, &memory.as_cow(buf)?),
595
// ... otherwise copy out of wasm memory and use
596
// `spawn_blocking` to do this write in a thread that can
597
// block.
598
None => {
599
let buf = memory.to_vec(buf)?;
600
f.run_blocking(move |f| do_write(f, &buf)).await
601
}
602
};
603
604
let nwritten = nwritten.map_err(|e| StreamError::LastOperationFailed(e.into()))?;
605
606
// If this was a write at the current position then update the
607
// current position with the result, otherwise the current
608
// position is left unmodified.
609
if let FdWrite::AtCur = write {
610
if append {
611
let len = self.filesystem().stat(fd).await?;
612
position.store(len.size, Ordering::Relaxed);
613
} else {
614
let pos = pos
615
.checked_add(nwritten as u64)
616
.ok_or(types::Errno::Overflow)?;
617
position.store(pos, Ordering::Relaxed);
618
}
619
}
620
Ok(nwritten.try_into()?)
621
}
622
Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
623
match write {
624
// Reject calls to `fd_pwrite` on stdio descriptors...
625
FdWrite::At(_) => return Err(types::Errno::Spipe.into()),
626
// ... but allow calls to `fd_write`
627
FdWrite::AtCur => {}
628
}
629
let stream = stream.borrowed();
630
drop(t);
631
let buf = first_non_empty_ciovec(memory, ciovs)?;
632
let n = BlockingMode::Blocking
633
.write(memory, &mut self.table, stream, buf)
634
.await?
635
.try_into()?;
636
Ok(n)
637
}
638
_ => Err(types::Errno::Badf.into()),
639
}
640
}
641
}
642
643
#[derive(Copy, Clone)]
644
enum FdWrite {
645
At(u64),
646
AtCur,
647
}
648
649
/// Adds asynchronous versions of all WASIp1 functions to the
650
/// [`wasmtime::Linker`] provided.
651
///
652
/// This method will add WASIp1 functions to `linker`. Access to [`WasiP1Ctx`]
653
/// is provided with `f` by projecting from the store-local state of `T` to
654
/// [`WasiP1Ctx`]. The closure `f` is invoked every time a WASIp1 function is
655
/// called to get access to [`WasiP1Ctx`] from `T`. The returned [`WasiP1Ctx`] is
656
/// used to implement I/O and controls what each function will return.
657
///
658
/// It's recommended that [`WasiP1Ctx`] is stored as a field in `T` or that `T =
659
/// WasiP1Ctx` itself. The closure `f` should be a small projection (e.g. `&mut
660
/// arg.field`) or something otherwise "small" as it will be executed every time
661
/// a WASI call is made.
662
///
663
/// If you're looking for a synchronous version see [`add_to_linker_sync`].
664
///
665
/// # Examples
666
///
667
/// If the `T` in `Linker<T>` is just `WasiP1Ctx`:
668
///
669
/// ```no_run
670
/// use wasmtime::{Result, Linker, Engine, Config};
671
/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
672
///
673
/// fn main() -> Result<()> {
674
/// let engine = Engine::default();
675
///
676
/// let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
677
/// p1::add_to_linker_async(&mut linker, |cx| cx)?;
678
///
679
/// // ... continue to add more to `linker` as necessary and use it ...
680
///
681
/// Ok(())
682
/// }
683
/// ```
684
///
685
/// If the `T` in `Linker<T>` is custom state:
686
///
687
/// ```no_run
688
/// use wasmtime::{Result, Linker, Engine, Config};
689
/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
690
///
691
/// struct MyState {
692
/// // .. other custom state here ..
693
///
694
/// wasi: WasiP1Ctx,
695
/// }
696
///
697
/// fn main() -> Result<()> {
698
/// let engine = Engine::default();
699
///
700
/// let mut linker: Linker<MyState> = Linker::new(&engine);
701
/// p1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?;
702
///
703
/// // ... continue to add more to `linker` as necessary and use it ...
704
///
705
/// Ok(())
706
/// }
707
/// ```
708
pub fn add_to_linker_async<T: Send + 'static>(
709
linker: &mut wasmtime::Linker<T>,
710
f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
711
) -> wasmtime::Result<()> {
712
crate::p1::wasi_snapshot_preview1::add_to_linker(linker, f)
713
}
714
715
/// Adds synchronous versions of all WASIp1 functions to the
716
/// [`wasmtime::Linker`] provided.
717
///
718
/// This method will add WASIp1 functions to `linker`. Access to [`WasiP1Ctx`]
719
/// is provided with `f` by projecting from the store-local state of `T` to
720
/// [`WasiP1Ctx`]. The closure `f` is invoked every time a WASIp1 function is
721
/// called to get access to [`WasiP1Ctx`] from `T`. The returned [`WasiP1Ctx`] is
722
/// used to implement I/O and controls what each function will return.
723
///
724
/// It's recommended that [`WasiP1Ctx`] is stored as a field in `T` or that `T =
725
/// WasiP1Ctx` itself. The closure `f` should be a small projection (e.g. `&mut
726
/// arg.field`) or something otherwise "small" as it will be executed every time
727
/// a WASI call is made.
728
///
729
/// If you're looking for a synchronous version see [`add_to_linker_async`].
730
///
731
/// # Examples
732
///
733
/// If the `T` in `Linker<T>` is just `WasiP1Ctx`:
734
///
735
/// ```no_run
736
/// use wasmtime::{Result, Linker, Engine};
737
/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
738
///
739
/// fn main() -> Result<()> {
740
/// let engine = Engine::default();
741
///
742
/// let mut linker: Linker<WasiP1Ctx> = Linker::new(&engine);
743
/// p1::add_to_linker_async(&mut linker, |cx| cx)?;
744
///
745
/// // ... continue to add more to `linker` as necessary and use it ...
746
///
747
/// Ok(())
748
/// }
749
/// ```
750
///
751
/// If the `T` in `Linker<T>` is custom state:
752
///
753
/// ```no_run
754
/// use wasmtime::{Result, Linker, Engine};
755
/// use wasmtime_wasi::p1::{self, WasiP1Ctx};
756
///
757
/// struct MyState {
758
/// // .. other custom state here ..
759
///
760
/// wasi: WasiP1Ctx,
761
/// }
762
///
763
/// fn main() -> Result<()> {
764
/// let engine = Engine::default();
765
///
766
/// let mut linker: Linker<MyState> = Linker::new(&engine);
767
/// p1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?;
768
///
769
/// // ... continue to add more to `linker` as necessary and use it ...
770
///
771
/// Ok(())
772
/// }
773
/// ```
774
pub fn add_to_linker_sync<T: Send + 'static>(
775
linker: &mut wasmtime::Linker<T>,
776
f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static,
777
) -> wasmtime::Result<()> {
778
sync::add_wasi_snapshot_preview1_to_linker(linker, f)
779
}
780
781
// Generate the wasi_snapshot_preview1::WasiSnapshotPreview1 trait,
782
// and the module types.
783
// None of the generated modules, traits, or types should be used externally
784
// to this module.
785
wiggle::from_witx!({
786
witx: ["witx/p1/wasi_snapshot_preview1.witx"],
787
async: {
788
wasi_snapshot_preview1::{
789
fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
790
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
791
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
792
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
793
path_rename, path_symlink, path_unlink_file
794
}
795
},
796
errors: { errno => trappable Error },
797
});
798
799
pub(crate) mod sync {
800
use std::future::Future;
801
use wasmtime::Result;
802
803
wiggle::wasmtime_integration!({
804
witx: ["witx/p1/wasi_snapshot_preview1.witx"],
805
target: super,
806
block_on[in_tokio]: {
807
wasi_snapshot_preview1::{
808
fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
809
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
810
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
811
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
812
path_rename, path_symlink, path_unlink_file
813
}
814
},
815
errors: { errno => trappable Error },
816
});
817
818
// Small wrapper around `in_tokio` to add a `Result` layer which is always
819
// `Ok`
820
fn in_tokio<F: Future>(future: F) -> Result<F::Output> {
821
Ok(crate::runtime::in_tokio(future))
822
}
823
}
824
825
impl wiggle::GuestErrorType for types::Errno {
826
fn success() -> Self {
827
Self::Success
828
}
829
}
830
831
impl From<StreamError> for types::Error {
832
fn from(err: StreamError) -> Self {
833
match err {
834
StreamError::Closed => types::Errno::Io.into(),
835
StreamError::LastOperationFailed(e) => match e.downcast::<std::io::Error>() {
836
Ok(err) => filesystem::ErrorCode::from(err).into(),
837
Err(e) => {
838
tracing::debug!("dropping error {e:?}");
839
types::Errno::Io.into()
840
}
841
},
842
StreamError::Trap(e) => types::Error::trap(e),
843
}
844
}
845
}
846
847
impl From<FsError> for types::Error {
848
fn from(err: FsError) -> Self {
849
match err.downcast() {
850
Ok(code) => code.into(),
851
Err(e) => types::Error::trap(e),
852
}
853
}
854
}
855
856
fn systimespec(set: bool, ts: types::Timestamp, now: bool) -> Result<filesystem::NewTimestamp> {
857
if set && now {
858
Err(types::Errno::Inval.into())
859
} else if set {
860
Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime {
861
seconds: ts / 1_000_000_000,
862
nanoseconds: (ts % 1_000_000_000) as _,
863
}))
864
} else if now {
865
Ok(filesystem::NewTimestamp::Now)
866
} else {
867
Ok(filesystem::NewTimestamp::NoChange)
868
}
869
}
870
871
impl TryFrom<wall_clock::Datetime> for types::Timestamp {
872
type Error = types::Errno;
873
874
fn try_from(
875
wall_clock::Datetime {
876
seconds,
877
nanoseconds,
878
}: wall_clock::Datetime,
879
) -> Result<Self, Self::Error> {
880
types::Timestamp::from(seconds)
881
.checked_mul(1_000_000_000)
882
.and_then(|ns| ns.checked_add(nanoseconds.into()))
883
.ok_or(types::Errno::Overflow)
884
}
885
}
886
887
impl From<types::Lookupflags> for filesystem::PathFlags {
888
fn from(flags: types::Lookupflags) -> Self {
889
if flags.contains(types::Lookupflags::SYMLINK_FOLLOW) {
890
filesystem::PathFlags::SYMLINK_FOLLOW
891
} else {
892
filesystem::PathFlags::empty()
893
}
894
}
895
}
896
897
impl From<types::Oflags> for filesystem::OpenFlags {
898
fn from(flags: types::Oflags) -> Self {
899
let mut out = filesystem::OpenFlags::empty();
900
if flags.contains(types::Oflags::CREAT) {
901
out |= filesystem::OpenFlags::CREATE;
902
}
903
if flags.contains(types::Oflags::DIRECTORY) {
904
out |= filesystem::OpenFlags::DIRECTORY;
905
}
906
if flags.contains(types::Oflags::EXCL) {
907
out |= filesystem::OpenFlags::EXCLUSIVE;
908
}
909
if flags.contains(types::Oflags::TRUNC) {
910
out |= filesystem::OpenFlags::TRUNCATE;
911
}
912
out
913
}
914
}
915
916
impl From<types::Advice> for filesystem::Advice {
917
fn from(advice: types::Advice) -> Self {
918
match advice {
919
types::Advice::Normal => filesystem::Advice::Normal,
920
types::Advice::Sequential => filesystem::Advice::Sequential,
921
types::Advice::Random => filesystem::Advice::Random,
922
types::Advice::Willneed => filesystem::Advice::WillNeed,
923
types::Advice::Dontneed => filesystem::Advice::DontNeed,
924
types::Advice::Noreuse => filesystem::Advice::NoReuse,
925
}
926
}
927
}
928
929
impl TryFrom<filesystem::DescriptorType> for types::Filetype {
930
type Error = wasmtime::Error;
931
932
fn try_from(ty: filesystem::DescriptorType) -> Result<Self, Self::Error> {
933
match ty {
934
filesystem::DescriptorType::RegularFile => Ok(types::Filetype::RegularFile),
935
filesystem::DescriptorType::Directory => Ok(types::Filetype::Directory),
936
filesystem::DescriptorType::BlockDevice => Ok(types::Filetype::BlockDevice),
937
filesystem::DescriptorType::CharacterDevice => Ok(types::Filetype::CharacterDevice),
938
// p1 never had a FIFO code.
939
filesystem::DescriptorType::Fifo => Ok(types::Filetype::Unknown),
940
// TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and
941
// FILETYPE_SOCKET_DGRAM.
942
filesystem::DescriptorType::Socket => {
943
bail!("sockets are not currently supported")
944
}
945
filesystem::DescriptorType::SymbolicLink => Ok(types::Filetype::SymbolicLink),
946
filesystem::DescriptorType::Unknown => Ok(types::Filetype::Unknown),
947
}
948
}
949
}
950
951
impl From<IsATTY> for types::Filetype {
952
fn from(isatty: IsATTY) -> Self {
953
match isatty {
954
IsATTY::Yes => types::Filetype::CharacterDevice,
955
IsATTY::No => types::Filetype::Unknown,
956
}
957
}
958
}
959
960
impl From<crate::filesystem::ErrorCode> for types::Errno {
961
fn from(code: crate::filesystem::ErrorCode) -> Self {
962
match code {
963
crate::filesystem::ErrorCode::Access => types::Errno::Acces,
964
crate::filesystem::ErrorCode::Already => types::Errno::Already,
965
crate::filesystem::ErrorCode::BadDescriptor => types::Errno::Badf,
966
crate::filesystem::ErrorCode::Busy => types::Errno::Busy,
967
crate::filesystem::ErrorCode::Exist => types::Errno::Exist,
968
crate::filesystem::ErrorCode::FileTooLarge => types::Errno::Fbig,
969
crate::filesystem::ErrorCode::IllegalByteSequence => types::Errno::Ilseq,
970
crate::filesystem::ErrorCode::InProgress => types::Errno::Inprogress,
971
crate::filesystem::ErrorCode::Interrupted => types::Errno::Intr,
972
crate::filesystem::ErrorCode::Invalid => types::Errno::Inval,
973
crate::filesystem::ErrorCode::Io => types::Errno::Io,
974
crate::filesystem::ErrorCode::IsDirectory => types::Errno::Isdir,
975
crate::filesystem::ErrorCode::Loop => types::Errno::Loop,
976
crate::filesystem::ErrorCode::TooManyLinks => types::Errno::Mlink,
977
crate::filesystem::ErrorCode::NameTooLong => types::Errno::Nametoolong,
978
crate::filesystem::ErrorCode::NoEntry => types::Errno::Noent,
979
crate::filesystem::ErrorCode::InsufficientMemory => types::Errno::Nomem,
980
crate::filesystem::ErrorCode::InsufficientSpace => types::Errno::Nospc,
981
crate::filesystem::ErrorCode::Unsupported => types::Errno::Notsup,
982
crate::filesystem::ErrorCode::NotDirectory => types::Errno::Notdir,
983
crate::filesystem::ErrorCode::NotEmpty => types::Errno::Notempty,
984
crate::filesystem::ErrorCode::Overflow => types::Errno::Overflow,
985
crate::filesystem::ErrorCode::NotPermitted => types::Errno::Perm,
986
crate::filesystem::ErrorCode::Pipe => types::Errno::Pipe,
987
crate::filesystem::ErrorCode::InvalidSeek => types::Errno::Spipe,
988
}
989
}
990
}
991
992
impl From<filesystem::ErrorCode> for types::Errno {
993
fn from(code: filesystem::ErrorCode) -> Self {
994
match code {
995
filesystem::ErrorCode::Access => types::Errno::Acces,
996
filesystem::ErrorCode::WouldBlock => types::Errno::Again,
997
filesystem::ErrorCode::Already => types::Errno::Already,
998
filesystem::ErrorCode::BadDescriptor => types::Errno::Badf,
999
filesystem::ErrorCode::Busy => types::Errno::Busy,
1000
filesystem::ErrorCode::Deadlock => types::Errno::Deadlk,
1001
filesystem::ErrorCode::Quota => types::Errno::Dquot,
1002
filesystem::ErrorCode::Exist => types::Errno::Exist,
1003
filesystem::ErrorCode::FileTooLarge => types::Errno::Fbig,
1004
filesystem::ErrorCode::IllegalByteSequence => types::Errno::Ilseq,
1005
filesystem::ErrorCode::InProgress => types::Errno::Inprogress,
1006
filesystem::ErrorCode::Interrupted => types::Errno::Intr,
1007
filesystem::ErrorCode::Invalid => types::Errno::Inval,
1008
filesystem::ErrorCode::Io => types::Errno::Io,
1009
filesystem::ErrorCode::IsDirectory => types::Errno::Isdir,
1010
filesystem::ErrorCode::Loop => types::Errno::Loop,
1011
filesystem::ErrorCode::TooManyLinks => types::Errno::Mlink,
1012
filesystem::ErrorCode::MessageSize => types::Errno::Msgsize,
1013
filesystem::ErrorCode::NameTooLong => types::Errno::Nametoolong,
1014
filesystem::ErrorCode::NoDevice => types::Errno::Nodev,
1015
filesystem::ErrorCode::NoEntry => types::Errno::Noent,
1016
filesystem::ErrorCode::NoLock => types::Errno::Nolck,
1017
filesystem::ErrorCode::InsufficientMemory => types::Errno::Nomem,
1018
filesystem::ErrorCode::InsufficientSpace => types::Errno::Nospc,
1019
filesystem::ErrorCode::Unsupported => types::Errno::Notsup,
1020
filesystem::ErrorCode::NotDirectory => types::Errno::Notdir,
1021
filesystem::ErrorCode::NotEmpty => types::Errno::Notempty,
1022
filesystem::ErrorCode::NotRecoverable => types::Errno::Notrecoverable,
1023
filesystem::ErrorCode::NoTty => types::Errno::Notty,
1024
filesystem::ErrorCode::NoSuchDevice => types::Errno::Nxio,
1025
filesystem::ErrorCode::Overflow => types::Errno::Overflow,
1026
filesystem::ErrorCode::NotPermitted => types::Errno::Perm,
1027
filesystem::ErrorCode::Pipe => types::Errno::Pipe,
1028
filesystem::ErrorCode::ReadOnly => types::Errno::Rofs,
1029
filesystem::ErrorCode::InvalidSeek => types::Errno::Spipe,
1030
filesystem::ErrorCode::TextFileBusy => types::Errno::Txtbsy,
1031
filesystem::ErrorCode::CrossDevice => types::Errno::Xdev,
1032
}
1033
}
1034
}
1035
1036
impl From<std::num::TryFromIntError> for types::Error {
1037
fn from(_: std::num::TryFromIntError) -> Self {
1038
types::Errno::Overflow.into()
1039
}
1040
}
1041
1042
impl From<GuestError> for types::Error {
1043
fn from(err: GuestError) -> Self {
1044
use wiggle::GuestError::*;
1045
match err {
1046
InvalidFlagValue { .. } => types::Errno::Inval.into(),
1047
InvalidEnumValue { .. } => types::Errno::Inval.into(),
1048
// As per
1049
// https://github.com/WebAssembly/wasi/blob/main/legacy/tools/witx-docs.md#pointers
1050
//
1051
// > If a misaligned pointer is passed to a function, the function
1052
// > shall trap.
1053
// >
1054
// > If an out-of-bounds pointer is passed to a function and the
1055
// > function needs to dereference it, the function shall trap.
1056
//
1057
// so this turns OOB and misalignment errors into traps.
1058
PtrOverflow { .. } | PtrOutOfBounds { .. } | PtrNotAligned { .. } => {
1059
types::Error::trap(err.into())
1060
}
1061
InvalidUtf8 { .. } => types::Errno::Ilseq.into(),
1062
TryFromIntError { .. } => types::Errno::Overflow.into(),
1063
SliceLengthsDiffer { .. } => types::Errno::Fault.into(),
1064
InFunc { err, .. } => types::Error::from(*err),
1065
}
1066
}
1067
}
1068
1069
impl From<filesystem::ErrorCode> for types::Error {
1070
fn from(code: filesystem::ErrorCode) -> Self {
1071
types::Errno::from(code).into()
1072
}
1073
}
1074
1075
impl From<crate::filesystem::ErrorCode> for types::Error {
1076
fn from(code: crate::filesystem::ErrorCode) -> Self {
1077
types::Errno::from(code).into()
1078
}
1079
}
1080
1081
impl From<wasmtime::component::ResourceTableError> for types::Error {
1082
fn from(err: wasmtime::component::ResourceTableError) -> Self {
1083
types::Error::trap(err.into())
1084
}
1085
}
1086
1087
type Result<T, E = types::Error> = std::result::Result<T, E>;
1088
1089
fn write_bytes(
1090
memory: &mut GuestMemory<'_>,
1091
ptr: GuestPtr<u8>,
1092
buf: &[u8],
1093
) -> Result<GuestPtr<u8>, types::Error> {
1094
// NOTE: legacy implementation always returns Inval errno
1095
1096
let len = u32::try_from(buf.len())?;
1097
1098
memory.copy_from_slice(buf, ptr.as_array(len))?;
1099
let next = ptr.add(len)?;
1100
Ok(next)
1101
}
1102
1103
fn write_byte(memory: &mut GuestMemory<'_>, ptr: GuestPtr<u8>, byte: u8) -> Result<GuestPtr<u8>> {
1104
memory.write(ptr, byte)?;
1105
let next = ptr.add(1)?;
1106
Ok(next)
1107
}
1108
1109
fn read_string<'a>(memory: &'a GuestMemory<'_>, ptr: GuestPtr<str>) -> Result<String> {
1110
Ok(memory.as_cow_str(ptr)?.into_owned())
1111
}
1112
1113
// Returns the first non-empty buffer in `ciovs` or a single empty buffer if
1114
// they're all empty.
1115
fn first_non_empty_ciovec(
1116
memory: &GuestMemory<'_>,
1117
ciovs: types::CiovecArray,
1118
) -> Result<GuestPtr<[u8]>> {
1119
for iov in ciovs.iter() {
1120
let iov = memory.read(iov?)?;
1121
if iov.buf_len == 0 {
1122
continue;
1123
}
1124
return Ok(iov.buf.as_array(iov.buf_len));
1125
}
1126
Ok(GuestPtr::new((0, 0)))
1127
}
1128
1129
// Returns the first non-empty buffer in `iovs` or a single empty buffer if
1130
// they're all empty.
1131
fn first_non_empty_iovec(
1132
memory: &GuestMemory<'_>,
1133
iovs: types::IovecArray,
1134
) -> Result<GuestPtr<[u8]>> {
1135
for iov in iovs.iter() {
1136
let iov = memory.read(iov?)?;
1137
if iov.buf_len == 0 {
1138
continue;
1139
}
1140
return Ok(iov.buf.as_array(iov.buf_len));
1141
}
1142
Ok(GuestPtr::new((0, 0)))
1143
}
1144
1145
// Implement the WasiSnapshotPreview1 trait using only the traits that are
1146
// required for T, i.e., in terms of the preview 2 wit interface, and state
1147
// stored in the WasiP1Adapter struct.
1148
impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
1149
#[instrument(skip(self, memory))]
1150
fn args_get(
1151
&mut self,
1152
memory: &mut GuestMemory<'_>,
1153
argv: GuestPtr<GuestPtr<u8>>,
1154
argv_buf: GuestPtr<u8>,
1155
) -> Result<(), types::Error> {
1156
self.cli()
1157
.get_arguments()
1158
.context("failed to call `get-arguments`")
1159
.map_err(types::Error::trap)?
1160
.into_iter()
1161
.try_fold((argv, argv_buf), |(argv, argv_buf), arg| -> Result<_> {
1162
memory.write(argv, argv_buf)?;
1163
let argv = argv.add(1)?;
1164
1165
let argv_buf = write_bytes(memory, argv_buf, arg.as_bytes())?;
1166
let argv_buf = write_byte(memory, argv_buf, 0)?;
1167
1168
Ok((argv, argv_buf))
1169
})?;
1170
Ok(())
1171
}
1172
1173
#[instrument(skip(self, _memory))]
1174
fn args_sizes_get(
1175
&mut self,
1176
_memory: &mut GuestMemory<'_>,
1177
) -> Result<(types::Size, types::Size), types::Error> {
1178
let args = self
1179
.cli()
1180
.get_arguments()
1181
.context("failed to call `get-arguments`")
1182
.map_err(types::Error::trap)?;
1183
let num = args.len().try_into().map_err(|_| types::Errno::Overflow)?;
1184
let len = args
1185
.iter()
1186
.map(|buf| buf.len() + 1) // Each argument is expected to be `\0` terminated.
1187
.sum::<usize>()
1188
.try_into()
1189
.map_err(|_| types::Errno::Overflow)?;
1190
Ok((num, len))
1191
}
1192
1193
#[instrument(skip(self, memory))]
1194
fn environ_get(
1195
&mut self,
1196
memory: &mut GuestMemory<'_>,
1197
environ: GuestPtr<GuestPtr<u8>>,
1198
environ_buf: GuestPtr<u8>,
1199
) -> Result<(), types::Error> {
1200
self.cli()
1201
.get_environment()
1202
.context("failed to call `get-environment`")
1203
.map_err(types::Error::trap)?
1204
.into_iter()
1205
.try_fold(
1206
(environ, environ_buf),
1207
|(environ, environ_buf), (k, v)| -> Result<_, types::Error> {
1208
memory.write(environ, environ_buf)?;
1209
let environ = environ.add(1)?;
1210
1211
let environ_buf = write_bytes(memory, environ_buf, k.as_bytes())?;
1212
let environ_buf = write_byte(memory, environ_buf, b'=')?;
1213
let environ_buf = write_bytes(memory, environ_buf, v.as_bytes())?;
1214
let environ_buf = write_byte(memory, environ_buf, 0)?;
1215
1216
Ok((environ, environ_buf))
1217
},
1218
)?;
1219
Ok(())
1220
}
1221
1222
#[instrument(skip(self, _memory))]
1223
fn environ_sizes_get(
1224
&mut self,
1225
_memory: &mut GuestMemory<'_>,
1226
) -> Result<(types::Size, types::Size), types::Error> {
1227
let environ = self
1228
.cli()
1229
.get_environment()
1230
.context("failed to call `get-environment`")
1231
.map_err(types::Error::trap)?;
1232
let num = environ.len().try_into()?;
1233
let len = environ
1234
.iter()
1235
.map(|(k, v)| k.len() + 1 + v.len() + 1) // Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
1236
.sum::<usize>()
1237
.try_into()?;
1238
Ok((num, len))
1239
}
1240
1241
#[instrument(skip(self, _memory))]
1242
fn clock_res_get(
1243
&mut self,
1244
_memory: &mut GuestMemory<'_>,
1245
id: types::Clockid,
1246
) -> Result<types::Timestamp, types::Error> {
1247
let res = match id {
1248
types::Clockid::Realtime => wall_clock::Host::resolution(&mut self.clocks())
1249
.context("failed to call `wall_clock::resolution`")
1250
.map_err(types::Error::trap)?
1251
.try_into()?,
1252
types::Clockid::Monotonic => monotonic_clock::Host::resolution(&mut self.clocks())
1253
.context("failed to call `monotonic_clock::resolution`")
1254
.map_err(types::Error::trap)?,
1255
types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
1256
return Err(types::Errno::Badf.into());
1257
}
1258
};
1259
Ok(res)
1260
}
1261
1262
#[instrument(skip(self, _memory))]
1263
fn clock_time_get(
1264
&mut self,
1265
_memory: &mut GuestMemory<'_>,
1266
id: types::Clockid,
1267
_precision: types::Timestamp,
1268
) -> Result<types::Timestamp, types::Error> {
1269
let now = match id {
1270
types::Clockid::Realtime => wall_clock::Host::now(&mut self.clocks())
1271
.context("failed to call `wall_clock::now`")
1272
.map_err(types::Error::trap)?
1273
.try_into()?,
1274
types::Clockid::Monotonic => monotonic_clock::Host::now(&mut self.clocks())
1275
.context("failed to call `monotonic_clock::now`")
1276
.map_err(types::Error::trap)?,
1277
types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => {
1278
return Err(types::Errno::Badf.into());
1279
}
1280
};
1281
Ok(now)
1282
}
1283
1284
#[instrument(skip(self, _memory))]
1285
async fn fd_advise(
1286
&mut self,
1287
_memory: &mut GuestMemory<'_>,
1288
fd: types::Fd,
1289
offset: types::Filesize,
1290
len: types::Filesize,
1291
advice: types::Advice,
1292
) -> Result<(), types::Error> {
1293
let fd = self.get_file_fd(fd)?;
1294
self.filesystem()
1295
.advise(fd, offset, len, advice.into())
1296
.await?;
1297
Ok(())
1298
}
1299
1300
/// Force the allocation of space in a file.
1301
/// NOTE: This is similar to `posix_fallocate` in POSIX.
1302
#[instrument(skip(self, _memory))]
1303
fn fd_allocate(
1304
&mut self,
1305
_memory: &mut GuestMemory<'_>,
1306
fd: types::Fd,
1307
_offset: types::Filesize,
1308
_len: types::Filesize,
1309
) -> Result<(), types::Error> {
1310
self.get_file_fd(fd)?;
1311
Err(types::Errno::Notsup.into())
1312
}
1313
1314
/// Close a file descriptor.
1315
/// NOTE: This is similar to `close` in POSIX.
1316
#[instrument(skip(self, _memory))]
1317
async fn fd_close(
1318
&mut self,
1319
_memory: &mut GuestMemory<'_>,
1320
fd: types::Fd,
1321
) -> Result<(), types::Error> {
1322
let desc = {
1323
let fd = fd.into();
1324
let mut st = self.transact()?;
1325
let desc = st.descriptors.used.remove(&fd).ok_or(types::Errno::Badf)?;
1326
st.descriptors.free.insert(fd);
1327
desc
1328
};
1329
match desc {
1330
Descriptor::Stdin { stream, .. } => {
1331
streams::HostInputStream::drop(&mut self.table, stream)
1332
.await
1333
.context("failed to call `drop` on `input-stream`")
1334
}
1335
Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => {
1336
streams::HostOutputStream::drop(&mut self.table, stream)
1337
.await
1338
.context("failed to call `drop` on `output-stream`")
1339
}
1340
Descriptor::File(File { fd, .. }) | Descriptor::Directory { fd, .. } => {
1341
filesystem::HostDescriptor::drop(&mut self.filesystem(), fd)
1342
.context("failed to call `drop`")
1343
}
1344
}
1345
.map_err(types::Error::trap)
1346
}
1347
1348
/// Synchronize the data of a file to disk.
1349
/// NOTE: This is similar to `fdatasync` in POSIX.
1350
#[instrument(skip(self, _memory))]
1351
async fn fd_datasync(
1352
&mut self,
1353
_memory: &mut GuestMemory<'_>,
1354
fd: types::Fd,
1355
) -> Result<(), types::Error> {
1356
let fd = self.get_file_fd(fd)?;
1357
self.filesystem().sync_data(fd).await?;
1358
Ok(())
1359
}
1360
1361
/// Get the attributes of a file descriptor.
1362
/// NOTE: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.
1363
#[instrument(skip(self, _memory))]
1364
async fn fd_fdstat_get(
1365
&mut self,
1366
_memory: &mut GuestMemory<'_>,
1367
fd: types::Fd,
1368
) -> Result<types::Fdstat, types::Error> {
1369
let (fd, blocking, append) = match self.transact()?.get_descriptor(fd)? {
1370
Descriptor::Stdin { isatty, .. } => {
1371
let fs_rights_base = types::Rights::FD_READ;
1372
return Ok(types::Fdstat {
1373
fs_filetype: (*isatty).into(),
1374
fs_flags: types::Fdflags::empty(),
1375
fs_rights_base,
1376
fs_rights_inheriting: fs_rights_base,
1377
});
1378
}
1379
Descriptor::Stdout { isatty, .. } | Descriptor::Stderr { isatty, .. } => {
1380
let fs_rights_base = types::Rights::FD_WRITE;
1381
return Ok(types::Fdstat {
1382
fs_filetype: (*isatty).into(),
1383
fs_flags: types::Fdflags::empty(),
1384
fs_rights_base,
1385
fs_rights_inheriting: fs_rights_base,
1386
});
1387
}
1388
Descriptor::Directory {
1389
preopen_path: Some(_),
1390
..
1391
} => {
1392
// Hard-coded set or rights expected by many userlands:
1393
let fs_rights_base = types::Rights::PATH_CREATE_DIRECTORY
1394
| types::Rights::PATH_CREATE_FILE
1395
| types::Rights::PATH_LINK_SOURCE
1396
| types::Rights::PATH_LINK_TARGET
1397
| types::Rights::PATH_OPEN
1398
| types::Rights::FD_READDIR
1399
| types::Rights::PATH_READLINK
1400
| types::Rights::PATH_RENAME_SOURCE
1401
| types::Rights::PATH_RENAME_TARGET
1402
| types::Rights::PATH_SYMLINK
1403
| types::Rights::PATH_REMOVE_DIRECTORY
1404
| types::Rights::PATH_UNLINK_FILE
1405
| types::Rights::PATH_FILESTAT_GET
1406
| types::Rights::PATH_FILESTAT_SET_TIMES
1407
| types::Rights::FD_FILESTAT_GET
1408
| types::Rights::FD_FILESTAT_SET_TIMES;
1409
1410
let fs_rights_inheriting = fs_rights_base
1411
| types::Rights::FD_DATASYNC
1412
| types::Rights::FD_READ
1413
| types::Rights::FD_SEEK
1414
| types::Rights::FD_FDSTAT_SET_FLAGS
1415
| types::Rights::FD_SYNC
1416
| types::Rights::FD_TELL
1417
| types::Rights::FD_WRITE
1418
| types::Rights::FD_ADVISE
1419
| types::Rights::FD_ALLOCATE
1420
| types::Rights::FD_FILESTAT_GET
1421
| types::Rights::FD_FILESTAT_SET_SIZE
1422
| types::Rights::FD_FILESTAT_SET_TIMES
1423
| types::Rights::POLL_FD_READWRITE;
1424
1425
return Ok(types::Fdstat {
1426
fs_filetype: types::Filetype::Directory,
1427
fs_flags: types::Fdflags::empty(),
1428
fs_rights_base,
1429
fs_rights_inheriting,
1430
});
1431
}
1432
Descriptor::Directory { fd, .. } => (fd.borrowed(), BlockingMode::Blocking, false),
1433
Descriptor::File(File {
1434
fd,
1435
blocking_mode,
1436
append,
1437
..
1438
}) => (fd.borrowed(), *blocking_mode, *append),
1439
};
1440
let flags = self.filesystem().get_flags(fd.borrowed()).await?;
1441
let fs_filetype = self
1442
.filesystem()
1443
.get_type(fd.borrowed())
1444
.await?
1445
.try_into()
1446
.map_err(types::Error::trap)?;
1447
let mut fs_flags = types::Fdflags::empty();
1448
let mut fs_rights_base = types::Rights::all();
1449
if let types::Filetype::Directory = fs_filetype {
1450
fs_rights_base &= !types::Rights::FD_SEEK;
1451
fs_rights_base &= !types::Rights::FD_FILESTAT_SET_SIZE;
1452
fs_rights_base &= !types::Rights::PATH_FILESTAT_SET_SIZE;
1453
}
1454
if !flags.contains(filesystem::DescriptorFlags::READ) {
1455
fs_rights_base &= !types::Rights::FD_READ;
1456
fs_rights_base &= !types::Rights::FD_READDIR;
1457
}
1458
if !flags.contains(filesystem::DescriptorFlags::WRITE) {
1459
fs_rights_base &= !types::Rights::FD_WRITE;
1460
}
1461
if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {
1462
fs_flags |= types::Fdflags::DSYNC;
1463
}
1464
if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {
1465
fs_flags |= types::Fdflags::RSYNC;
1466
}
1467
if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {
1468
fs_flags |= types::Fdflags::SYNC;
1469
}
1470
if append {
1471
fs_flags |= types::Fdflags::APPEND;
1472
}
1473
if matches!(blocking, BlockingMode::NonBlocking) {
1474
fs_flags |= types::Fdflags::NONBLOCK;
1475
}
1476
Ok(types::Fdstat {
1477
fs_filetype,
1478
fs_flags,
1479
fs_rights_base,
1480
fs_rights_inheriting: fs_rights_base,
1481
})
1482
}
1483
1484
/// Adjust the flags associated with a file descriptor.
1485
/// NOTE: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.
1486
#[instrument(skip(self, _memory))]
1487
fn fd_fdstat_set_flags(
1488
&mut self,
1489
_memory: &mut GuestMemory<'_>,
1490
fd: types::Fd,
1491
flags: types::Fdflags,
1492
) -> Result<(), types::Error> {
1493
let mut st = self.transact()?;
1494
let File {
1495
append,
1496
blocking_mode,
1497
..
1498
} = st.get_file_mut(fd)?;
1499
1500
// Only support changing the NONBLOCK or APPEND flags.
1501
if flags.contains(types::Fdflags::DSYNC)
1502
|| flags.contains(types::Fdflags::SYNC)
1503
|| flags.contains(types::Fdflags::RSYNC)
1504
{
1505
return Err(types::Errno::Inval.into());
1506
}
1507
*append = flags.contains(types::Fdflags::APPEND);
1508
*blocking_mode = BlockingMode::from_fdflags(&flags);
1509
Ok(())
1510
}
1511
1512
/// Does not do anything if `fd` corresponds to a valid descriptor and returns `[types::Errno::Badf]` error otherwise.
1513
#[instrument(skip(self, _memory))]
1514
fn fd_fdstat_set_rights(
1515
&mut self,
1516
_memory: &mut GuestMemory<'_>,
1517
fd: types::Fd,
1518
_fs_rights_base: types::Rights,
1519
_fs_rights_inheriting: types::Rights,
1520
) -> Result<(), types::Error> {
1521
self.get_fd(fd)?;
1522
Err(types::Errno::Notsup.into())
1523
}
1524
1525
/// Return the attributes of an open file.
1526
#[instrument(skip(self, _memory))]
1527
async fn fd_filestat_get(
1528
&mut self,
1529
_memory: &mut GuestMemory<'_>,
1530
fd: types::Fd,
1531
) -> Result<types::Filestat, types::Error> {
1532
let t = self.transact()?;
1533
let desc = t.get_descriptor(fd)?;
1534
match desc {
1535
Descriptor::Stdin { isatty, .. }
1536
| Descriptor::Stdout { isatty, .. }
1537
| Descriptor::Stderr { isatty, .. } => Ok(types::Filestat {
1538
dev: 0,
1539
ino: 0,
1540
filetype: (*isatty).into(),
1541
nlink: 0,
1542
size: 0,
1543
atim: 0,
1544
mtim: 0,
1545
ctim: 0,
1546
}),
1547
Descriptor::Directory { fd, .. } | Descriptor::File(File { fd, .. }) => {
1548
let fd = fd.borrowed();
1549
drop(t);
1550
let filesystem::DescriptorStat {
1551
type_,
1552
link_count: nlink,
1553
size,
1554
data_access_timestamp,
1555
data_modification_timestamp,
1556
status_change_timestamp,
1557
} = self.filesystem().stat(fd.borrowed()).await?;
1558
let metadata_hash = self.filesystem().metadata_hash(fd).await?;
1559
let filetype = type_.try_into().map_err(types::Error::trap)?;
1560
let zero = wall_clock::Datetime {
1561
seconds: 0,
1562
nanoseconds: 0,
1563
};
1564
let atim = data_access_timestamp.unwrap_or(zero).try_into()?;
1565
let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?;
1566
let ctim = status_change_timestamp.unwrap_or(zero).try_into()?;
1567
Ok(types::Filestat {
1568
dev: 1,
1569
ino: metadata_hash.lower,
1570
filetype,
1571
nlink,
1572
size,
1573
atim,
1574
mtim,
1575
ctim,
1576
})
1577
}
1578
}
1579
}
1580
1581
/// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.
1582
/// NOTE: This is similar to `ftruncate` in POSIX.
1583
#[instrument(skip(self, _memory))]
1584
async fn fd_filestat_set_size(
1585
&mut self,
1586
_memory: &mut GuestMemory<'_>,
1587
fd: types::Fd,
1588
size: types::Filesize,
1589
) -> Result<(), types::Error> {
1590
let fd = self.get_file_fd(fd)?;
1591
self.filesystem().set_size(fd, size).await?;
1592
Ok(())
1593
}
1594
1595
/// Adjust the timestamps of an open file or directory.
1596
/// NOTE: This is similar to `futimens` in POSIX.
1597
#[instrument(skip(self, _memory))]
1598
async fn fd_filestat_set_times(
1599
&mut self,
1600
_memory: &mut GuestMemory<'_>,
1601
fd: types::Fd,
1602
atim: types::Timestamp,
1603
mtim: types::Timestamp,
1604
fst_flags: types::Fstflags,
1605
) -> Result<(), types::Error> {
1606
let atim = systimespec(
1607
fst_flags.contains(types::Fstflags::ATIM),
1608
atim,
1609
fst_flags.contains(types::Fstflags::ATIM_NOW),
1610
)?;
1611
let mtim = systimespec(
1612
fst_flags.contains(types::Fstflags::MTIM),
1613
mtim,
1614
fst_flags.contains(types::Fstflags::MTIM_NOW),
1615
)?;
1616
1617
let fd = self.get_fd(fd)?;
1618
self.filesystem().set_times(fd, atim, mtim).await?;
1619
Ok(())
1620
}
1621
1622
/// Read from a file descriptor.
1623
/// NOTE: This is similar to `readv` in POSIX.
1624
#[instrument(skip(self, memory))]
1625
async fn fd_read(
1626
&mut self,
1627
memory: &mut GuestMemory<'_>,
1628
fd: types::Fd,
1629
iovs: types::IovecArray,
1630
) -> Result<types::Size, types::Error> {
1631
let t = self.transact()?;
1632
let desc = t.get_descriptor(fd)?;
1633
match desc {
1634
Descriptor::File(File {
1635
fd,
1636
position,
1637
// NB: the nonblocking flag is intentionally ignored here and
1638
// blocking reads/writes are always performed.
1639
blocking_mode: _,
1640
..
1641
}) => {
1642
let fd = fd.borrowed();
1643
let position = position.clone();
1644
drop(t);
1645
let pos = position.load(Ordering::Relaxed);
1646
let file = self.table.get(&fd)?.file()?;
1647
let iov = first_non_empty_iovec(memory, iovs)?;
1648
let bytes_read = match (file.as_blocking_file(), memory.as_slice_mut(iov)?) {
1649
// Try to read directly into wasm memory where possible
1650
// when the current thread can block and additionally wasm
1651
// memory isn't shared.
1652
(Some(file), Some(mut buf)) => file
1653
.read_at(&mut buf, pos)
1654
.map_err(|e| StreamError::LastOperationFailed(e.into()))?,
1655
// ... otherwise fall back to performing the read on a
1656
// blocking thread and which copies the data back into wasm
1657
// memory.
1658
(_, buf) => {
1659
drop(buf);
1660
let mut buf = vec![0; iov.len() as usize];
1661
let buf = file
1662
.run_blocking(move |file| -> Result<_, types::Error> {
1663
let bytes_read = file
1664
.read_at(&mut buf, pos)
1665
.map_err(|e| StreamError::LastOperationFailed(e.into()))?;
1666
buf.truncate(bytes_read);
1667
Ok(buf)
1668
})
1669
.await?;
1670
let iov = iov.get_range(0..u32::try_from(buf.len())?).unwrap();
1671
memory.copy_from_slice(&buf, iov)?;
1672
buf.len()
1673
}
1674
};
1675
1676
let pos = pos
1677
.checked_add(bytes_read.try_into()?)
1678
.ok_or(types::Errno::Overflow)?;
1679
position.store(pos, Ordering::Relaxed);
1680
1681
Ok(bytes_read.try_into()?)
1682
}
1683
Descriptor::Stdin { stream, .. } => {
1684
let stream = stream.borrowed();
1685
drop(t);
1686
let buf = first_non_empty_iovec(memory, iovs)?;
1687
let read = BlockingMode::Blocking
1688
.read(&mut self.table, stream, buf.len().try_into()?)
1689
.await?;
1690
if read.len() > buf.len().try_into()? {
1691
return Err(types::Errno::Range.into());
1692
}
1693
let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
1694
memory.copy_from_slice(&read, buf)?;
1695
let n = read.len().try_into()?;
1696
Ok(n)
1697
}
1698
_ => return Err(types::Errno::Badf.into()),
1699
}
1700
}
1701
1702
/// Read from a file descriptor, without using and updating the file descriptor's offset.
1703
/// NOTE: This is similar to `preadv` in POSIX.
1704
#[instrument(skip(self, memory))]
1705
async fn fd_pread(
1706
&mut self,
1707
memory: &mut GuestMemory<'_>,
1708
fd: types::Fd,
1709
iovs: types::IovecArray,
1710
offset: types::Filesize,
1711
) -> Result<types::Size, types::Error> {
1712
let t = self.transact()?;
1713
let desc = t.get_descriptor(fd)?;
1714
let (buf, read) = match desc {
1715
Descriptor::File(File {
1716
fd, blocking_mode, ..
1717
}) => {
1718
let fd = fd.borrowed();
1719
let blocking_mode = *blocking_mode;
1720
drop(t);
1721
let buf = first_non_empty_iovec(memory, iovs)?;
1722
1723
let stream = self.filesystem().read_via_stream(fd, offset)?;
1724
let read = blocking_mode
1725
.read(&mut self.table, stream.borrowed(), buf.len().try_into()?)
1726
.await;
1727
streams::HostInputStream::drop(&mut self.table, stream)
1728
.await
1729
.map_err(|e| types::Error::trap(e))?;
1730
(buf, read?)
1731
}
1732
Descriptor::Stdin { .. } => {
1733
// NOTE: legacy implementation returns SPIPE here
1734
return Err(types::Errno::Spipe.into());
1735
}
1736
_ => return Err(types::Errno::Badf.into()),
1737
};
1738
if read.len() > buf.len().try_into()? {
1739
return Err(types::Errno::Range.into());
1740
}
1741
let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap();
1742
memory.copy_from_slice(&read, buf)?;
1743
let n = read.len().try_into()?;
1744
Ok(n)
1745
}
1746
1747
/// Write to a file descriptor.
1748
/// NOTE: This is similar to `writev` in POSIX.
1749
#[instrument(skip(self, memory))]
1750
async fn fd_write(
1751
&mut self,
1752
memory: &mut GuestMemory<'_>,
1753
fd: types::Fd,
1754
ciovs: types::CiovecArray,
1755
) -> Result<types::Size, types::Error> {
1756
self.fd_write_impl(memory, fd, ciovs, FdWrite::AtCur).await
1757
}
1758
1759
/// Write to a file descriptor, without using and updating the file descriptor's offset.
1760
/// NOTE: This is similar to `pwritev` in POSIX.
1761
#[instrument(skip(self, memory))]
1762
async fn fd_pwrite(
1763
&mut self,
1764
memory: &mut GuestMemory<'_>,
1765
fd: types::Fd,
1766
ciovs: types::CiovecArray,
1767
offset: types::Filesize,
1768
) -> Result<types::Size, types::Error> {
1769
self.fd_write_impl(memory, fd, ciovs, FdWrite::At(offset))
1770
.await
1771
}
1772
1773
/// Return a description of the given preopened file descriptor.
1774
#[instrument(skip(self, _memory))]
1775
fn fd_prestat_get(
1776
&mut self,
1777
_memory: &mut GuestMemory<'_>,
1778
fd: types::Fd,
1779
) -> Result<types::Prestat, types::Error> {
1780
if let Descriptor::Directory {
1781
preopen_path: Some(p),
1782
..
1783
} = self.transact()?.get_descriptor(fd)?
1784
{
1785
let pr_name_len = p.len().try_into()?;
1786
return Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len }));
1787
}
1788
Err(types::Errno::Badf.into()) // NOTE: legacy implementation returns BADF here
1789
}
1790
1791
/// Return a description of the given preopened file descriptor.
1792
#[instrument(skip(self, memory))]
1793
fn fd_prestat_dir_name(
1794
&mut self,
1795
memory: &mut GuestMemory<'_>,
1796
fd: types::Fd,
1797
path: GuestPtr<u8>,
1798
path_max_len: types::Size,
1799
) -> Result<(), types::Error> {
1800
let path_max_len = path_max_len.try_into()?;
1801
if let Descriptor::Directory {
1802
preopen_path: Some(p),
1803
..
1804
} = self.transact()?.get_descriptor(fd)?
1805
{
1806
if p.len() > path_max_len {
1807
return Err(types::Errno::Nametoolong.into());
1808
}
1809
write_bytes(memory, path, p.as_bytes())?;
1810
return Ok(());
1811
}
1812
Err(types::Errno::Notdir.into()) // NOTE: legacy implementation returns NOTDIR here
1813
}
1814
1815
/// Atomically replace a file descriptor by renumbering another file descriptor.
1816
#[instrument(skip(self, _memory))]
1817
fn fd_renumber(
1818
&mut self,
1819
_memory: &mut GuestMemory<'_>,
1820
from: types::Fd,
1821
to: types::Fd,
1822
) -> Result<(), types::Error> {
1823
let mut st = self.transact()?;
1824
let from = from.into();
1825
let to = to.into();
1826
if !st.descriptors.used.contains_key(&to) {
1827
return Err(types::Errno::Badf.into());
1828
}
1829
let btree_map::Entry::Occupied(desc) = st.descriptors.used.entry(from) else {
1830
return Err(types::Errno::Badf.into());
1831
};
1832
if from != to {
1833
let desc = desc.remove();
1834
st.descriptors.free.insert(from);
1835
st.descriptors.free.remove(&to);
1836
st.descriptors.used.insert(to, desc);
1837
}
1838
Ok(())
1839
}
1840
1841
/// Move the offset of a file descriptor.
1842
/// NOTE: This is similar to `lseek` in POSIX.
1843
#[instrument(skip(self, _memory))]
1844
async fn fd_seek(
1845
&mut self,
1846
_memory: &mut GuestMemory<'_>,
1847
fd: types::Fd,
1848
offset: types::Filedelta,
1849
whence: types::Whence,
1850
) -> Result<types::Filesize, types::Error> {
1851
let t = self.transact()?;
1852
let File { fd, position, .. } = t.get_seekable(fd)?;
1853
let fd = fd.borrowed();
1854
let position = position.clone();
1855
drop(t);
1856
let pos = match whence {
1857
types::Whence::Set if offset >= 0 => {
1858
offset.try_into().map_err(|_| types::Errno::Inval)?
1859
}
1860
types::Whence::Cur => position
1861
.load(Ordering::Relaxed)
1862
.checked_add_signed(offset)
1863
.ok_or(types::Errno::Inval)?,
1864
types::Whence::End => {
1865
let filesystem::DescriptorStat { size, .. } = self.filesystem().stat(fd).await?;
1866
size.checked_add_signed(offset).ok_or(types::Errno::Inval)?
1867
}
1868
_ => return Err(types::Errno::Inval.into()),
1869
};
1870
position.store(pos, Ordering::Relaxed);
1871
Ok(pos)
1872
}
1873
1874
/// Synchronize the data and metadata of a file to disk.
1875
/// NOTE: This is similar to `fsync` in POSIX.
1876
#[instrument(skip(self, _memory))]
1877
async fn fd_sync(
1878
&mut self,
1879
_memory: &mut GuestMemory<'_>,
1880
fd: types::Fd,
1881
) -> Result<(), types::Error> {
1882
let fd = self.get_file_fd(fd)?;
1883
self.filesystem().sync(fd).await?;
1884
Ok(())
1885
}
1886
1887
/// Return the current offset of a file descriptor.
1888
/// NOTE: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.
1889
#[instrument(skip(self, _memory))]
1890
fn fd_tell(
1891
&mut self,
1892
_memory: &mut GuestMemory<'_>,
1893
fd: types::Fd,
1894
) -> Result<types::Filesize, types::Error> {
1895
let pos = self
1896
.transact()?
1897
.get_seekable(fd)
1898
.map(|File { position, .. }| position.load(Ordering::Relaxed))?;
1899
Ok(pos)
1900
}
1901
1902
#[instrument(skip(self, memory))]
1903
async fn fd_readdir(
1904
&mut self,
1905
memory: &mut GuestMemory<'_>,
1906
fd: types::Fd,
1907
buf: GuestPtr<u8>,
1908
buf_len: types::Size,
1909
cookie: types::Dircookie,
1910
) -> Result<types::Size, types::Error> {
1911
let fd = self.get_dir_fd(fd)?;
1912
let stream = self.filesystem().read_directory(fd.borrowed()).await?;
1913
let dir_metadata_hash = self.filesystem().metadata_hash(fd.borrowed()).await?;
1914
let cookie = cookie.try_into().map_err(|_| types::Errno::Overflow)?;
1915
1916
let head = [
1917
(
1918
types::Dirent {
1919
d_next: 1u64.to_le(),
1920
d_ino: dir_metadata_hash.lower.to_le(),
1921
d_type: types::Filetype::Directory,
1922
d_namlen: 1u32.to_le(),
1923
},
1924
".".into(),
1925
),
1926
(
1927
types::Dirent {
1928
d_next: 2u64.to_le(),
1929
d_ino: dir_metadata_hash.lower.to_le(), // NOTE: incorrect, but legacy implementation returns `fd` inode here
1930
d_type: types::Filetype::Directory,
1931
d_namlen: 2u32.to_le(),
1932
},
1933
"..".into(),
1934
),
1935
];
1936
1937
let mut dir = Vec::new();
1938
for (entry, d_next) in self
1939
.table
1940
// remove iterator from table and use it directly:
1941
.delete(stream)?
1942
.into_iter()
1943
.zip(3u64..)
1944
{
1945
let filesystem::DirectoryEntry { type_, name } = entry?;
1946
let metadata_hash = self
1947
.filesystem()
1948
.metadata_hash_at(fd.borrowed(), filesystem::PathFlags::empty(), name.clone())
1949
.await?;
1950
let d_type = type_.try_into().map_err(types::Error::trap)?;
1951
let d_namlen: u32 = name.len().try_into().map_err(|_| types::Errno::Overflow)?;
1952
dir.push((
1953
types::Dirent {
1954
d_next: d_next.to_le(),
1955
d_ino: metadata_hash.lower.to_le(),
1956
d_type, // endian-invariant
1957
d_namlen: d_namlen.to_le(),
1958
},
1959
name,
1960
))
1961
}
1962
1963
// assume that `types::Dirent` size always fits in `u32`
1964
const DIRENT_SIZE: u32 = size_of::<types::Dirent>() as _;
1965
assert_eq!(
1966
types::Dirent::guest_size(),
1967
DIRENT_SIZE,
1968
"Dirent guest repr and host repr should match"
1969
);
1970
let mut buf = buf;
1971
let mut cap = buf_len;
1972
for (ref entry, path) in head.into_iter().chain(dir.into_iter()).skip(cookie) {
1973
let mut path = path.into_bytes();
1974
assert_eq!(
1975
1,
1976
size_of_val(&entry.d_type),
1977
"Dirent member d_type should be endian-invariant"
1978
);
1979
let entry_len = cap.min(DIRENT_SIZE);
1980
let entry = entry as *const _ as _;
1981
let entry = unsafe { slice::from_raw_parts(entry, entry_len as _) };
1982
cap = cap.checked_sub(entry_len).unwrap();
1983
buf = write_bytes(memory, buf, entry)?;
1984
if cap == 0 {
1985
return Ok(buf_len);
1986
}
1987
1988
if let Ok(cap) = cap.try_into() {
1989
// `path` cannot be longer than `usize`, only truncate if `cap` fits in `usize`
1990
path.truncate(cap);
1991
}
1992
cap = cap.checked_sub(path.len() as _).unwrap();
1993
buf = write_bytes(memory, buf, &path)?;
1994
if cap == 0 {
1995
return Ok(buf_len);
1996
}
1997
}
1998
Ok(buf_len.checked_sub(cap).unwrap())
1999
}
2000
2001
#[instrument(skip(self, memory))]
2002
async fn path_create_directory(
2003
&mut self,
2004
memory: &mut GuestMemory<'_>,
2005
dirfd: types::Fd,
2006
path: GuestPtr<str>,
2007
) -> Result<(), types::Error> {
2008
let dirfd = self.get_dir_fd(dirfd)?;
2009
let path = read_string(memory, path)?;
2010
self.filesystem()
2011
.create_directory_at(dirfd.borrowed(), path)
2012
.await?;
2013
Ok(())
2014
}
2015
2016
/// Return the attributes of a file or directory.
2017
/// NOTE: This is similar to `stat` in POSIX.
2018
#[instrument(skip(self, memory))]
2019
async fn path_filestat_get(
2020
&mut self,
2021
memory: &mut GuestMemory<'_>,
2022
dirfd: types::Fd,
2023
flags: types::Lookupflags,
2024
path: GuestPtr<str>,
2025
) -> Result<types::Filestat, types::Error> {
2026
let dirfd = self.get_dir_fd(dirfd)?;
2027
let path = read_string(memory, path)?;
2028
let filesystem::DescriptorStat {
2029
type_,
2030
link_count: nlink,
2031
size,
2032
data_access_timestamp,
2033
data_modification_timestamp,
2034
status_change_timestamp,
2035
} = self
2036
.filesystem()
2037
.stat_at(dirfd.borrowed(), flags.into(), path.clone())
2038
.await?;
2039
let metadata_hash = self
2040
.filesystem()
2041
.metadata_hash_at(dirfd, flags.into(), path)
2042
.await?;
2043
let filetype = type_.try_into().map_err(types::Error::trap)?;
2044
let zero = wall_clock::Datetime {
2045
seconds: 0,
2046
nanoseconds: 0,
2047
};
2048
let atim = data_access_timestamp.unwrap_or(zero).try_into()?;
2049
let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?;
2050
let ctim = status_change_timestamp.unwrap_or(zero).try_into()?;
2051
Ok(types::Filestat {
2052
dev: 1,
2053
ino: metadata_hash.lower,
2054
filetype,
2055
nlink,
2056
size,
2057
atim,
2058
mtim,
2059
ctim,
2060
})
2061
}
2062
2063
/// Adjust the timestamps of a file or directory.
2064
/// NOTE: This is similar to `utimensat` in POSIX.
2065
#[instrument(skip(self, memory))]
2066
async fn path_filestat_set_times(
2067
&mut self,
2068
memory: &mut GuestMemory<'_>,
2069
dirfd: types::Fd,
2070
flags: types::Lookupflags,
2071
path: GuestPtr<str>,
2072
atim: types::Timestamp,
2073
mtim: types::Timestamp,
2074
fst_flags: types::Fstflags,
2075
) -> Result<(), types::Error> {
2076
let atim = systimespec(
2077
fst_flags.contains(types::Fstflags::ATIM),
2078
atim,
2079
fst_flags.contains(types::Fstflags::ATIM_NOW),
2080
)?;
2081
let mtim = systimespec(
2082
fst_flags.contains(types::Fstflags::MTIM),
2083
mtim,
2084
fst_flags.contains(types::Fstflags::MTIM_NOW),
2085
)?;
2086
2087
let dirfd = self.get_dir_fd(dirfd)?;
2088
let path = read_string(memory, path)?;
2089
self.filesystem()
2090
.set_times_at(dirfd, flags.into(), path, atim, mtim)
2091
.await?;
2092
Ok(())
2093
}
2094
2095
/// Create a hard link.
2096
/// NOTE: This is similar to `linkat` in POSIX.
2097
#[instrument(skip(self, memory))]
2098
async fn path_link(
2099
&mut self,
2100
memory: &mut GuestMemory<'_>,
2101
src_fd: types::Fd,
2102
src_flags: types::Lookupflags,
2103
src_path: GuestPtr<str>,
2104
target_fd: types::Fd,
2105
target_path: GuestPtr<str>,
2106
) -> Result<(), types::Error> {
2107
let src_fd = self.get_dir_fd(src_fd)?;
2108
let target_fd = self.get_dir_fd(target_fd)?;
2109
let src_path = read_string(memory, src_path)?;
2110
let target_path = read_string(memory, target_path)?;
2111
self.filesystem()
2112
.link_at(src_fd, src_flags.into(), src_path, target_fd, target_path)
2113
.await?;
2114
Ok(())
2115
}
2116
2117
/// Open a file or directory.
2118
/// NOTE: This is similar to `openat` in POSIX.
2119
#[instrument(skip(self, memory))]
2120
async fn path_open(
2121
&mut self,
2122
memory: &mut GuestMemory<'_>,
2123
dirfd: types::Fd,
2124
dirflags: types::Lookupflags,
2125
path: GuestPtr<str>,
2126
oflags: types::Oflags,
2127
fs_rights_base: types::Rights,
2128
_fs_rights_inheriting: types::Rights,
2129
fdflags: types::Fdflags,
2130
) -> Result<types::Fd, types::Error> {
2131
let path = read_string(memory, path)?;
2132
2133
let mut flags = filesystem::DescriptorFlags::empty();
2134
if fs_rights_base.contains(types::Rights::FD_READ) {
2135
flags |= filesystem::DescriptorFlags::READ;
2136
}
2137
if fs_rights_base.contains(types::Rights::FD_WRITE) {
2138
flags |= filesystem::DescriptorFlags::WRITE;
2139
}
2140
if fdflags.contains(types::Fdflags::SYNC) {
2141
flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC;
2142
}
2143
if fdflags.contains(types::Fdflags::DSYNC) {
2144
flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC;
2145
}
2146
if fdflags.contains(types::Fdflags::RSYNC) {
2147
flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC;
2148
}
2149
2150
let t = self.transact()?;
2151
let dirfd = match t.get_descriptor(dirfd)? {
2152
Descriptor::Directory { fd, .. } => fd.borrowed(),
2153
Descriptor::File(_) => return Err(types::Errno::Notdir.into()),
2154
_ => return Err(types::Errno::Badf.into()),
2155
};
2156
drop(t);
2157
let fd = self
2158
.filesystem()
2159
.open_at(dirfd, dirflags.into(), path, oflags.into(), flags)
2160
.await?;
2161
let mut t = self.transact()?;
2162
let desc = match t.view.table.get(&fd)? {
2163
crate::filesystem::Descriptor::Dir(_) => Descriptor::Directory {
2164
fd,
2165
preopen_path: None,
2166
},
2167
crate::filesystem::Descriptor::File(_) => Descriptor::File(File {
2168
fd,
2169
position: Default::default(),
2170
append: fdflags.contains(types::Fdflags::APPEND),
2171
blocking_mode: BlockingMode::from_fdflags(&fdflags),
2172
}),
2173
};
2174
let fd = t.descriptors.push(desc)?;
2175
Ok(fd.into())
2176
}
2177
2178
/// Read the contents of a symbolic link.
2179
/// NOTE: This is similar to `readlinkat` in POSIX.
2180
#[instrument(skip(self, memory))]
2181
async fn path_readlink(
2182
&mut self,
2183
memory: &mut GuestMemory<'_>,
2184
dirfd: types::Fd,
2185
path: GuestPtr<str>,
2186
buf: GuestPtr<u8>,
2187
buf_len: types::Size,
2188
) -> Result<types::Size, types::Error> {
2189
let dirfd = self.get_dir_fd(dirfd)?;
2190
let path = read_string(memory, path)?;
2191
let mut path = self
2192
.filesystem()
2193
.readlink_at(dirfd, path)
2194
.await?
2195
.into_bytes();
2196
if let Ok(buf_len) = buf_len.try_into() {
2197
// `path` cannot be longer than `usize`, only truncate if `buf_len` fits in `usize`
2198
path.truncate(buf_len);
2199
}
2200
let n = path.len().try_into().map_err(|_| types::Errno::Overflow)?;
2201
write_bytes(memory, buf, &path)?;
2202
Ok(n)
2203
}
2204
2205
#[instrument(skip(self, memory))]
2206
async fn path_remove_directory(
2207
&mut self,
2208
memory: &mut GuestMemory<'_>,
2209
dirfd: types::Fd,
2210
path: GuestPtr<str>,
2211
) -> Result<(), types::Error> {
2212
let dirfd = self.get_dir_fd(dirfd)?;
2213
let path = read_string(memory, path)?;
2214
self.filesystem().remove_directory_at(dirfd, path).await?;
2215
Ok(())
2216
}
2217
2218
/// Rename a file or directory.
2219
/// NOTE: This is similar to `renameat` in POSIX.
2220
#[instrument(skip(self, memory))]
2221
async fn path_rename(
2222
&mut self,
2223
memory: &mut GuestMemory<'_>,
2224
src_fd: types::Fd,
2225
src_path: GuestPtr<str>,
2226
dest_fd: types::Fd,
2227
dest_path: GuestPtr<str>,
2228
) -> Result<(), types::Error> {
2229
let src_fd = self.get_dir_fd(src_fd)?;
2230
let dest_fd = self.get_dir_fd(dest_fd)?;
2231
let src_path = read_string(memory, src_path)?;
2232
let dest_path = read_string(memory, dest_path)?;
2233
self.filesystem()
2234
.rename_at(src_fd, src_path, dest_fd, dest_path)
2235
.await?;
2236
Ok(())
2237
}
2238
2239
#[instrument(skip(self, memory))]
2240
async fn path_symlink(
2241
&mut self,
2242
memory: &mut GuestMemory<'_>,
2243
src_path: GuestPtr<str>,
2244
dirfd: types::Fd,
2245
dest_path: GuestPtr<str>,
2246
) -> Result<(), types::Error> {
2247
let dirfd = self.get_dir_fd(dirfd)?;
2248
let src_path = read_string(memory, src_path)?;
2249
let dest_path = read_string(memory, dest_path)?;
2250
self.filesystem()
2251
.symlink_at(dirfd.borrowed(), src_path, dest_path)
2252
.await?;
2253
Ok(())
2254
}
2255
2256
#[instrument(skip(self, memory))]
2257
async fn path_unlink_file(
2258
&mut self,
2259
memory: &mut GuestMemory<'_>,
2260
dirfd: types::Fd,
2261
path: GuestPtr<str>,
2262
) -> Result<(), types::Error> {
2263
let dirfd = self.get_dir_fd(dirfd)?;
2264
let path = memory.as_cow_str(path)?.into_owned();
2265
self.filesystem()
2266
.unlink_file_at(dirfd.borrowed(), path)
2267
.await?;
2268
Ok(())
2269
}
2270
2271
#[instrument(skip(self, memory))]
2272
async fn poll_oneoff(
2273
&mut self,
2274
memory: &mut GuestMemory<'_>,
2275
subs: GuestPtr<types::Subscription>,
2276
events: GuestPtr<types::Event>,
2277
nsubscriptions: types::Size,
2278
) -> Result<types::Size, types::Error> {
2279
if nsubscriptions == 0 {
2280
// Indefinite sleeping is not supported in p1.
2281
return Err(types::Errno::Inval.into());
2282
}
2283
2284
// This is a special case where `poll_oneoff` is just sleeping
2285
// on a single relative timer event. This special case was added
2286
// after experimental observations showed that std::thread::sleep
2287
// results in more consistent sleep times. This design ensures that
2288
// wasmtime can handle real-time requirements more accurately.
2289
if nsubscriptions == 1 {
2290
let sub = memory.read(subs)?;
2291
if let types::SubscriptionU::Clock(clocksub) = sub.u {
2292
if !clocksub
2293
.flags
2294
.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
2295
&& self.wasi.filesystem.allow_blocking_current_thread
2296
{
2297
std::thread::sleep(std::time::Duration::from_nanos(clocksub.timeout));
2298
memory.write(
2299
events,
2300
types::Event {
2301
userdata: sub.userdata,
2302
error: types::Errno::Success,
2303
type_: types::Eventtype::Clock,
2304
fd_readwrite: types::EventFdReadwrite {
2305
flags: types::Eventrwflags::empty(),
2306
nbytes: 1,
2307
},
2308
},
2309
)?;
2310
return Ok(1);
2311
}
2312
}
2313
}
2314
2315
let subs = subs.as_array(nsubscriptions);
2316
let events = events.as_array(nsubscriptions);
2317
2318
let n = usize::try_from(nsubscriptions).unwrap_or(usize::MAX);
2319
let mut pollables = Vec::with_capacity(n);
2320
for sub in subs.iter() {
2321
let sub = memory.read(sub?)?;
2322
let p = match sub.u {
2323
types::SubscriptionU::Clock(types::SubscriptionClock {
2324
id,
2325
timeout,
2326
flags,
2327
..
2328
}) => {
2329
let absolute = flags.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME);
2330
let (timeout, absolute) = match id {
2331
types::Clockid::Monotonic => (timeout, absolute),
2332
types::Clockid::Realtime if !absolute => (timeout, false),
2333
types::Clockid::Realtime => {
2334
let now = wall_clock::Host::now(&mut self.clocks())
2335
.context("failed to call `wall_clock::now`")
2336
.map_err(types::Error::trap)?;
2337
2338
// Convert `timeout` to `Datetime` format.
2339
let seconds = timeout / 1_000_000_000;
2340
let nanoseconds = timeout % 1_000_000_000;
2341
2342
let timeout = if now.seconds < seconds
2343
|| now.seconds == seconds
2344
&& u64::from(now.nanoseconds) < nanoseconds
2345
{
2346
// `now` is less than `timeout`, which is expressible as u64,
2347
// subtract the nanosecond counts directly
2348
now.seconds * 1_000_000_000 + u64::from(now.nanoseconds) - timeout
2349
} else {
2350
0
2351
};
2352
(timeout, false)
2353
}
2354
_ => return Err(types::Errno::Inval.into()),
2355
};
2356
if absolute {
2357
monotonic_clock::Host::subscribe_instant(&mut self.clocks(), timeout)
2358
.context("failed to call `monotonic_clock::subscribe_instant`")
2359
.map_err(types::Error::trap)?
2360
} else {
2361
monotonic_clock::Host::subscribe_duration(&mut self.clocks(), timeout)
2362
.context("failed to call `monotonic_clock::subscribe_duration`")
2363
.map_err(types::Error::trap)?
2364
}
2365
}
2366
types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
2367
file_descriptor,
2368
}) => {
2369
let stream = {
2370
let t = self.transact()?;
2371
let desc = t.get_descriptor(file_descriptor)?;
2372
match desc {
2373
Descriptor::Stdin { stream, .. } => stream.borrowed(),
2374
Descriptor::File(File { fd, position, .. }) => {
2375
let pos = position.load(Ordering::Relaxed);
2376
let fd = fd.borrowed();
2377
drop(t);
2378
self.filesystem().read_via_stream(fd, pos)?
2379
}
2380
// TODO: Support sockets
2381
_ => return Err(types::Errno::Badf.into()),
2382
}
2383
};
2384
streams::HostInputStream::subscribe(&mut self.table, stream)
2385
.context("failed to call `subscribe` on `input-stream`")
2386
.map_err(types::Error::trap)?
2387
}
2388
types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite {
2389
file_descriptor,
2390
}) => {
2391
let stream = {
2392
let t = self.transact()?;
2393
let desc = t.get_descriptor(file_descriptor)?;
2394
match desc {
2395
Descriptor::Stdout { stream, .. }
2396
| Descriptor::Stderr { stream, .. } => stream.borrowed(),
2397
Descriptor::File(File {
2398
fd,
2399
position,
2400
append,
2401
..
2402
}) => {
2403
let fd = fd.borrowed();
2404
let position = position.clone();
2405
let append = *append;
2406
drop(t);
2407
if append {
2408
self.filesystem().append_via_stream(fd)?
2409
} else {
2410
let pos = position.load(Ordering::Relaxed);
2411
self.filesystem().write_via_stream(fd, pos)?
2412
}
2413
}
2414
// TODO: Support sockets
2415
_ => return Err(types::Errno::Badf.into()),
2416
}
2417
};
2418
streams::HostOutputStream::subscribe(&mut self.table, stream)
2419
.context("failed to call `subscribe` on `output-stream`")
2420
.map_err(types::Error::trap)?
2421
}
2422
};
2423
pollables.push(p);
2424
}
2425
let ready: HashSet<_> = self
2426
.table
2427
.poll(pollables)
2428
.await
2429
.context("failed to call `poll-oneoff`")
2430
.map_err(types::Error::trap)?
2431
.into_iter()
2432
.collect();
2433
2434
let mut count: types::Size = 0;
2435
for (sub, event) in (0..)
2436
.zip(subs.iter())
2437
.filter_map(|(idx, sub)| ready.contains(&idx).then_some(sub))
2438
.zip(events.iter())
2439
{
2440
let sub = memory.read(sub?)?;
2441
let event = event?;
2442
let e = match sub.u {
2443
types::SubscriptionU::Clock(..) => types::Event {
2444
userdata: sub.userdata,
2445
error: types::Errno::Success,
2446
type_: types::Eventtype::Clock,
2447
fd_readwrite: types::EventFdReadwrite {
2448
flags: types::Eventrwflags::empty(),
2449
nbytes: 0,
2450
},
2451
},
2452
types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
2453
file_descriptor,
2454
}) => {
2455
let t = self.transact()?;
2456
let desc = t.get_descriptor(file_descriptor)?;
2457
match desc {
2458
Descriptor::Stdin { .. } => types::Event {
2459
userdata: sub.userdata,
2460
error: types::Errno::Success,
2461
type_: types::Eventtype::FdRead,
2462
fd_readwrite: types::EventFdReadwrite {
2463
flags: types::Eventrwflags::empty(),
2464
nbytes: 1,
2465
},
2466
},
2467
Descriptor::File(File { fd, position, .. }) => {
2468
let fd = fd.borrowed();
2469
let position = position.clone();
2470
drop(t);
2471
match self.filesystem().stat(fd).await? {
2472
filesystem::DescriptorStat { size, .. } => {
2473
let pos = position.load(Ordering::Relaxed);
2474
let nbytes = size.saturating_sub(pos);
2475
types::Event {
2476
userdata: sub.userdata,
2477
error: types::Errno::Success,
2478
type_: types::Eventtype::FdRead,
2479
fd_readwrite: types::EventFdReadwrite {
2480
flags: if nbytes == 0 {
2481
types::Eventrwflags::FD_READWRITE_HANGUP
2482
} else {
2483
types::Eventrwflags::empty()
2484
},
2485
nbytes: 1,
2486
},
2487
}
2488
}
2489
}
2490
}
2491
// TODO: Support sockets
2492
_ => return Err(types::Errno::Badf.into()),
2493
}
2494
}
2495
types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite {
2496
file_descriptor,
2497
}) => {
2498
let t = self.transact()?;
2499
let desc = t.get_descriptor(file_descriptor)?;
2500
match desc {
2501
Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => types::Event {
2502
userdata: sub.userdata,
2503
error: types::Errno::Success,
2504
type_: types::Eventtype::FdWrite,
2505
fd_readwrite: types::EventFdReadwrite {
2506
flags: types::Eventrwflags::empty(),
2507
nbytes: 1,
2508
},
2509
},
2510
Descriptor::File(_) => types::Event {
2511
userdata: sub.userdata,
2512
error: types::Errno::Success,
2513
type_: types::Eventtype::FdWrite,
2514
fd_readwrite: types::EventFdReadwrite {
2515
flags: types::Eventrwflags::empty(),
2516
nbytes: 1,
2517
},
2518
},
2519
// TODO: Support sockets
2520
_ => return Err(types::Errno::Badf.into()),
2521
}
2522
}
2523
};
2524
memory.write(event, e)?;
2525
count = count
2526
.checked_add(1)
2527
.ok_or_else(|| types::Error::from(types::Errno::Overflow))?
2528
}
2529
Ok(count)
2530
}
2531
2532
#[instrument(skip(self, _memory))]
2533
fn proc_exit(
2534
&mut self,
2535
_memory: &mut GuestMemory<'_>,
2536
status: types::Exitcode,
2537
) -> wasmtime::Error {
2538
// Check that the status is within WASI's range.
2539
if status >= 126 {
2540
return wasmtime::Error::msg("exit with invalid exit status outside of [0..126)");
2541
}
2542
crate::I32Exit(status as i32).into()
2543
}
2544
2545
#[instrument(skip(self, _memory))]
2546
fn proc_raise(
2547
&mut self,
2548
_memory: &mut GuestMemory<'_>,
2549
_sig: types::Signal,
2550
) -> Result<(), types::Error> {
2551
Err(types::Errno::Notsup.into())
2552
}
2553
2554
#[instrument(skip(self, _memory))]
2555
fn sched_yield(&mut self, _memory: &mut GuestMemory<'_>) -> Result<(), types::Error> {
2556
// No such thing in preview 2. Intentionally left empty.
2557
Ok(())
2558
}
2559
2560
#[instrument(skip(self, memory))]
2561
fn random_get(
2562
&mut self,
2563
memory: &mut GuestMemory<'_>,
2564
buf: GuestPtr<u8>,
2565
buf_len: types::Size,
2566
) -> Result<(), types::Error> {
2567
let rand = self
2568
.wasi
2569
.random
2570
.get_random_bytes(buf_len.into())
2571
.context("failed to call `get-random-bytes`")
2572
.map_err(types::Error::trap)?;
2573
write_bytes(memory, buf, &rand)?;
2574
Ok(())
2575
}
2576
2577
#[instrument(skip(self, _memory))]
2578
fn sock_accept(
2579
&mut self,
2580
_memory: &mut GuestMemory<'_>,
2581
fd: types::Fd,
2582
flags: types::Fdflags,
2583
) -> Result<types::Fd, types::Error> {
2584
tracing::warn!("p1 sock_accept is not implemented");
2585
self.transact()?.get_descriptor(fd)?;
2586
Err(types::Errno::Notsock.into())
2587
}
2588
2589
#[instrument(skip(self, _memory))]
2590
fn sock_recv(
2591
&mut self,
2592
_memory: &mut GuestMemory<'_>,
2593
fd: types::Fd,
2594
ri_data: types::IovecArray,
2595
ri_flags: types::Riflags,
2596
) -> Result<(types::Size, types::Roflags), types::Error> {
2597
tracing::warn!("p1 sock_recv is not implemented");
2598
self.transact()?.get_descriptor(fd)?;
2599
Err(types::Errno::Notsock.into())
2600
}
2601
2602
#[instrument(skip(self, _memory))]
2603
fn sock_send(
2604
&mut self,
2605
_memory: &mut GuestMemory<'_>,
2606
fd: types::Fd,
2607
si_data: types::CiovecArray,
2608
_si_flags: types::Siflags,
2609
) -> Result<types::Size, types::Error> {
2610
tracing::warn!("p1 sock_send is not implemented");
2611
self.transact()?.get_descriptor(fd)?;
2612
Err(types::Errno::Notsock.into())
2613
}
2614
2615
#[instrument(skip(self, _memory))]
2616
fn sock_shutdown(
2617
&mut self,
2618
_memory: &mut GuestMemory<'_>,
2619
fd: types::Fd,
2620
how: types::Sdflags,
2621
) -> Result<(), types::Error> {
2622
tracing::warn!("p1 sock_shutdown is not implemented");
2623
self.transact()?.get_descriptor(fd)?;
2624
Err(types::Errno::Notsock.into())
2625
}
2626
}
2627
2628
trait ResourceExt<T> {
2629
fn borrowed(&self) -> Resource<T>;
2630
}
2631
2632
impl<T: 'static> ResourceExt<T> for Resource<T> {
2633
fn borrowed(&self) -> Resource<T> {
2634
Resource::new_borrow(self.rep())
2635
}
2636
}
2637
2638