Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wasi-preview1-component-adapter/src/descriptors.rs
1692 views
1
use crate::bindings::wasi::cli::{stderr, stdin, stdout};
2
use crate::bindings::wasi::io::streams::{InputStream, OutputStream};
3
use crate::{BlockingMode, ImportAlloc, State, TrappingUnwrap, WasmStr};
4
use core::cell::{Cell, OnceCell, UnsafeCell};
5
use core::mem::MaybeUninit;
6
use core::num::NonZeroUsize;
7
use wasi::{Errno, Fd};
8
9
#[cfg(not(feature = "proxy"))]
10
use crate::File;
11
#[cfg(not(feature = "proxy"))]
12
use crate::bindings::wasi::filesystem::types as filesystem;
13
14
pub const MAX_DESCRIPTORS: usize = 128;
15
16
#[repr(C)]
17
pub enum Descriptor {
18
/// A closed descriptor, holding a reference to the previous closed
19
/// descriptor to support reusing them.
20
Closed(Option<Fd>),
21
22
/// Input and/or output wasi-streams, along with stream metadata.
23
Streams(Streams),
24
25
Bad,
26
}
27
28
/// Input and/or output wasi-streams, along with a stream type that
29
/// identifies what kind of stream they are and possibly supporting
30
/// type-specific operations like seeking.
31
pub struct Streams {
32
/// The input stream, if present.
33
pub input: OnceCell<InputStream>,
34
35
/// The output stream, if present.
36
pub output: OnceCell<OutputStream>,
37
38
/// Information about the source of the stream.
39
pub type_: StreamType,
40
}
41
42
impl Streams {
43
/// Return the input stream, initializing it on the fly if needed.
44
pub fn get_read_stream(&self) -> Result<&InputStream, Errno> {
45
match self.input.get() {
46
Some(wasi_stream) => Ok(wasi_stream),
47
None => {
48
let input = match &self.type_ {
49
// For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read
50
// or write.
51
#[cfg(not(feature = "proxy"))]
52
StreamType::File(File {
53
descriptor_type: filesystem::DescriptorType::Directory,
54
..
55
}) => return Err(wasi::ERRNO_BADF),
56
// For files, we may have adjusted the position for seeking, so
57
// create a new stream.
58
#[cfg(not(feature = "proxy"))]
59
StreamType::File(file) => {
60
let input = file.fd.read_via_stream(file.position.get())?;
61
input
62
}
63
_ => return Err(wasi::ERRNO_BADF),
64
};
65
self.input.set(input).trapping_unwrap();
66
Ok(self.input.get().trapping_unwrap())
67
}
68
}
69
}
70
71
/// Return the output stream, initializing it on the fly if needed.
72
pub fn get_write_stream(&self) -> Result<&OutputStream, Errno> {
73
match self.output.get() {
74
Some(wasi_stream) => Ok(wasi_stream),
75
None => {
76
let output = match &self.type_ {
77
// For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read
78
// or write.
79
#[cfg(not(feature = "proxy"))]
80
StreamType::File(File {
81
descriptor_type: filesystem::DescriptorType::Directory,
82
..
83
}) => return Err(wasi::ERRNO_BADF),
84
// For files, we may have adjusted the position for seeking, so
85
// create a new stream.
86
#[cfg(not(feature = "proxy"))]
87
StreamType::File(file) => {
88
let output = if file.append {
89
file.fd.append_via_stream()?
90
} else {
91
file.fd.write_via_stream(file.position.get())?
92
};
93
output
94
}
95
_ => return Err(wasi::ERRNO_BADF),
96
};
97
self.output.set(output).trapping_unwrap();
98
Ok(self.output.get().trapping_unwrap())
99
}
100
}
101
}
102
}
103
104
pub enum StreamType {
105
/// Streams for implementing stdio.
106
Stdio(Stdio),
107
108
/// Streaming data with a file.
109
#[cfg(not(feature = "proxy"))]
110
File(File),
111
}
112
113
pub enum Stdio {
114
Stdin,
115
Stdout,
116
Stderr,
117
}
118
119
impl Stdio {
120
pub fn filetype(&self) -> wasi::Filetype {
121
#[cfg(not(feature = "proxy"))]
122
let is_terminal = {
123
use crate::bindings::wasi::cli;
124
match self {
125
Stdio::Stdin => cli::terminal_stdin::get_terminal_stdin().is_some(),
126
Stdio::Stdout => cli::terminal_stdout::get_terminal_stdout().is_some(),
127
Stdio::Stderr => cli::terminal_stderr::get_terminal_stderr().is_some(),
128
}
129
};
130
#[cfg(feature = "proxy")]
131
let is_terminal = false;
132
if is_terminal {
133
wasi::FILETYPE_CHARACTER_DEVICE
134
} else {
135
wasi::FILETYPE_UNKNOWN
136
}
137
}
138
}
139
140
#[repr(C)]
141
pub struct Descriptors {
142
/// Storage of mapping from preview1 file descriptors to preview2 file
143
/// descriptors.
144
table: UnsafeCell<MaybeUninit<[Descriptor; MAX_DESCRIPTORS]>>,
145
table_len: Cell<u16>,
146
147
/// Points to the head of a free-list of closed file descriptors.
148
closed: Option<Fd>,
149
}
150
151
#[cfg(not(feature = "proxy"))]
152
#[link(wasm_import_module = "wasi:filesystem/[email protected]")]
153
unsafe extern "C" {
154
#[link_name = "get-directories"]
155
fn wasi_filesystem_get_directories(rval: *mut PreopenList);
156
}
157
158
impl Descriptors {
159
pub fn new(state: &State) -> Self {
160
let d = Descriptors {
161
table: UnsafeCell::new(MaybeUninit::uninit()),
162
table_len: Cell::new(0),
163
closed: None,
164
};
165
166
fn new_once<T>(val: T) -> OnceCell<T> {
167
let cell = OnceCell::new();
168
let _ = cell.set(val);
169
cell
170
}
171
172
d.push(Descriptor::Streams(Streams {
173
input: new_once(stdin::get_stdin()),
174
output: OnceCell::new(),
175
type_: StreamType::Stdio(Stdio::Stdin),
176
}))
177
.trapping_unwrap();
178
d.push(Descriptor::Streams(Streams {
179
input: OnceCell::new(),
180
output: new_once(stdout::get_stdout()),
181
type_: StreamType::Stdio(Stdio::Stdout),
182
}))
183
.trapping_unwrap();
184
d.push(Descriptor::Streams(Streams {
185
input: OnceCell::new(),
186
output: new_once(stderr::get_stderr()),
187
type_: StreamType::Stdio(Stdio::Stderr),
188
}))
189
.trapping_unwrap();
190
191
#[cfg(not(feature = "proxy"))]
192
d.open_preopens(state);
193
d
194
}
195
196
#[cfg(not(feature = "proxy"))]
197
fn open_preopens(&self, state: &State) {
198
unsafe {
199
let alloc = ImportAlloc::CountAndDiscardStrings {
200
strings_size: 0,
201
alloc: state.temporary_alloc(),
202
};
203
let (preopens, _) = state.with_import_alloc(alloc, || {
204
let mut preopens = PreopenList {
205
base: std::ptr::null(),
206
len: 0,
207
};
208
wasi_filesystem_get_directories(&mut preopens);
209
preopens
210
});
211
for i in 0..preopens.len {
212
let preopen = preopens.base.add(i).read();
213
// Expectation is that the descriptor index is initialized with
214
// stdio (0,1,2) and no others, so that preopens are 3..
215
let descriptor_type = preopen.descriptor.get_type().trapping_unwrap();
216
self.push(Descriptor::Streams(Streams {
217
input: OnceCell::new(),
218
output: OnceCell::new(),
219
type_: StreamType::File(File {
220
fd: preopen.descriptor,
221
descriptor_type,
222
position: Cell::new(0),
223
append: false,
224
blocking_mode: BlockingMode::Blocking,
225
preopen_name_len: NonZeroUsize::new(preopen.path.len),
226
}),
227
}))
228
.trapping_unwrap();
229
}
230
}
231
}
232
233
#[cfg(not(feature = "proxy"))]
234
pub unsafe fn get_preopen_path(&self, state: &State, fd: Fd, path: *mut u8, len: usize) {
235
let nth = fd - 3;
236
let alloc = ImportAlloc::GetPreopenPath {
237
cur: 0,
238
nth,
239
alloc: unsafe { state.temporary_alloc() },
240
};
241
let (preopens, _) = state.with_import_alloc(alloc, || {
242
let mut preopens = PreopenList {
243
base: std::ptr::null(),
244
len: 0,
245
};
246
unsafe {
247
wasi_filesystem_get_directories(&mut preopens);
248
}
249
preopens
250
});
251
252
// NB: we just got owned handles for all preopened directories. We're
253
// only interested in one individual string allocation, however, so
254
// discard all of the descriptors and close them since we otherwise
255
// don't want to leak them.
256
for i in 0..preopens.len {
257
let preopen = unsafe { preopens.base.add(i).read() };
258
drop(preopen.descriptor);
259
260
if (i as u32) != nth {
261
continue;
262
}
263
assert!(preopen.path.len <= len);
264
unsafe {
265
core::ptr::copy(preopen.path.ptr, path, preopen.path.len);
266
}
267
}
268
}
269
270
fn push(&self, desc: Descriptor) -> Result<Fd, Errno> {
271
unsafe {
272
let table = (*self.table.get()).as_mut_ptr();
273
let len = usize::from(self.table_len.get());
274
if len >= (*table).len() {
275
return Err(wasi::ERRNO_NOMEM);
276
}
277
(&raw mut (*table)[len]).write(desc);
278
self.table_len.set(u16::try_from(len + 1).trapping_unwrap());
279
Ok(Fd::from(u32::try_from(len).trapping_unwrap()))
280
}
281
}
282
283
fn table(&self) -> &[Descriptor] {
284
unsafe {
285
std::slice::from_raw_parts(
286
(*self.table.get()).as_ptr().cast(),
287
usize::from(self.table_len.get()),
288
)
289
}
290
}
291
292
fn table_mut(&mut self) -> &mut [Descriptor] {
293
unsafe {
294
std::slice::from_raw_parts_mut(
295
(*self.table.get()).as_mut_ptr().cast(),
296
usize::from(self.table_len.get()),
297
)
298
}
299
}
300
301
pub fn open(&mut self, d: Descriptor) -> Result<Fd, Errno> {
302
match self.closed {
303
// No closed descriptors: expand table
304
None => self.push(d),
305
Some(freelist_head) => {
306
// Pop an item off the freelist
307
let freelist_desc = self.get_mut(freelist_head).trapping_unwrap();
308
let next_closed = match freelist_desc {
309
Descriptor::Closed(next) => *next,
310
_ => unreachable!("impossible: freelist points to a closed descriptor"),
311
};
312
// Write descriptor to the entry at the head of the list
313
*freelist_desc = d;
314
// Point closed to the following item
315
self.closed = next_closed;
316
Ok(freelist_head)
317
}
318
}
319
}
320
321
pub fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> {
322
self.table()
323
.get(usize::try_from(fd).trapping_unwrap())
324
.ok_or(wasi::ERRNO_BADF)
325
}
326
327
pub fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> {
328
self.table_mut()
329
.get_mut(usize::try_from(fd).trapping_unwrap())
330
.ok_or(wasi::ERRNO_BADF)
331
}
332
333
// Close an fd.
334
pub fn close(&mut self, fd: Fd) -> Result<(), Errno> {
335
// Throw an error if closing an fd which is already closed
336
match self.get(fd)? {
337
Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?,
338
_ => {}
339
}
340
// Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list:
341
let last_closed = self.closed;
342
let prev = std::mem::replace(self.get_mut(fd)?, Descriptor::Closed(last_closed));
343
self.closed = Some(fd);
344
drop(prev);
345
Ok(())
346
}
347
348
// Expand the table by pushing a closed descriptor to the end. Used for renumbering.
349
fn push_closed(&mut self) -> Result<(), Errno> {
350
let old_closed = self.closed;
351
let new_closed = self.push(Descriptor::Closed(old_closed))?;
352
self.closed = Some(new_closed);
353
Ok(())
354
}
355
356
// Implementation of fd_renumber
357
pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> {
358
// First, ensure to_fd/from_fd is in bounds:
359
if let Descriptor::Closed(_) = self.get(to_fd)? {
360
return Err(wasi::ERRNO_BADF);
361
}
362
if let Descriptor::Closed(_) = self.get(from_fd)? {
363
return Err(wasi::ERRNO_BADF);
364
}
365
// Expand table until to_fd is in bounds as well:
366
while self.table_len.get() as u32 <= to_fd {
367
self.push_closed()?;
368
}
369
// Throw an error if renumbering a closed fd
370
match self.get(from_fd)? {
371
Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?,
372
_ => {}
373
}
374
// Close from_fd and put its contents into to_fd
375
if from_fd != to_fd {
376
// Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list:
377
let last_closed = self.closed;
378
let desc = std::mem::replace(self.get_mut(from_fd)?, Descriptor::Closed(last_closed));
379
self.closed = Some(from_fd);
380
// TODO FIXME if this overwrites a preopen, do we need to clear it from the preopen table?
381
*self.get_mut(to_fd)? = desc;
382
}
383
Ok(())
384
}
385
386
// A bunch of helper functions implemented in terms of the above pub functions:
387
388
pub fn get_stream_with_error_mut(
389
&mut self,
390
fd: Fd,
391
error: Errno,
392
) -> Result<&mut Streams, Errno> {
393
match self.get_mut(fd)? {
394
Descriptor::Streams(streams) => Ok(streams),
395
Descriptor::Closed(_) | Descriptor::Bad => Err(error),
396
}
397
}
398
399
#[cfg(not(feature = "proxy"))]
400
pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> {
401
match self.get(fd)? {
402
Descriptor::Streams(Streams {
403
type_:
404
StreamType::File(File {
405
descriptor_type: filesystem::DescriptorType::Directory,
406
..
407
}),
408
..
409
}) => Err(wasi::ERRNO_BADF),
410
Descriptor::Streams(Streams {
411
type_: StreamType::File(file),
412
..
413
}) => Ok(file),
414
Descriptor::Closed(_) => Err(wasi::ERRNO_BADF),
415
_ => Err(error),
416
}
417
}
418
419
#[cfg(not(feature = "proxy"))]
420
pub fn get_file(&self, fd: Fd) -> Result<&File, Errno> {
421
self.get_file_with_error(fd, wasi::ERRNO_INVAL)
422
}
423
424
#[cfg(not(feature = "proxy"))]
425
pub fn get_dir(&self, fd: Fd) -> Result<&File, Errno> {
426
match self.get(fd)? {
427
Descriptor::Streams(Streams {
428
type_:
429
StreamType::File(
430
file @ File {
431
descriptor_type: filesystem::DescriptorType::Directory,
432
..
433
},
434
),
435
..
436
}) => Ok(file),
437
Descriptor::Streams(Streams {
438
type_: StreamType::File(File { .. }),
439
..
440
}) => Err(wasi::ERRNO_NOTDIR),
441
_ => Err(wasi::ERRNO_BADF),
442
}
443
}
444
445
#[cfg(not(feature = "proxy"))]
446
pub fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> {
447
self.get_file_with_error(fd, wasi::ERRNO_SPIPE)
448
}
449
450
pub fn get_seekable_stream_mut(&mut self, fd: Fd) -> Result<&mut Streams, Errno> {
451
self.get_stream_with_error_mut(fd, wasi::ERRNO_SPIPE)
452
}
453
454
pub fn get_read_stream(&self, fd: Fd) -> Result<&InputStream, Errno> {
455
match self.get(fd)? {
456
Descriptor::Streams(streams) => streams.get_read_stream(),
457
Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),
458
}
459
}
460
461
pub fn get_write_stream(&self, fd: Fd) -> Result<&OutputStream, Errno> {
462
match self.get(fd)? {
463
Descriptor::Streams(streams) => streams.get_write_stream(),
464
Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),
465
}
466
}
467
}
468
469
#[cfg(not(feature = "proxy"))]
470
#[repr(C)]
471
pub struct Preopen {
472
pub descriptor: filesystem::Descriptor,
473
pub path: WasmStr,
474
}
475
476
#[cfg(not(feature = "proxy"))]
477
#[repr(C)]
478
pub struct PreopenList {
479
pub base: *const Preopen,
480
pub len: usize,
481
}
482
483