Path: blob/main/crates/wasi/src/p3/filesystem/host.rs
1692 views
use crate::filesystem::{Descriptor, Dir, File, WasiFilesystem, WasiFilesystemCtxView};1use crate::p3::bindings::clocks::wall_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::{8DEFAULT_BUFFER_CAPACITY, FallibleIteratorProducer, FutureOneshotProducer, FutureReadyProducer,9StreamEmptyProducer,10};11use crate::{DirPerms, FilePerms};12use anyhow::Context as _;13use bytes::BytesMut;14use core::mem;15use core::pin::Pin;16use core::task::{Context, Poll, ready};17use std::io::{self, Cursor};18use std::sync::Arc;19use system_interface::fs::FileIoExt as _;20use tokio::sync::{mpsc, oneshot};21use tokio::task::{JoinHandle, spawn_blocking};22use wasmtime::StoreContextMut;23use wasmtime::component::{24Accessor, Destination, FutureReader, Resource, ResourceTable, Source, StreamConsumer,25StreamProducer, StreamReader, StreamResult,26};2728fn get_descriptor<'a>(29table: &'a ResourceTable,30fd: &'a Resource<Descriptor>,31) -> FilesystemResult<&'a Descriptor> {32table33.get(fd)34.context("failed to get descriptor resource from table")35.map_err(FilesystemError::trap)36}3738fn get_file<'a>(39table: &'a ResourceTable,40fd: &'a Resource<Descriptor>,41) -> FilesystemResult<&'a File> {42let file = get_descriptor(table, fd).map(Descriptor::file)??;43Ok(file)44}4546fn get_dir<'a>(47table: &'a ResourceTable,48fd: &'a Resource<Descriptor>,49) -> FilesystemResult<&'a Dir> {50let dir = get_descriptor(table, fd).map(Descriptor::dir)??;51Ok(dir)52}5354trait AccessorExt {55fn get_descriptor(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Descriptor>;56fn get_file(&self, fd: &Resource<Descriptor>) -> FilesystemResult<File>;57fn get_dir(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Dir>;58fn get_dir_pair(59&self,60a: &Resource<Descriptor>,61b: &Resource<Descriptor>,62) -> FilesystemResult<(Dir, Dir)>;63}6465impl<T> AccessorExt for Accessor<T, WasiFilesystem> {66fn get_descriptor(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Descriptor> {67self.with(|mut store| {68let fd = get_descriptor(store.get().table, fd)?;69Ok(fd.clone())70})71}7273fn get_file(&self, fd: &Resource<Descriptor>) -> FilesystemResult<File> {74self.with(|mut store| {75let file = get_file(store.get().table, fd)?;76Ok(file.clone())77})78}7980fn get_dir(&self, fd: &Resource<Descriptor>) -> FilesystemResult<Dir> {81self.with(|mut store| {82let dir = get_dir(store.get().table, fd)?;83Ok(dir.clone())84})85}8687fn get_dir_pair(88&self,89a: &Resource<Descriptor>,90b: &Resource<Descriptor>,91) -> FilesystemResult<(Dir, Dir)> {92self.with(|mut store| {93let table = store.get().table;94let a = get_dir(table, a)?;95let b = get_dir(table, b)?;96Ok((a.clone(), b.clone()))97})98}99}100101fn systemtime_from(t: wall_clock::Datetime) -> Result<std::time::SystemTime, ErrorCode> {102std::time::SystemTime::UNIX_EPOCH103.checked_add(core::time::Duration::new(t.seconds, t.nanoseconds))104.ok_or(ErrorCode::Overflow)105}106107fn systemtimespec_from(t: NewTimestamp) -> Result<Option<fs_set_times::SystemTimeSpec>, ErrorCode> {108use fs_set_times::SystemTimeSpec;109match t {110NewTimestamp::NoChange => Ok(None),111NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)),112NewTimestamp::Timestamp(st) => {113let st = systemtime_from(st)?;114Ok(Some(SystemTimeSpec::Absolute(st)))115}116}117}118119struct ReadStreamProducer {120file: File,121offset: u64,122result: Option<oneshot::Sender<Result<(), ErrorCode>>>,123task: Option<JoinHandle<std::io::Result<BytesMut>>>,124}125126impl Drop for ReadStreamProducer {127fn drop(&mut self) {128self.close(Ok(()))129}130}131132impl ReadStreamProducer {133fn close(&mut self, res: Result<(), ErrorCode>) {134if let Some(tx) = self.result.take() {135_ = tx.send(res);136}137}138139/// Update the internal `offset` field after reading `amt` bytes from the file.140fn complete_read(&mut self, amt: usize) -> StreamResult {141let Ok(amt) = amt.try_into() else {142self.close(Err(ErrorCode::Overflow));143return StreamResult::Dropped;144};145let Some(amt) = self.offset.checked_add(amt) else {146self.close(Err(ErrorCode::Overflow));147return StreamResult::Dropped;148};149self.offset = amt;150StreamResult::Completed151}152}153154impl<D> StreamProducer<D> for ReadStreamProducer {155type Item = u8;156type Buffer = Cursor<BytesMut>;157158fn poll_produce<'a>(159mut self: Pin<&mut Self>,160cx: &mut Context<'_>,161store: StoreContextMut<'a, D>,162mut dst: Destination<'a, Self::Item, Self::Buffer>,163// Intentionally ignore this as in blocking mode everything is always164// ready and otherwise spawned blocking work can't be cancelled.165_finish: bool,166) -> Poll<wasmtime::Result<StreamResult>> {167if let Some(file) = self.file.as_blocking_file() {168// Once a blocking file, always a blocking file, so assert as such.169assert!(self.task.is_none());170let mut dst = dst.as_direct(store, DEFAULT_BUFFER_CAPACITY);171let buf = dst.remaining();172if buf.is_empty() {173return Poll::Ready(Ok(StreamResult::Completed));174}175return match file.read_at(buf, self.offset) {176Ok(0) => {177self.close(Ok(()));178Poll::Ready(Ok(StreamResult::Dropped))179}180Ok(n) => {181dst.mark_written(n);182Poll::Ready(Ok(self.complete_read(n)))183}184Err(err) => {185self.close(Err(err.into()));186Poll::Ready(Ok(StreamResult::Dropped))187}188};189}190191// Lazily spawn a read task if one hasn't already been spawned yet.192let me = &mut *self;193let task = me.task.get_or_insert_with(|| {194let mut buf = dst.take_buffer().into_inner();195buf.resize(DEFAULT_BUFFER_CAPACITY, 0);196let file = Arc::clone(me.file.as_file());197let offset = me.offset;198spawn_blocking(move || {199file.read_at(&mut buf, offset).map(|n| {200buf.truncate(n);201buf202})203})204});205206// Await the completion of the read task. Note that this is not a207// cancellable await point because we can't cancel the other task, so208// the `finish` parameter is ignored.209let res = ready!(Pin::new(task).poll(cx)).expect("I/O task should not panic");210self.task = None;211match res {212Ok(buf) if buf.is_empty() => {213self.close(Ok(()));214Poll::Ready(Ok(StreamResult::Dropped))215}216Ok(buf) => {217let n = buf.len();218dst.set_buffer(Cursor::new(buf));219Poll::Ready(Ok(self.complete_read(n)))220}221Err(err) => {222self.close(Err(err.into()));223Poll::Ready(Ok(StreamResult::Dropped))224}225}226}227}228229fn map_dir_entry(230entry: std::io::Result<cap_std::fs::DirEntry>,231) -> Result<Option<DirectoryEntry>, ErrorCode> {232match entry {233Ok(entry) => {234let meta = entry.metadata()?;235let Ok(name) = entry.file_name().into_string() else {236return Err(ErrorCode::IllegalByteSequence);237};238Ok(Some(DirectoryEntry {239type_: meta.file_type().into(),240name,241}))242}243Err(err) => {244// On windows, filter out files like `C:\DumpStack.log.tmp` which we245// can't get full metadata for.246#[cfg(windows)]247{248use windows_sys::Win32::Foundation::{249ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION,250};251if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)252|| err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)253{254return Ok(None);255}256}257Err(err.into())258}259}260}261262struct ReadDirStream {263rx: mpsc::Receiver<DirectoryEntry>,264task: JoinHandle<Result<(), ErrorCode>>,265result: Option<oneshot::Sender<Result<(), ErrorCode>>>,266}267268impl ReadDirStream {269fn new(270dir: Arc<cap_std::fs::Dir>,271result: oneshot::Sender<Result<(), ErrorCode>>,272) -> ReadDirStream {273let (tx, rx) = mpsc::channel(1);274ReadDirStream {275task: spawn_blocking(move || {276let entries = dir.entries()?;277for entry in entries {278if let Some(entry) = map_dir_entry(entry)? {279if let Err(_) = tx.blocking_send(entry) {280break;281}282}283}284Ok(())285}),286rx,287result: Some(result),288}289}290291fn close(&mut self, res: Result<(), ErrorCode>) {292self.rx.close();293self.task.abort();294let _ = self.result.take().unwrap().send(res);295}296}297298impl<D> StreamProducer<D> for ReadDirStream {299type Item = DirectoryEntry;300type Buffer = Option<DirectoryEntry>;301302fn poll_produce<'a>(303mut self: Pin<&mut Self>,304cx: &mut Context<'_>,305mut store: StoreContextMut<'a, D>,306mut dst: Destination<'a, Self::Item, Self::Buffer>,307finish: bool,308) -> Poll<wasmtime::Result<StreamResult>> {309// If this is a 0-length read then `mpsc::Receiver` does not expose an310// API to wait for an item to be available without taking it out of the311// channel. In lieu of that just say that we're complete and ready for a312// read.313if dst.remaining(&mut store) == Some(0) {314return Poll::Ready(Ok(StreamResult::Completed));315}316317match self.rx.poll_recv(cx) {318// If an item is on the channel then send that along and say that319// the read is now complete with one item being yielded.320Poll::Ready(Some(item)) => {321dst.set_buffer(Some(item));322Poll::Ready(Ok(StreamResult::Completed))323}324325// If there's nothing left on the channel then that means that an326// error occurred or the iterator is done. In both cases an327// un-cancellable wait for the spawned task is entered and we await328// its completion. Upon completion there our own stream is closed329// with the result (sending an error code on our oneshot) and then330// the stream is reported as dropped.331Poll::Ready(None) => {332let result = ready!(Pin::new(&mut self.task).poll(cx))333.expect("spawned task should not panic");334self.close(result);335Poll::Ready(Ok(StreamResult::Dropped))336}337338// If an item isn't ready yet then cancel this outstanding request339// if `finish` is set, otherwise propagate the `Pending` status.340Poll::Pending if finish => Poll::Ready(Ok(StreamResult::Cancelled)),341Poll::Pending => Poll::Pending,342}343}344}345346impl Drop for ReadDirStream {347fn drop(&mut self) {348if self.result.is_some() {349self.close(Ok(()));350}351}352}353354struct WriteStreamConsumer {355file: File,356location: WriteLocation,357result: Option<oneshot::Sender<Result<(), ErrorCode>>>,358buffer: BytesMut,359task: Option<JoinHandle<std::io::Result<(BytesMut, usize)>>>,360}361362#[derive(Copy, Clone)]363enum WriteLocation {364End,365Offset(u64),366}367368impl WriteStreamConsumer {369fn new_at(file: File, offset: u64, result: oneshot::Sender<Result<(), ErrorCode>>) -> Self {370Self {371file,372location: WriteLocation::Offset(offset),373result: Some(result),374buffer: BytesMut::default(),375task: None,376}377}378379fn new_append(file: File, result: oneshot::Sender<Result<(), ErrorCode>>) -> Self {380Self {381file,382location: WriteLocation::End,383result: Some(result),384buffer: BytesMut::default(),385task: None,386}387}388389fn close(&mut self, res: Result<(), ErrorCode>) {390_ = self.result.take().unwrap().send(res);391}392393/// Update the internal `offset` field after writing `amt` bytes from the file.394fn complete_write(&mut self, amt: usize) -> StreamResult {395match &mut self.location {396WriteLocation::End => StreamResult::Completed,397WriteLocation::Offset(offset) => {398let Ok(amt) = amt.try_into() else {399self.close(Err(ErrorCode::Overflow));400return StreamResult::Dropped;401};402let Some(amt) = offset.checked_add(amt) else {403self.close(Err(ErrorCode::Overflow));404return StreamResult::Dropped;405};406*offset = amt;407StreamResult::Completed408}409}410}411}412413impl WriteLocation {414fn write(&self, file: &cap_std::fs::File, bytes: &[u8]) -> io::Result<usize> {415match *self {416WriteLocation::End => file.append(bytes),417WriteLocation::Offset(at) => file.write_at(bytes, at),418}419}420}421422impl<D> StreamConsumer<D> for WriteStreamConsumer {423type Item = u8;424425fn poll_consume(426mut self: Pin<&mut Self>,427cx: &mut Context<'_>,428store: StoreContextMut<D>,429src: Source<Self::Item>,430// Intentionally ignore this as in blocking mode everything is always431// ready and otherwise spawned blocking work can't be cancelled.432_finish: bool,433) -> Poll<wasmtime::Result<StreamResult>> {434let mut src = src.as_direct(store);435if let Some(file) = self.file.as_blocking_file() {436// Once a blocking file, always a blocking file, so assert as such.437assert!(self.task.is_none());438return match self.location.write(file, src.remaining()) {439Ok(n) => {440src.mark_read(n);441Poll::Ready(Ok(self.complete_write(n)))442}443Err(err) => {444self.close(Err(err.into()));445Poll::Ready(Ok(StreamResult::Dropped))446}447};448}449let me = &mut *self;450let task = me.task.get_or_insert_with(|| {451debug_assert!(me.buffer.is_empty());452me.buffer.extend_from_slice(src.remaining());453let buf = mem::take(&mut me.buffer);454let file = Arc::clone(me.file.as_file());455let location = me.location;456spawn_blocking(move || location.write(&file, &buf).map(|n| (buf, n)))457});458let res = ready!(Pin::new(task).poll(cx)).expect("I/O task should not panic");459self.task = None;460match res {461Ok((buf, n)) => {462src.mark_read(n);463self.buffer = buf;464self.buffer.clear();465Poll::Ready(Ok(self.complete_write(n)))466}467Err(err) => {468self.close(Err(err.into()));469Poll::Ready(Ok(StreamResult::Dropped))470}471}472}473}474475impl Drop for WriteStreamConsumer {476fn drop(&mut self) {477if self.result.is_some() {478self.close(Ok(()))479}480}481}482483impl types::Host for WasiFilesystemCtxView<'_> {484fn convert_error_code(&mut self, error: FilesystemError) -> wasmtime::Result<ErrorCode> {485error.downcast()486}487}488489impl types::HostDescriptorWithStore for WasiFilesystem {490async fn read_via_stream<U>(491store: &Accessor<U, Self>,492fd: Resource<Descriptor>,493offset: Filesize,494) -> wasmtime::Result<(StreamReader<u8>, FutureReader<Result<(), ErrorCode>>)> {495let instance = store.instance();496store.with(|mut store| {497let file = get_file(store.get().table, &fd)?;498if !file.perms.contains(FilePerms::READ) {499return Ok((500StreamReader::new(instance, &mut store, StreamEmptyProducer::default()),501FutureReader::new(502instance,503&mut store,504FutureReadyProducer(Err(ErrorCode::NotPermitted)),505),506));507}508509let file = file.clone();510let (result_tx, result_rx) = oneshot::channel();511Ok((512StreamReader::new(513instance,514&mut store,515ReadStreamProducer {516file,517offset,518result: Some(result_tx),519task: None,520},521),522FutureReader::new(instance, &mut store, FutureOneshotProducer(result_rx)),523))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)> {641let instance = store.instance();642store.with(|mut store| {643let dir = get_dir(store.get().table, &fd)?;644if !dir.perms.contains(DirPerms::READ) {645return Ok((646StreamReader::new(instance, &mut store, StreamEmptyProducer::default()),647FutureReader::new(648instance,649&mut store,650FutureReadyProducer(Err(ErrorCode::NotPermitted)),651),652));653}654let allow_blocking_current_thread = dir.allow_blocking_current_thread;655let dir = Arc::clone(dir.as_dir());656let (result_tx, result_rx) = oneshot::channel();657let stream = if allow_blocking_current_thread {658match dir.entries() {659Ok(readdir) => StreamReader::new(660instance,661&mut store,662FallibleIteratorProducer::new(663readdir.filter_map(|e| map_dir_entry(e).transpose()),664result_tx,665),666),667Err(e) => {668result_tx.send(Err(e.into())).unwrap();669StreamReader::new(instance, &mut store, StreamEmptyProducer::default())670}671}672} else {673StreamReader::new(instance, &mut store, ReadDirStream::new(dir, result_tx))674};675Ok((676stream,677FutureReader::new(instance, &mut store, FutureOneshotProducer(result_rx)),678))679})680}681682async fn sync<U>(store: &Accessor<U, Self>, fd: Resource<Descriptor>) -> FilesystemResult<()> {683let fd = store.get_descriptor(&fd)?;684fd.sync().await?;685Ok(())686}687688async fn create_directory_at<U>(689store: &Accessor<U, Self>,690fd: Resource<Descriptor>,691path: String,692) -> FilesystemResult<()> {693let dir = store.get_dir(&fd)?;694dir.create_directory_at(path).await?;695Ok(())696}697698async fn stat<U>(699store: &Accessor<U, Self>,700fd: Resource<Descriptor>,701) -> FilesystemResult<DescriptorStat> {702let fd = store.get_descriptor(&fd)?;703let stat = fd.stat().await?;704Ok(stat.into())705}706707async fn stat_at<U>(708store: &Accessor<U, Self>,709fd: Resource<Descriptor>,710path_flags: PathFlags,711path: String,712) -> FilesystemResult<DescriptorStat> {713let dir = store.get_dir(&fd)?;714let stat = dir.stat_at(path_flags.into(), path).await?;715Ok(stat.into())716}717718async fn set_times_at<U>(719store: &Accessor<U, Self>,720fd: Resource<Descriptor>,721path_flags: PathFlags,722path: String,723data_access_timestamp: NewTimestamp,724data_modification_timestamp: NewTimestamp,725) -> FilesystemResult<()> {726let dir = store.get_dir(&fd)?;727let atim = systemtimespec_from(data_access_timestamp)?;728let mtim = systemtimespec_from(data_modification_timestamp)?;729dir.set_times_at(path_flags.into(), path, atim, mtim)730.await?;731Ok(())732}733734async fn link_at<U>(735store: &Accessor<U, Self>,736fd: Resource<Descriptor>,737old_path_flags: PathFlags,738old_path: String,739new_fd: Resource<Descriptor>,740new_path: String,741) -> FilesystemResult<()> {742let (old_dir, new_dir) = store.get_dir_pair(&fd, &new_fd)?;743old_dir744.link_at(old_path_flags.into(), old_path, &new_dir, new_path)745.await?;746Ok(())747}748749async fn open_at<U>(750store: &Accessor<U, Self>,751fd: Resource<Descriptor>,752path_flags: PathFlags,753path: String,754open_flags: OpenFlags,755flags: DescriptorFlags,756) -> FilesystemResult<Resource<Descriptor>> {757let (allow_blocking_current_thread, dir) = store.with(|mut store| {758let store = store.get();759let dir = get_dir(&store.table, &fd)?;760FilesystemResult::Ok((store.ctx.allow_blocking_current_thread, dir.clone()))761})?;762let fd = dir763.open_at(764path_flags.into(),765path,766open_flags.into(),767flags.into(),768allow_blocking_current_thread,769)770.await?;771let fd = store.with(|mut store| store.get().table.push(fd))?;772Ok(fd)773}774775async fn readlink_at<U>(776store: &Accessor<U, Self>,777fd: Resource<Descriptor>,778path: String,779) -> FilesystemResult<String> {780let dir = store.get_dir(&fd)?;781let path = dir.readlink_at(path).await?;782Ok(path)783}784785async fn remove_directory_at<U>(786store: &Accessor<U, Self>,787fd: Resource<Descriptor>,788path: String,789) -> FilesystemResult<()> {790let dir = store.get_dir(&fd)?;791dir.remove_directory_at(path).await?;792Ok(())793}794795async fn rename_at<U>(796store: &Accessor<U, Self>,797fd: Resource<Descriptor>,798old_path: String,799new_fd: Resource<Descriptor>,800new_path: String,801) -> FilesystemResult<()> {802let (old_dir, new_dir) = store.get_dir_pair(&fd, &new_fd)?;803old_dir.rename_at(old_path, &new_dir, new_path).await?;804Ok(())805}806807async fn symlink_at<U>(808store: &Accessor<U, Self>,809fd: Resource<Descriptor>,810old_path: String,811new_path: String,812) -> FilesystemResult<()> {813let dir = store.get_dir(&fd)?;814dir.symlink_at(old_path, new_path).await?;815Ok(())816}817818async fn unlink_file_at<U>(819store: &Accessor<U, Self>,820fd: Resource<Descriptor>,821path: String,822) -> FilesystemResult<()> {823let dir = store.get_dir(&fd)?;824dir.unlink_file_at(path).await?;825Ok(())826}827828async fn is_same_object<U>(829store: &Accessor<U, Self>,830fd: Resource<Descriptor>,831other: Resource<Descriptor>,832) -> wasmtime::Result<bool> {833let (fd, other) = store.with(|mut store| {834let table = store.get().table;835let fd = get_descriptor(table, &fd)?.clone();836let other = get_descriptor(table, &other)?.clone();837anyhow::Ok((fd, other))838})?;839fd.is_same_object(&other).await840}841842async fn metadata_hash<U>(843store: &Accessor<U, Self>,844fd: Resource<Descriptor>,845) -> FilesystemResult<MetadataHashValue> {846let fd = store.get_descriptor(&fd)?;847let meta = fd.metadata_hash().await?;848Ok(meta.into())849}850851async fn metadata_hash_at<U>(852store: &Accessor<U, Self>,853fd: Resource<Descriptor>,854path_flags: PathFlags,855path: String,856) -> FilesystemResult<MetadataHashValue> {857let dir = store.get_dir(&fd)?;858let meta = dir.metadata_hash_at(path_flags.into(), path).await?;859Ok(meta.into())860}861}862863impl types::HostDescriptor for WasiFilesystemCtxView<'_> {864fn drop(&mut self, fd: Resource<Descriptor>) -> wasmtime::Result<()> {865self.table866.delete(fd)867.context("failed to delete descriptor resource from table")?;868Ok(())869}870}871872impl preopens::Host for WasiFilesystemCtxView<'_> {873fn get_directories(&mut self) -> wasmtime::Result<Vec<(Resource<Descriptor>, String)>> {874self.get_directories()875}876}877878879