1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-28 16:20:57 -05:00

chore(ext/fs): decouple fs trait from deno_core (#18732)

This commit removes the dependencies on `deno_core` for the Fs trait.
This allows to move the trait into a different crate that does not
depend on core in the limit.

This adds a new `bounds` field to `deno_core::extension!` that expands
to `where` clauses on the generated code. This allows to add bounds to
the extension parameters, such as `Fs::File: Resource`.
This commit is contained in:
Luca Casonato 2023-04-17 16:10:59 +02:00 committed by GitHub
parent bad4b7554b
commit bcbdaac7e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 72 deletions

View file

@ -165,6 +165,7 @@ macro_rules! ops {
/// ///
/// * deps: a comma-separated list of module dependencies, eg: `deps = [ my_other_extension ]` /// * deps: a comma-separated list of module dependencies, eg: `deps = [ my_other_extension ]`
/// * parameters: a comma-separated list of parameters and base traits, eg: `parameters = [ P: MyTrait ]` /// * parameters: a comma-separated list of parameters and base traits, eg: `parameters = [ P: MyTrait ]`
/// * bounds: a comma-separated list of additional type bounds, eg: `bounds = [ P::MyAssociatedType: MyTrait ]`
/// * ops: a comma-separated list of [`OpDecl`]s to provide, eg: `ops = [ op_foo, op_bar ]` /// * ops: a comma-separated list of [`OpDecl`]s to provide, eg: `ops = [ op_foo, op_bar ]`
/// * esm: a comma-separated list of ESM module filenames (see [`include_js_files`]), eg: `esm = [ dir "dir", "my_file.js" ]` /// * esm: a comma-separated list of ESM module filenames (see [`include_js_files`]), eg: `esm = [ dir "dir", "my_file.js" ]`
/// * esm_setup_script: see [`ExtensionBuilder::esm_setup_script`] /// * esm_setup_script: see [`ExtensionBuilder::esm_setup_script`]
@ -179,6 +180,7 @@ macro_rules! extension {
$name:ident $name:ident
$(, deps = [ $( $dep:ident ),* ] )? $(, deps = [ $( $dep:ident ),* ] )?
$(, parameters = [ $( $param:ident : $type:ident ),+ ] )? $(, parameters = [ $( $param:ident : $type:ident ),+ ] )?
$(, bounds = [ $( $bound:path : $bound_type:ident ),+ ] )?
$(, ops_fn = $ops_symbol:ident $( < $ops_param:ident > )? )? $(, ops_fn = $ops_symbol:ident $( < $ops_param:ident > )? )?
$(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $( $op_param:ident ),* > )? ),+ $(,)? ] )? $(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $( $op_param:ident ),* > )? ),+ $(,)? ] )?
$(, esm_entry_point = $esm_entry_point:literal )? $(, esm_entry_point = $esm_entry_point:literal )?
@ -229,7 +231,9 @@ macro_rules! extension {
// If ops were specified, add those ops to the extension. // If ops were specified, add those ops to the extension.
#[inline(always)] #[inline(always)]
#[allow(unused_variables)] #[allow(unused_variables)]
fn with_ops $( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::ExtensionBuilder) { fn with_ops $( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::ExtensionBuilder)
$( where $( $bound : $bound_type ),+ )?
{
// If individual ops are specified, roll them up into a vector and apply them // If individual ops are specified, roll them up into a vector and apply them
$( $(
ext.ops(vec![ ext.ops(vec![
@ -247,7 +251,9 @@ macro_rules! extension {
// Includes the state and middleware functions, if defined. // Includes the state and middleware functions, if defined.
#[inline(always)] #[inline(always)]
#[allow(unused_variables)] #[allow(unused_variables)]
fn with_state_and_middleware$( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::ExtensionBuilder, $( $( $options_id : $options_type ),* )? ) { fn with_state_and_middleware$( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::ExtensionBuilder, $( $( $options_id : $options_type ),* )? )
$( where $( $bound : $bound_type ),+ )?
{
$crate::extension!(! __config__ ext $( parameters = [ $( $param : $type ),* ] )? $( config = { $( $options_id : $options_type ),* } )? $( state_fn = $state_fn )? ); $crate::extension!(! __config__ ext $( parameters = [ $( $param : $type ),* ] )? $( config = { $( $options_id : $options_type ),* } )? $( state_fn = $state_fn )? );
$( $(
@ -267,7 +273,9 @@ macro_rules! extension {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn init_js_only $( < $( $param : $type + 'static ),* > )? () -> $crate::Extension { pub fn init_js_only $( < $( $param : $type + 'static ),* > )? () -> $crate::Extension
$( where $( $bound : $bound_type ),+ )?
{
let mut ext = Self::ext(); let mut ext = Self::ext();
// If esm or JS was specified, add JS files // If esm or JS was specified, add JS files
Self::with_js(&mut ext); Self::with_js(&mut ext);
@ -277,7 +285,9 @@ macro_rules! extension {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn init_ops_and_esm $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension { pub fn init_ops_and_esm $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
$( where $( $bound : $bound_type ),+ )?
{
let mut ext = Self::ext(); let mut ext = Self::ext();
// If esm or JS was specified, add JS files // If esm or JS was specified, add JS files
Self::with_js(&mut ext); Self::with_js(&mut ext);
@ -288,7 +298,9 @@ macro_rules! extension {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn init_ops $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension { pub fn init_ops $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension
$( where $( $bound : $bound_type ),+ )?
{
let mut ext = Self::ext(); let mut ext = Self::ext();
Self::with_ops $( ::< $( $param ),+ > )?(&mut ext); Self::with_ops $( ::< $( $param ),+ > )?(&mut ext);
Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? ); Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );

View file

@ -5,32 +5,8 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use deno_core::error::not_supported;
use deno_core::error::resource_unavailable;
use deno_core::error::AnyError;
use deno_core::Resource;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use tokio::task::JoinError;
pub trait FsPermissions {
fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_read_blind(
&mut self,
p: &Path,
display: &str,
api_name: &str,
) -> Result<(), AnyError>;
fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_write_blind(
&mut self,
p: &Path,
display: &str,
api_name: &str,
) -> Result<(), AnyError>;
}
#[derive(Deserialize, Default, Debug, Clone, Copy)] #[derive(Deserialize, Default, Debug, Clone, Copy)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -74,21 +50,6 @@ impl OpenOptions {
mode, mode,
} }
} }
pub(crate) fn check<P: FsPermissions>(
&self,
permissions: &mut P,
path: &Path,
api_name: &str,
) -> Result<(), AnyError> {
if self.read {
permissions.check_read(path, api_name)?;
}
if self.write || self.append {
permissions.check_write(path, api_name)?;
}
Ok(())
}
} }
pub struct FsStat { pub struct FsStat {
@ -141,28 +102,6 @@ impl From<io::Error> for FsError {
} }
} }
impl From<JoinError> for FsError {
fn from(err: JoinError) -> Self {
if err.is_cancelled() {
todo!("async tasks must not be cancelled")
}
if err.is_panic() {
std::panic::resume_unwind(err.into_panic()); // resume the panic on the main thread
}
unreachable!()
}
}
impl From<FsError> for AnyError {
fn from(err: FsError) -> Self {
match err {
FsError::Io(err) => AnyError::from(err),
FsError::FileBusy => resource_unavailable(),
FsError::NotSupported => not_supported(),
}
}
}
pub type FsResult<T> = Result<T, FsError>; pub type FsResult<T> = Result<T, FsError>;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
@ -214,7 +153,7 @@ pub trait File {
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub trait FileSystem: Clone { pub trait FileSystem: Clone {
type File: File + Resource; type File: File;
fn cwd(&self) -> FsResult<PathBuf>; fn cwd(&self) -> FsResult<PathBuf>;
fn tmp_dir(&self) -> FsResult<PathBuf>; fn tmp_dir(&self) -> FsResult<PathBuf>;

View file

@ -9,7 +9,6 @@ pub use crate::interface::FileSystem;
pub use crate::interface::FsDirEntry; pub use crate::interface::FsDirEntry;
pub use crate::interface::FsError; pub use crate::interface::FsError;
pub use crate::interface::FsFileType; pub use crate::interface::FsFileType;
pub use crate::interface::FsPermissions;
pub use crate::interface::FsResult; pub use crate::interface::FsResult;
pub use crate::interface::FsStat; pub use crate::interface::FsStat;
pub use crate::interface::OpenOptions; pub use crate::interface::OpenOptions;
@ -17,11 +16,48 @@ use crate::ops::*;
pub use crate::std_fs::StdFs; pub use crate::std_fs::StdFs;
use deno_core::error::AnyError;
use deno_core::OpState; use deno_core::OpState;
use deno_core::Resource;
use std::cell::RefCell; use std::cell::RefCell;
use std::convert::From; use std::convert::From;
use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
pub trait FsPermissions {
fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_read_blind(
&mut self,
p: &Path,
display: &str,
api_name: &str,
) -> Result<(), AnyError>;
fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_write_blind(
&mut self,
p: &Path,
display: &str,
api_name: &str,
) -> Result<(), AnyError>;
fn check(
&mut self,
open_options: &OpenOptions,
path: &Path,
api_name: &str,
) -> Result<(), AnyError> {
if open_options.read {
self.check_read(path, api_name)?;
}
if open_options.write || open_options.append {
self.check_write(path, api_name)?;
}
Ok(())
}
}
struct UnstableChecker { struct UnstableChecker {
pub unstable: bool, pub unstable: bool,
} }
@ -52,6 +88,7 @@ pub(crate) fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
deno_core::extension!(deno_fs, deno_core::extension!(deno_fs,
deps = [ deno_web ], deps = [ deno_web ],
parameters = [Fs: FileSystem, P: FsPermissions], parameters = [Fs: FileSystem, P: FsPermissions],
bounds = [Fs::File: Resource],
ops = [ ops = [
op_cwd<Fs, P>, op_cwd<Fs, P>,
op_umask<Fs>, op_umask<Fs>,

View file

@ -9,18 +9,22 @@ use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use deno_core::error::custom_error; use deno_core::error::custom_error;
use deno_core::error::not_supported;
use deno_core::error::resource_unavailable;
use deno_core::error::type_error; use deno_core::error::type_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::op; use deno_core::op;
use deno_core::CancelFuture; use deno_core::CancelFuture;
use deno_core::CancelHandle; use deno_core::CancelHandle;
use deno_core::OpState; use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId; use deno_core::ResourceId;
use deno_core::ZeroCopyBuf; use deno_core::ZeroCopyBuf;
use rand::rngs::ThreadRng; use rand::rngs::ThreadRng;
use rand::thread_rng; use rand::thread_rng;
use rand::Rng; use rand::Rng;
use serde::Serialize; use serde::Serialize;
use tokio::task::JoinError;
use crate::check_unstable; use crate::check_unstable;
use crate::check_unstable2; use crate::check_unstable2;
@ -33,6 +37,28 @@ use crate::FileSystem;
use crate::FsPermissions; use crate::FsPermissions;
use crate::OpenOptions; use crate::OpenOptions;
impl From<JoinError> for FsError {
fn from(err: JoinError) -> Self {
if err.is_cancelled() {
todo!("async tasks must not be cancelled")
}
if err.is_panic() {
std::panic::resume_unwind(err.into_panic()); // resume the panic on the main thread
}
unreachable!()
}
}
impl From<FsError> for AnyError {
fn from(err: FsError) -> Self {
match err {
FsError::Io(err) => AnyError::from(err),
FsError::FileBusy => resource_unavailable(),
FsError::NotSupported => not_supported(),
}
}
}
#[op] #[op]
pub fn op_cwd<Fs, P>(state: &mut OpState) -> Result<String, AnyError> pub fn op_cwd<Fs, P>(state: &mut OpState) -> Result<String, AnyError>
where where
@ -76,13 +102,14 @@ fn op_open_sync<Fs, P>(
) -> Result<ResourceId, AnyError> ) -> Result<ResourceId, AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
P: FsPermissions + 'static, P: FsPermissions + 'static,
{ {
let path = PathBuf::from(path); let path = PathBuf::from(path);
let options = options.unwrap_or_else(OpenOptions::read); let options = options.unwrap_or_else(OpenOptions::read);
let permissions = state.borrow_mut::<P>(); let permissions = state.borrow_mut::<P>();
options.check(permissions, &path, "Deno.openSync()")?; permissions.check(&options, &path, "Deno.openSync()")?;
let fs = state.borrow::<Fs>(); let fs = state.borrow::<Fs>();
let file = fs.open_sync(&path, options).context_path("open", &path)?; let file = fs.open_sync(&path, options).context_path("open", &path)?;
@ -99,6 +126,7 @@ async fn op_open_async<Fs, P>(
) -> Result<ResourceId, AnyError> ) -> Result<ResourceId, AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
P: FsPermissions + 'static, P: FsPermissions + 'static,
{ {
let path = PathBuf::from(path); let path = PathBuf::from(path);
@ -107,7 +135,7 @@ where
let fs = { let fs = {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<P>(); let permissions = state.borrow_mut::<P>();
options.check(permissions, &path, "Deno.open()")?; permissions.check(&options, &path, "Deno.open()")?;
state.borrow::<Fs>().clone() state.borrow::<Fs>().clone()
}; };
let file = fs let file = fs
@ -1117,7 +1145,7 @@ where
let permissions = state.borrow_mut::<P>(); let permissions = state.borrow_mut::<P>();
let options = OpenOptions::write(create, append, create_new, mode); let options = OpenOptions::write(create, append, create_new, mode);
options.check(permissions, &path, "Deno.writeFileSync()")?; permissions.check(&options, &path, "Deno.writeFileSync()")?;
let fs = state.borrow::<Fs>(); let fs = state.borrow::<Fs>();
@ -1149,7 +1177,7 @@ where
let (fs, cancel_handle) = { let (fs, cancel_handle) = {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<P>(); let permissions = state.borrow_mut::<P>();
options.check(permissions, &path, "Deno.writeFile()")?; permissions.check(&options, &path, "Deno.writeFile()")?;
let cancel_handle = cancel_rid let cancel_handle = cancel_rid
.and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok()); .and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
(state.borrow::<Fs>().clone(), cancel_handle) (state.borrow::<Fs>().clone(), cancel_handle)
@ -1320,6 +1348,7 @@ fn op_seek_sync<Fs>(
) -> Result<u64, AnyError> ) -> Result<u64, AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let pos = to_seek_from(offset, whence)?; let pos = to_seek_from(offset, whence)?;
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
@ -1336,6 +1365,7 @@ async fn op_seek_async<Fs>(
) -> Result<u64, AnyError> ) -> Result<u64, AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let pos = to_seek_from(offset, whence)?; let pos = to_seek_from(offset, whence)?;
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
@ -1350,6 +1380,7 @@ fn op_fdatasync_sync<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
file.datasync_sync()?; file.datasync_sync()?;
@ -1363,6 +1394,7 @@ async fn op_fdatasync_async<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
file.datasync_async().await?; file.datasync_async().await?;
@ -1376,6 +1408,7 @@ fn op_fsync_sync<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
file.sync_sync()?; file.sync_sync()?;
@ -1389,6 +1422,7 @@ async fn op_fsync_async<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
file.sync_async().await?; file.sync_async().await?;
@ -1403,6 +1437,7 @@ fn op_fstat_sync<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
let stat = file.stat_sync()?; let stat = file.stat_sync()?;
@ -1418,6 +1453,7 @@ async fn op_fstat_async<Fs>(
) -> Result<SerializableStat, AnyError> ) -> Result<SerializableStat, AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
let stat = file.stat_async().await?; let stat = file.stat_async().await?;
@ -1432,6 +1468,7 @@ fn op_flock_sync<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
check_unstable(state, "Deno.flockSync"); check_unstable(state, "Deno.flockSync");
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
@ -1447,6 +1484,7 @@ async fn op_flock_async<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
check_unstable2(&state, "Deno.flock"); check_unstable2(&state, "Deno.flock");
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
@ -1461,6 +1499,7 @@ fn op_funlock_sync<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
check_unstable(state, "Deno.funlockSync"); check_unstable(state, "Deno.funlockSync");
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
@ -1475,6 +1514,7 @@ async fn op_funlock_async<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
check_unstable2(&state, "Deno.funlock"); check_unstable2(&state, "Deno.funlock");
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
@ -1490,6 +1530,7 @@ fn op_ftruncate_sync<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
file.truncate_sync(len)?; file.truncate_sync(len)?;
@ -1504,6 +1545,7 @@ async fn op_ftruncate_async<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
file.truncate_async(len).await?; file.truncate_async(len).await?;
@ -1521,6 +1563,7 @@ fn op_futime_sync<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.resource_table.get::<Fs::File>(rid)?; let file = state.resource_table.get::<Fs::File>(rid)?;
file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?; file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?;
@ -1538,6 +1581,7 @@ async fn op_futime_async<Fs>(
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where
Fs: FileSystem + 'static, Fs: FileSystem + 'static,
Fs::File: Resource,
{ {
let file = state.borrow().resource_table.get::<Fs::File>(rid)?; let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
file file