summaryrefslogtreecommitdiff
path: root/ext/fs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/fs')
-rw-r--r--ext/fs/30_fs.js6
-rw-r--r--ext/fs/Cargo.toml6
-rw-r--r--ext/fs/in_memory_fs.rs2
-rw-r--r--ext/fs/lib.rs53
-rw-r--r--ext/fs/ops.rs397
-rw-r--r--ext/fs/std_fs.rs36
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>> {