Path: blob/main/crates/wasi/src/p2/host/filesystem.rs
3079 views
use crate::filesystem::{Descriptor, WasiFilesystemCtxView};1use crate::p2::bindings::clocks::wall_clock;2use crate::p2::bindings::filesystem::preopens;3use crate::p2::bindings::filesystem::types::{4self, ErrorCode, HostDescriptor, HostDirectoryEntryStream,5};6use crate::p2::filesystem::{FileInputStream, FileOutputStream, ReaddirIterator};7use crate::p2::{FsError, FsResult};8use crate::{DirPerms, FilePerms};9use wasmtime::component::Resource;10use wasmtime_wasi_io::streams::{DynInputStream, DynOutputStream};1112mod sync;1314impl preopens::Host for WasiFilesystemCtxView<'_> {15fn get_directories(&mut self) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {16self.get_directories()17}18}1920impl types::Host for WasiFilesystemCtxView<'_> {21fn convert_error_code(&mut self, err: FsError) -> wasmtime::Result<ErrorCode> {22err.downcast()23}2425fn filesystem_error_code(26&mut self,27err: Resource<wasmtime::Error>,28) -> wasmtime::Result<Option<ErrorCode>> {29let err = self.table.get(&err)?;3031// Currently `err` always comes from the stream implementation which32// uses standard reads/writes so only check for `std::io::Error` here.33if let Some(err) = err.downcast_ref::<std::io::Error>() {34return Ok(Some(ErrorCode::from(err)));35}3637Ok(None)38}39}4041impl HostDescriptor for WasiFilesystemCtxView<'_> {42async fn advise(43&mut self,44fd: Resource<types::Descriptor>,45offset: types::Filesize,46len: types::Filesize,47advice: types::Advice,48) -> FsResult<()> {49let f = self.table.get(&fd)?.file()?;50f.advise(offset, len, advice.into()).await?;51Ok(())52}5354async fn sync_data(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {55let descriptor = self.table.get(&fd)?;56descriptor.sync_data().await?;57Ok(())58}5960async fn get_flags(61&mut self,62fd: Resource<types::Descriptor>,63) -> FsResult<types::DescriptorFlags> {64let descriptor = self.table.get(&fd)?;65let flags = descriptor.get_flags().await?;66Ok(flags.into())67}6869async fn get_type(70&mut self,71fd: Resource<types::Descriptor>,72) -> FsResult<types::DescriptorType> {73let descriptor = self.table.get(&fd)?;74let ty = descriptor.get_type().await?;75Ok(ty.into())76}7778async fn set_size(79&mut self,80fd: Resource<types::Descriptor>,81size: types::Filesize,82) -> FsResult<()> {83let f = self.table.get(&fd)?.file()?;84f.set_size(size).await?;85Ok(())86}8788async fn set_times(89&mut self,90fd: Resource<types::Descriptor>,91atim: types::NewTimestamp,92mtim: types::NewTimestamp,93) -> FsResult<()> {94let descriptor = self.table.get(&fd)?;95let atim = systemtimespec_from(atim)?;96let mtim = systemtimespec_from(mtim)?;97descriptor.set_times(atim, mtim).await?;98Ok(())99}100101async fn read(102&mut self,103fd: Resource<types::Descriptor>,104len: types::Filesize,105offset: types::Filesize,106) -> FsResult<(Vec<u8>, bool)> {107use std::io::IoSliceMut;108use system_interface::fs::FileIoExt;109110let f = self.table.get(&fd)?.file()?;111if !f.perms.contains(FilePerms::READ) {112return Err(ErrorCode::NotPermitted.into());113}114115let (mut buffer, r) = f116.run_blocking(move |f| {117let mut buffer = vec![0; len.try_into().unwrap_or(usize::MAX)];118let r = f.read_vectored_at(&mut [IoSliceMut::new(&mut buffer)], offset);119(buffer, r)120})121.await;122123let (bytes_read, state) = match r? {1240 => (0, true),125n => (n, false),126};127128buffer.truncate(bytes_read);129130Ok((buffer, state))131}132133async fn write(134&mut self,135fd: Resource<types::Descriptor>,136buf: Vec<u8>,137offset: types::Filesize,138) -> FsResult<types::Filesize> {139use std::io::IoSlice;140use system_interface::fs::FileIoExt;141142let f = self.table.get(&fd)?.file()?;143if !f.perms.contains(FilePerms::WRITE) {144return Err(ErrorCode::NotPermitted.into());145}146147let bytes_written = f148.run_blocking(move |f| f.write_vectored_at(&[IoSlice::new(&buf)], offset))149.await?;150151Ok(types::Filesize::try_from(bytes_written).expect("usize fits in Filesize"))152}153154async fn read_directory(155&mut self,156fd: Resource<types::Descriptor>,157) -> FsResult<Resource<types::DirectoryEntryStream>> {158let d = self.table.get(&fd)?.dir()?;159if !d.perms.contains(DirPerms::READ) {160return Err(ErrorCode::NotPermitted.into());161}162163enum ReaddirError {164Io(std::io::Error),165IllegalSequence,166}167impl From<std::io::Error> for ReaddirError {168fn from(e: std::io::Error) -> ReaddirError {169ReaddirError::Io(e)170}171}172173let entries = d174.run_blocking(|d| {175// Both `entries` and `metadata` perform syscalls, which is why they are done176// within this `block` call, rather than delay calculating the metadata177// for entries when they're demanded later in the iterator chain.178Ok::<_, std::io::Error>(179d.entries()?180.map(|entry| {181let entry = entry?;182let meta = entry.metadata()?;183let type_ = descriptortype_from(meta.file_type());184let name = entry185.file_name()186.into_string()187.map_err(|_| ReaddirError::IllegalSequence)?;188Ok(types::DirectoryEntry { type_, name })189})190.collect::<Vec<Result<types::DirectoryEntry, ReaddirError>>>(),191)192})193.await?194.into_iter();195196// On windows, filter out files like `C:\DumpStack.log.tmp` which we197// can't get full metadata for.198#[cfg(windows)]199let entries = entries.filter(|entry| {200use windows_sys::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION};201if let Err(ReaddirError::Io(err)) = entry {202if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)203|| err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)204{205return false;206}207}208true209});210let entries = entries.map(|r| match r {211Ok(r) => Ok(r),212Err(ReaddirError::Io(e)) => Err(e.into()),213Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()),214});215Ok(self.table.push(ReaddirIterator::new(entries))?)216}217218async fn sync(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {219let descriptor = self.table.get(&fd)?;220descriptor.sync().await?;221Ok(())222}223224async fn create_directory_at(225&mut self,226fd: Resource<types::Descriptor>,227path: String,228) -> FsResult<()> {229let d = self.table.get(&fd)?.dir()?;230d.create_directory_at(path).await?;231Ok(())232}233234async fn stat(&mut self, fd: Resource<types::Descriptor>) -> FsResult<types::DescriptorStat> {235let descriptor = self.table.get(&fd)?;236let stat = descriptor.stat().await?;237Ok(stat.try_into()?)238}239240async fn stat_at(241&mut self,242fd: Resource<types::Descriptor>,243path_flags: types::PathFlags,244path: String,245) -> FsResult<types::DescriptorStat> {246let d = self.table.get(&fd)?.dir()?;247let stat = d.stat_at(path_flags.into(), path).await?;248Ok(stat.try_into()?)249}250251async fn set_times_at(252&mut self,253fd: Resource<types::Descriptor>,254path_flags: types::PathFlags,255path: String,256atim: types::NewTimestamp,257mtim: types::NewTimestamp,258) -> FsResult<()> {259let d = self.table.get(&fd)?.dir()?;260let atim = systemtimespec_from(atim)?;261let mtim = systemtimespec_from(mtim)?;262d.set_times_at(path_flags.into(), path, atim, mtim).await?;263Ok(())264}265266async fn link_at(267&mut self,268fd: Resource<types::Descriptor>,269// TODO delete the path flags from this function270old_path_flags: types::PathFlags,271old_path: String,272new_descriptor: Resource<types::Descriptor>,273new_path: String,274) -> FsResult<()> {275let old_dir = self.table.get(&fd)?.dir()?;276let new_dir = self.table.get(&new_descriptor)?.dir()?;277old_dir278.link_at(old_path_flags.into(), old_path, new_dir, new_path)279.await?;280Ok(())281}282283async fn open_at(284&mut self,285fd: Resource<types::Descriptor>,286path_flags: types::PathFlags,287path: String,288oflags: types::OpenFlags,289flags: types::DescriptorFlags,290) -> FsResult<Resource<types::Descriptor>> {291let d = self.table.get(&fd)?.dir()?;292let fd = d293.open_at(294path_flags.into(),295path,296oflags.into(),297flags.into(),298self.ctx.allow_blocking_current_thread,299)300.await?;301let fd = self.table.push(fd)?;302Ok(fd)303}304305fn drop(&mut self, fd: Resource<types::Descriptor>) -> wasmtime::Result<()> {306// The Drop will close the file/dir, but if the close syscall307// blocks the thread, I will face god and walk backwards into hell.308// tokio::fs::File just uses std::fs::File's Drop impl to close, so309// it doesn't appear anyone else has found this to be a problem.310// (Not that they could solve it without async drop...)311self.table.delete(fd)?;312313Ok(())314}315316async fn readlink_at(317&mut self,318fd: Resource<types::Descriptor>,319path: String,320) -> FsResult<String> {321let d = self.table.get(&fd)?.dir()?;322let path = d.readlink_at(path).await?;323Ok(path)324}325326async fn remove_directory_at(327&mut self,328fd: Resource<types::Descriptor>,329path: String,330) -> FsResult<()> {331let d = self.table.get(&fd)?.dir()?;332d.remove_directory_at(path).await?;333Ok(())334}335336async fn rename_at(337&mut self,338fd: Resource<types::Descriptor>,339old_path: String,340new_fd: Resource<types::Descriptor>,341new_path: String,342) -> FsResult<()> {343let old_dir = self.table.get(&fd)?.dir()?;344let new_dir = self.table.get(&new_fd)?.dir()?;345old_dir.rename_at(old_path, new_dir, new_path).await?;346Ok(())347}348349async fn symlink_at(350&mut self,351fd: Resource<types::Descriptor>,352src_path: String,353dest_path: String,354) -> FsResult<()> {355let d = self.table.get(&fd)?.dir()?;356d.symlink_at(src_path, dest_path).await?;357Ok(())358}359360async fn unlink_file_at(361&mut self,362fd: Resource<types::Descriptor>,363path: String,364) -> FsResult<()> {365let d = self.table.get(&fd)?.dir()?;366d.unlink_file_at(path).await?;367Ok(())368}369370fn read_via_stream(371&mut self,372fd: Resource<types::Descriptor>,373offset: types::Filesize,374) -> FsResult<Resource<DynInputStream>> {375// Trap if fd lookup fails:376let f = self.table.get(&fd)?.file()?;377378if !f.perms.contains(FilePerms::READ) {379Err(types::ErrorCode::BadDescriptor)?;380}381382// Create a stream view for it.383let reader: DynInputStream = Box::new(FileInputStream::new(f, offset));384385// Insert the stream view into the table. Trap if the table is full.386let index = self.table.push(reader)?;387388Ok(index)389}390391fn write_via_stream(392&mut self,393fd: Resource<types::Descriptor>,394offset: types::Filesize,395) -> FsResult<Resource<DynOutputStream>> {396// Trap if fd lookup fails:397let f = self.table.get(&fd)?.file()?;398399if !f.perms.contains(FilePerms::WRITE) {400Err(types::ErrorCode::BadDescriptor)?;401}402403// Create a stream view for it.404let writer = FileOutputStream::write_at(f, offset);405let writer: DynOutputStream = Box::new(writer);406407// Insert the stream view into the table. Trap if the table is full.408let index = self.table.push(writer)?;409410Ok(index)411}412413fn append_via_stream(414&mut self,415fd: Resource<types::Descriptor>,416) -> FsResult<Resource<DynOutputStream>> {417// Trap if fd lookup fails:418let f = self.table.get(&fd)?.file()?;419420if !f.perms.contains(FilePerms::WRITE) {421Err(types::ErrorCode::BadDescriptor)?;422}423424// Create a stream view for it.425let appender = FileOutputStream::append(f);426let appender: DynOutputStream = Box::new(appender);427428// Insert the stream view into the table. Trap if the table is full.429let index = self.table.push(appender)?;430431Ok(index)432}433434async fn is_same_object(435&mut self,436a: Resource<types::Descriptor>,437b: Resource<types::Descriptor>,438) -> wasmtime::Result<bool> {439let descriptor_a = self.table.get(&a)?;440let descriptor_b = self.table.get(&b)?;441descriptor_a.is_same_object(descriptor_b).await442}443async fn metadata_hash(444&mut self,445fd: Resource<types::Descriptor>,446) -> FsResult<types::MetadataHashValue> {447let fd = self.table.get(&fd)?;448let meta = fd.metadata_hash().await?;449Ok(meta.into())450}451async fn metadata_hash_at(452&mut self,453fd: Resource<types::Descriptor>,454path_flags: types::PathFlags,455path: String,456) -> FsResult<types::MetadataHashValue> {457let d = self.table.get(&fd)?.dir()?;458let meta = d.metadata_hash_at(path_flags.into(), path).await?;459Ok(meta.into())460}461}462463impl HostDirectoryEntryStream for WasiFilesystemCtxView<'_> {464async fn read_directory_entry(465&mut self,466stream: Resource<types::DirectoryEntryStream>,467) -> FsResult<Option<types::DirectoryEntry>> {468let readdir = self.table.get(&stream)?;469readdir.next()470}471472fn drop(&mut self, stream: Resource<types::DirectoryEntryStream>) -> wasmtime::Result<()> {473self.table.delete(stream)?;474Ok(())475}476}477478impl From<types::Advice> for system_interface::fs::Advice {479fn from(advice: types::Advice) -> Self {480match advice {481types::Advice::Normal => Self::Normal,482types::Advice::Sequential => Self::Sequential,483types::Advice::Random => Self::Random,484types::Advice::WillNeed => Self::WillNeed,485types::Advice::DontNeed => Self::DontNeed,486types::Advice::NoReuse => Self::NoReuse,487}488}489}490491impl From<types::OpenFlags> for crate::filesystem::OpenFlags {492fn from(flags: types::OpenFlags) -> Self {493let mut out = Self::empty();494if flags.contains(types::OpenFlags::CREATE) {495out |= Self::CREATE;496}497if flags.contains(types::OpenFlags::DIRECTORY) {498out |= Self::DIRECTORY;499}500if flags.contains(types::OpenFlags::EXCLUSIVE) {501out |= Self::EXCLUSIVE;502}503if flags.contains(types::OpenFlags::TRUNCATE) {504out |= Self::TRUNCATE;505}506out507}508}509510impl From<types::PathFlags> for crate::filesystem::PathFlags {511fn from(flags: types::PathFlags) -> Self {512let mut out = Self::empty();513if flags.contains(types::PathFlags::SYMLINK_FOLLOW) {514out |= Self::SYMLINK_FOLLOW;515}516out517}518}519520impl From<crate::filesystem::DescriptorFlags> for types::DescriptorFlags {521fn from(flags: crate::filesystem::DescriptorFlags) -> Self {522let mut out = Self::empty();523if flags.contains(crate::filesystem::DescriptorFlags::READ) {524out |= Self::READ;525}526if flags.contains(crate::filesystem::DescriptorFlags::WRITE) {527out |= Self::WRITE;528}529if flags.contains(crate::filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) {530out |= Self::FILE_INTEGRITY_SYNC;531}532if flags.contains(crate::filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) {533out |= Self::DATA_INTEGRITY_SYNC;534}535if flags.contains(crate::filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) {536out |= Self::REQUESTED_WRITE_SYNC;537}538if flags.contains(crate::filesystem::DescriptorFlags::MUTATE_DIRECTORY) {539out |= Self::MUTATE_DIRECTORY;540}541out542}543}544545impl From<types::DescriptorFlags> for crate::filesystem::DescriptorFlags {546fn from(flags: types::DescriptorFlags) -> Self {547let mut out = Self::empty();548if flags.contains(types::DescriptorFlags::READ) {549out |= Self::READ;550}551if flags.contains(types::DescriptorFlags::WRITE) {552out |= Self::WRITE;553}554if flags.contains(types::DescriptorFlags::FILE_INTEGRITY_SYNC) {555out |= Self::FILE_INTEGRITY_SYNC;556}557if flags.contains(types::DescriptorFlags::DATA_INTEGRITY_SYNC) {558out |= Self::DATA_INTEGRITY_SYNC;559}560if flags.contains(types::DescriptorFlags::REQUESTED_WRITE_SYNC) {561out |= Self::REQUESTED_WRITE_SYNC;562}563if flags.contains(types::DescriptorFlags::MUTATE_DIRECTORY) {564out |= Self::MUTATE_DIRECTORY;565}566out567}568}569570impl From<crate::filesystem::MetadataHashValue> for types::MetadataHashValue {571fn from(572crate::filesystem::MetadataHashValue { lower, upper }: crate::filesystem::MetadataHashValue,573) -> Self {574Self { lower, upper }575}576}577578impl TryFrom<crate::filesystem::DescriptorStat> for types::DescriptorStat {579type Error = ErrorCode;580581fn try_from(582crate::filesystem::DescriptorStat {583type_,584link_count,585size,586data_access_timestamp,587data_modification_timestamp,588status_change_timestamp,589}: crate::filesystem::DescriptorStat,590) -> Result<Self, ErrorCode> {591Ok(Self {592type_: type_.into(),593link_count,594size,595data_access_timestamp: data_access_timestamp.map(|t| t.try_into()).transpose()?,596data_modification_timestamp: data_modification_timestamp597.map(|t| t.try_into())598.transpose()?,599status_change_timestamp: status_change_timestamp.map(|t| t.try_into()).transpose()?,600})601}602}603604impl From<crate::filesystem::DescriptorType> for types::DescriptorType {605fn from(ty: crate::filesystem::DescriptorType) -> Self {606match ty {607crate::filesystem::DescriptorType::Unknown => Self::Unknown,608crate::filesystem::DescriptorType::BlockDevice => Self::BlockDevice,609crate::filesystem::DescriptorType::CharacterDevice => Self::CharacterDevice,610crate::filesystem::DescriptorType::Directory => Self::Directory,611crate::filesystem::DescriptorType::SymbolicLink => Self::SymbolicLink,612crate::filesystem::DescriptorType::RegularFile => Self::RegularFile,613}614}615}616617#[cfg(unix)]618fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {619use rustix::io::Errno as RustixErrno;620if err.is_none() {621return None;622}623Some(match RustixErrno::from_raw_os_error(err.unwrap()) {624RustixErrno::PIPE => ErrorCode::Pipe,625RustixErrno::PERM => ErrorCode::NotPermitted,626RustixErrno::NOENT => ErrorCode::NoEntry,627RustixErrno::NOMEM => ErrorCode::InsufficientMemory,628RustixErrno::IO => ErrorCode::Io,629RustixErrno::BADF => ErrorCode::BadDescriptor,630RustixErrno::BUSY => ErrorCode::Busy,631RustixErrno::ACCESS => ErrorCode::Access,632RustixErrno::NOTDIR => ErrorCode::NotDirectory,633RustixErrno::ISDIR => ErrorCode::IsDirectory,634RustixErrno::INVAL => ErrorCode::Invalid,635RustixErrno::EXIST => ErrorCode::Exist,636RustixErrno::FBIG => ErrorCode::FileTooLarge,637RustixErrno::NOSPC => ErrorCode::InsufficientSpace,638RustixErrno::SPIPE => ErrorCode::InvalidSeek,639RustixErrno::MLINK => ErrorCode::TooManyLinks,640RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong,641RustixErrno::NOTEMPTY => ErrorCode::NotEmpty,642RustixErrno::LOOP => ErrorCode::Loop,643RustixErrno::OVERFLOW => ErrorCode::Overflow,644RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence,645RustixErrno::NOTSUP => ErrorCode::Unsupported,646RustixErrno::ALREADY => ErrorCode::Already,647RustixErrno::INPROGRESS => ErrorCode::InProgress,648RustixErrno::INTR => ErrorCode::Interrupted,649650#[allow(651unreachable_patterns,652reason = "on some platforms, these have the same value as other errno values"653)]654RustixErrno::OPNOTSUPP => ErrorCode::Unsupported,655656_ => return None,657})658}659#[cfg(windows)]660fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {661use windows_sys::Win32::Foundation;662Some(match raw_os_error.map(|code| code as u32) {663Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry,664Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry,665Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access,666Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access,667Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted,668Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor,669Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry,670Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory,671Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory,672Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty,673Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy,674Some(Foundation::ERROR_BUSY) => ErrorCode::Busy,675Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported,676Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist,677Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe,678Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong,679Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid,680Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid,681Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory,682Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist,683Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop,684Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory,685_ => return None,686})687}688689impl From<std::io::Error> for ErrorCode {690fn from(err: std::io::Error) -> ErrorCode {691ErrorCode::from(&err)692}693}694695impl<'a> From<&'a std::io::Error> for ErrorCode {696fn from(err: &'a std::io::Error) -> ErrorCode {697match from_raw_os_error(err.raw_os_error()) {698Some(errno) => errno,699None => {700tracing::debug!("unknown raw os error: {err}");701match err.kind() {702std::io::ErrorKind::NotFound => ErrorCode::NoEntry,703std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted,704std::io::ErrorKind::AlreadyExists => ErrorCode::Exist,705std::io::ErrorKind::InvalidInput => ErrorCode::Invalid,706_ => ErrorCode::Io,707}708}709}710}711}712713impl From<cap_rand::Error> for ErrorCode {714fn from(err: cap_rand::Error) -> ErrorCode {715// I picked Error::Io as a 'reasonable default', FIXME dan is this ok?716from_raw_os_error(err.raw_os_error()).unwrap_or(ErrorCode::Io)717}718}719720impl From<std::num::TryFromIntError> for ErrorCode {721fn from(_err: std::num::TryFromIntError) -> ErrorCode {722ErrorCode::Overflow723}724}725726fn descriptortype_from(ft: cap_std::fs::FileType) -> types::DescriptorType {727use cap_fs_ext::FileTypeExt;728use types::DescriptorType;729if ft.is_dir() {730DescriptorType::Directory731} else if ft.is_symlink() {732DescriptorType::SymbolicLink733} else if ft.is_block_device() {734DescriptorType::BlockDevice735} else if ft.is_char_device() {736DescriptorType::CharacterDevice737} else if ft.is_file() {738DescriptorType::RegularFile739} else {740DescriptorType::Unknown741}742}743744fn systemtime_from(t: wall_clock::Datetime) -> Result<std::time::SystemTime, ErrorCode> {745std::time::SystemTime::UNIX_EPOCH746.checked_add(core::time::Duration::new(t.seconds, t.nanoseconds))747.ok_or(ErrorCode::Overflow)748}749750fn systemtimespec_from(751t: types::NewTimestamp,752) -> Result<Option<fs_set_times::SystemTimeSpec>, ErrorCode> {753use fs_set_times::SystemTimeSpec;754match t {755types::NewTimestamp::NoChange => Ok(None),756types::NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)),757types::NewTimestamp::Timestamp(st) => {758let st = systemtime_from(st)?;759Ok(Some(SystemTimeSpec::Absolute(st)))760}761}762}763764impl From<crate::clocks::DatetimeError> for ErrorCode {765fn from(_: crate::clocks::DatetimeError) -> ErrorCode {766ErrorCode::Overflow767}768}769770#[cfg(test)]771mod test {772use super::*;773use wasmtime::component::ResourceTable;774775#[test]776fn table_readdir_works() {777let mut table = ResourceTable::new();778let ix = table779.push(ReaddirIterator::new(std::iter::empty()))780.unwrap();781let _ = table.get(&ix).unwrap();782table.delete(ix).unwrap();783}784}785786787