diff options
Diffstat (limited to 'ext/fs')
-rw-r--r-- | ext/fs/30_fs.js | 6 | ||||
-rw-r--r-- | ext/fs/Cargo.toml | 6 | ||||
-rw-r--r-- | ext/fs/in_memory_fs.rs | 2 | ||||
-rw-r--r-- | ext/fs/lib.rs | 53 | ||||
-rw-r--r-- | ext/fs/ops.rs | 397 | ||||
-rw-r--r-- | ext/fs/std_fs.rs | 36 |
6 files changed, 328 insertions, 172 deletions
diff --git a/ext/fs/30_fs.js b/ext/fs/30_fs.js index c8e19ac75..40513e7e0 100644 --- a/ext/fs/30_fs.js +++ b/ext/fs/30_fs.js @@ -346,9 +346,10 @@ const { 0: statStruct, 1: statBuf } = createByteStruct({ mtime: "date", atime: "date", birthtime: "date", + ctime: "date", dev: "u64", ino: "?u64", - mode: "?u64", + mode: "u64", nlink: "?u64", uid: "?u64", gid: "?u64", @@ -377,9 +378,10 @@ function parseFileInfo(response) { birthtime: response.birthtimeSet === true ? new Date(response.birthtime) : null, + ctime: response.ctimeSet === true ? new Date(response.ctime) : null, dev: response.dev, + mode: response.mode, ino: unix ? response.ino : null, - mode: unix ? response.mode : null, nlink: unix ? response.nlink : null, uid: unix ? response.uid : null, gid: unix ? response.gid : null, diff --git a/ext/fs/Cargo.toml b/ext/fs/Cargo.toml index 606c00ad8..ace1b89f3 100644 --- a/ext/fs/Cargo.toml +++ b/ext/fs/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_fs" -version = "0.81.0" +version = "0.87.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -19,6 +19,7 @@ sync_fs = [] [dependencies] async-trait.workspace = true base32.workspace = true +boxed_error.workspace = true deno_core.workspace = true deno_io.workspace = true deno_path_util.workspace = true @@ -28,9 +29,10 @@ libc.workspace = true rand.workspace = true rayon = "1.8.0" serde.workspace = true +thiserror.workspace = true [target.'cfg(unix)'.dependencies] -nix.workspace = true +nix = { workspace = true, features = ["fs", "user"] } [target.'cfg(windows)'.dependencies] winapi = { workspace = true, features = ["winbase"] } diff --git a/ext/fs/in_memory_fs.rs b/ext/fs/in_memory_fs.rs index e29b9d50c..34b77836d 100644 --- a/ext/fs/in_memory_fs.rs +++ b/ext/fs/in_memory_fs.rs @@ -229,6 +229,7 @@ impl FileSystem for InMemoryFs { mtime: None, atime: None, birthtime: None, + ctime: None, dev: 0, ino: 0, mode: 0, @@ -251,6 +252,7 @@ impl FileSystem for InMemoryFs { mtime: None, atime: None, birthtime: None, + ctime: None, dev: 0, ino: 0, mode: 0, diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs index bd49078b2..aed9a7085 100644 --- a/ext/fs/lib.rs +++ b/ext/fs/lib.rs @@ -14,14 +14,17 @@ pub use crate::interface::FileSystemRc; pub use crate::interface::FsDirEntry; pub use crate::interface::FsFileType; pub use crate::interface::OpenOptions; +pub use crate::ops::FsOpsError; +pub use crate::ops::FsOpsErrorKind; +pub use crate::ops::OperationError; pub use crate::std_fs::RealFs; pub use crate::sync::MaybeSend; pub use crate::sync::MaybeSync; use crate::ops::*; -use deno_core::error::AnyError; use deno_io::fs::FsError; +use deno_permissions::PermissionCheckError; use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; @@ -40,45 +43,51 @@ pub trait FsPermissions { &mut self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError>; + ) -> Result<PathBuf, PermissionCheckError>; #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_read_path<'a>( &mut self, path: &'a Path, api_name: &str, - ) -> Result<Cow<'a, Path>, AnyError>; - fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>; + ) -> Result<Cow<'a, Path>, PermissionCheckError>; + fn check_read_all( + &mut self, + api_name: &str, + ) -> Result<(), PermissionCheckError>; fn check_read_blind( &mut self, p: &Path, display: &str, api_name: &str, - ) -> Result<(), AnyError>; + ) -> Result<(), PermissionCheckError>; #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_write( &mut self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError>; + ) -> Result<PathBuf, PermissionCheckError>; #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_write_path<'a>( &mut self, path: &'a Path, api_name: &str, - ) -> Result<Cow<'a, Path>, AnyError>; + ) -> Result<Cow<'a, Path>, PermissionCheckError>; #[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"] fn check_write_partial( &mut self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError>; - fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>; + ) -> Result<PathBuf, PermissionCheckError>; + fn check_write_all( + &mut self, + api_name: &str, + ) -> Result<(), PermissionCheckError>; fn check_write_blind( &mut self, p: &Path, display: &str, api_name: &str, - ) -> Result<(), AnyError>; + ) -> Result<(), PermissionCheckError>; fn check<'a>( &mut self, @@ -138,7 +147,7 @@ impl FsPermissions for deno_permissions::PermissionsContainer { &mut self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { deno_permissions::PermissionsContainer::check_read(self, path, api_name) } @@ -146,7 +155,7 @@ impl FsPermissions for deno_permissions::PermissionsContainer { &mut self, path: &'a Path, api_name: &str, - ) -> Result<Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { deno_permissions::PermissionsContainer::check_read_path( self, path, @@ -158,7 +167,7 @@ impl FsPermissions for deno_permissions::PermissionsContainer { path: &Path, display: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { deno_permissions::PermissionsContainer::check_read_blind( self, path, display, api_name, ) @@ -168,7 +177,7 @@ impl FsPermissions for deno_permissions::PermissionsContainer { &mut self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { deno_permissions::PermissionsContainer::check_write(self, path, api_name) } @@ -176,7 +185,7 @@ impl FsPermissions for deno_permissions::PermissionsContainer { &mut self, path: &'a Path, api_name: &str, - ) -> Result<Cow<'a, Path>, AnyError> { + ) -> Result<Cow<'a, Path>, PermissionCheckError> { deno_permissions::PermissionsContainer::check_write_path( self, path, api_name, ) @@ -186,7 +195,7 @@ impl FsPermissions for deno_permissions::PermissionsContainer { &mut self, path: &str, api_name: &str, - ) -> Result<PathBuf, AnyError> { + ) -> Result<PathBuf, PermissionCheckError> { deno_permissions::PermissionsContainer::check_write_partial( self, path, api_name, ) @@ -197,17 +206,23 @@ impl FsPermissions for deno_permissions::PermissionsContainer { p: &Path, display: &str, api_name: &str, - ) -> Result<(), AnyError> { + ) -> Result<(), PermissionCheckError> { deno_permissions::PermissionsContainer::check_write_blind( self, p, display, api_name, ) } - fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError> { + fn check_read_all( + &mut self, + api_name: &str, + ) -> Result<(), PermissionCheckError> { deno_permissions::PermissionsContainer::check_read_all(self, api_name) } - fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError> { + fn check_write_all( + &mut self, + api_name: &str, + ) -> Result<(), PermissionCheckError> { deno_permissions::PermissionsContainer::check_write_all(self, api_name) } } diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs index b13d3a7d1..e3a511f8e 100644 --- a/ext/fs/ops.rs +++ b/ext/fs/ops.rs @@ -1,6 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::cell::RefCell; +use std::error::Error; +use std::fmt::Formatter; use std::io; use std::io::SeekFrom; use std::path::Path; @@ -8,10 +10,13 @@ use std::path::PathBuf; use std::path::StripPrefixError; use std::rc::Rc; -use deno_core::anyhow::bail; -use deno_core::error::custom_error; -use deno_core::error::type_error; -use deno_core::error::AnyError; +use crate::interface::AccessCheckFn; +use crate::interface::FileSystemRc; +use crate::interface::FsDirEntry; +use crate::interface::FsFileType; +use crate::FsPermissions; +use crate::OpenOptions; +use boxed_error::Boxed; use deno_core::op2; use deno_core::CancelFuture; use deno_core::CancelHandle; @@ -22,17 +27,76 @@ use deno_core::ToJsBuffer; use deno_io::fs::FileResource; use deno_io::fs::FsError; use deno_io::fs::FsStat; +use deno_permissions::PermissionCheckError; use rand::rngs::ThreadRng; use rand::thread_rng; use rand::Rng; use serde::Serialize; -use crate::interface::AccessCheckFn; -use crate::interface::FileSystemRc; -use crate::interface::FsDirEntry; -use crate::interface::FsFileType; -use crate::FsPermissions; -use crate::OpenOptions; +#[derive(Debug, Boxed)] +pub struct FsOpsError(pub Box<FsOpsErrorKind>); + +#[derive(Debug, thiserror::Error)] +pub enum FsOpsErrorKind { + #[error("{0}")] + Io(#[source] std::io::Error), + #[error("{0}")] + OperationError(#[source] OperationError), + #[error(transparent)] + Permission(#[from] PermissionCheckError), + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("File name or path {0:?} is not valid UTF-8")] + InvalidUtf8(std::ffi::OsString), + #[error("{0}")] + StripPrefix(#[from] StripPrefixError), + #[error("{0}")] + Canceled(#[from] deno_core::Canceled), + #[error("Invalid seek mode: {0}")] + InvalidSeekMode(i32), + #[error("Invalid control character in prefix or suffix: {0:?}")] + InvalidControlCharacter(String), + #[error("Invalid character in prefix or suffix: {0:?}")] + InvalidCharacter(String), + #[cfg(windows)] + #[error("Invalid trailing character in suffix")] + InvalidTrailingCharacter, + #[error("Requires {err} access to {path}, {}", print_not_capable_info(*.standalone, .err))] + NotCapableAccess { + // NotCapable + standalone: bool, + err: &'static str, + path: String, + }, + #[error("permission denied: {0}")] + NotCapable(&'static str), // NotCapable + #[error(transparent)] + Other(deno_core::error::AnyError), +} + +impl From<FsError> for FsOpsError { + fn from(err: FsError) -> Self { + match err { + FsError::Io(err) => FsOpsErrorKind::Io(err), + FsError::FileBusy => { + FsOpsErrorKind::Other(deno_core::error::resource_unavailable()) + } + FsError::NotSupported => { + FsOpsErrorKind::Other(deno_core::error::not_supported()) + } + FsError::NotCapable(err) => FsOpsErrorKind::NotCapable(err), + } + .into_box() + } +} + +fn print_not_capable_info(standalone: bool, err: &'static str) -> String { + if standalone { + format!("specify the required permissions during compilation using `deno compile --allow-{err}`") + } else { + format!("run again with the --allow-{err} flag") + } +} fn sync_permission_check<'a, P: FsPermissions + 'static>( permissions: &'a mut P, @@ -58,7 +122,7 @@ fn map_permission_error( operation: &'static str, error: FsError, path: &Path, -) -> AnyError { +) -> FsOpsError { match error { FsError::NotCapable(err) => { let path = format!("{path:?}"); @@ -67,14 +131,13 @@ fn map_permission_error( } else { (path.as_str(), "") }; - let msg = if deno_permissions::is_standalone() { - format!( - "Requires {err} access to {path}{truncated}, specify the required permissions during compilation using `deno compile --allow-{err}`") - } else { - format!( - "Requires {err} access to {path}{truncated}, run again with the --allow-{err} flag") - }; - custom_error("NotCapable", msg) + + FsOpsErrorKind::NotCapableAccess { + standalone: deno_permissions::is_standalone(), + err, + path: format!("{path}{truncated}"), + } + .into_box() } err => Err::<(), _>(err) .context_path(operation, path) @@ -85,7 +148,7 @@ fn map_permission_error( #[op2] #[string] -pub fn op_fs_cwd<P>(state: &mut OpState) -> Result<String, AnyError> +pub fn op_fs_cwd<P>(state: &mut OpState) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -102,7 +165,7 @@ where pub fn op_fs_chdir<P>( state: &mut OpState, #[string] directory: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -119,7 +182,7 @@ where pub fn op_fs_umask( state: &mut OpState, mask: Option<u32>, -) -> Result<u32, AnyError> +) -> Result<u32, FsOpsError> where { state.borrow::<FileSystemRc>().umask(mask).context("umask") @@ -131,7 +194,7 @@ pub fn op_fs_open_sync<P>( state: &mut OpState, #[string] path: String, #[serde] options: Option<OpenOptions>, -) -> Result<ResourceId, AnyError> +) -> Result<ResourceId, FsOpsError> where P: FsPermissions + 'static, { @@ -158,7 +221,7 @@ pub async fn op_fs_open_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, #[serde] options: Option<OpenOptions>, -) -> Result<ResourceId, AnyError> +) -> Result<ResourceId, FsOpsError> where P: FsPermissions + 'static, { @@ -186,7 +249,7 @@ pub fn op_fs_mkdir_sync<P>( #[string] path: String, recursive: bool, mode: Option<u32>, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -209,7 +272,7 @@ pub async fn op_fs_mkdir_async<P>( #[string] path: String, recursive: bool, mode: Option<u32>, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -233,7 +296,7 @@ pub fn op_fs_chmod_sync<P>( state: &mut OpState, #[string] path: String, mode: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -250,7 +313,7 @@ pub async fn op_fs_chmod_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, mode: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -271,7 +334,7 @@ pub fn op_fs_chown_sync<P>( #[string] path: String, uid: Option<u32>, gid: Option<u32>, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -290,7 +353,7 @@ pub async fn op_fs_chown_async<P>( #[string] path: String, uid: Option<u32>, gid: Option<u32>, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -310,7 +373,7 @@ pub fn op_fs_remove_sync<P>( state: &mut OpState, #[string] path: &str, recursive: bool, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -330,7 +393,7 @@ pub async fn op_fs_remove_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, recursive: bool, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -361,7 +424,7 @@ pub fn op_fs_copy_file_sync<P>( state: &mut OpState, #[string] from: &str, #[string] to: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -381,7 +444,7 @@ pub async fn op_fs_copy_file_async<P>( state: Rc<RefCell<OpState>>, #[string] from: String, #[string] to: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -405,7 +468,7 @@ pub fn op_fs_stat_sync<P>( state: &mut OpState, #[string] path: String, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -424,7 +487,7 @@ where pub async fn op_fs_stat_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, -) -> Result<SerializableStat, AnyError> +) -> Result<SerializableStat, FsOpsError> where P: FsPermissions + 'static, { @@ -446,7 +509,7 @@ pub fn op_fs_lstat_sync<P>( state: &mut OpState, #[string] path: String, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -465,7 +528,7 @@ where pub async fn op_fs_lstat_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, -) -> Result<SerializableStat, AnyError> +) -> Result<SerializableStat, FsOpsError> where P: FsPermissions + 'static, { @@ -487,7 +550,7 @@ where pub fn op_fs_realpath_sync<P>( state: &mut OpState, #[string] path: String, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -510,7 +573,7 @@ where pub async fn op_fs_realpath_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -538,7 +601,7 @@ where pub fn op_fs_read_dir_sync<P>( state: &mut OpState, #[string] path: String, -) -> Result<Vec<FsDirEntry>, AnyError> +) -> Result<Vec<FsDirEntry>, FsOpsError> where P: FsPermissions + 'static, { @@ -557,7 +620,7 @@ where pub async fn op_fs_read_dir_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, -) -> Result<Vec<FsDirEntry>, AnyError> +) -> Result<Vec<FsDirEntry>, FsOpsError> where P: FsPermissions + 'static, { @@ -582,7 +645,7 @@ pub fn op_fs_rename_sync<P>( state: &mut OpState, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -603,7 +666,7 @@ pub async fn op_fs_rename_async<P>( state: Rc<RefCell<OpState>>, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -628,7 +691,7 @@ pub fn op_fs_link_sync<P>( state: &mut OpState, #[string] oldpath: &str, #[string] newpath: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -650,7 +713,7 @@ pub async fn op_fs_link_async<P>( state: Rc<RefCell<OpState>>, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -677,7 +740,7 @@ pub fn op_fs_symlink_sync<P>( #[string] oldpath: &str, #[string] newpath: &str, #[serde] file_type: Option<FsFileType>, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -701,7 +764,7 @@ pub async fn op_fs_symlink_async<P>( #[string] oldpath: String, #[string] newpath: String, #[serde] file_type: Option<FsFileType>, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -728,7 +791,7 @@ where pub fn op_fs_read_link_sync<P>( state: &mut OpState, #[string] path: String, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -748,7 +811,7 @@ where pub async fn op_fs_read_link_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -773,7 +836,7 @@ pub fn op_fs_truncate_sync<P>( state: &mut OpState, #[string] path: &str, #[number] len: u64, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -793,7 +856,7 @@ pub async fn op_fs_truncate_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, #[number] len: u64, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -820,7 +883,7 @@ pub fn op_fs_utime_sync<P>( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -841,7 +904,7 @@ pub async fn op_fs_utime_async<P>( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -871,7 +934,7 @@ pub fn op_fs_make_temp_dir_sync<P>( #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -913,7 +976,7 @@ pub async fn op_fs_make_temp_dir_async<P>( #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -959,7 +1022,7 @@ pub fn op_fs_make_temp_file_sync<P>( #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -1007,7 +1070,7 @@ pub async fn op_fs_make_temp_file_async<P>( #[string] dir_arg: Option<String>, #[string] prefix: Option<String>, #[string] suffix: Option<String>, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -1069,7 +1132,7 @@ fn make_temp_check_sync<P>( state: &mut OpState, dir: Option<&str>, api_name: &str, -) -> Result<(PathBuf, FileSystemRc), AnyError> +) -> Result<(PathBuf, FileSystemRc), FsOpsError> where P: FsPermissions + 'static, { @@ -1091,7 +1154,7 @@ fn make_temp_check_async<P>( state: Rc<RefCell<OpState>>, dir: Option<&str>, api_name: &str, -) -> Result<(PathBuf, FileSystemRc), AnyError> +) -> Result<(PathBuf, FileSystemRc), FsOpsError> where P: FsPermissions + 'static, { @@ -1116,10 +1179,12 @@ where fn validate_temporary_filename_component( component: &str, #[allow(unused_variables)] suffix: bool, -) -> Result<(), AnyError> { +) -> Result<(), FsOpsError> { // Ban ASCII and Unicode control characters: these will often fail if let Some(c) = component.matches(|c: char| c.is_control()).next() { - bail!("Invalid control character in prefix or suffix: {:?}", c); + return Err( + FsOpsErrorKind::InvalidControlCharacter(c.to_string()).into_box(), + ); } // Windows has the most restrictive filenames. As temp files aren't normal files, we just // use this set of banned characters for all platforms because wildcard-like files can also @@ -1135,13 +1200,13 @@ fn validate_temporary_filename_component( .matches(|c: char| "<>:\"/\\|?*".contains(c)) .next() { - bail!("Invalid character in prefix or suffix: {:?}", c); + return Err(FsOpsErrorKind::InvalidCharacter(c.to_string()).into_box()); } // This check is only for Windows #[cfg(windows)] if suffix && component.ends_with(|c: char| ". ".contains(c)) { - bail!("Invalid trailing character in suffix"); + return Err(FsOpsErrorKind::InvalidTrailingCharacter.into_box()); } Ok(()) @@ -1152,7 +1217,7 @@ fn tmp_name( dir: &Path, prefix: Option<&str>, suffix: Option<&str>, -) -> Result<PathBuf, AnyError> { +) -> Result<PathBuf, FsOpsError> { let prefix = prefix.unwrap_or(""); validate_temporary_filename_component(prefix, false)?; let suffix = suffix.unwrap_or(""); @@ -1179,7 +1244,7 @@ pub fn op_fs_write_file_sync<P>( create: bool, create_new: bool, #[buffer] data: JsBuffer, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -1207,7 +1272,7 @@ pub async fn op_fs_write_file_async<P>( create_new: bool, #[buffer] data: JsBuffer, #[smi] cancel_rid: Option<ResourceId>, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -1255,7 +1320,7 @@ where pub fn op_fs_read_file_sync<P>( state: &mut OpState, #[string] path: String, -) -> Result<ToJsBuffer, AnyError> +) -> Result<ToJsBuffer, FsOpsError> where P: FsPermissions + 'static, { @@ -1277,7 +1342,7 @@ pub async fn op_fs_read_file_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, #[smi] cancel_rid: Option<ResourceId>, -) -> Result<ToJsBuffer, AnyError> +) -> Result<ToJsBuffer, FsOpsError> where P: FsPermissions + 'static, { @@ -1318,7 +1383,7 @@ where pub fn op_fs_read_file_text_sync<P>( state: &mut OpState, #[string] path: String, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -1340,7 +1405,7 @@ pub async fn op_fs_read_file_text_async<P>( state: Rc<RefCell<OpState>>, #[string] path: String, #[smi] cancel_rid: Option<ResourceId>, -) -> Result<String, AnyError> +) -> Result<String, FsOpsError> where P: FsPermissions + 'static, { @@ -1377,13 +1442,13 @@ where Ok(str) } -fn to_seek_from(offset: i64, whence: i32) -> Result<SeekFrom, AnyError> { +fn to_seek_from(offset: i64, whence: i32) -> Result<SeekFrom, FsOpsError> { let seek_from = match whence { 0 => SeekFrom::Start(offset as u64), 1 => SeekFrom::Current(offset), 2 => SeekFrom::End(offset), _ => { - return Err(type_error(format!("Invalid seek mode: {whence}"))); + return Err(FsOpsErrorKind::InvalidSeekMode(whence).into_box()); } }; Ok(seek_from) @@ -1396,9 +1461,10 @@ pub fn op_fs_seek_sync( #[smi] rid: ResourceId, #[number] offset: i64, #[smi] whence: i32, -) -> Result<u64, AnyError> { +) -> Result<u64, FsOpsError> { let pos = to_seek_from(offset, whence)?; - let file = FileResource::get_file(state, rid)?; + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; let cursor = file.seek_sync(pos)?; Ok(cursor) } @@ -1410,9 +1476,10 @@ pub async fn op_fs_seek_async( #[smi] rid: ResourceId, #[number] offset: i64, #[smi] whence: i32, -) -> Result<u64, AnyError> { +) -> Result<u64, FsOpsError> { let pos = to_seek_from(offset, whence)?; - let file = FileResource::get_file(&state.borrow(), rid)?; + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; let cursor = file.seek_async(pos).await?; Ok(cursor) } @@ -1421,8 +1488,9 @@ pub async fn op_fs_seek_async( pub fn op_fs_file_sync_data_sync( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; file.datasync_sync()?; Ok(()) } @@ -1431,8 +1499,9 @@ pub fn op_fs_file_sync_data_sync( pub async fn op_fs_file_sync_data_async( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; file.datasync_async().await?; Ok(()) } @@ -1441,8 +1510,9 @@ pub async fn op_fs_file_sync_data_async( pub fn op_fs_file_sync_sync( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; file.sync_sync()?; Ok(()) } @@ -1451,8 +1521,9 @@ pub fn op_fs_file_sync_sync( pub async fn op_fs_file_sync_async( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; file.sync_async().await?; Ok(()) } @@ -1462,8 +1533,9 @@ pub fn op_fs_file_stat_sync( state: &mut OpState, #[smi] rid: ResourceId, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; let stat = file.stat_sync()?; let serializable_stat = SerializableStat::from(stat); serializable_stat.write(stat_out_buf); @@ -1475,8 +1547,9 @@ pub fn op_fs_file_stat_sync( pub async fn op_fs_file_stat_async( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<SerializableStat, AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<SerializableStat, FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; let stat = file.stat_async().await?; Ok(stat.into()) } @@ -1486,8 +1559,9 @@ pub fn op_fs_flock_sync( state: &mut OpState, #[smi] rid: ResourceId, exclusive: bool, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; file.lock_sync(exclusive)?; Ok(()) } @@ -1497,8 +1571,9 @@ pub async fn op_fs_flock_async( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, exclusive: bool, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; file.lock_async(exclusive).await?; Ok(()) } @@ -1507,8 +1582,9 @@ pub async fn op_fs_flock_async( pub fn op_fs_funlock_sync( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; file.unlock_sync()?; Ok(()) } @@ -1517,8 +1593,9 @@ pub fn op_fs_funlock_sync( pub async fn op_fs_funlock_async( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; file.unlock_async().await?; Ok(()) } @@ -1528,8 +1605,9 @@ pub fn op_fs_ftruncate_sync( state: &mut OpState, #[smi] rid: ResourceId, #[number] len: u64, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; file.truncate_sync(len)?; Ok(()) } @@ -1539,8 +1617,9 @@ pub async fn op_fs_file_truncate_async( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, #[number] len: u64, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; file.truncate_async(len).await?; Ok(()) } @@ -1553,8 +1632,9 @@ pub fn op_fs_futime_sync( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> { - let file = FileResource::get_file(state, rid)?; +) -> Result<(), FsOpsError> { + let file = + FileResource::get_file(state, rid).map_err(FsOpsErrorKind::Resource)?; file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?; Ok(()) } @@ -1567,42 +1647,64 @@ pub async fn op_fs_futime_async( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result<(), FsOpsError> { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsErrorKind::Resource)?; file .utime_async(atime_secs, atime_nanos, mtime_secs, mtime_nanos) .await?; Ok(()) } -trait WithContext { - fn context<E: Into<Box<dyn std::error::Error + Send + Sync>>>( - self, - desc: E, - ) -> AnyError; +#[derive(Debug)] +pub struct OperationError { + operation: &'static str, + kind: OperationErrorKind, + pub err: FsError, } -impl WithContext for FsError { - fn context<E: Into<Box<dyn std::error::Error + Send + Sync>>>( - self, - desc: E, - ) -> AnyError { - match self { - FsError::Io(io) => { - AnyError::new(io::Error::new(io.kind(), desc)).context(io) +impl std::fmt::Display for OperationError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let FsError::Io(e) = &self.err { + std::fmt::Display::fmt(&e, f)?; + f.write_str(": ")?; + } + + f.write_str(self.operation)?; + + match &self.kind { + OperationErrorKind::Bare => Ok(()), + OperationErrorKind::WithPath(path) => write!(f, " '{}'", path.display()), + OperationErrorKind::WithTwoPaths(from, to) => { + write!(f, " '{}' -> '{}'", from.display(), to.display()) } - _ => self.into(), } } } +impl std::error::Error for OperationError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + if let FsError::Io(err) = &self.err { + Some(err) + } else { + None + } + } +} + +#[derive(Debug)] +pub enum OperationErrorKind { + Bare, + WithPath(PathBuf), + WithTwoPaths(PathBuf, PathBuf), +} + trait MapErrContext { type R; - fn context_fn<F, E>(self, f: F) -> Self::R + fn context_fn<F>(self, f: F) -> Self::R where - F: FnOnce() -> E, - E: Into<Box<dyn std::error::Error + Send + Sync>>; + F: FnOnce(FsError) -> OperationError; fn context(self, desc: &'static str) -> Self::R; @@ -1617,25 +1719,29 @@ trait MapErrContext { } impl<T> MapErrContext for Result<T, FsError> { - type R = Result<T, AnyError>; + type R = Result<T, FsOpsError>; - fn context_fn<F, E>(self, f: F) -> Self::R + fn context_fn<F>(self, f: F) -> Self::R where - F: FnOnce() -> E, - E: Into<Box<dyn std::error::Error + Send + Sync>>, + F: FnOnce(FsError) -> OperationError, { - self.map_err(|err| { - let message = f(); - err.context(message) - }) + self.map_err(|err| FsOpsErrorKind::OperationError(f(err)).into_box()) } - fn context(self, desc: &'static str) -> Self::R { - self.context_fn(move || desc) + fn context(self, operation: &'static str) -> Self::R { + self.context_fn(move |err| OperationError { + operation, + kind: OperationErrorKind::Bare, + err, + }) } fn context_path(self, operation: &'static str, path: &Path) -> Self::R { - self.context_fn(|| format!("{operation} '{}'", path.display())) + self.context_fn(|err| OperationError { + operation, + kind: OperationErrorKind::WithPath(path.to_path_buf()), + err, + }) } fn context_two_path( @@ -1644,21 +1750,20 @@ impl<T> MapErrContext for Result<T, FsError> { oldpath: &Path, newpath: &Path, ) -> Self::R { - self.context_fn(|| { - format!( - "{operation} '{}' -> '{}'", - oldpath.display(), - newpath.display() - ) + self.context_fn(|err| OperationError { + operation, + kind: OperationErrorKind::WithTwoPaths( + oldpath.to_path_buf(), + newpath.to_path_buf(), + ), + err, }) } } -fn path_into_string(s: std::ffi::OsString) -> Result<String, AnyError> { - s.into_string().map_err(|s| { - let message = format!("File name or path {s:?} is not valid UTF-8"); - custom_error("InvalidData", message) - }) +fn path_into_string(s: std::ffi::OsString) -> Result<String, FsOpsError> { + s.into_string() + .map_err(|e| FsOpsErrorKind::InvalidUtf8(e).into_box()) } macro_rules! create_struct_writer { @@ -1699,6 +1804,8 @@ create_struct_writer! { atime: u64, birthtime_set: bool, birthtime: u64, + ctime_set: bool, + ctime: u64, // Following are only valid under Unix. dev: u64, ino: u64, @@ -1730,6 +1837,8 @@ impl From<FsStat> for SerializableStat { atime: stat.atime.unwrap_or(0), birthtime_set: stat.birthtime.is_some(), birthtime: stat.birthtime.unwrap_or(0), + ctime_set: stat.ctime.is_some(), + ctime: stat.ctime.unwrap_or(0), dev: stat.dev, ino: stat.ino, diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs index 41a8569ba..73439d9ba 100644 --- a/ext/fs/std_fs.rs +++ b/ext/fs/std_fs.rs @@ -821,24 +821,46 @@ fn stat_extra( Ok(info.dwVolumeSerialNumber as u64) } + const WINDOWS_TICK: i64 = 10_000; // 100-nanosecond intervals in a millisecond + const SEC_TO_UNIX_EPOCH: i64 = 11_644_473_600; // Seconds between Windows epoch and Unix epoch + + fn windows_time_to_unix_time_msec(windows_time: &i64) -> i64 { + let milliseconds_since_windows_epoch = windows_time / WINDOWS_TICK; + milliseconds_since_windows_epoch - SEC_TO_UNIX_EPOCH * 1000 + } + use windows_sys::Wdk::Storage::FileSystem::FILE_ALL_INFORMATION; + use windows_sys::Win32::Foundation::NTSTATUS; unsafe fn query_file_information( handle: winapi::shared::ntdef::HANDLE, - ) -> std::io::Result<FILE_ALL_INFORMATION> { + ) -> Result<FILE_ALL_INFORMATION, NTSTATUS> { use windows_sys::Wdk::Storage::FileSystem::NtQueryInformationFile; + use windows_sys::Win32::Foundation::RtlNtStatusToDosError; + use windows_sys::Win32::Foundation::ERROR_MORE_DATA; + use windows_sys::Win32::System::IO::IO_STATUS_BLOCK; let mut info = std::mem::MaybeUninit::<FILE_ALL_INFORMATION>::zeroed(); + let mut io_status_block = + std::mem::MaybeUninit::<IO_STATUS_BLOCK>::zeroed(); let status = NtQueryInformationFile( handle as _, - std::ptr::null_mut(), + io_status_block.as_mut_ptr(), info.as_mut_ptr() as *mut _, std::mem::size_of::<FILE_ALL_INFORMATION>() as _, 18, /* FileAllInformation */ ); if status < 0 { - return Err(std::io::Error::last_os_error()); + let converted_status = RtlNtStatusToDosError(status); + + // If error more data is returned, then it means that the buffer is too small to get full filename information + // to have that we should retry. However, since we only use BasicInformation and StandardInformation, it is fine to ignore it + // since struct is populated with other data anyway. + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntqueryinformationfile#remarksdd + if converted_status != ERROR_MORE_DATA { + return Err(converted_status as NTSTATUS); + } } Ok(info.assume_init()) @@ -862,10 +884,13 @@ fn stat_extra( } let result = get_dev(file_handle); - CloseHandle(file_handle); fsstat.dev = result?; if let Ok(file_info) = query_file_information(file_handle) { + fsstat.ctime = Some(windows_time_to_unix_time_msec( + &file_info.BasicInformation.ChangeTime, + ) as u64); + if file_info.BasicInformation.FileAttributes & winapi::um::winnt::FILE_ATTRIBUTE_REPARSE_POINT != 0 @@ -898,6 +923,7 @@ fn stat_extra( } } + CloseHandle(file_handle); Ok(()) } } @@ -929,7 +955,7 @@ fn exists(path: &Path) -> bool { } fn realpath(path: &Path) -> FsResult<PathBuf> { - Ok(deno_core::strip_unc_prefix(path.canonicalize()?)) + Ok(deno_path_util::strip_unc_prefix(path.canonicalize()?)) } fn read_dir(path: &Path) -> FsResult<Vec<FsDirEntry>> { |