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