From 85a99eb405ef3ec5f8e478d93b2c866afbc53f95 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 06:38:17 -0700 Subject: [PATCH] refactor(ext/fs): use concrete error types (#26317) --- Cargo.lock | 13 +- Cargo.toml | 2 +- ext/fs/Cargo.toml | 1 + ext/fs/lib.rs | 2 + ext/fs/ops.rs | 575 ++++++++++++++++++++++++++++++---------------- runtime/errors.rs | 31 +++ 6 files changed, 425 insertions(+), 199 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcbbaaae03..9b2baf79c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1423,9 +1423,9 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.314.1" +version = "0.314.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcd11ab87426c611b7170138a768dad7170c8fb66d8095b773d25e58fd254ea" +checksum = "83138917579676069b423c3eb9be3c1e579f60dc022d85f6ded4c792456255ff" dependencies = [ "anyhow", "bincode", @@ -1600,6 +1600,7 @@ dependencies = [ "rand", "rayon", "serde", + "thiserror", "winapi", "windows-sys 0.52.0", ] @@ -1916,9 +1917,9 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.190.0" +version = "0.190.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a48a3e06cace18a2c49e148da067678c6af80e70757a8c3991301397cf6b9919" +checksum = "26f46d4e4f52f26c882b74a9b58810ea75252b807cf0966166ec333077cdfd85" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -6215,9 +6216,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.223.0" +version = "0.223.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127bb9f2024433d06789b242477c808fd7f7dc4c3278576dd5bc99c4e5c75ff" +checksum = "9cf3d859dda87ee96423c01244f10af864fa6d6a9fcdc2b77e0595078ea0ea11" dependencies = [ "num-bigint", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3bf5342b8e..e2f439e37b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ repository = "https://github.com/denoland/deno" [workspace.dependencies] deno_ast = { version = "=0.42.2", features = ["transpiling"] } -deno_core = { version = "0.314.1" } +deno_core = { version = "0.314.2" } deno_bench_util = { version = "0.167.0", path = "./bench_util" } deno_lockfile = "=0.23.1" diff --git a/ext/fs/Cargo.toml b/ext/fs/Cargo.toml index a33347f9c7..313c84fdb6 100644 --- a/ext/fs/Cargo.toml +++ b/ext/fs/Cargo.toml @@ -28,6 +28,7 @@ libc.workspace = true rand.workspace = true rayon = "1.8.0" serde.workspace = true +thiserror.workspace = true [target.'cfg(unix)'.dependencies] nix.workspace = true diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs index bd49078b2e..cd2baf22a9 100644 --- a/ext/fs/lib.rs +++ b/ext/fs/lib.rs @@ -14,6 +14,8 @@ 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::OperationError; pub use crate::std_fs::RealFs; pub use crate::sync::MaybeSend; pub use crate::sync::MaybeSync; diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs index b13d3a7d1a..a3f59da4ea 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,6 @@ 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 deno_core::op2; use deno_core::CancelFuture; use deno_core::CancelHandle; @@ -34,6 +32,67 @@ use crate::interface::FsFileType; use crate::FsPermissions; use crate::OpenOptions; +#[derive(Debug, thiserror::Error)] +pub enum FsOpsError { + #[error("{0}")] + Io(#[source] std::io::Error), + #[error("{0}")] + OperationError(#[source] OperationError), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[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 for FsOpsError { + fn from(err: FsError) -> Self { + match err { + FsError::Io(err) => FsOpsError::Io(err), + FsError::FileBusy => { + FsOpsError::Other(deno_core::error::resource_unavailable()) + } + FsError::NotSupported => { + FsOpsError::Other(deno_core::error::not_supported()) + } + FsError::NotCapable(err) => FsOpsError::NotCapable(err), + } + } +} + +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, api_name: &'static str, @@ -58,7 +117,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 +126,12 @@ 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) + + FsOpsError::NotCapableAccess { + standalone: deno_permissions::is_standalone(), + err, + path: format!("{path}{truncated}"), + } } err => Err::<(), _>(err) .context_path(operation, path) @@ -85,7 +142,7 @@ fn map_permission_error( #[op2] #[string] -pub fn op_fs_cwd

(state: &mut OpState) -> Result +pub fn op_fs_cwd

(state: &mut OpState) -> Result where P: FsPermissions + 'static, { @@ -93,7 +150,8 @@ where let path = fs.cwd()?; state .borrow_mut::

() - .check_read_blind(&path, "CWD", "Deno.cwd()")?; + .check_read_blind(&path, "CWD", "Deno.cwd()") + .map_err(FsOpsError::Permission)?; let path_str = path_into_string(path.into_os_string())?; Ok(path_str) } @@ -102,13 +160,14 @@ where pub fn op_fs_chdir

( state: &mut OpState, #[string] directory: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let d = state .borrow_mut::

() - .check_read(directory, "Deno.chdir()")?; + .check_read(directory, "Deno.chdir()") + .map_err(FsOpsError::Permission)?; state .borrow::() .chdir(&d) @@ -119,7 +178,7 @@ where pub fn op_fs_umask( state: &mut OpState, mask: Option, -) -> Result +) -> Result where { state.borrow::().umask(mask).context("umask") @@ -131,7 +190,7 @@ pub fn op_fs_open_sync

( state: &mut OpState, #[string] path: String, #[serde] options: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -158,7 +217,7 @@ pub async fn op_fs_open_async

( state: Rc>, #[string] path: String, #[serde] options: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -186,7 +245,7 @@ pub fn op_fs_mkdir_sync

( #[string] path: String, recursive: bool, mode: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -194,7 +253,8 @@ where let path = state .borrow_mut::

() - .check_write(&path, "Deno.mkdirSync()")?; + .check_write(&path, "Deno.mkdirSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.mkdir_sync(&path, recursive, Some(mode)) @@ -209,7 +269,7 @@ pub async fn op_fs_mkdir_async

( #[string] path: String, recursive: bool, mode: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -217,7 +277,10 @@ where let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.mkdir()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.mkdir()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -233,13 +296,14 @@ pub fn op_fs_chmod_sync

( state: &mut OpState, #[string] path: String, mode: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(&path, "Deno.chmodSync()")?; + .check_write(&path, "Deno.chmodSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.chmod_sync(&path, mode).context_path("chmod", &path)?; Ok(()) @@ -250,13 +314,16 @@ pub async fn op_fs_chmod_async

( state: Rc>, #[string] path: String, mode: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.chmod()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.chmod()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; fs.chmod_async(path.clone(), mode) @@ -271,13 +338,14 @@ pub fn op_fs_chown_sync

( #[string] path: String, uid: Option, gid: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(&path, "Deno.chownSync()")?; + .check_write(&path, "Deno.chownSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.chown_sync(&path, uid, gid) .context_path("chown", &path)?; @@ -290,13 +358,16 @@ pub async fn op_fs_chown_async

( #[string] path: String, uid: Option, gid: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.chown()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.chown()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; fs.chown_async(path.clone(), uid, gid) @@ -310,13 +381,14 @@ pub fn op_fs_remove_sync

( state: &mut OpState, #[string] path: &str, recursive: bool, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(path, "Deno.removeSync()")?; + .check_write(path, "Deno.removeSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.remove_sync(&path, recursive) @@ -330,7 +402,7 @@ pub async fn op_fs_remove_async

( state: Rc>, #[string] path: String, recursive: bool, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -339,11 +411,13 @@ where let path = if recursive { state .borrow_mut::

() - .check_write(&path, "Deno.remove()")? + .check_write(&path, "Deno.remove()") + .map_err(FsOpsError::Permission)? } else { state .borrow_mut::

() - .check_write_partial(&path, "Deno.remove()")? + .check_write_partial(&path, "Deno.remove()") + .map_err(FsOpsError::Permission)? }; (state.borrow::().clone(), path) @@ -361,13 +435,17 @@ pub fn op_fs_copy_file_sync

( state: &mut OpState, #[string] from: &str, #[string] to: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let permissions = state.borrow_mut::

(); - let from = permissions.check_read(from, "Deno.copyFileSync()")?; - let to = permissions.check_write(to, "Deno.copyFileSync()")?; + let from = permissions + .check_read(from, "Deno.copyFileSync()") + .map_err(FsOpsError::Permission)?; + let to = permissions + .check_write(to, "Deno.copyFileSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.copy_file_sync(&from, &to) @@ -381,15 +459,19 @@ pub async fn op_fs_copy_file_async

( state: Rc>, #[string] from: String, #[string] to: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, from, to) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - let from = permissions.check_read(&from, "Deno.copyFile()")?; - let to = permissions.check_write(&to, "Deno.copyFile()")?; + let from = permissions + .check_read(&from, "Deno.copyFile()") + .map_err(FsOpsError::Permission)?; + let to = permissions + .check_write(&to, "Deno.copyFile()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), from, to) }; @@ -405,13 +487,14 @@ pub fn op_fs_stat_sync

( state: &mut OpState, #[string] path: String, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.statSync()")?; + .check_read(&path, "Deno.statSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); let stat = fs.stat_sync(&path).context_path("stat", &path)?; let serializable_stat = SerializableStat::from(stat); @@ -424,14 +507,16 @@ where pub async fn op_fs_stat_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.stat()")?; + let path = permissions + .check_read(&path, "Deno.stat()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; let stat = fs @@ -446,13 +531,14 @@ pub fn op_fs_lstat_sync

( state: &mut OpState, #[string] path: String, #[buffer] stat_out_buf: &mut [u32], -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.lstatSync()")?; + .check_read(&path, "Deno.lstatSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); let stat = fs.lstat_sync(&path).context_path("lstat", &path)?; let serializable_stat = SerializableStat::from(stat); @@ -465,14 +551,16 @@ where pub async fn op_fs_lstat_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.lstat()")?; + let path = permissions + .check_read(&path, "Deno.lstat()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; let stat = fs @@ -487,15 +575,19 @@ where pub fn op_fs_realpath_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let fs = state.borrow::().clone(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.realPathSync()")?; + let path = permissions + .check_read(&path, "Deno.realPathSync()") + .map_err(FsOpsError::Permission)?; if path.is_relative() { - permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPathSync()")?; + permissions + .check_read_blind(&fs.cwd()?, "CWD", "Deno.realPathSync()") + .map_err(FsOpsError::Permission)?; } let resolved_path = @@ -510,7 +602,7 @@ where pub async fn op_fs_realpath_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -518,9 +610,13 @@ where let mut state = state.borrow_mut(); let fs = state.borrow::().clone(); let permissions = state.borrow_mut::

(); - let path = permissions.check_read(&path, "Deno.realPath()")?; + let path = permissions + .check_read(&path, "Deno.realPath()") + .map_err(FsOpsError::Permission)?; if path.is_relative() { - permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPath()")?; + permissions + .check_read_blind(&fs.cwd()?, "CWD", "Deno.realPath()") + .map_err(FsOpsError::Permission)?; } (fs, path) }; @@ -538,13 +634,14 @@ where pub fn op_fs_read_dir_sync

( state: &mut OpState, #[string] path: String, -) -> Result, AnyError> +) -> Result, FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.readDirSync()")?; + .check_read(&path, "Deno.readDirSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); let entries = fs.read_dir_sync(&path).context_path("readdir", &path)?; @@ -557,7 +654,7 @@ where pub async fn op_fs_read_dir_async

( state: Rc>, #[string] path: String, -) -> Result, AnyError> +) -> Result, FsOpsError> where P: FsPermissions + 'static, { @@ -565,7 +662,8 @@ where let mut state = state.borrow_mut(); let path = state .borrow_mut::

() - .check_read(&path, "Deno.readDir()")?; + .check_read(&path, "Deno.readDir()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -582,14 +680,20 @@ pub fn op_fs_rename_sync

( state: &mut OpState, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let permissions = state.borrow_mut::

(); - let _ = permissions.check_read(&oldpath, "Deno.renameSync()")?; - let oldpath = permissions.check_write(&oldpath, "Deno.renameSync()")?; - let newpath = permissions.check_write(&newpath, "Deno.renameSync()")?; + let _ = permissions + .check_read(&oldpath, "Deno.renameSync()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(&oldpath, "Deno.renameSync()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(&newpath, "Deno.renameSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.rename_sync(&oldpath, &newpath) @@ -603,16 +707,22 @@ pub async fn op_fs_rename_async

( state: Rc>, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, oldpath, newpath) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - _ = permissions.check_read(&oldpath, "Deno.rename()")?; - let oldpath = permissions.check_write(&oldpath, "Deno.rename()")?; - let newpath = permissions.check_write(&newpath, "Deno.rename()")?; + _ = permissions + .check_read(&oldpath, "Deno.rename()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(&oldpath, "Deno.rename()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(&newpath, "Deno.rename()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), oldpath, newpath) }; @@ -628,15 +738,23 @@ pub fn op_fs_link_sync

( state: &mut OpState, #[string] oldpath: &str, #[string] newpath: &str, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let permissions = state.borrow_mut::

(); - _ = permissions.check_read(oldpath, "Deno.linkSync()")?; - let oldpath = permissions.check_write(oldpath, "Deno.linkSync()")?; - _ = permissions.check_read(newpath, "Deno.linkSync()")?; - let newpath = permissions.check_write(newpath, "Deno.linkSync()")?; + _ = permissions + .check_read(oldpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(oldpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; + _ = permissions + .check_read(newpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(newpath, "Deno.linkSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.link_sync(&oldpath, &newpath) @@ -650,17 +768,25 @@ pub async fn op_fs_link_async

( state: Rc>, #[string] oldpath: String, #[string] newpath: String, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, oldpath, newpath) = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - _ = permissions.check_read(&oldpath, "Deno.link()")?; - let oldpath = permissions.check_write(&oldpath, "Deno.link()")?; - _ = permissions.check_read(&newpath, "Deno.link()")?; - let newpath = permissions.check_write(&newpath, "Deno.link()")?; + _ = permissions + .check_read(&oldpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; + let oldpath = permissions + .check_write(&oldpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; + _ = permissions + .check_read(&newpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; + let newpath = permissions + .check_write(&newpath, "Deno.link()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), oldpath, newpath) }; @@ -677,7 +803,7 @@ pub fn op_fs_symlink_sync

( #[string] oldpath: &str, #[string] newpath: &str, #[serde] file_type: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -685,8 +811,12 @@ where let newpath = PathBuf::from(newpath); let permissions = state.borrow_mut::

(); - permissions.check_write_all("Deno.symlinkSync()")?; - permissions.check_read_all("Deno.symlinkSync()")?; + permissions + .check_write_all("Deno.symlinkSync()") + .map_err(FsOpsError::Permission)?; + permissions + .check_read_all("Deno.symlinkSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.symlink_sync(&oldpath, &newpath, file_type) @@ -701,7 +831,7 @@ pub async fn op_fs_symlink_async

( #[string] oldpath: String, #[string] newpath: String, #[serde] file_type: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -711,8 +841,12 @@ where let fs = { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::

(); - permissions.check_write_all("Deno.symlink()")?; - permissions.check_read_all("Deno.symlink()")?; + permissions + .check_write_all("Deno.symlink()") + .map_err(FsOpsError::Permission)?; + permissions + .check_read_all("Deno.symlink()") + .map_err(FsOpsError::Permission)?; state.borrow::().clone() }; @@ -728,13 +862,14 @@ where pub fn op_fs_read_link_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_read(&path, "Deno.readLink()")?; + .check_read(&path, "Deno.readLink()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); @@ -748,7 +883,7 @@ where pub async fn op_fs_read_link_async

( state: Rc>, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -756,7 +891,8 @@ where let mut state = state.borrow_mut(); let path = state .borrow_mut::

() - .check_read(&path, "Deno.readLink()")?; + .check_read(&path, "Deno.readLink()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -773,13 +909,14 @@ pub fn op_fs_truncate_sync

( state: &mut OpState, #[string] path: &str, #[number] len: u64, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let path = state .borrow_mut::

() - .check_write(path, "Deno.truncateSync()")?; + .check_write(path, "Deno.truncateSync()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.truncate_sync(&path, len) @@ -793,7 +930,7 @@ pub async fn op_fs_truncate_async

( state: Rc>, #[string] path: String, #[number] len: u64, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -801,7 +938,8 @@ where let mut state = state.borrow_mut(); let path = state .borrow_mut::

() - .check_write(&path, "Deno.truncate()")?; + .check_write(&path, "Deno.truncate()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -820,11 +958,14 @@ pub fn op_fs_utime_sync

( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { - let path = state.borrow_mut::

().check_write(path, "Deno.utime()")?; + let path = state + .borrow_mut::

() + .check_write(path, "Deno.utime()") + .map_err(FsOpsError::Permission)?; let fs = state.borrow::(); fs.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos) @@ -841,13 +982,16 @@ pub async fn op_fs_utime_async

( #[smi] atime_nanos: u32, #[number] mtime_secs: i64, #[smi] mtime_nanos: u32, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { let (fs, path) = { let mut state = state.borrow_mut(); - let path = state.borrow_mut::

().check_write(&path, "Deno.utime()")?; + let path = state + .borrow_mut::

() + .check_write(&path, "Deno.utime()") + .map_err(FsOpsError::Permission)?; (state.borrow::().clone(), path) }; @@ -871,7 +1015,7 @@ pub fn op_fs_make_temp_dir_sync

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -913,7 +1057,7 @@ pub async fn op_fs_make_temp_dir_async

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -959,7 +1103,7 @@ pub fn op_fs_make_temp_file_sync

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1007,7 +1151,7 @@ pub async fn op_fs_make_temp_file_async

( #[string] dir_arg: Option, #[string] prefix: Option, #[string] suffix: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1069,18 +1213,22 @@ fn make_temp_check_sync

( state: &mut OpState, dir: Option<&str>, api_name: &str, -) -> Result<(PathBuf, FileSystemRc), AnyError> +) -> Result<(PathBuf, FileSystemRc), FsOpsError> where P: FsPermissions + 'static, { let fs = state.borrow::().clone(); let dir = match dir { - Some(dir) => state.borrow_mut::

().check_write(dir, api_name)?, + Some(dir) => state + .borrow_mut::

() + .check_write(dir, api_name) + .map_err(FsOpsError::Permission)?, None => { let dir = fs.tmp_dir().context("tmpdir")?; state .borrow_mut::

() - .check_write_blind(&dir, "TMP", api_name)?; + .check_write_blind(&dir, "TMP", api_name) + .map_err(FsOpsError::Permission)?; dir } }; @@ -1091,19 +1239,23 @@ fn make_temp_check_async

( state: Rc>, dir: Option<&str>, api_name: &str, -) -> Result<(PathBuf, FileSystemRc), AnyError> +) -> Result<(PathBuf, FileSystemRc), FsOpsError> where P: FsPermissions + 'static, { let mut state = state.borrow_mut(); let fs = state.borrow::().clone(); let dir = match dir { - Some(dir) => state.borrow_mut::

().check_write(dir, api_name)?, + Some(dir) => state + .borrow_mut::

() + .check_write(dir, api_name) + .map_err(FsOpsError::Permission)?, None => { let dir = fs.tmp_dir().context("tmpdir")?; state .borrow_mut::

() - .check_write_blind(&dir, "TMP", api_name)?; + .check_write_blind(&dir, "TMP", api_name) + .map_err(FsOpsError::Permission)?; dir } }; @@ -1116,10 +1268,10 @@ 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(FsOpsError::InvalidControlCharacter(c.to_string())); } // 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 +1287,13 @@ fn validate_temporary_filename_component( .matches(|c: char| "<>:\"/\\|?*".contains(c)) .next() { - bail!("Invalid character in prefix or suffix: {:?}", c); + return Err(FsOpsError::InvalidCharacter(c.to_string())); } // 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(FsOpsError::InvalidTrailingCharacter); } Ok(()) @@ -1152,7 +1304,7 @@ fn tmp_name( dir: &Path, prefix: Option<&str>, suffix: Option<&str>, -) -> Result { +) -> Result { let prefix = prefix.unwrap_or(""); validate_temporary_filename_component(prefix, false)?; let suffix = suffix.unwrap_or(""); @@ -1179,7 +1331,7 @@ pub fn op_fs_write_file_sync

( create: bool, create_new: bool, #[buffer] data: JsBuffer, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -1207,7 +1359,7 @@ pub async fn op_fs_write_file_async

( create_new: bool, #[buffer] data: JsBuffer, #[smi] cancel_rid: Option, -) -> Result<(), AnyError> +) -> Result<(), FsOpsError> where P: FsPermissions + 'static, { @@ -1255,7 +1407,7 @@ where pub fn op_fs_read_file_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1277,7 +1429,7 @@ pub async fn op_fs_read_file_async

( state: Rc>, #[string] path: String, #[smi] cancel_rid: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1318,7 +1470,7 @@ where pub fn op_fs_read_file_text_sync

( state: &mut OpState, #[string] path: String, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1340,7 +1492,7 @@ pub async fn op_fs_read_file_text_async

( state: Rc>, #[string] path: String, #[smi] cancel_rid: Option, -) -> Result +) -> Result where P: FsPermissions + 'static, { @@ -1377,13 +1529,13 @@ where Ok(str) } -fn to_seek_from(offset: i64, whence: i32) -> Result { +fn to_seek_from(offset: i64, whence: i32) -> Result { 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(FsOpsError::InvalidSeekMode(whence)); } }; Ok(seek_from) @@ -1396,9 +1548,10 @@ pub fn op_fs_seek_sync( #[smi] rid: ResourceId, #[number] offset: i64, #[smi] whence: i32, -) -> Result { +) -> Result { let pos = to_seek_from(offset, whence)?; - let file = FileResource::get_file(state, rid)?; + let file = + FileResource::get_file(state, rid).map_err(FsOpsError::Resource)?; let cursor = file.seek_sync(pos)?; Ok(cursor) } @@ -1410,9 +1563,10 @@ pub async fn op_fs_seek_async( #[smi] rid: ResourceId, #[number] offset: i64, #[smi] whence: i32, -) -> Result { +) -> Result { 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(FsOpsError::Resource)?; let cursor = file.seek_async(pos).await?; Ok(cursor) } @@ -1421,8 +1575,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(FsOpsError::Resource)?; file.datasync_sync()?; Ok(()) } @@ -1431,8 +1586,9 @@ pub fn op_fs_file_sync_data_sync( pub async fn op_fs_file_sync_data_async( state: Rc>, #[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(FsOpsError::Resource)?; file.datasync_async().await?; Ok(()) } @@ -1441,8 +1597,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(FsOpsError::Resource)?; file.sync_sync()?; Ok(()) } @@ -1451,8 +1608,9 @@ pub fn op_fs_file_sync_sync( pub async fn op_fs_file_sync_async( state: Rc>, #[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(FsOpsError::Resource)?; file.sync_async().await?; Ok(()) } @@ -1462,8 +1620,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(FsOpsError::Resource)?; let stat = file.stat_sync()?; let serializable_stat = SerializableStat::from(stat); serializable_stat.write(stat_out_buf); @@ -1475,8 +1634,9 @@ pub fn op_fs_file_stat_sync( pub async fn op_fs_file_stat_async( state: Rc>, #[smi] rid: ResourceId, -) -> Result { - let file = FileResource::get_file(&state.borrow(), rid)?; +) -> Result { + let file = FileResource::get_file(&state.borrow(), rid) + .map_err(FsOpsError::Resource)?; let stat = file.stat_async().await?; Ok(stat.into()) } @@ -1486,8 +1646,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(FsOpsError::Resource)?; file.lock_sync(exclusive)?; Ok(()) } @@ -1497,8 +1658,9 @@ pub async fn op_fs_flock_async( state: Rc>, #[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(FsOpsError::Resource)?; file.lock_async(exclusive).await?; Ok(()) } @@ -1507,8 +1669,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(FsOpsError::Resource)?; file.unlock_sync()?; Ok(()) } @@ -1517,8 +1680,9 @@ pub fn op_fs_funlock_sync( pub async fn op_fs_funlock_async( state: Rc>, #[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(FsOpsError::Resource)?; file.unlock_async().await?; Ok(()) } @@ -1528,8 +1692,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(FsOpsError::Resource)?; file.truncate_sync(len)?; Ok(()) } @@ -1539,8 +1704,9 @@ pub async fn op_fs_file_truncate_async( state: Rc>, #[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(FsOpsError::Resource)?; file.truncate_async(len).await?; Ok(()) } @@ -1553,8 +1719,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(FsOpsError::Resource)?; file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?; Ok(()) } @@ -1567,42 +1734,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(FsOpsError::Resource)?; file .utime_async(atime_secs, atime_nanos, mtime_secs, mtime_nanos) .await?; Ok(()) } -trait WithContext { - fn context>>( - self, - desc: E, - ) -> AnyError; +#[derive(Debug)] +pub struct OperationError { + operation: &'static str, + kind: OperationErrorKind, + pub err: FsError, } -impl WithContext for FsError { - fn context>>( - 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(self, f: F) -> Self::R + fn context_fn(self, f: F) -> Self::R where - F: FnOnce() -> E, - E: Into>; + F: FnOnce(FsError) -> OperationError; fn context(self, desc: &'static str) -> Self::R; @@ -1617,25 +1806,29 @@ trait MapErrContext { } impl MapErrContext for Result { - type R = Result; + type R = Result; - fn context_fn(self, f: F) -> Self::R + fn context_fn(self, f: F) -> Self::R where - F: FnOnce() -> E, - E: Into>, + F: FnOnce(FsError) -> OperationError, { - self.map_err(|err| { - let message = f(); - err.context(message) + self.map_err(|err| FsOpsError::OperationError(f(err))) + } + + fn context(self, operation: &'static str) -> Self::R { + self.context_fn(move |err| OperationError { + operation, + kind: OperationErrorKind::Bare, + err, }) } - fn context(self, desc: &'static str) -> Self::R { - self.context_fn(move || desc) - } - 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 +1837,19 @@ impl MapErrContext for Result { 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 { - 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 { + s.into_string().map_err(FsOpsError::InvalidUtf8) } macro_rules! create_struct_writer { diff --git a/runtime/errors.rs b/runtime/errors.rs index 935f62d264..25fc664a57 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -23,6 +23,8 @@ use deno_ffi::DlfcnError; use deno_ffi::IRError; use deno_ffi::ReprError; use deno_ffi::StaticError; +use deno_fs::FsOpsError; +use deno_io::fs::FsError; use deno_kv::KvCheckError; use deno_kv::KvError; use deno_kv::KvMutationError; @@ -366,6 +368,34 @@ fn get_broadcast_channel_error(error: &BroadcastChannelError) -> &'static str { } } +fn get_fs_error(error: &FsOpsError) -> &'static str { + match error { + FsOpsError::Io(e) => get_io_error_class(e), + FsOpsError::OperationError(e) => match &e.err { + FsError::Io(e) => get_io_error_class(e), + FsError::FileBusy => "Busy", + FsError::NotSupported => "NotSupported", + FsError::NotCapable(_) => "NotCapable", + }, + FsOpsError::Permission(e) + | FsOpsError::Resource(e) + | FsOpsError::Other(e) => get_error_class_name(e).unwrap_or("Error"), + FsOpsError::InvalidUtf8(_) => "InvalidData", + FsOpsError::StripPrefix(_) => "Error", + FsOpsError::Canceled(e) => { + let io_err: io::Error = e.to_owned().into(); + get_io_error_class(&io_err) + } + FsOpsError::InvalidSeekMode(_) => "TypeError", + FsOpsError::InvalidControlCharacter(_) => "Error", + FsOpsError::InvalidCharacter(_) => "Error", + #[cfg(windows)] + FsOpsError::InvalidTrailingCharacter => "Error", + FsOpsError::NotCapableAccess { .. } => "NotCapable", + FsOpsError::NotCapable(_) => "NotCapable", + } +} + fn get_kv_error(error: &KvError) -> &'static str { match error { KvError::DatabaseHandler(e) | KvError::Resource(e) | KvError::Kv(e) => { @@ -470,6 +500,7 @@ pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { .or_else(|| e.downcast_ref::().map(get_web_blob_error_class)) .or_else(|| e.downcast_ref::().map(|_| "TypeError")) .or_else(|| e.downcast_ref::().map(get_ffi_repr_error_class)) + .or_else(|| e.downcast_ref::().map(get_fs_error)) .or_else(|| { e.downcast_ref::() .map(get_ffi_dlfcn_error_class)