Path: blob/main/crates/wasi/src/p3/filesystem/host.rs
3088 views
use crate::filesystem::{Descriptor, Dir, File, WasiFilesystem, WasiFilesystemCtxView};1use crate::p3::bindings::clocks::system_clock;2use crate::p3::bindings::filesystem::types::{3self, Advice, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry, ErrorCode,4Filesize, MetadataHashValue, NewTimestamp, OpenFlags, PathFlags,5};6use crate::p3::filesystem::{FilesystemError, FilesystemResult, preopens};7use crate::p3::{DEFAULT_BUFFER_CAPACITY, FallibleIteratorProducer};8use crate::{DirPerms, FilePerms};9use bytes::BytesMut;10use core::pin::Pin;11use core::task::{Context, Poll, ready};12use core::{iter, mem};13use std::io::{self, Cursor};14use std::sync::Arc;15use system_interface::fs::FileIoExt as _;16use tokio::sync::{mpsc, oneshot};17use tokio::task::{JoinHandle, spawn_blocking};18use wasmtime::StoreContextMut;19use wasmtime::component::{20Access, Accessor, Destination, FutureReader, Resource, ResourceTable, Source, StreamConsumer,21StreamProducer, StreamReader, StreamResult,22};23use wasmtime::error::Context as _;2425fn get_descriptor<'a>(26table: &'a ResourceTable,27fd: &'a Resource<Descriptor>,28) -> FilesystemResult<&'a Descriptor> {29table30.get(fd)31.context("failed to get descriptor resource from table")32.map_err(FilesystemError::trap)33}3435fn get_file<'a>(36table: &'a ResourceTable,37fd: &'a Resource<Descriptor>,38) -> FilesystemResult<&'a File> {39let file = get_descriptor(table, fd).map(Descriptor::file)??;40Ok(file)41}4243fn get_dir<'a>(44table: &'a ResourceTable,45fd: &'a Resource<Descriptor>,46) -> FilesystemResult<&'a Dir> {47let dir = get_descriptor(table, fd).map(Descriptor::dir)??;48Ok(dir)49}5051trait AccessorExt {52fn get_descriptor(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Descriptor>;53fn get_file(&self, fd: &Resource<Descriptor>) -> FilesystemResult<File>;54fn get_dir(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Dir>;55fn get_dir_pair(56&self,57a: &Resource<Descriptor>,58b: &Resource<Descriptor>,59) -> FilesystemResult<(Dir, Dir)>;60}6162impl<T> AccessorExt for Accessor<T, WasiFilesystem> {63fn get_descriptor(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Descriptor> {64self.with(|mut store| {65let fd = get_descriptor(store.get().table, fd)?;66Ok(fd.clone())67})68}6970fn get_file(&self, fd: &Resource<Descriptor>) -> FilesystemResult<File> {71self.with(|mut store| {72let file = get_file(store.get().table, fd)?;73Ok(file.clone())74})75}7677fn get_dir(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Dir> {78self.with(|mut store| {79let dir = get_dir(store.get().table, fd)?;80Ok(dir.clone())81})82}8384fn get_dir_pair(85&self,86a: &Resource<Descriptor>,87b: &Resource<Descriptor>,88) -> FilesystemResult<(Dir, Dir)> {89self.with(|mut store| {90let table = store.get().table;91let a = get_dir(table, a)?;92let b = get_dir(table, b)?;93Ok((a.clone(), b.clone()))94})95}96}9798fn systemtime_from(t: system_clock::Instant) -> Result<std::time::SystemTime, ErrorCode> {99if let Ok(seconds) = t.seconds.try_into() {100std::time::SystemTime::UNIX_EPOCH101.checked_add(core::time::Duration::new(seconds, t.nanoseconds))102.ok_or(ErrorCode::Overflow)103} else {104std::time::SystemTime::UNIX_EPOCH105.checked_sub(core::time::Duration::new(106t.seconds.unsigned_abs(),107t.nanoseconds,108))109.ok_or(ErrorCode::Overflow)110}111}112113fn systemtimespec_from(t: NewTimestamp) -> Result<Option<fs_set_times::SystemTimeSpec>, ErrorCode> {114use fs_set_times::SystemTimeSpec;115match t {116NewTimestamp::NoChange => Ok(None),117NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)),118NewTimestamp::Timestamp(st) => {119let st = systemtime_from(st)?;120Ok(Some(SystemTimeSpec::Absolute(st)))121}122}123}124125struct ReadStreamProducer {126file: File,127offset: u64,128result: Option<oneshot::Sender<Result<(), ErrorCode>>>,129task: Option<JoinHandle<std::io::Result<BytesMut>>>,130}131132impl Drop for ReadStreamProducer {133fn drop(&mut self) {134self.close(Ok(()))135}136}137138impl ReadStreamProducer {139fn close(&mut self, res: Result<(), ErrorCode>) {140if let Some(tx) = self.result.take() {141_ = tx.send(res);142}143}144145/// Update the internal `offset` field after reading `amt` bytes from the file.146fn complete_read(&mut self, amt: usize) -> StreamResult {147let Ok(amt) = amt.try_into() else {148self.close(Err(ErrorCode::Overflow));149return StreamResult::Dropped;150};151let Some(amt) = self.offset.checked_add(amt) else {152self.close(Err(ErrorCode::Overflow));153return StreamResult::Dropped;154};155self.offset = amt;156StreamResult::Completed157}158}159160impl<D> StreamProducer<D> for ReadStreamProducer {161type Item = u8;162type Buffer = Cursor<BytesMut>;163164fn poll_produce<'a>(165mut self: Pin<&mut Self>,166cx: &mut Context<'_>,167store: StoreContextMut<'a, D>,168mut dst: Destination<'a, Self::Item, Self::Buffer>,169// Intentionally ignore this as in blocking mode everything is always170// ready and otherwise spawned blocking work can't be cancelled.171_finish: bool,172) -> Poll<wasmtime::Result<StreamResult>> {173if let Some(file) = self.file.as_blocking_file() {174// Once a blocking file, always a blocking file, so assert as such.175assert!(self.task.is_none());176let mut dst = dst.as_direct(store, DEFAULT_BUFFER_CAPACITY);177let buf = dst.remaining();178if buf.is_empty() {179return Poll::Ready(Ok(StreamResult::Completed));180}181return match file.read_at(buf, self.offset) {182Ok(0) => {183self.close(Ok(()));184Poll::Ready(Ok(StreamResult::Dropped))185}186Ok(n) => {187dst.mark_written(n);188Poll::Ready(Ok(self.complete_read(n)))189}190Err(err) => {191self.close(Err(err.into()));192Poll::Ready(Ok(StreamResult::Dropped))193}194};195}196197// Lazily spawn a read task if one hasn't already been spawned yet.198let me = &mut *self;199let task = me.task.get_or_insert_with(|| {200let mut buf = dst.take_buffer().into_inner();201buf.resize(DEFAULT_BUFFER_CAPACITY, 0);202let file = Arc::clone(me.file.as_file());203let offset = me.offset;204spawn_blocking(move || {205file.read_at(&mut buf, offset).map(|n| {206buf.truncate(n);207buf208})209})210});211212// Await the completion of the read task. Note that this is not a213// cancellable await point because we can't cancel the other task, so214// the `finish` parameter is ignored.215let res = ready!(Pin::new(task).poll(cx)).expect("I/O task should not panic");216self.task = None;217match res {218Ok(buf) if buf.is_empty() => {219self.close(Ok(()));220Poll::Ready(Ok(StreamResult::Dropped))221}222Ok(buf) => {223let n = buf.len();224dst.set_buffer(Cursor::new(buf));225Poll::Ready(Ok(self.complete_read(n)))226}227Err(err) => {228self.close(Err(err.into()));229Poll::Ready(Ok(StreamResult::Dropped))230}231}232}233}234235fn map_dir_entry(236entry: std::io::Result<cap_std::fs::DirEntry>,237) -> Result<Option<DirectoryEntry>, ErrorCode> {238match entry {239Ok(entry) => {240let meta = entry.metadata()?;241let Ok(name) = entry.file_name().into_string() else {242return Err(ErrorCode::IllegalByteSequence);243};244Ok(Some(DirectoryEntry {245type_: meta.file_type().into(),246name,247}))248}249Err(err) => {250// On windows, filter out files like `C:\DumpStack.log.tmp` which we251// can't get full metadata for.252#[cfg(windows)]253{254use windows_sys::Win32::Foundation::{255ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION,256};257if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)258|| err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)259{260return Ok(None);261}262}263Err(err.into())264}265}266}267268struct ReadDirStream {269rx: mpsc::Receiver<DirectoryEntry>,270task: JoinHandle<Result<(), ErrorCode>>,271result: Option<oneshot::Sender<Result<(), ErrorCode>>>,272}273274impl ReadDirStream {275fn new(276dir: Arc<cap_std::fs::Dir>,277result: oneshot::Sender<Result<(), ErrorCode>>,278) -> ReadDirStream {279let (tx, rx) = mpsc::channel(1);280ReadDirStream {281task: spawn_blocking(move || {282let entries = dir.entries()?;283for entry in entries {284if let Some(entry) = map_dir_entry(entry)? {285if let Err(_) = tx.blocking_send(entry) {286break;287}288}289}290Ok(())291}),292rx,293result: Some(result),294}295}296297fn close(&mut self, res: Result<(), ErrorCode>) {298self.rx.close();299self.task.abort();300let _ = self.result.take().unwrap().send(res);301}302}303304impl<D> StreamProducer<D> for ReadDirStream {305type Item = DirectoryEntry;306type Buffer = Option<DirectoryEntry>;307308fn poll_produce<'a>(309mut self: Pin<&mut Self>,310cx: &mut Context<'_>,311mut store: StoreContextMut<'a, D>,312mut dst: Destination<'a, Self::Item, Self::Buffer>,313finish: bool,314) -> Poll<wasmtime::Result<StreamResult>> {315// If this is a 0-length read then `mpsc::Receiver` does not expose an316// API to wait for an item to be available without taking it out of the317// channel. In lieu of that just say that we're complete and ready for a318// read.319if dst.remaining(&mut store) == Some(0) {320return Poll::Ready(Ok(StreamResult::Completed));321}322323match self.rx.poll_recv(cx) {324// If an item is on the channel then send that along and say that325// the read is now complete with one item being yielded.326Poll::Ready(Some(item)) => {327dst.set_buffer(Some(item));328Poll::Ready(Ok(StreamResult::Completed))329}330331// If there's nothing left on the channel then that means that an332// error occurred or the iterator is done. In both cases an333// un-cancellable wait for the spawned task is entered and we await334// its completion. Upon completion there our own stream is closed335// with the result (sending an error code on our oneshot) and then336// the stream is reported as dropped.337Poll::Ready(None) => {338let result = ready!(Pin::new(&mut self.task).poll(cx))339.expect("spawned task should not panic");340self.close(result);341Poll::Ready(Ok(StreamResult::Dropped))342}343344// If an item isn't ready yet then cancel this outstanding request345// if `finish` is set, otherwise propagate the `Pending` status.346Poll::Pending if finish => Poll::Ready(Ok(StreamResult::Cancelled)),347Poll::Pending => Poll::Pending,348}349}350}351352impl Drop for ReadDirStream {353fn drop(&mut self) {354if self.result.is_some() {355self.close(Ok(()));356}357}358}359360struct WriteStreamConsumer {361file: File,362location: WriteLocation,363result: Option<oneshot::Sender<Result<(), ErrorCode>>>,364buffer: BytesMut,365task: Option<JoinHandle<std::io::Result<(BytesMut, usize)>>>,366}367368#[derive(Copy, Clone)]369enum WriteLocation {370End,371Offset(u64),372}373374impl WriteStreamConsumer {375fn new_at(file: File, offset: u64, result: oneshot::Sender<Result<(), ErrorCode>>) -> Self {376Self {377file,378location: WriteLocation::Offset(offset),379result: Some(result),380buffer: BytesMut::default(),381task: None,382}383}384385fn new_append(file: File, result: oneshot::Sender<Result<(), ErrorCode>>) -> Self {386Self {387file,388location: WriteLocation::End,389result: Some(result),390buffer: BytesMut::default(),391task: None,392}393}394395fn close(&mut self, res: Result<(), ErrorCode>) {396_ = self.result.take().unwrap().send(res);397}398399/// Update the internal `offset` field after writing `amt` bytes from the file.400fn complete_write(&mut self, amt: usize) -> StreamResult {401match &mut self.location {402WriteLocation::End => StreamResult::Completed,403WriteLocation::Offset(offset) => {404let Ok(amt) = amt.try_into() else {405self.close(Err(ErrorCode::Overflow));406return StreamResult::Dropped;407};408let Some(amt) = offset.checked_add(amt) else {409self.close(Err(ErrorCode::Overflow));410return StreamResult::Dropped;411};412*offset = amt;413StreamResult::Completed414}415}416}417}418419impl WriteLocation {420fn write(&self, file: &cap_std::fs::File, bytes: &[u8]) -> io::Result<usize> {421match *self {422WriteLocation::End => file.append(bytes),423WriteLocation::Offset(at) => file.write_at(bytes, at),424}425}426}427428impl<D> StreamConsumer<D> for WriteStreamConsumer {429type Item = u8;430431fn poll_consume(432mut self: Pin<&mut Self>,433cx: &mut Context<'_>,434store: StoreContextMut<D>,435src: Source<Self::Item>,436// Intentionally ignore this as in blocking mode everything is always437// ready and otherwise spawned blocking work can't be cancelled.438_finish: bool,439) -> Poll<wasmtime::Result<StreamResult>> {440let mut src = src.as_direct(store);441if let Some(file) = self.file.as_blocking_file() {442// Once a blocking file, always a blocking file, so assert as such.443assert!(self.task.is_none());444return match self.location.write(file, src.remaining()) {445Ok(n) => {446src.mark_read(n);447Poll::Ready(Ok(self.complete_write(n)))448}449Err(err) => {450self.close(Err(err.into()));451Poll::Ready(Ok(StreamResult::Dropped))452}453};454}455let me = &mut *self;456let task = me.task.get_or_insert_with(|| {457debug_assert!(me.buffer.is_empty());458me.buffer.extend_from_slice(src.remaining());459let buf = mem::take(&mut me.buffer);460let file = Arc::clone(me.file.as_file());461let location = me.location;462spawn_blocking(move || location.write(&file, &buf).map(|n| (buf, n)))463});464let res = ready!(Pin::new(task).poll(cx)).expect("I/O task should not panic");465self.task = None;466match res {467Ok((buf, n)) => {468src.mark_read(n);469self.buffer = buf;470self.buffer.clear();471Poll::Ready(Ok(self.complete_write(n)))472}473Err(err) => {474self.close(Err(err.into()));475Poll::Ready(Ok(StreamResult::Dropped))476}477}478}479}480481impl Drop for WriteStreamConsumer {482fn drop(&mut self) {483if self.result.is_some() {484self.close(Ok(()))485}486}487}488489impl types::Host for WasiFilesystemCtxView<'_> {490fn convert_error_code(&mut self, error: FilesystemError) -> wasmtime::Result<ErrorCode> {491error.downcast()492}493}494495impl types::HostDescriptorWithStore for WasiFilesystem {496fn read_via_stream<U>(497mut store: Access<U, Self>,498fd: Resource<Descriptor>,499offset: Filesize,500) -> wasmtime::Result<(StreamReader<u8>, FutureReader<Result<(), ErrorCode>>)> {501let file = get_file(store.get().table, &fd)?;502if !file.perms.contains(FilePerms::READ) {503return Ok((504StreamReader::new(&mut store, iter::empty()),505FutureReader::new(&mut store, async {506wasmtime::error::Ok(Err(ErrorCode::NotPermitted))507}),508));509}510511let file = file.clone();512let (result_tx, result_rx) = oneshot::channel();513Ok((514StreamReader::new(515&mut store,516ReadStreamProducer {517file,518offset,519result: Some(result_tx),520task: None,521},522),523FutureReader::new(&mut store, result_rx),524))525}526527async fn write_via_stream<U>(528store: &Accessor<U, Self>,529fd: Resource<Descriptor>,530data: StreamReader<u8>,531offset: Filesize,532) -> FilesystemResult<()> {533let (result_tx, result_rx) = oneshot::channel();534store.with(|mut store| {535let file = get_file(store.get().table, &fd)?;536if !file.perms.contains(FilePerms::WRITE) {537return Err(ErrorCode::NotPermitted.into());538}539let file = file.clone();540data.pipe(store, WriteStreamConsumer::new_at(file, offset, result_tx));541FilesystemResult::Ok(())542})?;543result_rx544.await545.context("oneshot sender dropped")546.map_err(FilesystemError::trap)??;547Ok(())548}549550async fn append_via_stream<U>(551store: &Accessor<U, Self>,552fd: Resource<Descriptor>,553data: StreamReader<u8>,554) -> FilesystemResult<()> {555let (result_tx, result_rx) = oneshot::channel();556store.with(|mut store| {557let file = get_file(store.get().table, &fd)?;558if !file.perms.contains(FilePerms::WRITE) {559return Err(ErrorCode::NotPermitted.into());560}561let file = file.clone();562data.pipe(store, WriteStreamConsumer::new_append(file, result_tx));563FilesystemResult::Ok(())564})?;565result_rx566.await567.context("oneshot sender dropped")568.map_err(FilesystemError::trap)??;569Ok(())570}571572async fn advise<U>(573store: &Accessor<U, Self>,574fd: Resource<Descriptor>,575offset: Filesize,576length: Filesize,577advice: Advice,578) -> FilesystemResult<()> {579let file = store.get_file(&fd)?;580file.advise(offset, length, advice.into()).await?;581Ok(())582}583584async fn sync_data<U>(585store: &Accessor<U, Self>,586fd: Resource<Descriptor>,587) -> FilesystemResult<()> {588let fd = store.get_descriptor(&fd)?;589fd.sync_data().await?;590Ok(())591}592593async fn get_flags<U>(594store: &Accessor<U, Self>,595fd: Resource<Descriptor>,596) -> FilesystemResult<DescriptorFlags> {597let fd = store.get_descriptor(&fd)?;598let flags = fd.get_flags().await?;599Ok(flags.into())600}601602async fn get_type<U>(603store: &Accessor<U, Self>,604fd: Resource<Descriptor>,605) -> FilesystemResult<DescriptorType> {606let fd = store.get_descriptor(&fd)?;607let ty = fd.get_type().await?;608Ok(ty.into())609}610611async fn set_size<U>(612store: &Accessor<U, Self>,613fd: Resource<Descriptor>,614size: Filesize,615) -> FilesystemResult<()> {616let file = store.get_file(&fd)?;617file.set_size(size).await?;618Ok(())619}620621async fn set_times<U>(622store: &Accessor<U, Self>,623fd: Resource<Descriptor>,624data_access_timestamp: NewTimestamp,625data_modification_timestamp: NewTimestamp,626) -> FilesystemResult<()> {627let fd = store.get_descriptor(&fd)?;628let atim = systemtimespec_from(data_access_timestamp)?;629let mtim = systemtimespec_from(data_modification_timestamp)?;630fd.set_times(atim, mtim).await?;631Ok(())632}633634async fn read_directory<U>(635store: &Accessor<U, Self>,636fd: Resource<Descriptor>,637) -> wasmtime::Result<(638StreamReader<DirectoryEntry>,639FutureReader<Result<(), ErrorCode>>,640)> {641store.with(|mut store| {642let dir = get_dir(store.get().table, &fd)?;643if !dir.perms.contains(DirPerms::READ) {644return Ok((645StreamReader::new(&mut store, iter::empty()),646FutureReader::new(&mut store, async {647wasmtime::error::Ok(Err(ErrorCode::NotPermitted))648}),649));650}651let allow_blocking_current_thread = dir.allow_blocking_current_thread;652let dir = Arc::clone(dir.as_dir());653let (result_tx, result_rx) = oneshot::channel();654let stream = if allow_blocking_current_thread {655match dir.entries() {656Ok(readdir) => StreamReader::new(657&mut store,658FallibleIteratorProducer::new(659readdir.filter_map(|e| map_dir_entry(e).transpose()),660result_tx,661),662),663Err(e) => {664result_tx.send(Err(e.into())).unwrap();665StreamReader::new(&mut store, iter::empty())666}667}668} else {669StreamReader::new(&mut store, ReadDirStream::new(dir, result_tx))670};671Ok((stream, FutureReader::new(&mut store, result_rx)))672})673}674675async fn sync<U>(store: &Accessor<U, Self>, fd: Resource<Descriptor>) -> FilesystemResult<()> {676let fd = store.get_descriptor(&fd)?;677fd.sync().await?;678Ok(())679}680681async fn create_directory_at<U>(682store: &Accessor<U, Self>,683fd: Resource<Descriptor>,684path: String,685) -> FilesystemResult<()> {686let dir = store.get_dir(&fd)?;687dir.create_directory_at(path).await?;688Ok(())689}690691async fn stat<U>(692store: &Accessor<U, Self>,693fd: Resource<Descriptor>,694) -> FilesystemResult<DescriptorStat> {695let fd = store.get_descriptor(&fd)?;696let stat = fd.stat().await?;697Ok(stat.into())698}699700async fn stat_at<U>(701store: &Accessor<U, Self>,702fd: Resource<Descriptor>,703path_flags: PathFlags,704path: String,705) -> FilesystemResult<DescriptorStat> {706let dir = store.get_dir(&fd)?;707let stat = dir.stat_at(path_flags.into(), path).await?;708Ok(stat.into())709}710711async fn set_times_at<U>(712store: &Accessor<U, Self>,713fd: Resource<Descriptor>,714path_flags: PathFlags,715path: String,716data_access_timestamp: NewTimestamp,717data_modification_timestamp: NewTimestamp,718) -> FilesystemResult<()> {719let dir = store.get_dir(&fd)?;720let atim = systemtimespec_from(data_access_timestamp)?;721let mtim = systemtimespec_from(data_modification_timestamp)?;722dir.set_times_at(path_flags.into(), path, atim, mtim)723.await?;724Ok(())725}726727async fn link_at<U>(728store: &Accessor<U, Self>,729fd: Resource<Descriptor>,730old_path_flags: PathFlags,731old_path: String,732new_fd: Resource<Descriptor>,733new_path: String,734) -> FilesystemResult<()> {735let (old_dir, new_dir) = store.get_dir_pair(&fd, &new_fd)?;736old_dir737.link_at(old_path_flags.into(), old_path, &new_dir, new_path)738.await?;739Ok(())740}741742async fn open_at<U>(743store: &Accessor<U, Self>,744fd: Resource<Descriptor>,745path_flags: PathFlags,746path: String,747open_flags: OpenFlags,748flags: DescriptorFlags,749) -> FilesystemResult<Resource<Descriptor>> {750let (allow_blocking_current_thread, dir) = store.with(|mut store| {751let store = store.get();752let dir = get_dir(&store.table, &fd)?;753FilesystemResult::Ok((store.ctx.allow_blocking_current_thread, dir.clone()))754})?;755let fd = dir756.open_at(757path_flags.into(),758path,759open_flags.into(),760flags.into(),761allow_blocking_current_thread,762)763.await?;764let fd = store.with(|mut store| store.get().table.push(fd))?;765Ok(fd)766}767768async fn readlink_at<U>(769store: &Accessor<U, Self>,770fd: Resource<Descriptor>,771path: String,772) -> FilesystemResult<String> {773let dir = store.get_dir(&fd)?;774let path = dir.readlink_at(path).await?;775Ok(path)776}777778async fn remove_directory_at<U>(779store: &Accessor<U, Self>,780fd: Resource<Descriptor>,781path: String,782) -> FilesystemResult<()> {783let dir = store.get_dir(&fd)?;784dir.remove_directory_at(path).await?;785Ok(())786}787788async fn rename_at<U>(789store: &Accessor<U, Self>,790fd: Resource<Descriptor>,791old_path: String,792new_fd: Resource<Descriptor>,793new_path: String,794) -> FilesystemResult<()> {795let (old_dir, new_dir) = store.get_dir_pair(&fd, &new_fd)?;796old_dir.rename_at(old_path, &new_dir, new_path).await?;797Ok(())798}799800async fn symlink_at<U>(801store: &Accessor<U, Self>,802fd: Resource<Descriptor>,803old_path: String,804new_path: String,805) -> FilesystemResult<()> {806let dir = store.get_dir(&fd)?;807dir.symlink_at(old_path, new_path).await?;808Ok(())809}810811async fn unlink_file_at<U>(812store: &Accessor<U, Self>,813fd: Resource<Descriptor>,814path: String,815) -> FilesystemResult<()> {816let dir = store.get_dir(&fd)?;817dir.unlink_file_at(path).await?;818Ok(())819}820821async fn is_same_object<U>(822store: &Accessor<U, Self>,823fd: Resource<Descriptor>,824other: Resource<Descriptor>,825) -> wasmtime::Result<bool> {826let (fd, other) = store.with(|mut store| {827let table = store.get().table;828let fd = get_descriptor(table, &fd)?.clone();829let other = get_descriptor(table, &other)?.clone();830wasmtime::error::Ok((fd, other))831})?;832fd.is_same_object(&other).await833}834835async fn metadata_hash<U>(836store: &Accessor<U, Self>,837fd: Resource<Descriptor>,838) -> FilesystemResult<MetadataHashValue> {839let fd = store.get_descriptor(&fd)?;840let meta = fd.metadata_hash().await?;841Ok(meta.into())842}843844async fn metadata_hash_at<U>(845store: &Accessor<U, Self>,846fd: Resource<Descriptor>,847path_flags: PathFlags,848path: String,849) -> FilesystemResult<MetadataHashValue> {850let dir = store.get_dir(&fd)?;851let meta = dir.metadata_hash_at(path_flags.into(), path).await?;852Ok(meta.into())853}854}855856impl types::HostDescriptor for WasiFilesystemCtxView<'_> {857fn drop(&mut self, fd: Resource<Descriptor>) -> wasmtime::Result<()> {858self.table859.delete(fd)860.context("failed to delete descriptor resource from table")?;861Ok(())862}863}864865impl preopens::Host for WasiFilesystemCtxView<'_> {866fn get_directories(&mut self) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {867self.get_directories()868}869}870871872