Path: blob/main/crates/wasi-preview1-component-adapter/src/descriptors.rs
1692 views
use crate::bindings::wasi::cli::{stderr, stdin, stdout};1use crate::bindings::wasi::io::streams::{InputStream, OutputStream};2use crate::{BlockingMode, ImportAlloc, State, TrappingUnwrap, WasmStr};3use core::cell::{Cell, OnceCell, UnsafeCell};4use core::mem::MaybeUninit;5use core::num::NonZeroUsize;6use wasi::{Errno, Fd};78#[cfg(not(feature = "proxy"))]9use crate::File;10#[cfg(not(feature = "proxy"))]11use crate::bindings::wasi::filesystem::types as filesystem;1213pub const MAX_DESCRIPTORS: usize = 128;1415#[repr(C)]16pub enum Descriptor {17/// A closed descriptor, holding a reference to the previous closed18/// descriptor to support reusing them.19Closed(Option<Fd>),2021/// Input and/or output wasi-streams, along with stream metadata.22Streams(Streams),2324Bad,25}2627/// Input and/or output wasi-streams, along with a stream type that28/// identifies what kind of stream they are and possibly supporting29/// type-specific operations like seeking.30pub struct Streams {31/// The input stream, if present.32pub input: OnceCell<InputStream>,3334/// The output stream, if present.35pub output: OnceCell<OutputStream>,3637/// Information about the source of the stream.38pub type_: StreamType,39}4041impl Streams {42/// Return the input stream, initializing it on the fly if needed.43pub fn get_read_stream(&self) -> Result<&InputStream, Errno> {44match self.input.get() {45Some(wasi_stream) => Ok(wasi_stream),46None => {47let input = match &self.type_ {48// For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read49// or write.50#[cfg(not(feature = "proxy"))]51StreamType::File(File {52descriptor_type: filesystem::DescriptorType::Directory,53..54}) => return Err(wasi::ERRNO_BADF),55// For files, we may have adjusted the position for seeking, so56// create a new stream.57#[cfg(not(feature = "proxy"))]58StreamType::File(file) => {59let input = file.fd.read_via_stream(file.position.get())?;60input61}62_ => return Err(wasi::ERRNO_BADF),63};64self.input.set(input).trapping_unwrap();65Ok(self.input.get().trapping_unwrap())66}67}68}6970/// Return the output stream, initializing it on the fly if needed.71pub fn get_write_stream(&self) -> Result<&OutputStream, Errno> {72match self.output.get() {73Some(wasi_stream) => Ok(wasi_stream),74None => {75let output = match &self.type_ {76// For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read77// or write.78#[cfg(not(feature = "proxy"))]79StreamType::File(File {80descriptor_type: filesystem::DescriptorType::Directory,81..82}) => return Err(wasi::ERRNO_BADF),83// For files, we may have adjusted the position for seeking, so84// create a new stream.85#[cfg(not(feature = "proxy"))]86StreamType::File(file) => {87let output = if file.append {88file.fd.append_via_stream()?89} else {90file.fd.write_via_stream(file.position.get())?91};92output93}94_ => return Err(wasi::ERRNO_BADF),95};96self.output.set(output).trapping_unwrap();97Ok(self.output.get().trapping_unwrap())98}99}100}101}102103pub enum StreamType {104/// Streams for implementing stdio.105Stdio(Stdio),106107/// Streaming data with a file.108#[cfg(not(feature = "proxy"))]109File(File),110}111112pub enum Stdio {113Stdin,114Stdout,115Stderr,116}117118impl Stdio {119pub fn filetype(&self) -> wasi::Filetype {120#[cfg(not(feature = "proxy"))]121let is_terminal = {122use crate::bindings::wasi::cli;123match self {124Stdio::Stdin => cli::terminal_stdin::get_terminal_stdin().is_some(),125Stdio::Stdout => cli::terminal_stdout::get_terminal_stdout().is_some(),126Stdio::Stderr => cli::terminal_stderr::get_terminal_stderr().is_some(),127}128};129#[cfg(feature = "proxy")]130let is_terminal = false;131if is_terminal {132wasi::FILETYPE_CHARACTER_DEVICE133} else {134wasi::FILETYPE_UNKNOWN135}136}137}138139#[repr(C)]140pub struct Descriptors {141/// Storage of mapping from preview1 file descriptors to preview2 file142/// descriptors.143table: UnsafeCell<MaybeUninit<[Descriptor; MAX_DESCRIPTORS]>>,144table_len: Cell<u16>,145146/// Points to the head of a free-list of closed file descriptors.147closed: Option<Fd>,148}149150#[cfg(not(feature = "proxy"))]151#[link(wasm_import_module = "wasi:filesystem/[email protected]")]152unsafe extern "C" {153#[link_name = "get-directories"]154fn wasi_filesystem_get_directories(rval: *mut PreopenList);155}156157impl Descriptors {158pub fn new(state: &State) -> Self {159let d = Descriptors {160table: UnsafeCell::new(MaybeUninit::uninit()),161table_len: Cell::new(0),162closed: None,163};164165fn new_once<T>(val: T) -> OnceCell<T> {166let cell = OnceCell::new();167let _ = cell.set(val);168cell169}170171d.push(Descriptor::Streams(Streams {172input: new_once(stdin::get_stdin()),173output: OnceCell::new(),174type_: StreamType::Stdio(Stdio::Stdin),175}))176.trapping_unwrap();177d.push(Descriptor::Streams(Streams {178input: OnceCell::new(),179output: new_once(stdout::get_stdout()),180type_: StreamType::Stdio(Stdio::Stdout),181}))182.trapping_unwrap();183d.push(Descriptor::Streams(Streams {184input: OnceCell::new(),185output: new_once(stderr::get_stderr()),186type_: StreamType::Stdio(Stdio::Stderr),187}))188.trapping_unwrap();189190#[cfg(not(feature = "proxy"))]191d.open_preopens(state);192d193}194195#[cfg(not(feature = "proxy"))]196fn open_preopens(&self, state: &State) {197unsafe {198let alloc = ImportAlloc::CountAndDiscardStrings {199strings_size: 0,200alloc: state.temporary_alloc(),201};202let (preopens, _) = state.with_import_alloc(alloc, || {203let mut preopens = PreopenList {204base: std::ptr::null(),205len: 0,206};207wasi_filesystem_get_directories(&mut preopens);208preopens209});210for i in 0..preopens.len {211let preopen = preopens.base.add(i).read();212// Expectation is that the descriptor index is initialized with213// stdio (0,1,2) and no others, so that preopens are 3..214let descriptor_type = preopen.descriptor.get_type().trapping_unwrap();215self.push(Descriptor::Streams(Streams {216input: OnceCell::new(),217output: OnceCell::new(),218type_: StreamType::File(File {219fd: preopen.descriptor,220descriptor_type,221position: Cell::new(0),222append: false,223blocking_mode: BlockingMode::Blocking,224preopen_name_len: NonZeroUsize::new(preopen.path.len),225}),226}))227.trapping_unwrap();228}229}230}231232#[cfg(not(feature = "proxy"))]233pub unsafe fn get_preopen_path(&self, state: &State, fd: Fd, path: *mut u8, len: usize) {234let nth = fd - 3;235let alloc = ImportAlloc::GetPreopenPath {236cur: 0,237nth,238alloc: unsafe { state.temporary_alloc() },239};240let (preopens, _) = state.with_import_alloc(alloc, || {241let mut preopens = PreopenList {242base: std::ptr::null(),243len: 0,244};245unsafe {246wasi_filesystem_get_directories(&mut preopens);247}248preopens249});250251// NB: we just got owned handles for all preopened directories. We're252// only interested in one individual string allocation, however, so253// discard all of the descriptors and close them since we otherwise254// don't want to leak them.255for i in 0..preopens.len {256let preopen = unsafe { preopens.base.add(i).read() };257drop(preopen.descriptor);258259if (i as u32) != nth {260continue;261}262assert!(preopen.path.len <= len);263unsafe {264core::ptr::copy(preopen.path.ptr, path, preopen.path.len);265}266}267}268269fn push(&self, desc: Descriptor) -> Result<Fd, Errno> {270unsafe {271let table = (*self.table.get()).as_mut_ptr();272let len = usize::from(self.table_len.get());273if len >= (*table).len() {274return Err(wasi::ERRNO_NOMEM);275}276(&raw mut (*table)[len]).write(desc);277self.table_len.set(u16::try_from(len + 1).trapping_unwrap());278Ok(Fd::from(u32::try_from(len).trapping_unwrap()))279}280}281282fn table(&self) -> &[Descriptor] {283unsafe {284std::slice::from_raw_parts(285(*self.table.get()).as_ptr().cast(),286usize::from(self.table_len.get()),287)288}289}290291fn table_mut(&mut self) -> &mut [Descriptor] {292unsafe {293std::slice::from_raw_parts_mut(294(*self.table.get()).as_mut_ptr().cast(),295usize::from(self.table_len.get()),296)297}298}299300pub fn open(&mut self, d: Descriptor) -> Result<Fd, Errno> {301match self.closed {302// No closed descriptors: expand table303None => self.push(d),304Some(freelist_head) => {305// Pop an item off the freelist306let freelist_desc = self.get_mut(freelist_head).trapping_unwrap();307let next_closed = match freelist_desc {308Descriptor::Closed(next) => *next,309_ => unreachable!("impossible: freelist points to a closed descriptor"),310};311// Write descriptor to the entry at the head of the list312*freelist_desc = d;313// Point closed to the following item314self.closed = next_closed;315Ok(freelist_head)316}317}318}319320pub fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> {321self.table()322.get(usize::try_from(fd).trapping_unwrap())323.ok_or(wasi::ERRNO_BADF)324}325326pub fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> {327self.table_mut()328.get_mut(usize::try_from(fd).trapping_unwrap())329.ok_or(wasi::ERRNO_BADF)330}331332// Close an fd.333pub fn close(&mut self, fd: Fd) -> Result<(), Errno> {334// Throw an error if closing an fd which is already closed335match self.get(fd)? {336Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?,337_ => {}338}339// Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list:340let last_closed = self.closed;341let prev = std::mem::replace(self.get_mut(fd)?, Descriptor::Closed(last_closed));342self.closed = Some(fd);343drop(prev);344Ok(())345}346347// Expand the table by pushing a closed descriptor to the end. Used for renumbering.348fn push_closed(&mut self) -> Result<(), Errno> {349let old_closed = self.closed;350let new_closed = self.push(Descriptor::Closed(old_closed))?;351self.closed = Some(new_closed);352Ok(())353}354355// Implementation of fd_renumber356pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> {357// First, ensure to_fd/from_fd is in bounds:358if let Descriptor::Closed(_) = self.get(to_fd)? {359return Err(wasi::ERRNO_BADF);360}361if let Descriptor::Closed(_) = self.get(from_fd)? {362return Err(wasi::ERRNO_BADF);363}364// Expand table until to_fd is in bounds as well:365while self.table_len.get() as u32 <= to_fd {366self.push_closed()?;367}368// Throw an error if renumbering a closed fd369match self.get(from_fd)? {370Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?,371_ => {}372}373// Close from_fd and put its contents into to_fd374if from_fd != to_fd {375// Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list:376let last_closed = self.closed;377let desc = std::mem::replace(self.get_mut(from_fd)?, Descriptor::Closed(last_closed));378self.closed = Some(from_fd);379// TODO FIXME if this overwrites a preopen, do we need to clear it from the preopen table?380*self.get_mut(to_fd)? = desc;381}382Ok(())383}384385// A bunch of helper functions implemented in terms of the above pub functions:386387pub fn get_stream_with_error_mut(388&mut self,389fd: Fd,390error: Errno,391) -> Result<&mut Streams, Errno> {392match self.get_mut(fd)? {393Descriptor::Streams(streams) => Ok(streams),394Descriptor::Closed(_) | Descriptor::Bad => Err(error),395}396}397398#[cfg(not(feature = "proxy"))]399pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> {400match self.get(fd)? {401Descriptor::Streams(Streams {402type_:403StreamType::File(File {404descriptor_type: filesystem::DescriptorType::Directory,405..406}),407..408}) => Err(wasi::ERRNO_BADF),409Descriptor::Streams(Streams {410type_: StreamType::File(file),411..412}) => Ok(file),413Descriptor::Closed(_) => Err(wasi::ERRNO_BADF),414_ => Err(error),415}416}417418#[cfg(not(feature = "proxy"))]419pub fn get_file(&self, fd: Fd) -> Result<&File, Errno> {420self.get_file_with_error(fd, wasi::ERRNO_INVAL)421}422423#[cfg(not(feature = "proxy"))]424pub fn get_dir(&self, fd: Fd) -> Result<&File, Errno> {425match self.get(fd)? {426Descriptor::Streams(Streams {427type_:428StreamType::File(429file @ File {430descriptor_type: filesystem::DescriptorType::Directory,431..432},433),434..435}) => Ok(file),436Descriptor::Streams(Streams {437type_: StreamType::File(File { .. }),438..439}) => Err(wasi::ERRNO_NOTDIR),440_ => Err(wasi::ERRNO_BADF),441}442}443444#[cfg(not(feature = "proxy"))]445pub fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> {446self.get_file_with_error(fd, wasi::ERRNO_SPIPE)447}448449pub fn get_seekable_stream_mut(&mut self, fd: Fd) -> Result<&mut Streams, Errno> {450self.get_stream_with_error_mut(fd, wasi::ERRNO_SPIPE)451}452453pub fn get_read_stream(&self, fd: Fd) -> Result<&InputStream, Errno> {454match self.get(fd)? {455Descriptor::Streams(streams) => streams.get_read_stream(),456Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),457}458}459460pub fn get_write_stream(&self, fd: Fd) -> Result<&OutputStream, Errno> {461match self.get(fd)? {462Descriptor::Streams(streams) => streams.get_write_stream(),463Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),464}465}466}467468#[cfg(not(feature = "proxy"))]469#[repr(C)]470pub struct Preopen {471pub descriptor: filesystem::Descriptor,472pub path: WasmStr,473}474475#[cfg(not(feature = "proxy"))]476#[repr(C)]477pub struct PreopenList {478pub base: *const Preopen,479pub len: usize,480}481482483