summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2023-04-12 15:13:32 +0200
committerGitHub <noreply@github.com>2023-04-12 15:13:32 +0200
commitf90caa821c5a4acf28f7dec4071994ecf6f26e57 (patch)
treefa4af65399a16a9f2d17fa2b249f21be2c00b976
parent0e3f62d4446ae7b9a64dacf7befcaecede118222 (diff)
refactor(ext/fs): abstract FS via FileSystem trait (#18599)
This commit abstracts out the specifics of the underlying system calls FS operations behind a new `FileSystem` and `File` trait in the `ext/fs` extension. This allows other embedders to re-use ext/fs, but substituting in a different FS backend. This is likely not the final form of these traits. Eventually they will be entirely `deno_core::Resource` agnostic, and will live in a seperate crate. --------- Co-authored-by: Bartek IwaƄczuk <biwanczuk@gmail.com>
-rw-r--r--Cargo.lock3
-rw-r--r--cli/build.rs3
-rw-r--r--core/extensions.rs16
-rw-r--r--ext/fs/30_fs.js92
-rw-r--r--ext/fs/Cargo.toml3
-rw-r--r--ext/fs/clippy.toml45
-rw-r--r--ext/fs/interface.rs395
-rw-r--r--ext/fs/lib.rs2484
-rw-r--r--ext/fs/ops.rs1712
-rw-r--r--ext/fs/std_fs.rs929
-rw-r--r--ext/io/lib.rs81
-rw-r--r--runtime/build.rs16
-rw-r--r--runtime/permissions/mod.rs53
-rw-r--r--runtime/web_worker.rs3
-rw-r--r--runtime/worker.rs3
15 files changed, 3350 insertions, 2488 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d6874d452..7eca48a2c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1002,14 +1002,15 @@ dependencies = [
name = "deno_fs"
version = "0.7.0"
dependencies = [
+ "async-trait",
"deno_core",
- "deno_crypto",
"deno_io",
"filetime",
"fs3",
"libc",
"log",
"nix",
+ "rand",
"serde",
"tokio",
"winapi",
diff --git a/cli/build.rs b/cli/build.rs
index ddd942593..6d13f9a4d 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -8,6 +8,7 @@ use deno_core::Extension;
use deno_core::ExtensionFileSource;
use deno_core::ExtensionFileSourceCode;
use deno_runtime::deno_cache::SqliteBackedCache;
+use deno_runtime::deno_fs::StdFs;
use deno_runtime::deno_kv::sqlite::SqliteDbHandler;
use deno_runtime::permissions::PermissionsContainer;
use deno_runtime::*;
@@ -361,7 +362,7 @@ fn create_cli_snapshot(snapshot_path: PathBuf) {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Default::default()),
- deno_fs::deno_fs::init_ops::<PermissionsContainer>(false),
+ deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(false, StdFs),
deno_node::deno_node::init_ops::<deno_runtime::RuntimeNodeEnv>(None),
cli::init_ops_and_esm(), // NOTE: This needs to be init_ops_and_esm!
];
diff --git a/core/extensions.rs b/core/extensions.rs
index 4a7b49414..685ac0ab7 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -180,7 +180,7 @@ macro_rules! extension {
$(, deps = [ $( $dep:ident ),* ] )?
$(, parameters = [ $( $param:ident : $type:ident ),+ ] )?
$(, ops_fn = $ops_symbol:ident $( < $ops_param:ident > )? )?
- $(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $op_param:ident > )? ),+ $(,)? ] )?
+ $(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $( $op_param:ident ),* > )? ),+ $(,)? ] )?
$(, esm_entry_point = $esm_entry_point:literal )?
$(, esm = [ $( dir $dir_esm:literal , )? $( $esm:literal ),* $(,)? ] )?
$(, esm_setup_script = $esm_setup_script:expr )?
@@ -235,7 +235,7 @@ macro_rules! extension {
ext.ops(vec![
$(
$( #[ $m ] )*
- $( $op )::+ :: decl $( :: <$op_param> )? ()
+ $( $op )::+ :: decl $( :: < $($op_param),* > )? ()
),+
]);
)?
@@ -267,11 +267,11 @@ macro_rules! extension {
}
#[allow(dead_code)]
- pub fn init_js_only $( < $( $param : $type + 'static ),+ > )? () -> $crate::Extension {
+ pub fn init_js_only $( < $( $param : $type + 'static ),* > )? () -> $crate::Extension {
let mut ext = Self::ext();
// If esm or JS was specified, add JS files
Self::with_js(&mut ext);
- Self::with_ops $( ::<($( $param ),+)> )?(&mut ext);
+ Self::with_ops $( ::< $( $param ),+ > )?(&mut ext);
Self::with_customizer(&mut ext);
ext.take()
}
@@ -281,8 +281,8 @@ macro_rules! extension {
let mut ext = Self::ext();
// If esm or JS was specified, add JS files
Self::with_js(&mut ext);
- Self::with_ops $( ::<($( $param ),+)> )?(&mut ext);
- Self::with_state_and_middleware $( ::<($( $param ),+)> )?(&mut ext, $( $( $options_id , )* )? );
+ Self::with_ops $( ::< $( $param ),+ > )?(&mut ext);
+ Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
Self::with_customizer(&mut ext);
ext.take()
}
@@ -290,8 +290,8 @@ macro_rules! extension {
#[allow(dead_code)]
pub fn init_ops $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension {
let mut ext = Self::ext();
- Self::with_ops $( ::<($( $param ),+)> )?(&mut ext);
- Self::with_state_and_middleware $( ::<($( $param ),+)> )?(&mut ext, $( $( $options_id , )* )? );
+ Self::with_ops $( ::< $( $param ),+ > )?(&mut ext);
+ Self::with_state_and_middleware $( ::< $( $param ),+ > )?(&mut ext, $( $( $options_id , )* )? );
Self::with_customizer(&mut ext);
ext.take()
}
diff --git a/ext/fs/30_fs.js b/ext/fs/30_fs.js
index 1421de9eb..bddafb09e 100644
--- a/ext/fs/30_fs.js
+++ b/ext/fs/30_fs.js
@@ -85,43 +85,50 @@ function chdir(directory) {
}
function makeTempDirSync(options = {}) {
- return ops.op_make_temp_dir_sync(options);
+ return ops.op_make_temp_dir_sync(options.dir, options.prefix, options.suffix);
}
function makeTempDir(options = {}) {
- return core.opAsync("op_make_temp_dir_async", options);
+ return core.opAsync(
+ "op_make_temp_dir_async",
+ options.dir,
+ options.prefix,
+ options.suffix,
+ );
}
function makeTempFileSync(options = {}) {
- return ops.op_make_temp_file_sync(options);
+ return ops.op_make_temp_file_sync(
+ options.dir,
+ options.prefix,
+ options.suffix,
+ );
}
function makeTempFile(options = {}) {
- return core.opAsync("op_make_temp_file_async", options);
-}
-
-function mkdirArgs(path, options) {
- const args = { path: pathFromURL(path), recursive: false };
- if (options != null) {
- if (typeof options.recursive == "boolean") {
- args.recursive = options.recursive;
- }
- if (options.mode) {
- args.mode = options.mode;
- }
- }
- return args;
+ return core.opAsync(
+ "op_make_temp_file_async",
+ options.dir,
+ options.prefix,
+ options.suffix,
+ );
}
function mkdirSync(path, options) {
- ops.op_mkdir_sync(mkdirArgs(path, options));
+ ops.op_mkdir_sync(
+ pathFromURL(path),
+ options?.recursive ?? false,
+ options?.mode,
+ );
}
-async function mkdir(
- path,
- options,
-) {
- await core.opAsync2("op_mkdir_async", mkdirArgs(path, options));
+async function mkdir(path, options) {
+ await core.opAsync(
+ "op_mkdir_async",
+ pathFromURL(path),
+ options?.recursive ?? false,
+ options?.mode,
+ );
}
function readDirSync(path) {
@@ -306,36 +313,22 @@ async function fstat(rid) {
}
async function lstat(path) {
- const res = await core.opAsync("op_stat_async", {
- path: pathFromURL(path),
- lstat: true,
- });
+ const res = await core.opAsync("op_lstat_async", pathFromURL(path));
return parseFileInfo(res);
}
function lstatSync(path) {
- ops.op_stat_sync(
- pathFromURL(path),
- true,
- statBuf,
- );
+ ops.op_lstat_sync(pathFromURL(path), statBuf);
return statStruct(statBuf);
}
async function stat(path) {
- const res = await core.opAsync("op_stat_async", {
- path: pathFromURL(path),
- lstat: false,
- });
+ const res = await core.opAsync("op_stat_async", pathFromURL(path));
return parseFileInfo(res);
}
function statSync(path) {
- ops.op_stat_sync(
- pathFromURL(path),
- false,
- statBuf,
- );
+ ops.op_stat_sync(pathFromURL(path), statBuf);
return statStruct(statBuf);
}
@@ -343,7 +336,6 @@ function coerceLen(len) {
if (len == null || len < 0) {
return 0;
}
-
return len;
}
@@ -518,7 +510,7 @@ function seekSync(
offset,
whence,
) {
- return ops.op_seek_sync({ rid, offset, whence });
+ return ops.op_seek_sync(rid, offset, whence);
}
function seek(
@@ -526,7 +518,7 @@ function seek(
offset,
whence,
) {
- return core.opAsync("op_seek_async", { rid, offset, whence });
+ return core.opAsync("op_seek_async", rid, offset, whence);
}
function openSync(
@@ -534,11 +526,9 @@ function openSync(
options,
) {
if (options) checkOpenOptions(options);
- const mode = options?.mode;
const rid = ops.op_open_sync(
pathFromURL(path),
options,
- mode,
);
return new FsFile(rid);
@@ -549,12 +539,10 @@ async function open(
options,
) {
if (options) checkOpenOptions(options);
- const mode = options?.mode;
const rid = await core.opAsync(
"op_open_async",
pathFromURL(path),
options,
- mode,
);
return new FsFile(rid);
@@ -679,7 +667,7 @@ function checkOpenOptions(options) {
const File = FsFile;
function readFileSync(path) {
- return ops.op_readfile_sync(pathFromURL(path));
+ return ops.op_read_file_sync(pathFromURL(path));
}
async function readFile(path, options) {
@@ -694,7 +682,7 @@ async function readFile(path, options) {
try {
const read = await core.opAsync(
- "op_readfile_async",
+ "op_read_file_async",
pathFromURL(path),
cancelRid,
);
@@ -710,7 +698,7 @@ async function readFile(path, options) {
}
function readTextFileSync(path) {
- return ops.op_readfile_text_sync(pathFromURL(path));
+ return ops.op_read_file_text_sync(pathFromURL(path));
}
async function readTextFile(path, options) {
@@ -725,7 +713,7 @@ async function readTextFile(path, options) {
try {
const read = await core.opAsync(
- "op_readfile_text_async",
+ "op_read_file_text_async",
pathFromURL(path),
cancelRid,
);
diff --git a/ext/fs/Cargo.toml b/ext/fs/Cargo.toml
index 69b32c163..016d73ae6 100644
--- a/ext/fs/Cargo.toml
+++ b/ext/fs/Cargo.toml
@@ -14,13 +14,14 @@ description = "Ops for interacting with the file system"
path = "lib.rs"
[dependencies]
+async-trait.workspace = true
deno_core.workspace = true
-deno_crypto.workspace = true
deno_io.workspace = true
filetime = "0.2.16"
fs3 = "0.5.0"
libc.workspace = true
log.workspace = true
+rand.workspace = true
serde.workspace = true
tokio.workspace = true
diff --git a/ext/fs/clippy.toml b/ext/fs/clippy.toml
new file mode 100644
index 000000000..53676a90e
--- /dev/null
+++ b/ext/fs/clippy.toml
@@ -0,0 +1,45 @@
+disallowed-methods = [
+ { path = "std::env::current_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::canonicalize", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::is_dir", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::is_file", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::is_symlink", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::metadata", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::read_dir", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::read_link", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::Path::try_exists", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::exists", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using NodeFs trait" },
+ { path = "std::env::set_current_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::env::temp_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::canonicalize", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::copy", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::create_dir_all", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::create_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::DirBuilder::new", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::hard_link", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::metadata", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::OpenOptions::new", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::read_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::read_link", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::read_to_string", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::read", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::remove_dir_all", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::remove_dir", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::remove_file", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::rename", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::set_permissions", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::symlink_metadata", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::fs::write", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::canonicalize", reason = "File system operations should be done using FileSystem trait" },
+ { path = "std::path::Path::exists", reason = "File system operations should be done using FileSystem trait" },
+]
diff --git a/ext/fs/interface.rs b/ext/fs/interface.rs
new file mode 100644
index 000000000..a68260051
--- /dev/null
+++ b/ext/fs/interface.rs
@@ -0,0 +1,395 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use std::io;
+use std::path::Path;
+use std::path::PathBuf;
+use std::rc::Rc;
+
+use deno_core::error::not_supported;
+use deno_core::error::resource_unavailable;
+use deno_core::error::AnyError;
+use deno_core::Resource;
+use serde::Deserialize;
+use serde::Serialize;
+use tokio::task::JoinError;
+
+pub trait FsPermissions {
+ fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
+ fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
+ fn check_read_blind(
+ &mut self,
+ p: &Path,
+ display: &str,
+ api_name: &str,
+ ) -> Result<(), AnyError>;
+ fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
+ fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>;
+ fn check_write_blind(
+ &mut self,
+ p: &Path,
+ display: &str,
+ api_name: &str,
+ ) -> Result<(), AnyError>;
+}
+
+#[derive(Deserialize, Default, Debug, Clone, Copy)]
+#[serde(rename_all = "camelCase")]
+#[serde(default)]
+pub struct OpenOptions {
+ pub read: bool,
+ pub write: bool,
+ pub create: bool,
+ pub truncate: bool,
+ pub append: bool,
+ pub create_new: bool,
+ pub mode: Option<u32>,
+}
+
+impl OpenOptions {
+ pub fn read() -> Self {
+ Self {
+ read: true,
+ write: false,
+ create: false,
+ truncate: false,
+ append: false,
+ create_new: false,
+ mode: None,
+ }
+ }
+
+ pub fn write(
+ create: bool,
+ append: bool,
+ create_new: bool,
+ mode: Option<u32>,
+ ) -> Self {
+ Self {
+ read: false,
+ write: true,
+ create,
+ truncate: !append,
+ append,
+ create_new,
+ mode,
+ }
+ }
+
+ pub(crate) fn check<P: FsPermissions>(
+ &self,
+ permissions: &mut P,
+ path: &Path,
+ api_name: &str,
+ ) -> Result<(), AnyError> {
+ if self.read {
+ permissions.check_read(path, api_name)?;
+ }
+ if self.write || self.append {
+ permissions.check_write(path, api_name)?;
+ }
+ Ok(())
+ }
+}
+
+pub struct FsStat {
+ pub is_file: bool,
+ pub is_directory: bool,
+ pub is_symlink: bool,
+ pub size: u64,
+
+ pub mtime: Option<u64>,
+ pub atime: Option<u64>,
+ pub birthtime: Option<u64>,
+
+ pub dev: u64,
+ pub ino: u64,
+ pub mode: u32,
+ pub nlink: u64,
+ pub uid: u32,
+ pub gid: u32,
+ pub rdev: u64,
+ pub blksize: u64,
+ pub blocks: u64,
+}
+
+#[derive(Deserialize)]
+pub enum FsFileType {
+ #[serde(rename = "file")]
+ File,
+ #[serde(rename = "dir")]
+ Directory,
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct FsDirEntry {
+ pub name: String,
+ pub is_file: bool,
+ pub is_directory: bool,
+ pub is_symlink: bool,
+}
+
+pub enum FsError {
+ Io(io::Error),
+ FileBusy,
+ NotSupported,
+}
+
+impl From<io::Error> for FsError {
+ fn from(err: io::Error) -> Self {
+ Self::Io(err)
+ }
+}
+
+impl From<JoinError> for FsError {
+ fn from(err: JoinError) -> Self {
+ if err.is_cancelled() {
+ todo!("async tasks must not be cancelled")
+ }
+ if err.is_panic() {
+ std::panic::resume_unwind(err.into_panic()); // resume the panic on the main thread
+ }
+ unreachable!()
+ }
+}
+
+impl From<FsError> for AnyError {
+ fn from(err: FsError) -> Self {
+ match err {
+ FsError::Io(err) => AnyError::from(err),
+ FsError::FileBusy => resource_unavailable(),
+ FsError::NotSupported => not_supported(),
+ }
+ }
+}
+
+pub type FsResult<T> = Result<T, FsError>;
+
+#[async_trait::async_trait(?Send)]
+pub trait File {
+ fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()>;
+ async fn write_all_async(self: Rc<Self>, buf: Vec<u8>) -> FsResult<()>;
+
+ fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>>;
+ async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>>;
+
+ fn chmod_sync(self: Rc<Self>, pathmode: u32) -> FsResult<()>;
+ async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()>;
+
+ fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
+ async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
+
+ fn datasync_sync(self: Rc<Self>) -> FsResult<()>;
+ async fn datasync_async(self: Rc<Self>) -> FsResult<()>;
+
+ fn sync_sync(self: Rc<Self>) -> FsResult<()>;
+ async fn sync_async(self: Rc<Self>) -> FsResult<()>;
+
+ fn stat_sync(self: Rc<Self>) -> FsResult<FsStat>;
+ async fn stat_async(self: Rc<Self>) -> FsResult<FsStat>;
+
+ fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
+ async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
+ fn unlock_sync(self: Rc<Self>) -> FsResult<()>;
+ async fn unlock_async(self: Rc<Self>) -> FsResult<()>;
+
+ fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()>;
+ async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()>;
+
+ fn utime_sync(
+ self: Rc<Self>,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()>;
+ async fn utime_async(
+ self: Rc<Self>,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()>;
+}
+
+#[async_trait::async_trait(?Send)]
+pub trait FileSystem: Clone {
+ type File: File + Resource;
+
+ fn cwd(&self) -> FsResult<PathBuf>;
+ fn tmp_dir(&self) -> FsResult<PathBuf>;
+ fn chdir(&self, path: impl AsRef<Path>) -> FsResult<()>;
+ fn umask(&self, mask: Option<u32>) -> FsResult<u32>;
+
+ fn open_sync(
+ &self,
+ path: impl AsRef<Path>,
+ options: OpenOptions,
+ ) -> FsResult<Self::File>;
+ async fn open_async(
+ &self,
+ path: PathBuf,
+ options: OpenOptions,
+ ) -> FsResult<Self::File>;
+
+ fn mkdir_sync(
+ &self,
+ path: impl AsRef<Path>,
+ recusive: bool,
+ mode: u32,
+ ) -> FsResult<()>;
+ async fn mkdir_async(
+ &self,
+ path: PathBuf,
+ recusive: bool,
+ mode: u32,
+ ) -> FsResult<()>;
+
+ fn chmod_sync(&self, path: impl AsRef<Path>, mode: u32) -> FsResult<()>;
+ async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()>;
+
+ fn chown_sync(
+ &self,
+ path: impl AsRef<Path>,
+ uid: Option<u32>,
+ gid: Option<u32>,
+ ) -> FsResult<()>;
+ async fn chown_async(
+ &self,
+ path: PathBuf,
+ uid: Option<u32>,
+ gid: Option<u32>,
+ ) -> FsResult<()>;
+
+ fn remove_sync(
+ &self,
+ path: impl AsRef<Path>,
+ recursive: bool,
+ ) -> FsResult<()>;
+ async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()>;
+
+ fn copy_file_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ ) -> FsResult<()>;
+ async fn copy_file_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ ) -> FsResult<()>;
+
+ fn stat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat>;
+ async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat>;
+
+ fn lstat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat>;
+ async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat>;
+
+ fn realpath_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf>;
+ async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf>;
+
+ fn read_dir_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>>;
+ async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>>;
+
+ fn rename_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ ) -> FsResult<()>;
+ async fn rename_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ ) -> FsResult<()>;
+
+ fn link_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ ) -> FsResult<()>;
+ async fn link_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ ) -> FsResult<()>;
+
+ fn symlink_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ file_type: Option<FsFileType>,
+ ) -> FsResult<()>;
+ async fn symlink_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ file_type: Option<FsFileType>,
+ ) -> FsResult<()>;
+
+ fn read_link_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf>;
+ async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf>;
+
+ fn truncate_sync(&self, path: impl AsRef<Path>, len: u64) -> FsResult<()>;
+ async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()>;
+
+ fn utime_sync(
+ &self,
+ path: impl AsRef<Path>,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()>;
+ async fn utime_async(
+ &self,
+ path: PathBuf,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()>;
+
+ fn write_file_sync(
+ &self,
+ path: impl AsRef<Path>,
+ options: OpenOptions,
+ data: &[u8],
+ ) -> FsResult<()> {
+ let file = self.open_sync(path, options)?;
+ let file = Rc::new(file);
+ if let Some(mode) = options.mode {
+ file.clone().chmod_sync(mode)?;
+ }
+ file.write_all_sync(data)?;
+ Ok(())
+ }
+ async fn write_file_async(
+ &self,
+ path: PathBuf,
+ options: OpenOptions,
+ data: Vec<u8>,
+ ) -> FsResult<()> {
+ let file = self.open_async(path, options).await?;
+ let file = Rc::new(file);
+ if let Some(mode) = options.mode {
+ file.clone().chmod_async(mode).await?;
+ }
+ file.write_all_async(data).await?;
+ Ok(())
+ }
+
+ fn read_file_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
+ let options = OpenOptions::read();
+ let file = self.open_sync(path, options)?;
+ let file = Rc::new(file);
+ let buf = file.read_all_sync()?;
+ Ok(buf)
+ }
+ async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
+ let options = OpenOptions::read();
+ let file = self.clone().open_async(path, options).await?;
+ let file = Rc::new(file);
+ let buf = file.read_all_async().await?;
+ Ok(buf)
+ }
+}
diff --git a/ext/fs/lib.rs b/ext/fs/lib.rs
index c00f39599..c1418815c 100644
--- a/ext/fs/lib.rs
+++ b/ext/fs/lib.rs
@@ -1,74 +1,26 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-// Some deserializer fields are only used on Unix and Windows build fails without it
-use deno_core::error::custom_error;
-use deno_core::error::type_error;
-use deno_core::error::AnyError;
-use deno_core::op;
-use deno_core::CancelFuture;
-use deno_core::CancelHandle;
+mod interface;
+mod ops;
+mod std_fs;
+
+pub use crate::interface::File;
+pub use crate::interface::FileSystem;
+pub use crate::interface::FsDirEntry;
+pub use crate::interface::FsError;
+pub use crate::interface::FsFileType;
+pub use crate::interface::FsPermissions;
+pub use crate::interface::FsResult;
+pub use crate::interface::FsStat;
+pub use crate::interface::OpenOptions;
+use crate::ops::*;
+
+pub use crate::std_fs::StdFs;
+
use deno_core::OpState;
-use deno_core::ResourceId;
-use deno_core::ZeroCopyBuf;
-use deno_crypto::rand::thread_rng;
-use deno_crypto::rand::Rng;
-use deno_io::StdFileResource;
-use log::debug;
-use serde::Deserialize;
-use serde::Serialize;
-use std::borrow::Cow;
use std::cell::RefCell;
use std::convert::From;
-use std::env::current_dir;
-use std::env::set_current_dir;
-use std::env::temp_dir;
-use std::io;
-use std::io::Error;
-use std::io::Seek;
-use std::io::SeekFrom;
-use std::io::Write;
-use std::path::Path;
-use std::path::PathBuf;
use std::rc::Rc;
-use std::time::SystemTime;
-use std::time::UNIX_EPOCH;
-
-/// Similar to `std::fs::canonicalize()` but strips UNC prefixes on Windows.
-fn canonicalize_path(path: &Path) -> Result<PathBuf, Error> {
- let mut canonicalized_path = path.canonicalize()?;
- if cfg!(windows) {
- canonicalized_path = PathBuf::from(
- canonicalized_path
- .display()
- .to_string()
- .trim_start_matches("\\\\?\\"),
- );
- }
- Ok(canonicalized_path)
-}
-
-/// A utility function to map OsStrings to Strings
-fn 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)
- })
-}
-
-#[cfg(unix)]
-pub fn get_nix_error_class(error: &nix::Error) -> &'static str {
- match error {
- nix::Error::ECHILD => "NotFound",
- nix::Error::EINVAL => "TypeError",
- nix::Error::ENOENT => "NotFound",
- nix::Error::ENOTTY => "BadResource",
- nix::Error::EPERM => "PermissionDenied",
- nix::Error::ESRCH => "NotFound",
- nix::Error::UnknownErrno => "Error",
- &nix::Error::ENOTSUP => unreachable!(),
- _ => "Error",
- }
-}
struct UnstableChecker {
pub unstable: bool,
@@ -87,2352 +39,92 @@ impl UnstableChecker {
}
/// Helper for checking unstable features. Used for sync ops.
-fn check_unstable(state: &OpState, api_name: &str) {
+pub(crate) fn check_unstable(state: &OpState, api_name: &str) {
state.borrow::<UnstableChecker>().check_unstable(api_name)
}
/// Helper for checking unstable features. Used for async ops.
-fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
+pub(crate) fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
let state = state.borrow();
state.borrow::<UnstableChecker>().check_unstable(api_name)
}
-pub trait FsPermissions {
- fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
- fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
- fn check_read_blind(
- &mut self,
- p: &Path,
- display: &str,
- api_name: &str,
- ) -> Result<(), AnyError>;
- fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
- fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>;
-}
-
-#[cfg(not(unix))]
-use deno_core::error::generic_error;
-#[cfg(not(unix))]
-use deno_core::error::not_supported;
-
deno_core::extension!(deno_fs,
- deps = [ deno_web, deno_io ],
- parameters = [P: FsPermissions],
+ deps = [ deno_web ],
+ parameters = [Fs: FileSystem, P: FsPermissions],
ops = [
- op_open_sync<P>,
- op_open_async<P>,
- op_write_file_sync<P>,
- op_write_file_async<P>,
- op_seek_sync,
- op_seek_async,
- op_fdatasync_sync,
- op_fdatasync_async,
- op_fsync_sync,
- op_fsync_async,
- op_fstat_sync,
- op_fstat_async,
- op_flock_sync,
- op_flock_async,
- op_funlock_sync,
- op_funlock_async,
- op_umask,
- op_chdir<P>,
- op_mkdir_sync<P>,
- op_mkdir_async<P>,
- op_chmod_sync<P>,
- op_chmod_async<P>,
- op_chown_sync<P>,
- op_chown_async<P>,
- op_remove_sync<P>,
- op_remove_async<P>,
- op_copy_file_sync<P>,
- op_copy_file_async<P>,
- op_stat_sync<P>,
- op_stat_async<P>,
- op_realpath_sync<P>,
- op_realpath_async<P>,
- op_read_dir_sync<P>,
- op_read_dir_async<P>,
- op_rename_sync<P>,
- op_rename_async<P>,
- op_link_sync<P>,
- op_link_async<P>,
- op_symlink_sync<P>,
- op_symlink_async<P>,
- op_read_link_sync<P>,
- op_read_link_async<P>,
- op_ftruncate_sync,
- op_ftruncate_async,
- op_truncate_sync<P>,
- op_truncate_async<P>,
- op_make_temp_dir_sync<P>,
- op_make_temp_dir_async<P>,
- op_make_temp_file_sync<P>,
- op_make_temp_file_async<P>,
- op_cwd<P>,
- op_futime_sync,
- op_futime_async,
- op_utime_sync<P>,
- op_utime_async<P>,
- op_readfile_sync<P>,
- op_readfile_text_sync<P>,
- op_readfile_async<P>,
- op_readfile_text_async<P>,
+ op_cwd<Fs, P>,
+ op_umask<Fs>,
+ op_chdir<Fs, P>,
+
+ op_open_sync<Fs, P>,
+ op_open_async<Fs, P>,
+ op_mkdir_sync<Fs, P>,
+ op_mkdir_async<Fs, P>,
+ op_chmod_sync<Fs, P>,
+ op_chmod_async<Fs, P>,
+ op_chown_sync<Fs, P>,
+ op_chown_async<Fs, P>,
+ op_remove_sync<Fs, P>,
+ op_remove_async<Fs, P>,
+ op_copy_file_sync<Fs, P>,
+ op_copy_file_async<Fs, P>,
+ op_stat_sync<Fs, P>,
+ op_stat_async<Fs, P>,
+ op_lstat_sync<Fs, P>,
+ op_lstat_async<Fs, P>,
+ op_realpath_sync<Fs, P>,
+ op_realpath_async<Fs, P>,
+ op_read_dir_sync<Fs, P>,
+ op_read_dir_async<Fs, P>,
+ op_rename_sync<Fs, P>,
+ op_rename_async<Fs, P>,
+ op_link_sync<Fs, P>,
+ op_link_async<Fs, P>,
+ op_symlink_sync<Fs, P>,
+ op_symlink_async<Fs, P>,
+ op_read_link_sync<Fs, P>,
+ op_read_link_async<Fs, P>,
+ op_truncate_sync<Fs, P>,
+ op_truncate_async<Fs, P>,
+ op_utime_sync<Fs, P>,
+ op_utime_async<Fs, P>,
+ op_make_temp_dir_sync<Fs, P>,
+ op_make_temp_dir_async<Fs, P>,
+ op_make_temp_file_sync<Fs, P>,
+ op_make_temp_file_async<Fs, P>,
+ op_write_file_sync<Fs, P>,
+ op_write_file_async<Fs, P>,
+ op_read_file_sync<Fs, P>,
+ op_read_file_async<Fs, P>,
+ op_read_file_text_sync<Fs, P>,
+ op_read_file_text_async<Fs, P>,
+
+ op_seek_sync<Fs>,
+ op_seek_async<Fs>,
+ op_fdatasync_sync<Fs>,
+ op_fdatasync_async<Fs>,
+ op_fsync_sync<Fs>,
+ op_fsync_async<Fs>,
+ op_fstat_sync<Fs>,
+ op_fstat_async<Fs>,
+ op_flock_sync<Fs>,
+ op_flock_async<Fs>,
+ op_funlock_sync<Fs>,
+ op_funlock_async<Fs>,
+ op_ftruncate_sync<Fs>,
+ op_ftruncate_async<Fs>,
+ op_futime_sync<Fs>,
+ op_futime_async<Fs>,
+
],
esm = [ "30_fs.js" ],
options = {
- unstable: bool
+ unstable: bool,
+ fs: Fs,
},
state = |state, options| {
state.put(UnstableChecker { unstable: options.unstable });
+ state.put(options.fs);
},
);
-
-fn default_err_mapper(err: Error, desc: String) -> AnyError {
- AnyError::new(Error::new(err.kind(), desc)).context(err)
-}
-
-#[derive(Deserialize, Default, Debug)]
-#[serde(rename_all = "camelCase")]
-#[serde(default)]
-pub struct OpenOptions {
- read: bool,
- write: bool,
- create: bool,
- truncate: bool,
- append: bool,
- create_new: bool,
-}
-
-#[inline]
-fn open_helper<P>(
- state: &mut OpState,
- path: &str,
- mode: Option<u32>,
- options: Option<&OpenOptions>,
- api_name: &str,
-) -> Result<(PathBuf, std::fs::OpenOptions), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(path).to_path_buf();
-
- let mut open_options = std::fs::OpenOptions::new();
-
- if let Some(mode) = mode {
- // mode only used if creating the file on Unix
- // if not specified, defaults to 0o666
- #[cfg(unix)]
- {
- use std::os::unix::fs::OpenOptionsExt;
- open_options.mode(mode & 0o777);
- }
- #[cfg(not(unix))]
- let _ = mode; // avoid unused warning
- }
-
- let permissions = state.borrow_mut::<P>();
-
- match options {
- None => {
- permissions.check_read(&path, api_name)?;
- open_options
- .read(true)
- .create(false)
- .write(false)
- .truncate(false)
- .append(false)
- .create_new(false);
- }
- Some(options) => {
- if options.read {
- permissions.check_read(&path, api_name)?;
- }
-
- if options.write || options.append {
- permissions.check_write(&path, api_name)?;
- }
-
- open_options
- .read(options.read)
- .create(options.create)
- .write(options.write)
- .truncate(options.truncate)
- .append(options.append)
- .create_new(options.create_new);
- }
- }
-
- Ok((path, open_options))
-}
-
-#[op]
-fn op_open_sync<P>(
- state: &mut OpState,
- path: String,
- options: Option<OpenOptions>,
- mode: Option<u32>,
-) -> Result<ResourceId, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let (path, open_options) =
- open_helper::<P>(state, &path, mode, options.as_ref(), "Deno.openSync()")?;
- let std_file = open_options.open(&path).map_err(|err| {
- default_err_mapper(err, format!("open '{}'", path.display()))
- })?;
- let resource = StdFileResource::fs_file(std_file);
- let rid = state.resource_table.add(resource);
- Ok(rid)
-}
-
-#[op]
-async fn op_open_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- options: Option<OpenOptions>,
- mode: Option<u32>,
-) -> Result<ResourceId, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let (path, open_options) = open_helper::<P>(
- &mut state.borrow_mut(),
- &path,
- mode,
- options.as_ref(),
- "Deno.open()",
- )?;
- let std_file = tokio::task::spawn_blocking(move || {
- open_options.open(&path).map_err(|err| {
- default_err_mapper(err, format!("open '{}'", path.display()))
- })
- })
- .await?;
- let resource = StdFileResource::fs_file(std_file?);
- let rid = state.borrow_mut().resource_table.add(resource);
- Ok(rid)
-}
-
-#[inline]
-fn write_open_options(
- create: bool,
- append: bool,
- create_new: bool,
-) -> OpenOptions {
- OpenOptions {
- read: false,
- write: true,
- create,
- truncate: !append,
- append,
- create_new,
- }
-}
-
-#[op]
-fn op_write_file_sync<P>(
- state: &mut OpState,
- path: String,
- mode: Option<u32>,
- append: bool,
- create: bool,
- create_new: bool,
- data: ZeroCopyBuf,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let (path, open_options) = open_helper::<P>(
- state,
- &path,
- mode,
- Some(&write_open_options(create, append, create_new)),
- "Deno.writeFileSync()",
- )?;
- write_file(&path, open_options, mode, data)
-}
-
-#[op]
-async fn op_write_file_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- mode: Option<u32>,
- append: bool,
- create: bool,
- create_new: bool,
- data: ZeroCopyBuf,
- cancel_rid: Option<ResourceId>,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let (path, open_options) = open_helper::<P>(
- &mut state.borrow_mut(),
- &path,
- mode,
- Some(&write_open_options(create, append, create_new)),
- "Deno.writeFile()",
- )?;
-
- let write_future = tokio::task::spawn_blocking(move || {
- write_file(&path, open_options, mode, data)
- });
-
- let cancel_handle = cancel_rid.and_then(|rid| {
- state
- .borrow_mut()
- .resource_table
- .get::<CancelHandle>(rid)
- .ok()
- });
-
- if let Some(cancel_handle) = cancel_handle {
- let write_future_rv = write_future.or_cancel(cancel_handle).await;
-
- if let Some(cancel_rid) = cancel_rid {
- state.borrow_mut().resource_table.close(cancel_rid).ok();
- };
-
- return write_future_rv??;
- }
-
- write_future.await?
-}
-
-fn write_file(
- path: &Path,
- open_options: std::fs::OpenOptions,
- _mode: Option<u32>,
- data: ZeroCopyBuf,
-) -> Result<(), AnyError> {
- let mut std_file = open_options.open(path).map_err(|err| {
- default_err_mapper(err, format!("open '{}'", path.display()))
- })?;
-
- // need to chmod the file if it already exists and a mode is specified
- #[cfg(unix)]
- if let Some(mode) = _mode {
- use std::os::unix::fs::PermissionsExt;
- let permissions = PermissionsExt::from_mode(mode & 0o777);
- std_file.set_permissions(permissions).map_err(|err| {
- default_err_mapper(err, format!("chmod '{}'", path.display()))
- })?;
- }
-
- std_file.write_all(&data)?;
- Ok(())
-}
-
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SeekArgs {
- rid: ResourceId,
- offset: i64,
- whence: i32,
-}
-
-fn seek_helper(args: SeekArgs) -> Result<(u32, SeekFrom), AnyError> {
- let rid = args.rid;
- let offset = args.offset;
- let whence = args.whence as u32;
- // Translate seek mode to Rust repr.
- 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}")));
- }
- };
-
- Ok((rid, seek_from))
-}
-
-#[op]
-fn op_seek_sync(state: &mut OpState, args: SeekArgs) -> Result<u64, AnyError> {
- let (rid, seek_from) = seek_helper(args)?;
- StdFileResource::with_file(state, rid, |std_file| {
- std_file.seek(seek_from).map_err(AnyError::from)
- })
-}
-
-#[op]
-async fn op_seek_async(
- state: Rc<RefCell<OpState>>,
- args: SeekArgs,
-) -> Result<u64, AnyError> {
- let (rid, seek_from) = seek_helper(args)?;
-
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- std_file.seek(seek_from).map_err(AnyError::from)
- })
- .await
-}
-
-#[op]
-fn op_fdatasync_sync(
- state: &mut OpState,
- rid: ResourceId,
-) -> Result<(), AnyError> {
- StdFileResource::with_file(state, rid, |std_file| {
- std_file.sync_data().map_err(AnyError::from)
- })
-}
-
-#[op]
-async fn op_fdatasync_async(
- state: Rc<RefCell<OpState>>,
- rid: ResourceId,
-) -> Result<(), AnyError> {
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- std_file.sync_data().map_err(AnyError::from)
- })
- .await
-}
-
-#[op]
-fn op_fsync_sync(state: &mut OpState, rid: ResourceId) -> Result<(), AnyError> {
- StdFileResource::with_file(state, rid, |std_file| {
- std_file.sync_all().map_err(AnyError::from)
- })
-}
-
-#[op]
-async fn op_fsync_async(
- state: Rc<RefCell<OpState>>,
- rid: ResourceId,
-) -> Result<(), AnyError> {
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- std_file.sync_all().map_err(AnyError::from)
- })
- .await
-}
-
-#[op]
-fn op_fstat_sync(
- state: &mut OpState,
- rid: ResourceId,
- out_buf: &mut [u32],
-) -> Result<(), AnyError> {
- let metadata = StdFileResource::with_file(state, rid, |std_file| {
- std_file.metadata().map_err(AnyError::from)
- })?;
- let stat = get_stat(metadata);
- stat.write(out_buf);
- Ok(())
-}
-
-#[op]
-async fn op_fstat_async(
- state: Rc<RefCell<OpState>>,
- rid: ResourceId,
-) -> Result<FsStat, AnyError> {
- let metadata =
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- std_file.metadata().map_err(AnyError::from)
- })
- .await?;
- Ok(get_stat(metadata))
-}
-
-#[op]
-fn op_flock_sync(
- state: &mut OpState,
- rid: ResourceId,
- exclusive: bool,
-) -> Result<(), AnyError> {
- use fs3::FileExt;
- check_unstable(state, "Deno.flockSync");
-
- StdFileResource::with_file(state, rid, |std_file| {
- if exclusive {
- std_file.lock_exclusive()?;
- } else {
- std_file.lock_shared()?;
- }
- Ok(())
- })
-}
-
-#[op]
-async fn op_flock_async(
- state: Rc<RefCell<OpState>>,
- rid: ResourceId,
- exclusive: bool,
-) -> Result<(), AnyError> {
- use fs3::FileExt;
- check_unstable2(&state, "Deno.flock");
-
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- if exclusive {
- std_file.lock_exclusive()?;
- } else {
- std_file.lock_shared()?;
- }
- Ok(())
- })
- .await
-}
-
-#[op]
-fn op_funlock_sync(
- state: &mut OpState,
- rid: ResourceId,
-) -> Result<(), AnyError> {
- use fs3::FileExt;
- check_unstable(state, "Deno.funlockSync");
-
- StdFileResource::with_file(state, rid, |std_file| {
- std_file.unlock()?;
- Ok(())
- })
-}
-
-#[op]
-async fn op_funlock_async(
- state: Rc<RefCell<OpState>>,
- rid: ResourceId,
-) -> Result<(), AnyError> {
- use fs3::FileExt;
- check_unstable2(&state, "Deno.funlock");
-
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- std_file.unlock()?;
- Ok(())
- })
- .await
-}
-
-#[op]
-fn op_umask(state: &mut OpState, mask: Option<u32>) -> Result<u32, AnyError> {
- check_unstable(state, "Deno.umask");
- // TODO implement umask for Windows
- // see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc
- // and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019
- #[cfg(not(unix))]
- {
- let _ = mask; // avoid unused warning.
- Err(not_supported())
- }
- #[cfg(unix)]
- {
- use nix::sys::stat::mode_t;
- use nix::sys::stat::umask;
- use nix::sys::stat::Mode;
- let r = if let Some(mask) = mask {
- // If mask provided, return previous.
- umask(Mode::from_bits_truncate(mask as mode_t))
- } else {
- // If no mask provided, we query the current. Requires two syscalls.
- let prev = umask(Mode::from_bits_truncate(0o777));
- let _ = umask(prev);
- prev
- };
- #[cfg(target_os = "linux")]
- {
- Ok(r.bits())
- }
- #[cfg(target_os = "macos")]
- {
- Ok(r.bits() as u32)
- }
- }
-}
-
-#[op]
-fn op_chdir<P>(state: &mut OpState, directory: &str) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let d = PathBuf::from(&directory);
- state.borrow_mut::<P>().check_read(&d, "Deno.chdir()")?;
- set_current_dir(&d)
- .map_err(|err| default_err_mapper(err, format!("chdir '{directory}'")))?;
- Ok(())
-}
-
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct MkdirArgs {
- path: String,
- recursive: bool,
- mode: Option<u32>,
-}
-
-#[op]
-fn op_mkdir_sync<P>(
- state: &mut OpState,
- args: MkdirArgs,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(&args.path).to_path_buf();
- let mode = args.mode.unwrap_or(0o777) & 0o777;
- state
- .borrow_mut::<P>()
- .check_write(&path, "Deno.mkdirSync()")?;
- debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive);
- let mut builder = std::fs::DirBuilder::new();
- builder.recursive(args.recursive);
- #[cfg(unix)]
- {
- use std::os::unix::fs::DirBuilderExt;
- builder.mode(mode);
- }
- builder.create(&path).map_err(|err| {
- default_err_mapper(err, format!("mkdir '{}'", path.display()))
- })?;
- Ok(())
-}
-
-#[op]
-async fn op_mkdir_async<P>(
- state: Rc<RefCell<OpState>>,
- args: MkdirArgs,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(&args.path).to_path_buf();
- let mode = args.mode.unwrap_or(0o777) & 0o777;
-
- {
- let mut state = state.borrow_mut();
- state.borrow_mut::<P>().check_write(&path, "Deno.mkdir()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive);
- let mut builder = std::fs::DirBuilder::new();
- builder.recursive(args.recursive);
- #[cfg(unix)]
- {
- use std::os::unix::fs::DirBuilderExt;
- builder.mode(mode);
- }
- builder.create(&path).map_err(|err| {
- default_err_mapper(err, format!("mkdir '{}'", path.display()))
- })?;
- Ok(())
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_chmod_sync<P>(
- state: &mut OpState,
- path: &str,
- mode: u32,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(path);
- let mode = mode & 0o777;
-
- state
- .borrow_mut::<P>()
- .check_write(path, "Deno.chmodSync()")?;
- raw_chmod(path, mode)
-}
-
-#[op]
-async fn op_chmod_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- mode: u32,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(&path).to_path_buf();
- let mode = mode & 0o777;
-
- {
- let mut state = state.borrow_mut();
- state.borrow_mut::<P>().check_write(&path, "Deno.chmod()")?;
- }
-
- tokio::task::spawn_blocking(move || raw_chmod(&path, mode))
- .await
- .unwrap()
-}
-
-fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> {
- let err_mapper =
- |err| default_err_mapper(err, format!("chmod '{}'", path.display()));
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt;
- let permissions = PermissionsExt::from_mode(_raw_mode);
- std::fs::set_permissions(path, permissions).map_err(err_mapper)?;
- Ok(())
- }
- // TODO Implement chmod for Windows (#4357)
- #[cfg(not(unix))]
- {
- // Still check file/dir exists on Windows
- let _metadata = std::fs::metadata(path).map_err(err_mapper)?;
- Err(not_supported())
- }
-}
-
-#[op]
-fn op_chown_sync<P>(
- state: &mut OpState,
- path: &str,
- #[cfg_attr(windows, allow(unused_variables))] uid: Option<u32>,
- #[cfg_attr(windows, allow(unused_variables))] gid: Option<u32>,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(path).to_path_buf();
- state
- .borrow_mut::<P>()
- .check_write(&path, "Deno.chownSync()")?;
- #[cfg(unix)]
- {
- use nix::unistd::chown;
- use nix::unistd::Gid;
- use nix::unistd::Uid;
- let nix_uid = uid.map(Uid::from_raw);
- let nix_gid = gid.map(Gid::from_raw);
- chown(&path, nix_uid, nix_gid).map_err(|err| {
- custom_error(
- get_nix_error_class(&err),
- format!("{}, chown '{}'", err.desc(), path.display()),
- )
- })?;
- Ok(())
- }
- // TODO Implement chown for Windows
- #[cfg(not(unix))]
- {
- Err(generic_error("Not implemented"))
- }
-}
-
-#[op]
-async fn op_chown_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- #[cfg_attr(windows, allow(unused_variables))] uid: Option<u32>,
- #[cfg_attr(windows, allow(unused_variables))] gid: Option<u32>,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(&path).to_path_buf();
-
- {
- let mut state = state.borrow_mut();
- state.borrow_mut::<P>().check_write(&path, "Deno.chown()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- #[cfg(unix)]
- {
- use nix::unistd::chown;
- use nix::unistd::Gid;
- use nix::unistd::Uid;
- let nix_uid = uid.map(Uid::from_raw);
- let nix_gid = gid.map(Gid::from_raw);
- chown(&path, nix_uid, nix_gid).map_err(|err| {
- custom_error(
- get_nix_error_class(&err),
- format!("{}, chown '{}'", err.desc(), path.display()),
- )
- })?;
- Ok(())
- }
- // TODO Implement chown for Windows
- #[cfg(not(unix))]
- Err(not_supported())
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_remove_sync<P>(
- state: &mut OpState,
- path: &str,
- recursive: bool,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(path);
-
- state
- .borrow_mut::<P>()
- .check_write(&path, "Deno.removeSync()")?;
-
- #[cfg(not(unix))]
- use std::os::windows::prelude::MetadataExt;
-
- let err_mapper =
- |err| default_err_mapper(err, format!("remove '{}'", path.display()));
- let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?;
-
- let file_type = metadata.file_type();
- if file_type.is_file() {
- std::fs::remove_file(&path).map_err(err_mapper)?;
- } else if recursive {
- std::fs::remove_dir_all(&path).map_err(err_mapper)?;
- } else if file_type.is_symlink() {
- #[cfg(unix)]
- std::fs::remove_file(&path).map_err(err_mapper)?;
- #[cfg(not(unix))]
- {
- use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
- if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
- std::fs::remove_dir(&path).map_err(err_mapper)?;
- } else {
- std::fs::remove_file(&path).map_err(err_mapper)?;
- }
- }
- } else if file_type.is_dir() {
- std::fs::remove_dir(&path).map_err(err_mapper)?;
- } else {
- // pipes, sockets, etc...
- std::fs::remove_file(&path).map_err(err_mapper)?;
- }
- Ok(())
-}
-
-#[op]
-async fn op_remove_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- recursive: bool,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
-
- {
- let mut state = state.borrow_mut();
- state
- .borrow_mut::<P>()
- .check_write(&path, "Deno.remove()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- #[cfg(not(unix))]
- use std::os::windows::prelude::MetadataExt;
- let err_mapper =
- |err| default_err_mapper(err, format!("remove '{}'", path.display()));
- let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?;
-
- debug!("op_remove_async {} {}", path.display(), recursive);
- let file_type = metadata.file_type();
- if file_type.is_file() {
- std::fs::remove_file(&path).map_err(err_mapper)?;
- } else if recursive {
- std::fs::remove_dir_all(&path).map_err(err_mapper)?;
- } else if file_type.is_symlink() {
- #[cfg(unix)]
- std::fs::remove_file(&path).map_err(err_mapper)?;
- #[cfg(not(unix))]
- {
- use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
- if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
- std::fs::remove_dir(&path).map_err(err_mapper)?;
- } else {
- std::fs::remove_file(&path).map_err(err_mapper)?;
- }
- }
- } else if file_type.is_dir() {
- std::fs::remove_dir(&path).map_err(err_mapper)?;
- } else {
- // pipes, sockets, etc...
- std::fs::remove_file(&path).map_err(err_mapper)?;
- }
- Ok(())
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_copy_file_sync<P>(
- state: &mut OpState,
- from: &str,
- to: &str,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let from_path = PathBuf::from(from);
- let to_path = PathBuf::from(to);
-
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&from_path, "Deno.copyFileSync()")?;
- permissions.check_write(&to_path, "Deno.copyFileSync()")?;
-
- // On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
- // See https://github.com/rust-lang/rust/issues/54800
- // Once the issue is resolved, we should remove this workaround.
- if cfg!(unix) && !from_path.is_file() {
- return Err(custom_error(
- "NotFound",
- format!(
- "File not found, copy '{}' -> '{}'",
- from_path.display(),
- to_path.display()
- ),
- ));
- }
-
- let err_mapper = |err| {
- default_err_mapper(
- err,
- format!("copy '{}' -> '{}'", from_path.display(), to_path.display()),
- )
- };
-
- #[cfg(target_os = "macos")]
- {
- use libc::clonefile;
- use libc::stat;
- use libc::unlink;
- use std::ffi::CString;
- use std::io::Read;
- use std::os::unix::fs::OpenOptionsExt;
- use std::os::unix::fs::PermissionsExt;
-
- let from = CString::new(from).unwrap();
- let to = CString::new(to).unwrap();
-
- // SAFETY: `from` and `to` are valid C strings.
- // std::fs::copy does open() + fcopyfile() on macOS. We try to use
- // clonefile() instead, which is more efficient.
- unsafe {
- let mut st = std::mem::zeroed();
- let ret = stat(from.as_ptr(), &mut st);
- if ret != 0 {
- return Err(err_mapper(Error::last_os_error()));
- }
-
- if st.st_size > 128 * 1024 {
- // Try unlink. If it fails, we are going to try clonefile() anyway.
- let _ = unlink(to.as_ptr());
- // Matches rust stdlib behavior for io::copy.
- // https://github.com/rust-lang/rust/blob/3fdd578d72a24d4efc2fe2ad18eec3b6ba72271e/library/std/src/sys/unix/fs.rs#L1613-L1616
- if clonefile(from.as_ptr(), to.as_ptr(), 0) == 0 {
- return Ok(());
- }
- } else {
- // Do a regular copy. fcopyfile() is an overkill for < 128KB
- // files.
- let mut buf = [0u8; 128 * 1024];
- let mut from_file =
- std::fs::File::open(&from_path).map_err(err_mapper)?;
- let perm = from_file.metadata().map_err(err_mapper)?.permissions();
-
- let mut to_file = std::fs::OpenOptions::new()
- // create the file with the correct mode right away
- .mode(perm.mode())
- .write(true)
- .create(true)
- .truncate(true)
- .open(&to_path)
- .map_err(err_mapper)?;
- let writer_metadata = to_file.metadata()?;
- if writer_metadata.is_file() {
- // Set the correct file permissions, in case the file already existed.
- // Don't set the permissions on already existing non-files like
- // pipes/FIFOs or device nodes.
- to_file.set_permissions(perm)?;
- }
- loop {
- let nread = from_file.read(&mut buf).map_err(err_mapper)?;
- if nread == 0 {
- break;
- }
- to_file.write_all(&buf[..nread]).map_err(err_mapper)?;
- }
- return Ok(());
- }
- }
-
- // clonefile() failed, fall back to std::fs::copy().
- }
-
- // returns size of from as u64 (we ignore)
- std::fs::copy(&from_path, &to_path).map_err(err_mapper)?;
- Ok(())
-}
-
-#[op]
-async fn op_copy_file_async<P>(
- state: Rc<RefCell<OpState>>,
- from: String,
- to: String,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let from = PathBuf::from(&from);
- let to = PathBuf::from(&to);
-
- {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&from, "Deno.copyFile()")?;
- permissions.check_write(&to, "Deno.copyFile()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- // On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
- // See https://github.com/rust-lang/rust/issues/54800
- // Once the issue is resolved, we should remove this workaround.
- if cfg!(unix) && !from.is_file() {
- return Err(custom_error(
- "NotFound",
- format!(
- "File not found, copy '{}' -> '{}'",
- from.display(),
- to.display()
- ),
- ));
- }
- // returns size of from as u64 (we ignore)
- std::fs::copy(&from, &to).map_err(|err| {
- default_err_mapper(
- err,
- format!("copy '{}' -> '{}'", from.display(), to.display()),
- )
- })?;
- Ok(())
- })
- .await
- .unwrap()
-}
-
-fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> (u64, bool) {
- match maybe_time {
- Ok(time) => (
- time
- .duration_since(UNIX_EPOCH)
- .map(|t| t.as_millis() as u64)
- .unwrap_or_else(|err| err.duration().as_millis() as u64),
- true,
- ),
- Err(_) => (0, false),
- }
-}
-
-macro_rules! create_struct_writer {
- (pub struct $name:ident { $($field:ident: $type:ty),* $(,)? }) => {
- impl $name {
- fn write(self, buf: &mut [u32]) {
- let mut offset = 0;
- $(
- let value = self.$field as u64;
- buf[offset] = value as u32;
- buf[offset + 1] = (value >> 32) as u32;
- #[allow(unused_assignments)]
- {
- offset += 2;
- }
- )*
- }
- }
-
- #[derive(Serialize)]
- #[serde(rename_all = "camelCase")]
- struct $name {
- $($field: $type),*
- }
- };
-}
-
-create_struct_writer! {
- pub struct FsStat {
- is_file: bool,
- is_directory: bool,
- is_symlink: bool,
- size: u64,
- // In milliseconds, like JavaScript. Available on both Unix or Windows.
- mtime_set: bool,
- mtime: u64,
- atime_set: bool,
- atime: u64,
- birthtime_set: bool,
- birthtime: u64,
- // Following are only valid under Unix.
- dev: u64,
- ino: u64,
- mode: u32,
- nlink: u64,
- uid: u32,
- gid: u32,
- rdev: u64,
- blksize: u64,
- blocks: u64,
- }
-}
-
-#[inline(always)]
-fn get_stat(metadata: std::fs::Metadata) -> FsStat {
- // Unix stat member (number types only). 0 if not on unix.
- macro_rules! usm {
- ($member:ident) => {{
- #[cfg(unix)]
- {
- metadata.$member()
- }
- #[cfg(not(unix))]
- {
- 0
- }
- }};
- }
-
- #[cfg(unix)]
- use std::os::unix::fs::MetadataExt;
- let (mtime, mtime_set) = to_msec(metadata.modified());
- let (atime, atime_set) = to_msec(metadata.accessed());
- let (birthtime, birthtime_set) = to_msec(metadata.created());
-
- FsStat {
- is_file: metadata.is_file(),
- is_directory: metadata.is_dir(),
- is_symlink: metadata.file_type().is_symlink(),
- size: metadata.len(),
- // In milliseconds, like JavaScript. Available on both Unix or Windows.
- mtime_set,
- mtime,
- atime_set,
- atime,
- birthtime_set,
- birthtime,
- // Following are only valid under Unix.
- dev: usm!(dev),
- ino: usm!(ino),
- mode: usm!(mode),
- nlink: usm!(nlink),
- uid: usm!(uid),
- gid: usm!(gid),
- rdev: usm!(rdev),
- blksize: usm!(blksize),
- blocks: usm!(blocks),
- }
-}
-
-#[cfg(windows)]
-#[inline(always)]
-fn get_stat2(metadata: std::fs::Metadata, dev: u64) -> FsStat {
- let (mtime, mtime_set) = to_msec(metadata.modified());
- let (atime, atime_set) = to_msec(metadata.accessed());
- let (birthtime, birthtime_set) = to_msec(metadata.created());
-
- FsStat {
- is_file: metadata.is_file(),
- is_directory: metadata.is_dir(),
- is_symlink: metadata.file_type().is_symlink(),
- size: metadata.len(),
- mtime_set,
- mtime,
- atime_set,
- atime,
- birthtime_set,
- birthtime,
- dev,
- ino: 0,
- mode: 0,
- nlink: 0,
- uid: 0,
- gid: 0,
- rdev: 0,
- blksize: 0,
- blocks: 0,
- }
-}
-
-#[cfg(not(windows))]
-#[inline(always)]
-fn get_stat2(metadata: std::fs::Metadata) -> FsStat {
- #[cfg(unix)]
- use std::os::unix::fs::MetadataExt;
- let (mtime, mtime_set) = to_msec(metadata.modified());
- let (atime, atime_set) = to_msec(metadata.accessed());
- let (birthtime, birthtime_set) = to_msec(metadata.created());
-
- FsStat {
- is_file: metadata.is_file(),
- is_directory: metadata.is_dir(),
- is_symlink: metadata.file_type().is_symlink(),
- size: metadata.len(),
- mtime_set,
- mtime,
- atime_set,
- atime,
- birthtime_set,
- birthtime,
- dev: metadata.dev(),
- ino: metadata.ino(),
- mode: metadata.mode(),
- nlink: metadata.nlink(),
- uid: metadata.uid(),
- gid: metadata.gid(),
- rdev: metadata.rdev(),
- blksize: metadata.blksize(),
- blocks: metadata.blocks(),
- }
-}
-
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct StatArgs {
- path: String,
- lstat: bool,
-}
-
-#[cfg(not(windows))]
-fn do_stat(path: PathBuf, lstat: bool) -> Result<FsStat, AnyError> {
- let err_mapper =
- |err| default_err_mapper(err, format!("stat '{}'", path.display()));
- let metadata = if lstat {
- std::fs::symlink_metadata(&path).map_err(err_mapper)?
- } else {
- std::fs::metadata(&path).map_err(err_mapper)?
- };
-
- Ok(get_stat2(metadata))
-}
-
-#[cfg(windows)]
-fn do_stat(path: PathBuf, lstat: bool) -> Result<FsStat, AnyError> {
- use std::os::windows::prelude::OsStrExt;
-
- use winapi::um::fileapi::CreateFileW;
- use winapi::um::fileapi::OPEN_EXISTING;
- use winapi::um::handleapi::CloseHandle;
- use winapi::um::handleapi::INVALID_HANDLE_VALUE;
- use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
- use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
- use winapi::um::winnt::FILE_SHARE_DELETE;
- use winapi::um::winnt::FILE_SHARE_READ;
- use winapi::um::winnt::FILE_SHARE_WRITE;
-
- let err_mapper =
- |err| default_err_mapper(err, format!("stat '{}'", path.display()));
- let metadata = if lstat {
- std::fs::symlink_metadata(&path).map_err(err_mapper)?
- } else {
- std::fs::metadata(&path).map_err(err_mapper)?
- };
-
- let (p, file_flags) = if lstat {
- (
- path,
- FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
- )
- } else {
- (path.canonicalize()?, FILE_FLAG_BACKUP_SEMANTICS)
- };
- // SAFETY: winapi calls
- unsafe {
- let mut path: Vec<_> = p.as_os_str().encode_wide().collect();
- path.push(0);
- let file_handle = CreateFileW(
- path.as_ptr(),
- 0,
- FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
- std::ptr::null_mut(),
- OPEN_EXISTING,
- file_flags,
- std::ptr::null_mut(),
- );
- if file_handle == INVALID_HANDLE_VALUE {
- return Err(std::io::Error::last_os_error().into());
- }
-
- let result = get_dev(file_handle);
- CloseHandle(file_handle);
- let dev = result?;
-
- Ok(get_stat2(metadata, dev))
- }
-}
-
-#[cfg(windows)]
-use winapi::um::fileapi::GetFileInformationByHandle;
-#[cfg(windows)]
-use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION;
-
-#[cfg(windows)]
-unsafe fn get_dev(
- handle: winapi::shared::ntdef::HANDLE,
-) -> std::io::Result<u64> {
- use winapi::shared::minwindef::FALSE;
-
- let info = {
- let mut info =
- std::mem::MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::zeroed();
- if GetFileInformationByHandle(handle, info.as_mut_ptr()) == FALSE {
- return Err(std::io::Error::last_os_error());
- }
-
- info.assume_init()
- };
-
- Ok(info.dwVolumeSerialNumber as u64)
-}
-
-#[op]
-fn op_stat_sync<P>(
- state: &mut OpState,
- path: &str,
- lstat: bool,
- out_buf: &mut [u32],
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(path);
- state
- .borrow_mut::<P>()
- .check_read(&path, "Deno.statSync()")?;
-
- let stat = do_stat(path, lstat)?;
- stat.write(out_buf);
-
- Ok(())
-}
-
-#[op]
-async fn op_stat_async<P>(
- state: Rc<RefCell<OpState>>,
- args: StatArgs,
-) -> Result<FsStat, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&args.path);
- let lstat = args.lstat;
-
- {
- let mut state = state.borrow_mut();
- state.borrow_mut::<P>().check_read(&path, "Deno.stat()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- debug!("op_stat_async {} {}", path.display(), lstat);
- do_stat(path, lstat)
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_realpath_sync<P>(
- state: &mut OpState,
- path: String,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
-
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.realPathSync()")?;
- if path.is_relative() {
- permissions.check_read_blind(
- &current_dir()?,
- "CWD",
- "Deno.realPathSync()",
- )?;
- }
-
- debug!("op_realpath_sync {}", path.display());
- // corresponds to the realpath on Unix and
- // CreateFile and GetFinalPathNameByHandle on Windows
- let realpath = canonicalize_path(&path).map_err(|error| {
- default_err_mapper(error, format!("op_realpath_sync '{}'", path.display()))
- })?;
- let realpath_str = into_string(realpath.into_os_string())?;
- Ok(realpath_str)
-}
-
-#[op]
-async fn op_realpath_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
-
- {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&path, "Deno.realPath()")?;
- if path.is_relative() {
- permissions.check_read_blind(
- &current_dir()?,
- "CWD",
- "Deno.realPath()",
- )?;
- }
- }
-
- tokio::task::spawn_blocking(move || {
- debug!("op_realpath_async {}", path.display());
- // corresponds to the realpath on Unix and
- // CreateFile and GetFinalPathNameByHandle on Windows
- let realpath = canonicalize_path(&path).map_err(|error| {
- default_err_mapper(
- error,
- format!("op_realpath_async '{}'", path.display()),
- )
- })?;
- let realpath_str = into_string(realpath.into_os_string())?;
- Ok(realpath_str)
- })
- .await
- .unwrap()
-}
-
-#[derive(Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct DirEntry {
- name: String,
- is_file: bool,
- is_directory: bool,
- is_symlink: bool,
-}
-
-#[op]
-fn op_read_dir_sync<P>(
- state: &mut OpState,
- path: String,
-) -> Result<Vec<DirEntry>, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
-
- state
- .borrow_mut::<P>()
- .check_read(&path, "Deno.readDirSync()")?;
-
- debug!("op_read_dir_sync {}", path.display());
-
- let entries: Vec<_> = std::fs::read_dir(&path)
- .map_err(|err| {
- default_err_mapper(err, format!("readdir '{}'", path.display()))
- })?
- .filter_map(|entry| {
- let entry = entry.unwrap();
- // Not all filenames can be encoded as UTF-8. Skip those for now.
- if let Ok(name) = into_string(entry.file_name()) {
- Some(DirEntry {
- name,
- is_file: entry
- .file_type()
- .map(|file_type| file_type.is_file())
- .unwrap_or(false),
- is_directory: entry
- .file_type()
- .map(|file_type| file_type.is_dir())
- .unwrap_or(false),
- is_symlink: entry
- .file_type()
- .map(|file_type| file_type.is_symlink())
- .unwrap_or(false),
- })
- } else {
- None
- }
- })
- .collect();
-
- Ok(entries)
-}
-
-#[op]
-async fn op_read_dir_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
-) -> Result<Vec<DirEntry>, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
- {
- let mut state = state.borrow_mut();
- state
- .borrow_mut::<P>()
- .check_read(&path, "Deno.readDir()")?;
- }
- tokio::task::spawn_blocking(move || {
- debug!("op_read_dir_async {}", path.display());
-
- let entries: Vec<_> = std::fs::read_dir(&path)
- .map_err(|err| {
- default_err_mapper(err, format!("readdir '{}'", path.display()))
- })?
- .filter_map(|entry| {
- let entry = entry.unwrap();
- // Not all filenames can be encoded as UTF-8. Skip those for now.
- if let Ok(name) = into_string(entry.file_name()) {
- Some(DirEntry {
- name,
- is_file: entry
- .file_type()
- .map(|file_type| file_type.is_file())
- .unwrap_or(false),
- is_directory: entry
- .file_type()
- .map(|file_type| file_type.is_dir())
- .unwrap_or(false),
- is_symlink: entry
- .file_type()
- .map(|file_type| file_type.is_symlink())
- .unwrap_or(false),
- })
- } else {
- None
- }
- })
- .collect();
-
- Ok(entries)
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_rename_sync<P>(
- state: &mut OpState,
- oldpath: &str,
- newpath: &str,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let oldpath = PathBuf::from(oldpath);
- let newpath = PathBuf::from(newpath);
-
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&oldpath, "Deno.renameSync()")?;
- permissions.check_write(&oldpath, "Deno.renameSync()")?;
- permissions.check_write(&newpath, "Deno.renameSync()")?;
-
- std::fs::rename(&oldpath, &newpath).map_err(|err| {
- default_err_mapper(
- err,
- format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()),
- )
- })?;
- Ok(())
-}
-
-#[op]
-async fn op_rename_async<P>(
- state: Rc<RefCell<OpState>>,
- oldpath: String,
- newpath: String,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let oldpath = PathBuf::from(&oldpath);
- let newpath = PathBuf::from(&newpath);
- {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&oldpath, "Deno.rename()")?;
- permissions.check_write(&oldpath, "Deno.rename()")?;
- permissions.check_write(&newpath, "Deno.rename()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- std::fs::rename(&oldpath, &newpath).map_err(|err| {
- default_err_mapper(
- err,
- format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()),
- )
- })?;
- Ok(())
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_link_sync<P>(
- state: &mut OpState,
- oldpath: &str,
- newpath: &str,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let oldpath = PathBuf::from(oldpath);
- let newpath = PathBuf::from(newpath);
-
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&oldpath, "Deno.linkSync()")?;
- permissions.check_write(&oldpath, "Deno.linkSync()")?;
- permissions.check_read(&newpath, "Deno.linkSync()")?;
- permissions.check_write(&newpath, "Deno.linkSync()")?;
-
- std::fs::hard_link(&oldpath, &newpath).map_err(|err| {
- default_err_mapper(
- err,
- format!("link '{}' -> '{}'", oldpath.display(), newpath.display()),
- )
- })?;
- Ok(())
-}
-
-#[op]
-async fn op_link_async<P>(
- state: Rc<RefCell<OpState>>,
- oldpath: String,
- newpath: String,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let oldpath = PathBuf::from(&oldpath);
- let newpath = PathBuf::from(&newpath);
-
- {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<P>();
- permissions.check_read(&oldpath, "Deno.link()")?;
- permissions.check_write(&oldpath, "Deno.link()")?;
- permissions.check_read(&newpath, "Deno.link()")?;
- permissions.check_write(&newpath, "Deno.link()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- let err_mapper = |err| {
- default_err_mapper(
- err,
- format!("link '{}' -> '{}'", oldpath.display(), newpath.display()),
- )
- };
- std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?;
- Ok(())
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_symlink_sync<P>(
- state: &mut OpState,
- oldpath: &str,
- newpath: &str,
- _type: Option<String>,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let oldpath = PathBuf::from(oldpath);
- let newpath = PathBuf::from(newpath);
-
- state
- .borrow_mut::<P>()
- .check_write_all("Deno.symlinkSync()")?;
- state
- .borrow_mut::<P>()
- .check_read_all("Deno.symlinkSync()")?;
-
- let err_mapper = |err| {
- default_err_mapper(
- err,
- format!("symlink '{}' -> '{}'", oldpath.display(), newpath.display()),
- )
- };
-
- #[cfg(unix)]
- {
- use std::os::unix::fs::symlink;
- symlink(&oldpath, &newpath).map_err(err_mapper)?;
- Ok(())
- }
- #[cfg(not(unix))]
- {
- use std::os::windows::fs::symlink_dir;
- use std::os::windows::fs::symlink_file;
-
- match _type {
- Some(ty) => match ty.as_ref() {
- "file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
- "dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
- _ => return Err(type_error("unsupported type")),
- },
- None => {
- let old_meta = std::fs::metadata(&oldpath);
- match old_meta {
- Ok(metadata) => {
- if metadata.is_file() {
- symlink_file(&oldpath, &newpath).map_err(err_mapper)?
- } else if metadata.is_dir() {
- symlink_dir(&oldpath, &newpath).map_err(err_mapper)?
- }
- }
- Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())),
- }
- }
- };
- Ok(())
- }
-}
-
-#[op]
-async fn op_symlink_async<P>(
- state: Rc<RefCell<OpState>>,
- oldpath: String,
- newpath: String,
- _type: Option<String>,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let oldpath = PathBuf::from(&oldpath);
- let newpath = PathBuf::from(&newpath);
-
- {
- let mut state = state.borrow_mut();
- state.borrow_mut::<P>().check_write_all("Deno.symlink()")?;
- state.borrow_mut::<P>().check_read_all("Deno.symlink()")?;
- }
-
- tokio::task::spawn_blocking(move || {
- let err_mapper = |err| default_err_mapper(err, format!(
- "symlink '{}' -> '{}'",
- oldpath.display(),
- newpath.display()
- ));
-
- #[cfg(unix)]
- {
- use std::os::unix::fs::symlink;
- symlink(&oldpath, &newpath).map_err(err_mapper)?;
- Ok(())
- }
- #[cfg(not(unix))]
- {
- use std::os::windows::fs::{symlink_dir, symlink_file};
-
- match _type {
- Some(ty) => match ty.as_ref() {
- "file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
- "dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
- _ => return Err(type_error("unsupported type")),
- },
- None => {
- let old_meta = std::fs::metadata(&oldpath);
- match old_meta {
- Ok(metadata) => {
- if metadata.is_file() {
- symlink_file(&oldpath, &newpath).map_err(err_mapper)?
- } else if metadata.is_dir() {
- symlink_dir(&oldpath, &newpath).map_err(err_mapper)?
- }
- }
- Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())),
- }
- }
- };
- Ok(())
- }
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_read_link_sync<P>(
- state: &mut OpState,
- path: String,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
-
- state
- .borrow_mut::<P>()
- .check_read(&path, "Deno.readLink()")?;
-
- debug!("op_read_link_value {}", path.display());
- let target = std::fs::read_link(&path)
- .map_err(|err| {
- default_err_mapper(err, format!("readlink '{}'", path.display()))
- })?
- .into_os_string();
- let targetstr = into_string(target)?;
- Ok(targetstr)
-}
-
-#[op]
-async fn op_read_link_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
- {
- let mut state = state.borrow_mut();
- state
- .borrow_mut::<P>()
- .check_read(&path, "Deno.readLink()")?;
- }
- tokio::task::spawn_blocking(move || {
- debug!("op_read_link_async {}", path.display());
- let target = std::fs::read_link(&path)
- .map_err(|err| {
- default_err_mapper(err, format!("readlink '{}'", path.display()))
- })?
- .into_os_string();
- let targetstr = into_string(target)?;
- Ok(targetstr)
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_ftruncate_sync(
- state: &mut OpState,
- rid: u32,
- len: i32,
-) -> Result<(), AnyError> {
- let len = len as u64;
- StdFileResource::with_file(state, rid, |std_file| {
- std_file.set_len(len).map_err(AnyError::from)
- })?;
- Ok(())
-}
-
-#[op]
-async fn op_ftruncate_async(
- state: Rc<RefCell<OpState>>,
- rid: ResourceId,
- len: i32,
-) -> Result<(), AnyError> {
- let len = len as u64;
-
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- std_file.set_len(len)?;
- Ok(())
- })
- .await
-}
-
-#[op]
-fn op_truncate_sync<P>(
- state: &mut OpState,
- path: &str,
- len: u64,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(path);
-
- state
- .borrow_mut::<P>()
- .check_write(&path, "Deno.truncateSync()")?;
-
- debug!("op_truncate_sync {} {}", path.display(), len);
- let err_mapper =
- |err| default_err_mapper(err, format!("truncate '{}'", path.display()));
- let f = std::fs::OpenOptions::new()
- .write(true)
- .open(&path)
- .map_err(err_mapper)?;
- f.set_len(len).map_err(err_mapper)?;
- Ok(())
-}
-
-#[op]
-async fn op_truncate_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- len: u64,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
-
- {
- let mut state = state.borrow_mut();
- state
- .borrow_mut::<P>()
- .check_write(&path, "Deno.truncate()")?;
- }
- tokio::task::spawn_blocking(move || {
- debug!("op_truncate_async {} {}", path.display(), len);
- let err_mapper =
- |err| default_err_mapper(err, format!("truncate '{}'", path.display()));
- let f = std::fs::OpenOptions::new()
- .write(true)
- .open(&path)
- .map_err(err_mapper)?;
- f.set_len(len).map_err(err_mapper)?;
- Ok(())
- })
- .await
- .unwrap()
-}
-
-fn make_temp(
- dir: Option<&Path>,
- prefix: Option<&str>,
- suffix: Option<&str>,
- is_dir: bool,
-) -> std::io::Result<PathBuf> {
- let prefix_ = prefix.unwrap_or("");
- let suffix_ = suffix.unwrap_or("");
- let mut buf: PathBuf = match dir {
- Some(p) => p.to_path_buf(),
- None => temp_dir(),
- }
- .join("_");
- let mut rng = thread_rng();
- const MAX_TRIES: u32 = 10;
- for _ in 0..MAX_TRIES {
- let unique = rng.gen::<u32>();
- buf.set_file_name(format!("{prefix_}{unique:08x}{suffix_}"));
- let r = if is_dir {
- #[allow(unused_mut)]
- let mut builder = std::fs::DirBuilder::new();
- #[cfg(unix)]
- {
- use std::os::unix::fs::DirBuilderExt;
- builder.mode(0o700);
- }
- builder.create(buf.as_path())
- } else {
- let mut open_options = std::fs::OpenOptions::new();
- open_options.write(true).create_new(true);
- #[cfg(unix)]
- {
- use std::os::unix::fs::OpenOptionsExt;
- open_options.mode(0o600);
- }
- open_options.open(buf.as_path()).map(drop)
- };
- match r {
- Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
- Ok(_) => return Ok(buf),
- Err(e) => return Err(e),
- }
- }
- Err(io::Error::new(
- io::ErrorKind::AlreadyExists,
- "too many temp files exist",
- ))
-}
-
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct MakeTempArgs {
- dir: Option<String>,
- prefix: Option<String>,
- suffix: Option<String>,
-}
-
-#[op]
-fn op_make_temp_dir_sync<P>(
- state: &mut OpState,
- args: MakeTempArgs,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let dir = args.dir.map(|s| PathBuf::from(&s));
- let prefix = args.prefix.map(String::from);
- let suffix = args.suffix.map(String::from);
-
- state.borrow_mut::<P>().check_write(
- dir.clone().unwrap_or_else(temp_dir).as_path(),
- "Deno.makeTempDirSync()",
- )?;
-
- // TODO(piscisaureus): use byte vector for paths, not a string.
- // See https://github.com/denoland/deno/issues/627.
- // We can't assume that paths are always valid utf8 strings.
- let path = make_temp(
- // Converting Option<String> to Option<&str>
- dir.as_deref(),
- prefix.as_deref(),
- suffix.as_deref(),
- true,
- )?;
- let path_str = into_string(path.into_os_string())?;
-
- Ok(path_str)
-}
-
-#[op]
-async fn op_make_temp_dir_async<P>(
- state: Rc<RefCell<OpState>>,
- args: MakeTempArgs,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let dir = args.dir.map(|s| PathBuf::from(&s));
- let prefix = args.prefix.map(String::from);
- let suffix = args.suffix.map(String::from);
- {
- let mut state = state.borrow_mut();
- state.borrow_mut::<P>().check_write(
- dir.clone().unwrap_or_else(temp_dir).as_path(),
- "Deno.makeTempDir()",
- )?;
- }
- tokio::task::spawn_blocking(move || {
- // TODO(piscisaureus): use byte vector for paths, not a string.
- // See https://github.com/denoland/deno/issues/627.
- // We can't assume that paths are always valid utf8 strings.
- let path = make_temp(
- // Converting Option<String> to Option<&str>
- dir.as_deref(),
- prefix.as_deref(),
- suffix.as_deref(),
- true,
- )?;
- let path_str = into_string(path.into_os_string())?;
-
- Ok(path_str)
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_make_temp_file_sync<P>(
- state: &mut OpState,
- args: MakeTempArgs,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let dir = args.dir.map(|s| PathBuf::from(&s));
- let prefix = args.prefix.map(String::from);
- let suffix = args.suffix.map(String::from);
-
- state.borrow_mut::<P>().check_write(
- dir.clone().unwrap_or_else(temp_dir).as_path(),
- "Deno.makeTempFileSync()",
- )?;
-
- // TODO(piscisaureus): use byte vector for paths, not a string.
- // See https://github.com/denoland/deno/issues/627.
- // We can't assume that paths are always valid utf8 strings.
- let path = make_temp(
- // Converting Option<String> to Option<&str>
- dir.as_deref(),
- prefix.as_deref(),
- suffix.as_deref(),
- false,
- )?;
- let path_str = into_string(path.into_os_string())?;
-
- Ok(path_str)
-}
-
-#[op]
-async fn op_make_temp_file_async<P>(
- state: Rc<RefCell<OpState>>,
- args: MakeTempArgs,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let dir = args.dir.map(|s| PathBuf::from(&s));
- let prefix = args.prefix.map(String::from);
- let suffix = args.suffix.map(String::from);
- {
- let mut state = state.borrow_mut();
- state.borrow_mut::<P>().check_write(
- dir.clone().unwrap_or_else(temp_dir).as_path(),
- "Deno.makeTempFile()",
- )?;
- }
- tokio::task::spawn_blocking(move || {
- // TODO(piscisaureus): use byte vector for paths, not a string.
- // See https://github.com/denoland/deno/issues/627.
- // We can't assume that paths are always valid utf8 strings.
- let path = make_temp(
- // Converting Option<String> to Option<&str>
- dir.as_deref(),
- prefix.as_deref(),
- suffix.as_deref(),
- false,
- )?;
- let path_str = into_string(path.into_os_string())?;
-
- Ok(path_str)
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_futime_sync(
- state: &mut OpState,
- rid: ResourceId,
- atime_secs: i64,
- atime_nanos: u32,
- mtime_secs: i64,
- mtime_nanos: u32,
-) -> Result<(), AnyError> {
- let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
- let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
-
- StdFileResource::with_file(state, rid, |std_file| {
- filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))
- .map_err(AnyError::from)
- })?;
-
- Ok(())
-}
-
-#[op]
-async fn op_futime_async(
- state: Rc<RefCell<OpState>>,
- rid: ResourceId,
- atime_secs: i64,
- atime_nanos: u32,
- mtime_secs: i64,
- mtime_nanos: u32,
-) -> Result<(), AnyError> {
- let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
- let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
-
- StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
- filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))?;
- Ok(())
- })
- .await
-}
-
-#[op]
-fn op_utime_sync<P>(
- state: &mut OpState,
- path: &str,
- atime_secs: i64,
- atime_nanos: u32,
- mtime_secs: i64,
- mtime_nanos: u32,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(path);
- let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
- let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
-
- state.borrow_mut::<P>().check_write(&path, "Deno.utime()")?;
- filetime::set_file_times(&path, atime, mtime).map_err(|err| {
- default_err_mapper(err, format!("utime '{}'", path.display()))
- })?;
- Ok(())
-}
-
-#[op]
-async fn op_utime_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- atime_secs: i64,
- atime_nanos: u32,
- mtime_secs: i64,
- mtime_nanos: u32,
-) -> Result<(), AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = PathBuf::from(&path);
- let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
- let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
-
- state
- .borrow_mut()
- .borrow_mut::<P>()
- .check_write(&path, "Deno.utime()")?;
-
- tokio::task::spawn_blocking(move || {
- filetime::set_file_times(&path, atime, mtime).map_err(|err| {
- default_err_mapper(err, format!("utime '{}'", path.display()))
- })?;
- Ok(())
- })
- .await
- .unwrap()
-}
-
-#[op]
-fn op_cwd<P>(state: &mut OpState) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = current_dir()?;
- state
- .borrow_mut::<P>()
- .check_read_blind(&path, "CWD", "Deno.cwd()")?;
- let path_str = into_string(path.into_os_string())?;
- Ok(path_str)
-}
-
-#[op]
-fn op_readfile_sync<P>(
- state: &mut OpState,
- path: String,
-) -> Result<ZeroCopyBuf, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(&path);
- state
- .borrow_mut::<P>()
- .check_read(path, "Deno.readFileSync()")?;
- Ok(
- std::fs::read(path)
- .map_err(|err| {
- default_err_mapper(err, format!("readfile '{}'", path.display()))
- })?
- .into(),
- )
-}
-
-#[op]
-fn op_readfile_text_sync<P>(
- state: &mut OpState,
- path: String,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- let path = Path::new(&path);
- state
- .borrow_mut::<P>()
- .check_read(path, "Deno.readTextFileSync()")?;
- Ok(string_from_utf8_lossy(std::fs::read(path).map_err(
- |err| default_err_mapper(err, format!("readfile '{}'", path.display())),
- )?))
-}
-
-#[op]
-async fn op_readfile_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- cancel_rid: Option<ResourceId>,
-) -> Result<ZeroCopyBuf, AnyError>
-where
- P: FsPermissions + 'static,
-{
- {
- let path = Path::new(&path);
- let mut state = state.borrow_mut();
- state
- .borrow_mut::<P>()
- .check_read(path, "Deno.readFile()")?;
- }
-
- let read_future = tokio::task::spawn_blocking(move || {
- let path = Path::new(&path);
- std::fs::read(path).map(ZeroCopyBuf::from).map_err(|err| {
- default_err_mapper(err, format!("readfile '{}'", path.display()))
- })
- });
-
- let cancel_handle = cancel_rid.and_then(|rid| {
- state
- .borrow_mut()
- .resource_table
- .get::<CancelHandle>(rid)
- .ok()
- });
-
- if let Some(cancel_handle) = cancel_handle {
- let read_future_rv = read_future.or_cancel(cancel_handle).await;
-
- if let Some(cancel_rid) = cancel_rid {
- state.borrow_mut().resource_table.close(cancel_rid).ok();
- };
-
- return read_future_rv??;
- }
-
- read_future.await?
-}
-
-#[op]
-async fn op_readfile_text_async<P>(
- state: Rc<RefCell<OpState>>,
- path: String,
- cancel_rid: Option<ResourceId>,
-) -> Result<String, AnyError>
-where
- P: FsPermissions + 'static,
-{
- {
- let path = Path::new(&path);
- let mut state = state.borrow_mut();
- state
- .borrow_mut::<P>()
- .check_read(path, "Deno.readTextFile()")?;
- }
-
- let read_future = tokio::task::spawn_blocking(move || {
- let path = Path::new(&path);
- Ok(string_from_utf8_lossy(std::fs::read(path).map_err(
- |err| default_err_mapper(err, format!("readfile '{}'", path.display())),
- )?))
- });
-
- let cancel_handle = cancel_rid.and_then(|rid| {
- state
- .borrow_mut()
- .resource_table
- .get::<CancelHandle>(rid)
- .ok()
- });
-
- if let Some(cancel_handle) = cancel_handle {
- let read_future_rv = read_future.or_cancel(cancel_handle).await;
-
- if let Some(cancel_rid) = cancel_rid {
- state.borrow_mut().resource_table.close(cancel_rid).ok();
- };
-
- return read_future_rv??;
- }
-
- read_future.await?
-}
-
-// Like String::from_utf8_lossy but operates on owned values
-fn string_from_utf8_lossy(buf: Vec<u8>) -> String {
- match String::from_utf8_lossy(&buf) {
- // buf contained non-utf8 chars than have been patched
- Cow::Owned(s) => s,
- // SAFETY: if Borrowed then the buf only contains utf8 chars,
- // we do this instead of .into_owned() to avoid copying the input buf
- Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) },
- }
-}
diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs
new file mode 100644
index 000000000..c1381d89c
--- /dev/null
+++ b/ext/fs/ops.rs
@@ -0,0 +1,1712 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::io;
+use std::io::SeekFrom;
+use std::path::Path;
+use std::path::PathBuf;
+use std::rc::Rc;
+
+use deno_core::error::custom_error;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::CancelFuture;
+use deno_core::CancelHandle;
+use deno_core::OpState;
+use deno_core::ResourceId;
+use deno_core::ZeroCopyBuf;
+use rand::rngs::ThreadRng;
+use rand::thread_rng;
+use rand::Rng;
+use serde::Serialize;
+
+use crate::check_unstable;
+use crate::check_unstable2;
+use crate::interface::FsDirEntry;
+use crate::interface::FsError;
+use crate::interface::FsFileType;
+use crate::interface::FsStat;
+use crate::File;
+use crate::FileSystem;
+use crate::FsPermissions;
+use crate::OpenOptions;
+
+#[op]
+pub fn op_cwd<Fs, P>(state: &mut OpState) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let fs = state.borrow::<Fs>();
+ let path = fs.cwd()?;
+ state
+ .borrow_mut::<P>()
+ .check_read_blind(&path, "CWD", "Deno.cwd()")?;
+ let path_str = path_into_string(path.into_os_string())?;
+ Ok(path_str)
+}
+
+#[op]
+fn op_chdir<Fs, P>(state: &mut OpState, directory: &str) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let d = PathBuf::from(&directory);
+ state.borrow_mut::<P>().check_read(&d, "Deno.chdir()")?;
+ state.borrow::<Fs>().chdir(&d).context_path("chdir", &d)
+}
+
+#[op]
+fn op_umask<Fs>(state: &mut OpState, mask: Option<u32>) -> Result<u32, AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ check_unstable(state, "Deno.umask");
+ state.borrow::<Fs>().umask(mask).context("umask")
+}
+
+#[op]
+fn op_open_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+ options: Option<OpenOptions>,
+) -> Result<ResourceId, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let options = options.unwrap_or_else(OpenOptions::read);
+ let permissions = state.borrow_mut::<P>();
+ options.check(permissions, &path, "Deno.openSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ let file = fs.open_sync(&path, options).context_path("open", &path)?;
+
+ let rid = state.resource_table.add(file);
+ Ok(rid)
+}
+
+#[op]
+async fn op_open_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ options: Option<OpenOptions>,
+) -> Result<ResourceId, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let options = options.unwrap_or_else(OpenOptions::read);
+ let fs = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ options.check(permissions, &path, "Deno.open()")?;
+ state.borrow::<Fs>().clone()
+ };
+ let file = fs
+ .open_async(path.clone(), options)
+ .await
+ .context_path("open", &path)?;
+
+ let rid = state.borrow_mut().resource_table.add(file);
+ Ok(rid)
+}
+
+#[op]
+fn op_mkdir_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+ recursive: bool,
+ mode: Option<u32>,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let mode = mode.unwrap_or(0o777) & 0o777;
+
+ state
+ .borrow_mut::<P>()
+ .check_write(&path, "Deno.mkdirSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.mkdir_sync(&path, recursive, mode)
+ .context_path("mkdir", &path)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_mkdir_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ recursive: bool,
+ mode: Option<u32>,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let mode = mode.unwrap_or(0o777) & 0o777;
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ state.borrow_mut::<P>().check_write(&path, "Deno.mkdir()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.mkdir_async(path.clone(), recursive, mode)
+ .await
+ .context_path("mkdir", &path)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_chmod_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+ mode: u32,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ state
+ .borrow_mut::<P>()
+ .check_write(&path, "Deno.chmodSync()")?;
+ let fs = state.borrow::<Fs>();
+ fs.chmod_sync(&path, mode).context_path("chmod", &path)?;
+ Ok(())
+}
+
+#[op]
+async fn op_chmod_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ mode: u32,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ let fs = {
+ let mut state = state.borrow_mut();
+ state.borrow_mut::<P>().check_write(&path, "Deno.chmod()")?;
+ state.borrow::<Fs>().clone()
+ };
+ fs.chmod_async(path.clone(), mode)
+ .await
+ .context_path("chmod", &path)?;
+ Ok(())
+}
+
+#[op]
+fn op_chown_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+ uid: Option<u32>,
+ gid: Option<u32>,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ state
+ .borrow_mut::<P>()
+ .check_write(&path, "Deno.chownSync()")?;
+ let fs = state.borrow::<Fs>();
+ fs.chown_sync(&path, uid, gid)
+ .context_path("chown", &path)?;
+ Ok(())
+}
+
+#[op]
+async fn op_chown_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ uid: Option<u32>,
+ gid: Option<u32>,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ let fs = {
+ let mut state = state.borrow_mut();
+ state.borrow_mut::<P>().check_write(&path, "Deno.chown()")?;
+ state.borrow::<Fs>().clone()
+ };
+ fs.chown_async(path.clone(), uid, gid)
+ .await
+ .context_path("chown", &path)?;
+ Ok(())
+}
+
+#[op]
+fn op_remove_sync<Fs, P>(
+ state: &mut OpState,
+ path: &str,
+ recursive: bool,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ state
+ .borrow_mut::<P>()
+ .check_write(&path, "Deno.removeSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.remove_sync(&path, recursive)
+ .context_path("remove", &path)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_remove_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ recursive: bool,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ state
+ .borrow_mut::<P>()
+ .check_write(&path, "Deno.remove()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.remove_async(path.clone(), recursive)
+ .await
+ .context_path("remove", &path)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_copy_file_sync<Fs, P>(
+ state: &mut OpState,
+ from: &str,
+ to: &str,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let from = PathBuf::from(from);
+ let to = PathBuf::from(to);
+
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&from, "Deno.copyFileSync()")?;
+ permissions.check_write(&to, "Deno.copyFileSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.copy_file_sync(&from, &to)
+ .context_two_path("copy", &from, &to)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_copy_file_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ from: String,
+ to: String,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let from = PathBuf::from(from);
+ let to = PathBuf::from(to);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&from, "Deno.copyFile()")?;
+ permissions.check_write(&to, "Deno.copyFile()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.copy_file_async(from.clone(), to.clone())
+ .await
+ .context_two_path("copy", &from, &to)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_stat_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+ stat_out_buf: &mut [u32],
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ state
+ .borrow_mut::<P>()
+ .check_read(&path, "Deno.statSync()")?;
+ let fs = state.borrow::<Fs>();
+ let stat = fs.stat_sync(&path).context_path("stat", &path)?;
+ let serializable_stat = SerializableStat::from(stat);
+ serializable_stat.write(stat_out_buf);
+ Ok(())
+}
+
+#[op]
+async fn op_stat_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+) -> Result<SerializableStat, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ let fs = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.stat()")?;
+ state.borrow::<Fs>().clone()
+ };
+ let stat = fs
+ .stat_async(path.clone())
+ .await
+ .context_path("stat", &path)?;
+ Ok(SerializableStat::from(stat))
+}
+
+#[op]
+fn op_lstat_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+ stat_out_buf: &mut [u32],
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ state
+ .borrow_mut::<P>()
+ .check_read(&path, "Deno.lstatSync()")?;
+ let fs = state.borrow::<Fs>();
+ let stat = fs.lstat_sync(&path).context_path("lstat", &path)?;
+ let serializable_stat = SerializableStat::from(stat);
+ serializable_stat.write(stat_out_buf);
+ Ok(())
+}
+
+#[op]
+async fn op_lstat_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+) -> Result<SerializableStat, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+ let fs = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.lstat()")?;
+ state.borrow::<Fs>().clone()
+ };
+ let stat = fs
+ .lstat_async(path.clone())
+ .await
+ .context_path("lstat", &path)?;
+ Ok(SerializableStat::from(stat))
+}
+
+#[op]
+fn op_realpath_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs = state.borrow::<Fs>().clone();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.realPathSync()")?;
+ if path.is_relative() {
+ permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPathSync()")?;
+ }
+
+ let resolved_path =
+ fs.realpath_sync(&path).context_path("realpath", &path)?;
+
+ let path_string = path_into_string(resolved_path.into_os_string())?;
+ Ok(path_string)
+}
+
+#[op]
+async fn op_realpath_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs;
+ {
+ let mut state = state.borrow_mut();
+ fs = state.borrow::<Fs>().clone();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.realPath()")?;
+ if path.is_relative() {
+ permissions.check_read_blind(&fs.cwd()?, "CWD", "Deno.realPath()")?;
+ }
+ }
+ let resolved_path = fs
+ .realpath_async(path.clone())
+ .await
+ .context_path("realpath", &path)?;
+
+ let path_string = path_into_string(resolved_path.into_os_string())?;
+ Ok(path_string)
+}
+
+#[op]
+fn op_read_dir_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+) -> Result<Vec<FsDirEntry>, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ state
+ .borrow_mut::<P>()
+ .check_read(&path, "Deno.readDirSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ let entries = fs.read_dir_sync(&path).context_path("readdir", &path)?;
+
+ Ok(entries)
+}
+
+#[op]
+async fn op_read_dir_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+) -> Result<Vec<FsDirEntry>, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ state
+ .borrow_mut::<P>()
+ .check_read(&path, "Deno.readDir()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ let entries = fs
+ .read_dir_async(path.clone())
+ .await
+ .context_path("readdir", &path)?;
+
+ Ok(entries)
+}
+
+#[op]
+fn op_rename_sync<Fs, P>(
+ state: &mut OpState,
+ oldpath: String,
+ newpath: String,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let oldpath = PathBuf::from(oldpath);
+ let newpath = PathBuf::from(newpath);
+
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&oldpath, "Deno.renameSync()")?;
+ permissions.check_write(&oldpath, "Deno.renameSync()")?;
+ permissions.check_write(&newpath, "Deno.renameSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.rename_sync(&oldpath, &newpath)
+ .context_two_path("rename", &oldpath, &newpath)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_rename_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ oldpath: String,
+ newpath: String,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let oldpath = PathBuf::from(oldpath);
+ let newpath = PathBuf::from(newpath);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&oldpath, "Deno.rename()")?;
+ permissions.check_write(&oldpath, "Deno.rename()")?;
+ permissions.check_write(&newpath, "Deno.rename()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.rename_async(oldpath.clone(), newpath.clone())
+ .await
+ .context_two_path("rename", &oldpath, &newpath)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_link_sync<Fs, P>(
+ state: &mut OpState,
+ oldpath: &str,
+ newpath: &str,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let oldpath = PathBuf::from(oldpath);
+ let newpath = PathBuf::from(newpath);
+
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&oldpath, "Deno.linkSync()")?;
+ permissions.check_write(&oldpath, "Deno.linkSync()")?;
+ permissions.check_read(&newpath, "Deno.linkSync()")?;
+ permissions.check_write(&newpath, "Deno.linkSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.link_sync(&oldpath, &newpath)
+ .context_two_path("link", &oldpath, &newpath)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_link_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ oldpath: String,
+ newpath: String,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let oldpath = PathBuf::from(&oldpath);
+ let newpath = PathBuf::from(&newpath);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&oldpath, "Deno.link()")?;
+ permissions.check_write(&oldpath, "Deno.link()")?;
+ permissions.check_read(&newpath, "Deno.link()")?;
+ permissions.check_write(&newpath, "Deno.link()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.link_async(oldpath.clone(), newpath.clone())
+ .await
+ .context_two_path("link", &oldpath, &newpath)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_symlink_sync<Fs, P>(
+ state: &mut OpState,
+ oldpath: &str,
+ newpath: &str,
+ file_type: Option<FsFileType>,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let oldpath = PathBuf::from(oldpath);
+ let newpath = PathBuf::from(newpath);
+
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_write_all("Deno.symlinkSync()")?;
+ permissions.check_read_all("Deno.symlinkSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.symlink_sync(&oldpath, &newpath, file_type)
+ .context_two_path("symlink", &oldpath, &newpath)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_symlink_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ oldpath: String,
+ newpath: String,
+ file_type: Option<FsFileType>,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let oldpath = PathBuf::from(&oldpath);
+ let newpath = PathBuf::from(&newpath);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_write_all("Deno.symlink()")?;
+ permissions.check_read_all("Deno.symlink()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.symlink_async(oldpath.clone(), newpath.clone(), file_type)
+ .await
+ .context_two_path("symlink", &oldpath, &newpath)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_read_link_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ state
+ .borrow_mut::<P>()
+ .check_read(&path, "Deno.readLink()")?;
+
+ let fs = state.borrow::<Fs>();
+
+ let target = fs.read_link_sync(&path).context_path("readlink", &path)?;
+ let target_string = path_into_string(target.into_os_string())?;
+ Ok(target_string)
+}
+
+#[op]
+async fn op_read_link_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ state
+ .borrow_mut::<P>()
+ .check_read(&path, "Deno.readLink()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ let target = fs
+ .read_link_async(path.clone())
+ .await
+ .context_path("readlink", &path)?;
+ let target_string = path_into_string(target.into_os_string())?;
+ Ok(target_string)
+}
+
+#[op]
+fn op_truncate_sync<Fs, P>(
+ state: &mut OpState,
+ path: &str,
+ len: u64,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ state
+ .borrow_mut::<P>()
+ .check_write(&path, "Deno.truncateSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.truncate_sync(&path, len)
+ .context_path("truncate", &path)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_truncate_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ len: u64,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ state
+ .borrow_mut::<P>()
+ .check_write(&path, "Deno.truncate()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.truncate_async(path.clone(), len)
+ .await
+ .context_path("truncate", &path)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_utime_sync<Fs, P>(
+ state: &mut OpState,
+ path: &str,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ state.borrow_mut::<P>().check_write(&path, "Deno.utime()")?;
+
+ let fs = state.borrow::<Fs>();
+ fs.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
+ .context_path("utime", &path)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_utime_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ state.borrow_mut::<P>().check_write(&path, "Deno.utime()")?;
+ state.borrow::<Fs>().clone()
+ };
+
+ fs.utime_async(
+ path.clone(),
+ atime_secs,
+ atime_nanos,
+ mtime_secs,
+ mtime_nanos,
+ )
+ .await
+ .context_path("utime", &path)?;
+
+ Ok(())
+}
+
+#[op]
+fn op_make_temp_dir_sync<Fs, P>(
+ state: &mut OpState,
+ dir: Option<String>,
+ prefix: Option<String>,
+ suffix: Option<String>,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let (dir, fs) = make_temp_check_sync::<Fs, P>(state, dir)?;
+
+ let mut rng = thread_rng();
+
+ const MAX_TRIES: u32 = 10;
+ for _ in 0..MAX_TRIES {
+ let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?;
+ match fs.mkdir_sync(&path, false, 0o700) {
+ Ok(_) => return path_into_string(path.into_os_string()),
+ Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
+ continue;
+ }
+ Err(e) => return Err(e).context("tmpdir"),
+ }
+ }
+
+ Err(FsError::Io(io::Error::new(
+ io::ErrorKind::AlreadyExists,
+ "too many temp dirs exist",
+ )))
+ .context("tmpdir")
+}
+
+#[op]
+async fn op_make_temp_dir_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ dir: Option<String>,
+ prefix: Option<String>,
+ suffix: Option<String>,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let (dir, fs) = make_temp_check_async::<Fs, P>(state, dir)?;
+
+ let mut rng = thread_rng();
+
+ const MAX_TRIES: u32 = 10;
+ for _ in 0..MAX_TRIES {
+ let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?;
+ match fs.clone().mkdir_async(path.clone(), false, 0o700).await {
+ Ok(_) => return path_into_string(path.into_os_string()),
+ Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
+ continue;
+ }
+ Err(e) => return Err(e).context("tmpdir"),
+ }
+ }
+
+ Err(FsError::Io(io::Error::new(
+ io::ErrorKind::AlreadyExists,
+ "too many temp dirs exist",
+ )))
+ .context("tmpdir")
+}
+
+#[op]
+fn op_make_temp_file_sync<Fs, P>(
+ state: &mut OpState,
+ dir: Option<String>,
+ prefix: Option<String>,
+ suffix: Option<String>,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let (dir, fs) = make_temp_check_sync::<Fs, P>(state, dir)?;
+
+ let open_opts = OpenOptions {
+ write: true,
+ create_new: true,
+ mode: Some(0o600),
+ ..Default::default()
+ };
+
+ let mut rng = thread_rng();
+
+ const MAX_TRIES: u32 = 10;
+ for _ in 0..MAX_TRIES {
+ let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?;
+ match fs.open_sync(&path, open_opts) {
+ Ok(_) => return path_into_string(path.into_os_string()),
+ Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
+ continue;
+ }
+ Err(e) => return Err(e).context("tmpfile"),
+ }
+ }
+
+ Err(FsError::Io(io::Error::new(
+ io::ErrorKind::AlreadyExists,
+ "too many temp files exist",
+ )))
+ .context("tmpfile")
+}
+
+#[op]
+async fn op_make_temp_file_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ dir: Option<String>,
+ prefix: Option<String>,
+ suffix: Option<String>,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let (dir, fs) = make_temp_check_async::<Fs, P>(state, dir)?;
+
+ let open_opts = OpenOptions {
+ write: true,
+ create_new: true,
+ mode: Some(0o600),
+ ..Default::default()
+ };
+
+ let mut rng = thread_rng();
+
+ const MAX_TRIES: u32 = 10;
+ for _ in 0..MAX_TRIES {
+ let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?;
+ match fs.clone().open_async(path.clone(), open_opts).await {
+ Ok(_) => return path_into_string(path.into_os_string()),
+ Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
+ continue;
+ }
+ Err(e) => return Err(e).context("tmpfile"),
+ }
+ }
+ Err(FsError::Io(io::Error::new(
+ io::ErrorKind::AlreadyExists,
+ "too many temp files exist",
+ )))
+ .context("tmpfile")
+}
+
+fn make_temp_check_sync<Fs, P>(
+ state: &mut OpState,
+ dir: Option<String>,
+) -> Result<(PathBuf, Fs), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let fs = state.borrow::<Fs>().clone();
+ let dir = match dir {
+ Some(dir) => {
+ let dir = PathBuf::from(dir);
+ state
+ .borrow_mut::<P>()
+ .check_write(&dir, "Deno.makeTempFile()")?;
+ dir
+ }
+ None => {
+ let dir = fs.tmp_dir().context("tmpdir")?;
+ state.borrow_mut::<P>().check_write_blind(
+ &dir,
+ "TMP",
+ "Deno.makeTempFile()",
+ )?;
+ dir
+ }
+ };
+ Ok((dir, fs))
+}
+
+fn make_temp_check_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ dir: Option<String>,
+) -> Result<(PathBuf, Fs), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let mut state = state.borrow_mut();
+ let fs = state.borrow::<Fs>().clone();
+ let dir = match dir {
+ Some(dir) => {
+ let dir = PathBuf::from(dir);
+ state
+ .borrow_mut::<P>()
+ .check_write(&dir, "Deno.makeTempFile()")?;
+ dir
+ }
+ None => {
+ let dir = fs.tmp_dir().context("tmpdir")?;
+ state.borrow_mut::<P>().check_write_blind(
+ &dir,
+ "TMP",
+ "Deno.makeTempFile()",
+ )?;
+ dir
+ }
+ };
+ Ok((dir, fs))
+}
+
+fn tmp_name(
+ rng: &mut ThreadRng,
+ dir: &Path,
+ prefix: Option<&str>,
+ suffix: Option<&str>,
+) -> Result<PathBuf, AnyError> {
+ let prefix = prefix.unwrap_or("");
+ let suffix = suffix.unwrap_or("");
+
+ let mut path = dir.join("_");
+
+ let unique = rng.gen::<u32>();
+ path.set_file_name(format!("{prefix}{unique:08x}{suffix}"));
+
+ Ok(path)
+}
+
+#[op]
+fn op_write_file_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+ mode: Option<u32>,
+ append: bool,
+ create: bool,
+ create_new: bool,
+ data: ZeroCopyBuf,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let permissions = state.borrow_mut::<P>();
+ let options = OpenOptions::write(create, append, create_new, mode);
+ options.check(permissions, &path, "Deno.writeFileSync()")?;
+
+ let fs = state.borrow::<Fs>();
+
+ fs.write_file_sync(&path, options, &data)
+ .context_path("writefile", &path)?;
+
+ Ok(())
+}
+
+#[op]
+async fn op_write_file_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ mode: Option<u32>,
+ append: bool,
+ create: bool,
+ create_new: bool,
+ data: ZeroCopyBuf,
+ cancel_rid: Option<ResourceId>,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let options = OpenOptions::write(create, append, create_new, mode);
+
+ let (fs, cancel_handle) = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ options.check(permissions, &path, "Deno.writeFile()")?;
+ let cancel_handle = cancel_rid
+ .and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
+ (state.borrow::<Fs>().clone(), cancel_handle)
+ };
+
+ let fut = fs.write_file_async(path.clone(), options, data.to_vec());
+
+ if let Some(cancel_handle) = cancel_handle {
+ let res = fut.or_cancel(cancel_handle).await;
+
+ if let Some(cancel_rid) = cancel_rid {
+ state.borrow_mut().resource_table.close(cancel_rid).ok();
+ };
+
+ res?.context_path("writefile", &path)?;
+ } else {
+ fut.await.context_path("writefile", &path)?;
+ }
+
+ Ok(())
+}
+
+#[op]
+fn op_read_file_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+) -> Result<ZeroCopyBuf, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.readFileSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ let buf = fs.read_file_sync(path).context("readfile")?;
+
+ Ok(buf.into())
+}
+
+#[op]
+async fn op_read_file_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ cancel_rid: Option<ResourceId>,
+) -> Result<ZeroCopyBuf, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let (fs, cancel_handle) = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.readFile()")?;
+ let cancel_handle = cancel_rid
+ .and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
+ (state.borrow::<Fs>().clone(), cancel_handle)
+ };
+
+ let fut = fs.read_file_async(path.clone());
+
+ let buf = if let Some(cancel_handle) = cancel_handle {
+ let res = fut.or_cancel(cancel_handle).await;
+
+ if let Some(cancel_rid) = cancel_rid {
+ state.borrow_mut().resource_table.close(cancel_rid).ok();
+ };
+
+ res?.context_path("readfile", &path)?
+ } else {
+ fut.await.context_path("readfile", &path)?
+ };
+
+ Ok(buf.into())
+}
+
+#[op]
+fn op_read_file_text_sync<Fs, P>(
+ state: &mut OpState,
+ path: String,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.readFileSync()")?;
+
+ let fs = state.borrow::<Fs>();
+ let buf = fs.read_file_sync(path).context("readfile")?;
+
+ Ok(string_from_utf8_lossy(buf))
+}
+
+#[op]
+async fn op_read_file_text_async<Fs, P>(
+ state: Rc<RefCell<OpState>>,
+ path: String,
+ cancel_rid: Option<ResourceId>,
+) -> Result<String, AnyError>
+where
+ Fs: FileSystem + 'static,
+ P: FsPermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let (fs, cancel_handle) = {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<P>();
+ permissions.check_read(&path, "Deno.readFile()")?;
+ let cancel_handle = cancel_rid
+ .and_then(|rid| state.resource_table.get::<CancelHandle>(rid).ok());
+ (state.borrow::<Fs>().clone(), cancel_handle)
+ };
+
+ let fut = fs.read_file_async(path.clone());
+
+ let buf = if let Some(cancel_handle) = cancel_handle {
+ let res = fut.or_cancel(cancel_handle).await;
+
+ if let Some(cancel_rid) = cancel_rid {
+ state.borrow_mut().resource_table.close(cancel_rid).ok();
+ };
+
+ res?.context_path("readfile", &path)?
+ } else {
+ fut.await.context_path("readfile", &path)?
+ };
+
+ Ok(string_from_utf8_lossy(buf))
+}
+
+// Like String::from_utf8_lossy but operates on owned values
+fn string_from_utf8_lossy(buf: Vec<u8>) -> String {
+ match String::from_utf8_lossy(&buf) {
+ // buf contained non-utf8 chars than have been patched
+ Cow::Owned(s) => s,
+ // SAFETY: if Borrowed then the buf only contains utf8 chars,
+ // we do this instead of .into_owned() to avoid copying the input buf
+ Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) },
+ }
+}
+
+fn to_seek_from(offset: i64, whence: i32) -> Result<SeekFrom, AnyError> {
+ 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}")));
+ }
+ };
+ Ok(seek_from)
+}
+
+#[op]
+fn op_seek_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+ offset: i64,
+ whence: i32,
+) -> Result<u64, AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let pos = to_seek_from(offset, whence)?;
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ let cursor = file.seek_sync(pos)?;
+ Ok(cursor)
+}
+
+#[op]
+async fn op_seek_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ offset: i64,
+ whence: i32,
+) -> Result<u64, AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let pos = to_seek_from(offset, whence)?;
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ let cursor = file.seek_async(pos).await?;
+ Ok(cursor)
+}
+
+#[op]
+fn op_fdatasync_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ file.datasync_sync()?;
+ Ok(())
+}
+
+#[op]
+async fn op_fdatasync_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ file.datasync_async().await?;
+ Ok(())
+}
+
+#[op]
+fn op_fsync_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ file.sync_sync()?;
+ Ok(())
+}
+
+#[op]
+async fn op_fsync_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ file.sync_async().await?;
+ Ok(())
+}
+
+#[op]
+fn op_fstat_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+ stat_out_buf: &mut [u32],
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ let stat = file.stat_sync()?;
+ let serializable_stat = SerializableStat::from(stat);
+ serializable_stat.write(stat_out_buf);
+ Ok(())
+}
+
+#[op]
+async fn op_fstat_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+) -> Result<SerializableStat, AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ let stat = file.stat_async().await?;
+ Ok(stat.into())
+}
+
+#[op]
+fn op_flock_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+ exclusive: bool,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ check_unstable(state, "Deno.flockSync");
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ file.lock_sync(exclusive)?;
+ Ok(())
+}
+
+#[op]
+async fn op_flock_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ exclusive: bool,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ check_unstable2(&state, "Deno.flock");
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ file.lock_async(exclusive).await?;
+ Ok(())
+}
+
+#[op]
+fn op_funlock_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ check_unstable(state, "Deno.funlockSync");
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ file.unlock_sync()?;
+ Ok(())
+}
+
+#[op]
+async fn op_funlock_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ check_unstable2(&state, "Deno.funlock");
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ file.unlock_async().await?;
+ Ok(())
+}
+
+#[op]
+fn op_ftruncate_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+ len: u64,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ file.truncate_sync(len)?;
+ Ok(())
+}
+
+#[op]
+async fn op_ftruncate_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ len: u64,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ file.truncate_async(len).await?;
+ Ok(())
+}
+
+#[op]
+fn op_futime_sync<Fs>(
+ state: &mut OpState,
+ rid: ResourceId,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.resource_table.get::<Fs::File>(rid)?;
+ file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?;
+ Ok(())
+}
+
+#[op]
+async fn op_futime_async<Fs>(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+) -> Result<(), AnyError>
+where
+ Fs: FileSystem + 'static,
+{
+ let file = state.borrow().resource_table.get::<Fs::File>(rid)?;
+ 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;
+}
+
+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)
+ }
+ _ => self.into(),
+ }
+ }
+}
+
+trait MapErrContext {
+ type R;
+
+ fn context_fn<F, E>(self, f: F) -> Self::R
+ where
+ F: FnOnce() -> E,
+ E: Into<Box<dyn std::error::Error + Send + Sync>>;
+
+ fn context(self, desc: &'static str) -> Self::R;
+
+ fn context_path(self, operation: &'static str, path: &Path) -> Self::R;
+
+ fn context_two_path(
+ self,
+ operation: &'static str,
+ from: &Path,
+ to: &Path,
+ ) -> Self::R;
+}
+
+impl<T> MapErrContext for Result<T, FsError> {
+ type R = Result<T, AnyError>;
+
+ fn context_fn<F, E>(self, f: F) -> Self::R
+ where
+ F: FnOnce() -> E,
+ E: Into<Box<dyn std::error::Error + Send + Sync>>,
+ {
+ self.map_err(|err| {
+ let message = f();
+ err.context(message)
+ })
+ }
+
+ 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()))
+ }
+
+ fn context_two_path(
+ self,
+ operation: &'static str,
+ oldpath: &Path,
+ newpath: &Path,
+ ) -> Self::R {
+ self.context_fn(|| {
+ format!(
+ "{operation} '{}' -> '{}'",
+ oldpath.display(),
+ newpath.display()
+ )
+ })
+ }
+}
+
+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)
+ })
+}
+
+macro_rules! create_struct_writer {
+ (pub struct $name:ident { $($field:ident: $type:ty),* $(,)? }) => {
+ impl $name {
+ fn write(self, buf: &mut [u32]) {
+ let mut offset = 0;
+ $(
+ let value = self.$field as u64;
+ buf[offset] = value as u32;
+ buf[offset + 1] = (value >> 32) as u32;
+ #[allow(unused_assignments)]
+ {
+ offset += 2;
+ }
+ )*
+ }
+ }
+
+ #[derive(Serialize)]
+ #[serde(rename_all = "camelCase")]
+ struct $name {
+ $($field: $type),*
+ }
+ };
+}
+
+create_struct_writer! {
+ pub struct SerializableStat {
+ is_file: bool,
+ is_directory: bool,
+ is_symlink: bool,
+ size: u64,
+ // In milliseconds, like JavaScript. Available on both Unix or Windows.
+ mtime_set: bool,
+ mtime: u64,
+ atime_set: bool,
+ atime: u64,
+ birthtime_set: bool,
+ birthtime: u64,
+ // Following are only valid under Unix.
+ dev: u64,
+ ino: u64,
+ mode: u32,
+ nlink: u64,
+ uid: u32,
+ gid: u32,
+ rdev: u64,
+ blksize: u64,
+ blocks: u64,
+ }
+}
+
+impl From<FsStat> for SerializableStat {
+ fn from(stat: FsStat) -> Self {
+ SerializableStat {
+ is_file: stat.is_file,
+ is_directory: stat.is_directory,
+ is_symlink: stat.is_symlink,
+ size: stat.size,
+
+ mtime_set: stat.mtime.is_some(),
+ mtime: stat.mtime.unwrap_or(0),
+ atime_set: stat.atime.is_some(),
+ atime: stat.atime.unwrap_or(0),
+ birthtime_set: stat.birthtime.is_some(),
+ birthtime: stat.birthtime.unwrap_or(0),
+
+ dev: stat.dev,
+ ino: stat.ino,
+ mode: stat.mode,
+ nlink: stat.nlink,
+ uid: stat.uid,
+ gid: stat.gid,
+ rdev: stat.rdev,
+ blksize: stat.blksize,
+ blocks: stat.blocks,
+ }
+ }
+}
diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs
new file mode 100644
index 000000000..28c375ff1
--- /dev/null
+++ b/ext/fs/std_fs.rs
@@ -0,0 +1,929 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+#![allow(clippy::disallowed_methods)]
+
+use std::fs;
+use std::io;
+use std::io::Read;
+use std::io::Seek;
+use std::io::Write;
+use std::path::Path;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::time::SystemTime;
+use std::time::UNIX_EPOCH;
+
+use deno_io::StdFileResource;
+use fs3::FileExt;
+
+use crate::interface::FsDirEntry;
+use crate::interface::FsError;
+use crate::interface::FsFileType;
+use crate::interface::FsResult;
+use crate::interface::FsStat;
+use crate::File;
+use crate::FileSystem;
+use crate::OpenOptions;
+
+#[derive(Clone)]
+pub struct StdFs;
+
+#[async_trait::async_trait(?Send)]
+impl FileSystem for StdFs {
+ type File = StdFileResource;
+
+ fn cwd(&self) -> FsResult<PathBuf> {
+ std::env::current_dir().map_err(Into::into)
+ }
+
+ fn tmp_dir(&self) -> FsResult<PathBuf> {
+ Ok(std::env::temp_dir())
+ }
+
+ fn chdir(&self, path: impl AsRef<Path>) -> FsResult<()> {
+ std::env::set_current_dir(path).map_err(Into::into)
+ }
+
+ #[cfg(not(unix))]
+ fn umask(&self, _mask: Option<u32>) -> FsResult<u32> {
+ // TODO implement umask for Windows
+ // see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc
+ // and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019
+ Err(FsError::NotSupported)
+ }
+
+ #[cfg(unix)]
+ fn umask(&self, mask: Option<u32>) -> FsResult<u32> {
+ use nix::sys::stat::mode_t;
+ use nix::sys::stat::umask;
+ use nix::sys::stat::Mode;
+ let r = if let Some(mask) = mask {
+ // If mask provided, return previous.
+ umask(Mode::from_bits_truncate(mask as mode_t))
+ } else {
+ // If no mask provided, we query the current. Requires two syscalls.
+ let prev = umask(Mode::from_bits_truncate(0o777));
+ let _ = umask(prev);
+ prev
+ };
+ #[cfg(target_os = "linux")]
+ {
+ Ok(r.bits())
+ }
+ #[cfg(target_os = "macos")]
+ {
+ Ok(r.bits() as u32)
+ }
+ }
+
+ fn open_sync(
+ &self,
+ path: impl AsRef<Path>,
+ options: OpenOptions,
+ ) -> FsResult<Self::File> {
+ let opts = open_options(options);
+ let std_file = opts.open(path)?;
+ Ok(StdFileResource::fs_file(std_file))
+ }
+ async fn open_async(
+ &self,
+ path: PathBuf,
+ options: OpenOptions,
+ ) -> FsResult<Self::File> {
+ let opts = open_options(options);
+ let std_file =
+ tokio::task::spawn_blocking(move || opts.open(path)).await??;
+ Ok(StdFileResource::fs_file(std_file))
+ }
+
+ fn mkdir_sync(
+ &self,
+ path: impl AsRef<Path>,
+ recursive: bool,
+ mode: u32,
+ ) -> FsResult<()> {
+ mkdir(path, recursive, mode)
+ }
+ async fn mkdir_async(
+ &self,
+ path: PathBuf,
+ recursive: bool,
+ mode: u32,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || mkdir(path, recursive, mode)).await?
+ }
+
+ fn chmod_sync(&self, path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
+ chmod(path, mode)
+ }
+ async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || chmod(path, mode)).await?
+ }
+
+ fn chown_sync(
+ &self,
+ path: impl AsRef<Path>,
+ uid: Option<u32>,
+ gid: Option<u32>,
+ ) -> FsResult<()> {
+ chown(path, uid, gid)
+ }
+ async fn chown_async(
+ &self,
+ path: PathBuf,
+ uid: Option<u32>,
+ gid: Option<u32>,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || chown(path, uid, gid)).await?
+ }
+
+ fn remove_sync(
+ &self,
+ path: impl AsRef<Path>,
+ recursive: bool,
+ ) -> FsResult<()> {
+ remove(path, recursive)
+ }
+ async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || remove(path, recursive)).await?
+ }
+
+ fn copy_file_sync(
+ &self,
+ from: impl AsRef<Path>,
+ to: impl AsRef<Path>,
+ ) -> FsResult<()> {
+ copy_file(from, to)
+ }
+ async fn copy_file_async(&self, from: PathBuf, to: PathBuf) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || copy_file(from, to)).await?
+ }
+
+ fn stat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
+ stat(path).map(Into::into)
+ }
+ async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat> {
+ tokio::task::spawn_blocking(move || stat(path))
+ .await?
+ .map(Into::into)
+ }
+
+ fn lstat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
+ lstat(path).map(Into::into)
+ }
+ async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat> {
+ tokio::task::spawn_blocking(move || lstat(path))
+ .await?
+ .map(Into::into)
+ }
+
+ fn realpath_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
+ realpath(path)
+ }
+ async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf> {
+ tokio::task::spawn_blocking(move || realpath(path)).await?
+ }
+
+ fn read_dir_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
+ read_dir(path)
+ }
+ async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>> {
+ tokio::task::spawn_blocking(move || read_dir(path)).await?
+ }
+
+ fn rename_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ ) -> FsResult<()> {
+ fs::rename(oldpath, newpath).map_err(Into::into)
+ }
+ async fn rename_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || fs::rename(oldpath, newpath))
+ .await?
+ .map_err(Into::into)
+ }
+
+ fn link_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ ) -> FsResult<()> {
+ fs::hard_link(oldpath, newpath).map_err(Into::into)
+ }
+ async fn link_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || fs::hard_link(oldpath, newpath))
+ .await?
+ .map_err(Into::into)
+ }
+
+ fn symlink_sync(
+ &self,
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ file_type: Option<FsFileType>,
+ ) -> FsResult<()> {
+ symlink(oldpath, newpath, file_type)
+ }
+ async fn symlink_async(
+ &self,
+ oldpath: PathBuf,
+ newpath: PathBuf,
+ file_type: Option<FsFileType>,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || symlink(oldpath, newpath, file_type))
+ .await?
+ }
+
+ fn read_link_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
+ fs::read_link(path).map_err(Into::into)
+ }
+ async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf> {
+ tokio::task::spawn_blocking(move || fs::read_link(path))
+ .await?
+ .map_err(Into::into)
+ }
+
+ fn truncate_sync(&self, path: impl AsRef<Path>, len: u64) -> FsResult<()> {
+ truncate(path, len)
+ }
+ async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || truncate(path, len)).await?
+ }
+
+ fn utime_sync(
+ &self,
+ path: impl AsRef<Path>,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
+ let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
+ filetime::set_file_times(path, atime, mtime).map_err(Into::into)
+ }
+ async fn utime_async(
+ &self,
+ path: PathBuf,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
+ let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
+ tokio::task::spawn_blocking(move || {
+ filetime::set_file_times(path, atime, mtime).map_err(Into::into)
+ })
+ .await?
+ }
+
+ fn write_file_sync(
+ &self,
+ path: impl AsRef<Path>,
+ options: OpenOptions,
+ data: &[u8],
+ ) -> FsResult<()> {
+ let opts = open_options(options);
+ let mut file = opts.open(path)?;
+ #[cfg(unix)]
+ if let Some(mode) = options.mode {
+ use std::os::unix::fs::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))?;
+ }
+ file.write_all(data)?;
+ Ok(())
+ }
+
+ async fn write_file_async(
+ &self,
+ path: PathBuf,
+ options: OpenOptions,
+ data: Vec<u8>,
+ ) -> FsResult<()> {
+ tokio::task::spawn_blocking(move || {
+ let opts = open_options(options);
+ let mut file = opts.open(path)?;
+ #[cfg(unix)]
+ if let Some(mode) = options.mode {
+ use std::os::unix::fs::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))?;
+ }
+ file.write_all(&data)?;
+ Ok(())
+ })
+ .await?
+ }
+
+ fn read_file_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
+ fs::read(path).map_err(Into::into)
+ }
+ async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
+ tokio::task::spawn_blocking(move || fs::read(path))
+ .await?
+ .map_err(Into::into)
+ }
+}
+
+fn mkdir(path: impl AsRef<Path>, recursive: bool, mode: u32) -> FsResult<()> {
+ let mut builder = fs::DirBuilder::new();
+ builder.recursive(recursive);
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::DirBuilderExt;
+ builder.mode(mode);
+ }
+ #[cfg(not(unix))]
+ {
+ _ = mode;
+ }
+ builder.create(path).map_err(Into::into)
+}
+
+#[cfg(unix)]
+fn chmod(path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
+ use std::os::unix::fs::PermissionsExt;
+ let permissions = fs::Permissions::from_mode(mode);
+ fs::set_permissions(path, permissions)?;
+ Ok(())
+}
+
+// TODO: implement chmod for Windows (#4357)
+#[cfg(not(unix))]
+fn chmod(path: impl AsRef<Path>, _mode: u32) -> FsResult<()> {
+ // Still check file/dir exists on Windows
+ std::fs::metadata(path)?;
+ Err(FsError::NotSupported)
+}
+
+#[cfg(unix)]
+fn chown(
+ path: impl AsRef<Path>,
+ uid: Option<u32>,
+ gid: Option<u32>,
+) -> FsResult<()> {
+ use nix::unistd::chown;
+ use nix::unistd::Gid;
+ use nix::unistd::Uid;
+ let owner = uid.map(Uid::from_raw);
+ let group = gid.map(Gid::from_raw);
+ let res = chown(path.as_ref(), owner, group);
+ if let Err(err) = res {
+ return Err(io::Error::from_raw_os_error(err as i32).into());
+ }
+ Ok(())
+}
+
+// TODO: implement chown for Windows
+#[cfg(not(unix))]
+fn chown(
+ _path: impl AsRef<Path>,
+ _uid: Option<u32>,
+ _gid: Option<u32>,
+) -> FsResult<()> {
+ Err(FsError::NotSupported)
+}
+
+fn remove(path: impl AsRef<Path>, recursive: bool) -> FsResult<()> {
+ // TODO: this is racy. This should open fds, and then `unlink` those.
+ let metadata = fs::symlink_metadata(&path)?;
+
+ let file_type = metadata.file_type();
+ let res = if file_type.is_dir() {
+ if recursive {
+ fs::remove_dir_all(&path)
+ } else {
+ fs::remove_dir(&path)
+ }
+ } else if file_type.is_symlink() {
+ #[cfg(unix)]
+ {
+ fs::remove_file(&path)
+ }
+ #[cfg(not(unix))]
+ {
+ use std::os::windows::prelude::MetadataExt;
+ use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
+ if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
+ fs::remove_dir(&path)
+ } else {
+ fs::remove_file(&path)
+ }
+ }
+ } else {
+ fs::remove_file(&path)
+ };
+
+ res.map_err(Into::into)
+}
+
+fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> FsResult<()> {
+ #[cfg(target_os = "macos")]
+ {
+ use libc::clonefile;
+ use libc::stat;
+ use libc::unlink;
+ use std::ffi::CString;
+ use std::os::unix::fs::OpenOptionsExt;
+ use std::os::unix::fs::PermissionsExt;
+ use std::os::unix::prelude::OsStrExt;
+
+ let from_str = CString::new(from.as_ref().as_os_str().as_bytes()).unwrap();
+ let to_str = CString::new(to.as_ref().as_os_str().as_bytes()).unwrap();
+
+ // SAFETY: `from` and `to` are valid C strings.
+ // std::fs::copy does open() + fcopyfile() on macOS. We try to use
+ // clonefile() instead, which is more efficient.
+ unsafe {
+ let mut st = std::mem::zeroed();
+ let ret = stat(from_str.as_ptr(), &mut st);
+ if ret != 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+
+ if st.st_size > 128 * 1024 {
+ // Try unlink. If it fails, we are going to try clonefile() anyway.
+ let _ = unlink(to_str.as_ptr());
+ // Matches rust stdlib behavior for io::copy.
+ // https://github.com/rust-lang/rust/blob/3fdd578d72a24d4efc2fe2ad18eec3b6ba72271e/library/std/src/sys/unix/fs.rs#L1613-L1616
+ if clonefile(from_str.as_ptr(), to_str.as_ptr(), 0) == 0 {
+ return Ok(());
+ }
+ } else {
+ // Do a regular copy. fcopyfile() is an overkill for < 128KB
+ // files.
+ let mut buf = [0u8; 128 * 1024];
+ let mut from_file = fs::File::open(&from)?;
+ let perm = from_file.metadata()?.permissions();
+
+ let mut to_file = fs::OpenOptions::new()
+ // create the file with the correct mode right away
+ .mode(perm.mode())
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(&to)?;
+ let writer_metadata = to_file.metadata()?;
+ if writer_metadata.is_file() {
+ // Set the correct file permissions, in case the file already existed.
+ // Don't set the permissions on already existing non-files like
+ // pipes/FIFOs or device nodes.
+ to_file.set_permissions(perm)?;
+ }
+ loop {
+ let nread = from_file.read(&mut buf)?;
+ if nread == 0 {
+ break;
+ }
+ to_file.write_all(&buf[..nread])?;
+ }
+ return Ok(());
+ }
+ }
+
+ // clonefile() failed, fall back to std::fs::copy().
+ }
+
+ fs::copy(from, to)?;
+
+ Ok(())
+}
+
+#[cfg(not(windows))]
+fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::metadata(path)?;
+ Ok(metadata_to_fsstat(metadata))
+}
+
+#[cfg(windows)]
+fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::metadata(path.as_ref())?;
+ let mut fsstat = metadata_to_fsstat(metadata);
+ use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
+ let path = path.as_ref().canonicalize()?;
+ stat_extra(&mut fsstat, &path, FILE_FLAG_BACKUP_SEMANTICS)?;
+ Ok(fsstat)
+}
+
+#[cfg(not(windows))]
+fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::symlink_metadata(path)?;
+ Ok(metadata_to_fsstat(metadata))
+}
+
+#[cfg(windows)]
+fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
+ let metadata = fs::symlink_metadata(path.as_ref())?;
+ let mut fsstat = metadata_to_fsstat(metadata);
+ use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
+ use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
+ stat_extra(
+ &mut fsstat,
+ path.as_ref(),
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ )?;
+ Ok(fsstat)
+}
+
+#[cfg(windows)]
+fn stat_extra(
+ fsstat: &mut FsStat,
+ path: &Path,
+ file_flags: winapi::shared::minwindef::DWORD,
+) -> FsResult<()> {
+ use std::os::windows::prelude::OsStrExt;
+
+ use winapi::um::fileapi::CreateFileW;
+ use winapi::um::fileapi::OPEN_EXISTING;
+ use winapi::um::handleapi::CloseHandle;
+ use winapi::um::handleapi::INVALID_HANDLE_VALUE;
+ use winapi::um::winnt::FILE_SHARE_DELETE;
+ use winapi::um::winnt::FILE_SHARE_READ;
+ use winapi::um::winnt::FILE_SHARE_WRITE;
+
+ unsafe fn get_dev(
+ handle: winapi::shared::ntdef::HANDLE,
+ ) -> std::io::Result<u64> {
+ use winapi::shared::minwindef::FALSE;
+ use winapi::um::fileapi::GetFileInformationByHandle;
+ use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION;
+
+ let info = {
+ let mut info =
+ std::mem::MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::zeroed();
+ if GetFileInformationByHandle(handle, info.as_mut_ptr()) == FALSE {
+ return Err(std::io::Error::last_os_error());
+ }
+
+ info.assume_init()
+ };
+
+ Ok(info.dwVolumeSerialNumber as u64)
+ }
+
+ // SAFETY: winapi calls
+ unsafe {
+ let mut path: Vec<_> = path.as_os_str().encode_wide().collect();
+ path.push(0);
+ let file_handle = CreateFileW(
+ path.as_ptr(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ std::ptr::null_mut(),
+ OPEN_EXISTING,
+ file_flags,
+ std::ptr::null_mut(),
+ );
+ if file_handle == INVALID_HANDLE_VALUE {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ let result = get_dev(file_handle);
+ CloseHandle(file_handle);
+ fsstat.dev = result?;
+
+ Ok(())
+ }
+}
+
+#[inline(always)]
+fn metadata_to_fsstat(metadata: fs::Metadata) -> FsStat {
+ macro_rules! unix_or_zero {
+ ($member:ident) => {{
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::MetadataExt;
+ metadata.$member()
+ }
+ #[cfg(not(unix))]
+ {
+ 0
+ }
+ }};
+ }
+
+ #[inline(always)]
+ fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Option<u64> {
+ match maybe_time {
+ Ok(time) => Some(
+ time
+ .duration_since(UNIX_EPOCH)
+ .map(|t| t.as_millis() as u64)
+ .unwrap_or_else(|err| err.duration().as_millis() as u64),
+ ),
+ Err(_) => None,
+ }
+ }
+
+ FsStat {
+ is_file: metadata.is_file(),
+ is_directory: metadata.is_dir(),
+ is_symlink: metadata.file_type().is_symlink(),
+ size: metadata.len(),
+
+ mtime: to_msec(metadata.modified()),
+ atime: to_msec(metadata.accessed()),
+ birthtime: to_msec(metadata.created()),
+
+ dev: unix_or_zero!(dev),
+ ino: unix_or_zero!(ino),
+ mode: unix_or_zero!(mode),
+ nlink: unix_or_zero!(nlink),
+ uid: unix_or_zero!(uid),
+ gid: unix_or_zero!(gid),
+ rdev: unix_or_zero!(rdev),
+ blksize: unix_or_zero!(blksize),
+ blocks: unix_or_zero!(blocks),
+ }
+}
+
+fn realpath(path: impl AsRef<Path>) -> FsResult<PathBuf> {
+ let canonicalized_path = path.as_ref().canonicalize()?;
+ #[cfg(windows)]
+ let canonicalized_path = PathBuf::from(
+ canonicalized_path
+ .display()
+ .to_string()
+ .trim_start_matches("\\\\?\\"),
+ );
+ Ok(canonicalized_path)
+}
+
+fn read_dir(path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
+ let entries = fs::read_dir(path)?
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ let name = entry.file_name().into_string().ok()?;
+ let metadata = entry.file_type();
+ macro_rules! method_or_false {
+ ($method:ident) => {
+ if let Ok(metadata) = &metadata {
+ metadata.$method()
+ } else {
+ false
+ }
+ };
+ }
+ Some(FsDirEntry {
+ name,
+ is_file: method_or_false!(is_file),
+ is_directory: method_or_false!(is_dir),
+ is_symlink: method_or_false!(is_symlink),
+ })
+ })
+ .collect();
+
+ Ok(entries)
+}
+
+#[cfg(not(windows))]
+fn symlink(
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ _file_type: Option<FsFileType>,
+) -> FsResult<()> {
+ std::os::unix::fs::symlink(oldpath.as_ref(), newpath.as_ref())?;
+ Ok(())
+}
+
+#[cfg(windows)]
+fn symlink(
+ oldpath: impl AsRef<Path>,
+ newpath: impl AsRef<Path>,
+ file_type: Option<FsFileType>,
+) -> FsResult<()> {
+ let file_type = match file_type {
+ Some(file_type) => file_type,
+ None => {
+ let old_meta = fs::metadata(&oldpath);
+ match old_meta {
+ Ok(metadata) => {
+ if metadata.is_file() {
+ FsFileType::File
+ } else if metadata.is_dir() {
+ FsFileType::Directory
+ } else {
+ return Err(FsError::Io(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "On Windows the target must be a file or directory",
+ )));
+ }
+ }
+ Err(err) if err.kind() == io::ErrorKind::NotFound => {
+ return Err(FsError::Io(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "On Windows an `options` argument is required if the target does not exist",
+ )))
+ }
+ Err(err) => return Err(err.into()),
+ }
+ }
+ };
+
+ match file_type {
+ FsFileType::File => {
+ std::os::windows::fs::symlink_file(&oldpath, &newpath)?;
+ }
+ FsFileType::Directory => {
+ std::os::windows::fs::symlink_dir(&oldpath, &newpath)?;
+ }
+ };
+
+ Ok(())
+}
+
+fn truncate(path: impl AsRef<Path>, len: u64) -> FsResult<()> {
+ let file = fs::OpenOptions::new().write(true).open(path)?;
+ file.set_len(len)?;
+ Ok(())
+}
+
+fn open_options(options: OpenOptions) -> fs::OpenOptions {
+ let mut open_options = fs::OpenOptions::new();
+ if let Some(mode) = options.mode {
+ // mode only used if creating the file on Unix
+ // if not specified, defaults to 0o666
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::OpenOptionsExt;
+ open_options.mode(mode & 0o777);
+ }
+ #[cfg(not(unix))]
+ let _ = mode; // avoid unused warning
+ }
+ open_options.read(options.read);
+ open_options.create(options.create);
+ open_options.write(options.write);
+ open_options.truncate(options.truncate);
+ open_options.append(options.append);
+ open_options.create_new(options.create_new);
+ open_options
+}
+
+fn sync<T>(
+ resource: Rc<StdFileResource>,
+ f: impl FnOnce(&mut fs::File) -> io::Result<T>,
+) -> FsResult<T> {
+ let res = resource
+ .with_file2(|file| f(file))
+ .ok_or(FsError::FileBusy)??;
+ Ok(res)
+}
+
+async fn nonblocking<T: Send + 'static>(
+ resource: Rc<StdFileResource>,
+ f: impl FnOnce(&mut fs::File) -> io::Result<T> + Send + 'static,
+) -> FsResult<T> {
+ let res = resource.with_file_blocking_task2(f).await?;
+ Ok(res)
+}
+
+#[async_trait::async_trait(?Send)]
+impl File for StdFileResource {
+ fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()> {
+ sync(self, |file| file.write_all(buf))
+ }
+ async fn write_all_async(self: Rc<Self>, buf: Vec<u8>) -> FsResult<()> {
+ nonblocking(self, move |file| file.write_all(&buf)).await
+ }
+
+ fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>> {
+ sync(self, |file| {
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf)?;
+ Ok(buf)
+ })
+ }
+ async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>> {
+ nonblocking(self, |file| {
+ let mut buf = Vec::new();
+ file.read_to_end(&mut buf)?;
+ Ok(buf)
+ })
+ .await
+ }
+
+ fn chmod_sync(self: Rc<Self>, mode: u32) -> FsResult<()> {
+ #[cfg(unix)]
+ {
+ sync(self, |file| {
+ use std::os::unix::prelude::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))
+ })
+ }
+ #[cfg(not(unix))]
+ Err(FsError::NotSupported)
+ }
+
+ async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()> {
+ #[cfg(unix)]
+ {
+ nonblocking(self, move |file| {
+ use std::os::unix::prelude::PermissionsExt;
+ file.set_permissions(fs::Permissions::from_mode(mode))
+ })
+ .await
+ }
+ #[cfg(not(unix))]
+ Err(FsError::NotSupported)
+ }
+
+ fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
+ sync(self, |file| file.seek(pos))
+ }
+ async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
+ nonblocking(self, move |file| file.seek(pos)).await
+ }
+
+ fn datasync_sync(self: Rc<Self>) -> FsResult<()> {
+ sync(self, |file| file.sync_data())
+ }
+ async fn datasync_async(self: Rc<Self>) -> FsResult<()> {
+ nonblocking(self, |file| file.sync_data()).await
+ }
+
+ fn sync_sync(self: Rc<Self>) -> FsResult<()> {
+ sync(self, |file| file.sync_all())
+ }
+ async fn sync_async(self: Rc<Self>) -> FsResult<()> {
+ nonblocking(self, |file| file.sync_all()).await
+ }
+
+ fn stat_sync(self: Rc<Self>) -> FsResult<FsStat> {
+ sync(self, |file| file.metadata().map(metadata_to_fsstat))
+ }
+ async fn stat_async(self: Rc<Self>) -> FsResult<FsStat> {
+ nonblocking(self, |file| file.metadata().map(metadata_to_fsstat)).await
+ }
+
+ fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
+ sync(self, |file| {
+ if exclusive {
+ file.lock_exclusive()
+ } else {
+ file.lock_shared()
+ }
+ })
+ }
+ async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
+ nonblocking(self, move |file| {
+ if exclusive {
+ file.lock_exclusive()
+ } else {
+ file.lock_shared()
+ }
+ })
+ .await
+ }
+
+ fn unlock_sync(self: Rc<Self>) -> FsResult<()> {
+ sync(self, |file| file.unlock())
+ }
+ async fn unlock_async(self: Rc<Self>) -> FsResult<()> {
+ nonblocking(self, |file| file.unlock()).await
+ }
+
+ fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()> {
+ sync(self, |file| file.set_len(len))
+ }
+ async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()> {
+ nonblocking(self, move |file| file.set_len(len)).await
+ }
+
+ fn utime_sync(
+ self: Rc<Self>,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
+ let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
+ sync(self, |file| {
+ filetime::set_file_handle_times(file, Some(atime), Some(mtime))
+ })
+ }
+ async fn utime_async(
+ self: Rc<Self>,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
+ let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
+ nonblocking(self, move |file| {
+ filetime::set_file_handle_times(file, Some(atime), Some(mtime))
+ })
+ .await
+ }
+}
diff --git a/ext/io/lib.rs b/ext/io/lib.rs
index 69f8c9da5..c85b4baf6 100644
--- a/ext/io/lib.rs
+++ b/ext/io/lib.rs
@@ -20,6 +20,7 @@ use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fs::File as StdFile;
+use std::io;
use std::io::ErrorKind;
use std::io::Read;
use std::io::Write;
@@ -452,21 +453,21 @@ impl StdFileResource {
}
}
- fn with_inner_and_metadata<TResult>(
+ fn with_inner_and_metadata<TResult, E>(
&self,
action: impl FnOnce(
&mut StdFileResourceInner,
&Arc<Mutex<FileMetadata>>,
- ) -> Result<TResult, AnyError>,
- ) -> Result<TResult, AnyError> {
+ ) -> Result<TResult, E>,
+ ) -> Option<Result<TResult, E>> {
match self.cell.try_borrow_mut() {
Ok(mut cell) => {
let mut file = cell.take().unwrap();
let result = action(&mut file.inner, &file.meta_data);
cell.replace(file);
- result
+ Some(result)
}
- Err(_) => Err(resource_unavailable()),
+ Err(_) => None,
}
}
@@ -537,11 +538,16 @@ impl StdFileResource {
}
fn read_byob_sync(&self, buf: &mut [u8]) -> Result<usize, AnyError> {
- self.with_inner_and_metadata(|inner, _| inner.read(buf).map_err(Into::into))
+ self
+ .with_inner_and_metadata(|inner, _| inner.read(buf))
+ .ok_or_else(resource_unavailable)?
+ .map_err(Into::into)
}
fn write_sync(&self, data: &[u8]) -> Result<usize, AnyError> {
- self.with_inner_and_metadata(|inner, _| inner.write_and_maybe_flush(data))
+ self
+ .with_inner_and_metadata(|inner, _| inner.write_and_maybe_flush(data))
+ .ok_or_else(resource_unavailable)?
}
fn with_resource<F, R>(
@@ -565,10 +571,19 @@ impl StdFileResource {
F: FnOnce(&mut StdFile) -> Result<R, AnyError>,
{
Self::with_resource(state, rid, move |resource| {
- resource.with_inner_and_metadata(move |inner, _| inner.with_file(f))
+ resource
+ .with_inner_and_metadata(move |inner, _| inner.with_file(f))
+ .ok_or_else(resource_unavailable)?
})
}
+ pub fn with_file2<F, R>(self: Rc<Self>, f: F) -> Option<Result<R, io::Error>>
+ where
+ F: FnOnce(&mut StdFile) -> Result<R, io::Error>,
+ {
+ self.with_inner_and_metadata(move |inner, _| inner.with_file(f))
+ }
+
pub fn with_file_and_metadata<F, R>(
state: &mut OpState,
rid: ResourceId,
@@ -578,9 +593,11 @@ impl StdFileResource {
F: FnOnce(&mut StdFile, &Arc<Mutex<FileMetadata>>) -> Result<R, AnyError>,
{
Self::with_resource(state, rid, move |resource| {
- resource.with_inner_and_metadata(move |inner, metadata| {
- inner.with_file(move |file| f(file, metadata))
- })
+ resource
+ .with_inner_and_metadata(move |inner, metadata| {
+ inner.with_file(move |file| f(file, metadata))
+ })
+ .ok_or_else(resource_unavailable)?
})
}
@@ -602,6 +619,18 @@ impl StdFileResource {
.await
}
+ pub async fn with_file_blocking_task2<F, R: Send + 'static>(
+ self: Rc<Self>,
+ f: F,
+ ) -> Result<R, io::Error>
+ where
+ F: (FnOnce(&mut StdFile) -> Result<R, io::Error>) + Send + 'static,
+ {
+ self
+ .with_inner_blocking_task(move |inner| inner.with_file(f))
+ .await
+ }
+
pub fn clone_file(
state: &mut OpState,
rid: ResourceId,
@@ -616,13 +645,15 @@ impl StdFileResource {
rid: u32,
) -> Result<std::process::Stdio, AnyError> {
Self::with_resource(state, rid, |resource| {
- resource.with_inner_and_metadata(|inner, _| match inner.kind {
- StdFileResourceKind::File => {
- let file = inner.file.try_clone()?;
- Ok(file.into())
- }
- _ => Ok(std::process::Stdio::inherit()),
- })
+ resource
+ .with_inner_and_metadata(|inner, _| match inner.kind {
+ StdFileResourceKind::File => {
+ let file = inner.file.try_clone()?;
+ Ok(file.into())
+ }
+ _ => Ok(std::process::Stdio::inherit()),
+ })
+ .ok_or_else(resource_unavailable)?
})
}
}
@@ -679,8 +710,8 @@ impl Resource for StdFileResource {
use std::os::unix::io::AsRawFd;
self
.with_inner_and_metadata(move |std_file, _| {
- Ok(std_file.with_file(|f| f.as_raw_fd()))
- })
+ Ok::<_, ()>(std_file.with_file(|f| f.as_raw_fd()))
+ })?
.ok()
}
}
@@ -694,9 +725,11 @@ pub fn op_print(
) -> Result<(), AnyError> {
let rid = if is_err { 2 } else { 1 };
StdFileResource::with_resource(state, rid, move |resource| {
- resource.with_inner_and_metadata(|inner, _| {
- inner.write_all_and_maybe_flush(msg.as_bytes())?;
- Ok(())
- })
+ resource
+ .with_inner_and_metadata(|inner, _| {
+ inner.write_all_and_maybe_flush(msg.as_bytes())?;
+ Ok(())
+ })
+ .ok_or_else(resource_unavailable)?
})
}
diff --git a/runtime/build.rs b/runtime/build.rs
index eb8cc34a6..d47bee941 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -18,6 +18,7 @@ mod startup_snapshot {
use deno_core::Extension;
use deno_core::ExtensionFileSource;
use deno_core::ModuleCode;
+ use deno_fs::StdFs;
use std::path::Path;
fn transpile_ts_for_snapshotting(
@@ -164,6 +165,10 @@ mod startup_snapshot {
unreachable!("snapshotting!")
}
+ fn check_read_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
+ unreachable!("snapshotting!")
+ }
+
fn check_read_blind(
&mut self,
_path: &Path,
@@ -181,11 +186,16 @@ mod startup_snapshot {
unreachable!("snapshotting!")
}
- fn check_read_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
+ fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
unreachable!("snapshotting!")
}
- fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
+ fn check_write_blind(
+ &mut self,
+ _path: &Path,
+ _display: &str,
+ _api_name: &str,
+ ) -> Result<(), AnyError> {
unreachable!("snapshotting!")
}
}
@@ -310,7 +320,7 @@ mod startup_snapshot {
deno_napi::deno_napi::init_ops_and_esm::<Permissions>(),
deno_http::deno_http::init_ops_and_esm(),
deno_io::deno_io::init_ops_and_esm(Default::default()),
- deno_fs::deno_fs::init_ops_and_esm::<Permissions>(false),
+ deno_fs::deno_fs::init_ops_and_esm::<_, Permissions>(false, StdFs),
runtime::init_ops_and_esm(),
// FIXME(bartlomieju): these extensions are specified last, because they
// depend on `runtime`, even though it should be other way around
diff --git a/runtime/permissions/mod.rs b/runtime/permissions/mod.rs
index 7e1772ee3..b15750313 100644
--- a/runtime/permissions/mod.rs
+++ b/runtime/permissions/mod.rs
@@ -667,6 +667,40 @@ impl UnaryPermission<WriteDescriptor> {
}
result
}
+
+ /// As `check()`, but permission error messages will anonymize the path
+ /// by replacing it with the given `display`.
+ pub fn check_blind(
+ &mut self,
+ path: &Path,
+ display: &str,
+ api_name: &str,
+ ) -> Result<(), AnyError> {
+ let resolved_path = resolve_from_cwd(path)?;
+ let (result, prompted, is_allow_all) =
+ self.query(Some(&resolved_path)).check(
+ self.name,
+ Some(api_name),
+ Some(&format!("<{display}>")),
+ self.prompt,
+ );
+ if prompted {
+ if result.is_ok() {
+ if is_allow_all {
+ self.granted_list.clear();
+ self.global_state = PermissionState::Granted;
+ } else {
+ self.granted_list.insert(WriteDescriptor(resolved_path));
+ }
+ } else {
+ self.global_state = PermissionState::Denied;
+ if !is_allow_all {
+ self.denied_list.insert(WriteDescriptor(resolved_path));
+ }
+ }
+ }
+ result
+ }
}
impl Default for UnaryPermission<WriteDescriptor> {
@@ -1793,6 +1827,16 @@ impl PermissionsContainer {
}
#[inline(always)]
+ pub fn check_write_blind(
+ &mut self,
+ path: &Path,
+ display: &str,
+ api_name: &str,
+ ) -> Result<(), AnyError> {
+ self.0.lock().write.check_blind(path, display, api_name)
+ }
+
+ #[inline(always)]
pub fn check_run(
&mut self,
cmd: &str,
@@ -1931,6 +1975,15 @@ impl deno_fs::FsPermissions for PermissionsContainer {
self.0.lock().write.check(path, Some(api_name))
}
+ fn check_write_blind(
+ &mut self,
+ p: &Path,
+ display: &str,
+ api_name: &str,
+ ) -> Result<(), AnyError> {
+ self.0.lock().write.check_blind(p, display, api_name)
+ }
+
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError> {
self.0.lock().read.check_all(Some(api_name))
}
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index 4be40c9b0..0d743cfc6 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -34,6 +34,7 @@ use deno_core::RuntimeOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::Snapshot;
use deno_core::SourceMapGetter;
+use deno_fs::StdFs;
use deno_io::Stdio;
use deno_kv::sqlite::SqliteDbHandler;
use deno_node::RequireNpmResolver;
@@ -440,7 +441,7 @@ impl WebWorker {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Some(options.stdio)),
- deno_fs::deno_fs::init_ops::<PermissionsContainer>(unstable),
+ deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs),
deno_node::deno_node::init_ops::<crate::RuntimeNodeEnv>(
options.npm_resolver,
),
diff --git a/runtime/worker.rs b/runtime/worker.rs
index ea1e5e046..14abd12b5 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -30,6 +30,7 @@ use deno_core::RuntimeOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::Snapshot;
use deno_core::SourceMapGetter;
+use deno_fs::StdFs;
use deno_io::Stdio;
use deno_kv::sqlite::SqliteDbHandler;
use deno_node::RequireNpmResolver;
@@ -264,7 +265,7 @@ impl MainWorker {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Some(options.stdio)),
- deno_fs::deno_fs::init_ops::<PermissionsContainer>(unstable),
+ deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs),
deno_node::deno_node::init_ops::<crate::RuntimeNodeEnv>(
options.npm_resolver,
),