From f90caa821c5a4acf28f7dec4071994ecf6f26e57 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Wed, 12 Apr 2023 15:13:32 +0200 Subject: [PATCH] refactor(ext/fs): abstract FS via FileSystem trait (#18599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Cargo.lock | 3 +- cli/build.rs | 3 +- core/extensions.rs | 16 +- ext/fs/30_fs.js | 92 +- ext/fs/Cargo.toml | 3 +- ext/fs/clippy.toml | 45 + ext/fs/interface.rs | 395 ++++++ ext/fs/lib.rs | 2484 ++---------------------------------- ext/fs/ops.rs | 1712 +++++++++++++++++++++++++ ext/fs/std_fs.rs | 929 ++++++++++++++ ext/io/lib.rs | 81 +- runtime/build.rs | 16 +- runtime/permissions/mod.rs | 53 + runtime/web_worker.rs | 3 +- runtime/worker.rs | 3 +- 15 files changed, 3350 insertions(+), 2488 deletions(-) create mode 100644 ext/fs/clippy.toml create mode 100644 ext/fs/interface.rs create mode 100644 ext/fs/ops.rs create mode 100644 ext/fs/std_fs.rs diff --git a/Cargo.lock b/Cargo.lock index d6874d452f..7eca48a2c8 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 ddd942593f..6d13f9a4d6 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::(), deno_http::deno_http::init_ops(), deno_io::deno_io::init_ops(Default::default()), - deno_fs::deno_fs::init_ops::(false), + deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(false, StdFs), deno_node::deno_node::init_ops::(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 4a7b494146..685ac0ab7c 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 1421de9eb1..bddafb09ee 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 69b32c1630..016d73ae69 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 0000000000..53676a90e6 --- /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 0000000000..a682600519 --- /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, +} + +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, + ) -> Self { + Self { + read: false, + write: true, + create, + truncate: !append, + append, + create_new, + mode, + } + } + + pub(crate) fn check( + &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, + pub atime: Option, + pub birthtime: Option, + + 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 for FsError { + fn from(err: io::Error) -> Self { + Self::Io(err) + } +} + +impl From 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 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 = Result; + +#[async_trait::async_trait(?Send)] +pub trait File { + fn write_all_sync(self: Rc, buf: &[u8]) -> FsResult<()>; + async fn write_all_async(self: Rc, buf: Vec) -> FsResult<()>; + + fn read_all_sync(self: Rc) -> FsResult>; + async fn read_all_async(self: Rc) -> FsResult>; + + fn chmod_sync(self: Rc, pathmode: u32) -> FsResult<()>; + async fn chmod_async(self: Rc, mode: u32) -> FsResult<()>; + + fn seek_sync(self: Rc, pos: io::SeekFrom) -> FsResult; + async fn seek_async(self: Rc, pos: io::SeekFrom) -> FsResult; + + fn datasync_sync(self: Rc) -> FsResult<()>; + async fn datasync_async(self: Rc) -> FsResult<()>; + + fn sync_sync(self: Rc) -> FsResult<()>; + async fn sync_async(self: Rc) -> FsResult<()>; + + fn stat_sync(self: Rc) -> FsResult; + async fn stat_async(self: Rc) -> FsResult; + + fn lock_sync(self: Rc, exclusive: bool) -> FsResult<()>; + async fn lock_async(self: Rc, exclusive: bool) -> FsResult<()>; + fn unlock_sync(self: Rc) -> FsResult<()>; + async fn unlock_async(self: Rc) -> FsResult<()>; + + fn truncate_sync(self: Rc, len: u64) -> FsResult<()>; + async fn truncate_async(self: Rc, len: u64) -> FsResult<()>; + + fn utime_sync( + self: Rc, + atime_secs: i64, + atime_nanos: u32, + mtime_secs: i64, + mtime_nanos: u32, + ) -> FsResult<()>; + async fn utime_async( + self: Rc, + 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; + fn tmp_dir(&self) -> FsResult; + fn chdir(&self, path: impl AsRef) -> FsResult<()>; + fn umask(&self, mask: Option) -> FsResult; + + fn open_sync( + &self, + path: impl AsRef, + options: OpenOptions, + ) -> FsResult; + async fn open_async( + &self, + path: PathBuf, + options: OpenOptions, + ) -> FsResult; + + fn mkdir_sync( + &self, + path: impl AsRef, + recusive: bool, + mode: u32, + ) -> FsResult<()>; + async fn mkdir_async( + &self, + path: PathBuf, + recusive: bool, + mode: u32, + ) -> FsResult<()>; + + fn chmod_sync(&self, path: impl AsRef, mode: u32) -> FsResult<()>; + async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()>; + + fn chown_sync( + &self, + path: impl AsRef, + uid: Option, + gid: Option, + ) -> FsResult<()>; + async fn chown_async( + &self, + path: PathBuf, + uid: Option, + gid: Option, + ) -> FsResult<()>; + + fn remove_sync( + &self, + path: impl AsRef, + recursive: bool, + ) -> FsResult<()>; + async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()>; + + fn copy_file_sync( + &self, + oldpath: impl AsRef, + newpath: impl AsRef, + ) -> FsResult<()>; + async fn copy_file_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + ) -> FsResult<()>; + + fn stat_sync(&self, path: impl AsRef) -> FsResult; + async fn stat_async(&self, path: PathBuf) -> FsResult; + + fn lstat_sync(&self, path: impl AsRef) -> FsResult; + async fn lstat_async(&self, path: PathBuf) -> FsResult; + + fn realpath_sync(&self, path: impl AsRef) -> FsResult; + async fn realpath_async(&self, path: PathBuf) -> FsResult; + + fn read_dir_sync(&self, path: impl AsRef) -> FsResult>; + async fn read_dir_async(&self, path: PathBuf) -> FsResult>; + + fn rename_sync( + &self, + oldpath: impl AsRef, + newpath: impl AsRef, + ) -> FsResult<()>; + async fn rename_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + ) -> FsResult<()>; + + fn link_sync( + &self, + oldpath: impl AsRef, + newpath: impl AsRef, + ) -> FsResult<()>; + async fn link_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + ) -> FsResult<()>; + + fn symlink_sync( + &self, + oldpath: impl AsRef, + newpath: impl AsRef, + file_type: Option, + ) -> FsResult<()>; + async fn symlink_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + file_type: Option, + ) -> FsResult<()>; + + fn read_link_sync(&self, path: impl AsRef) -> FsResult; + async fn read_link_async(&self, path: PathBuf) -> FsResult; + + fn truncate_sync(&self, path: impl AsRef, len: u64) -> FsResult<()>; + async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()>; + + fn utime_sync( + &self, + path: impl AsRef, + 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, + 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, + ) -> 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) -> FsResult> { + 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> { + 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 c00f395997..c1418815c6 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 { - 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 { - 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::().check_unstable(api_name) } /// Helper for checking unstable features. Used for async ops. -fn check_unstable2(state: &Rc>, api_name: &str) { +pub(crate) fn check_unstable2(state: &Rc>, api_name: &str) { let state = state.borrow(); state.borrow::().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

, - op_open_async

, - op_write_file_sync

, - op_write_file_async

, - 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

, - op_mkdir_sync

, - op_mkdir_async

, - op_chmod_sync

, - op_chmod_async

, - op_chown_sync

, - op_chown_async

, - op_remove_sync

, - op_remove_async

, - op_copy_file_sync

, - op_copy_file_async

, - op_stat_sync

, - op_stat_async

, - op_realpath_sync

, - op_realpath_async

, - op_read_dir_sync

, - op_read_dir_async

, - op_rename_sync

, - op_rename_async

, - op_link_sync

, - op_link_async

, - op_symlink_sync

, - op_symlink_async

, - op_read_link_sync

, - op_read_link_async

, - op_ftruncate_sync, - op_ftruncate_async, - op_truncate_sync

, - op_truncate_async

, - op_make_temp_dir_sync

, - op_make_temp_dir_async

, - op_make_temp_file_sync

, - op_make_temp_file_async

, - op_cwd

, - op_futime_sync, - op_futime_async, - op_utime_sync

, - op_utime_async

, - op_readfile_sync

, - op_readfile_text_sync

, - op_readfile_async

, - op_readfile_text_async

, + op_cwd, + op_umask, + op_chdir, + + op_open_sync, + op_open_async, + op_mkdir_sync, + op_mkdir_async, + op_chmod_sync, + op_chmod_async, + op_chown_sync, + op_chown_async, + op_remove_sync, + op_remove_async, + op_copy_file_sync, + op_copy_file_async, + op_stat_sync, + op_stat_async, + op_lstat_sync, + op_lstat_async, + op_realpath_sync, + op_realpath_async, + op_read_dir_sync, + op_read_dir_async, + op_rename_sync, + op_rename_async, + op_link_sync, + op_link_async, + op_symlink_sync, + op_symlink_async, + op_read_link_sync, + op_read_link_async, + op_truncate_sync, + op_truncate_async, + op_utime_sync, + op_utime_async, + op_make_temp_dir_sync, + op_make_temp_dir_async, + op_make_temp_file_sync, + op_make_temp_file_async, + op_write_file_sync, + op_write_file_async, + op_read_file_sync, + op_read_file_async, + op_read_file_text_sync, + op_read_file_text_async, + + 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_ftruncate_sync, + op_ftruncate_async, + op_futime_sync, + op_futime_async, + ], 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

( - state: &mut OpState, - path: &str, - mode: Option, - 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::

(); - - 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

( - state: &mut OpState, - path: String, - options: Option, - mode: Option, -) -> Result -where - P: FsPermissions + 'static, -{ - let (path, open_options) = - open_helper::

(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

( - state: Rc>, - path: String, - options: Option, - mode: Option, -) -> Result -where - P: FsPermissions + 'static, -{ - let (path, open_options) = open_helper::

( - &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

( - state: &mut OpState, - path: String, - mode: Option, - append: bool, - create: bool, - create_new: bool, - data: ZeroCopyBuf, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let (path, open_options) = open_helper::

( - 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

( - state: Rc>, - path: String, - mode: Option, - append: bool, - create: bool, - create_new: bool, - data: ZeroCopyBuf, - cancel_rid: Option, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let (path, open_options) = open_helper::

( - &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::(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, - 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 { - 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>, - args: SeekArgs, -) -> Result { - 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>, - 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>, - 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>, - rid: ResourceId, -) -> Result { - 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>, - 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>, - 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) -> Result { - 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

(state: &mut OpState, directory: &str) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let d = PathBuf::from(&directory); - state.borrow_mut::

().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, -} - -#[op] -fn op_mkdir_sync

( - 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::

() - .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

( - state: Rc>, - 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::

().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

( - 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::

() - .check_write(path, "Deno.chmodSync()")?; - raw_chmod(path, mode) -} - -#[op] -async fn op_chmod_async

( - state: Rc>, - 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::

().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

( - state: &mut OpState, - path: &str, - #[cfg_attr(windows, allow(unused_variables))] uid: Option, - #[cfg_attr(windows, allow(unused_variables))] gid: Option, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(path).to_path_buf(); - state - .borrow_mut::

() - .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

( - state: Rc>, - path: String, - #[cfg_attr(windows, allow(unused_variables))] uid: Option, - #[cfg_attr(windows, allow(unused_variables))] gid: Option, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(&path).to_path_buf(); - - { - let mut state = state.borrow_mut(); - state.borrow_mut::

().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

( - state: &mut OpState, - path: &str, - recursive: bool, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(path); - - state - .borrow_mut::

() - .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

( - state: Rc>, - path: String, - recursive: bool, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - { - let mut state = state.borrow_mut(); - state - .borrow_mut::

() - .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

( - 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::

(); - 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

( - state: Rc>, - 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::

(); - 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) -> (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 { - 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 { - 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 { - use winapi::shared::minwindef::FALSE; - - let info = { - let mut info = - std::mem::MaybeUninit::::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

( - 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::

() - .check_read(&path, "Deno.statSync()")?; - - let stat = do_stat(path, lstat)?; - stat.write(out_buf); - - Ok(()) -} - -#[op] -async fn op_stat_async

( - state: Rc>, - args: StatArgs, -) -> Result -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&args.path); - let lstat = args.lstat; - - { - let mut state = state.borrow_mut(); - state.borrow_mut::

().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

( - state: &mut OpState, - path: String, -) -> Result -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - let permissions = state.borrow_mut::

(); - permissions.check_read(&path, "Deno.realPathSync()")?; - if path.is_relative() { - permissions.check_read_blind( - ¤t_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

( - state: Rc>, - path: String, -) -> Result -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::

(); - permissions.check_read(&path, "Deno.realPath()")?; - if path.is_relative() { - permissions.check_read_blind( - ¤t_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

( - state: &mut OpState, - path: String, -) -> Result, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - state - .borrow_mut::

() - .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

( - state: Rc>, - path: String, -) -> Result, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - { - let mut state = state.borrow_mut(); - state - .borrow_mut::

() - .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

( - 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::

(); - 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

( - state: Rc>, - 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::

(); - 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

( - 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::

(); - 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

( - state: Rc>, - 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::

(); - 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

( - state: &mut OpState, - oldpath: &str, - newpath: &str, - _type: Option, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let oldpath = PathBuf::from(oldpath); - let newpath = PathBuf::from(newpath); - - state - .borrow_mut::

() - .check_write_all("Deno.symlinkSync()")?; - state - .borrow_mut::

() - .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

( - state: Rc>, - oldpath: String, - newpath: String, - _type: Option, -) -> 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::

().check_write_all("Deno.symlink()")?; - state.borrow_mut::

().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

( - state: &mut OpState, - path: String, -) -> Result -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - state - .borrow_mut::

() - .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

( - state: Rc>, - path: String, -) -> Result -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - { - let mut state = state.borrow_mut(); - state - .borrow_mut::

() - .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>, - 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

( - state: &mut OpState, - path: &str, - len: u64, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(path); - - state - .borrow_mut::

() - .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

( - state: Rc>, - path: String, - len: u64, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - { - let mut state = state.borrow_mut(); - state - .borrow_mut::

() - .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 { - 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::(); - 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, - prefix: Option, - suffix: Option, -} - -#[op] -fn op_make_temp_dir_sync

( - state: &mut OpState, - args: MakeTempArgs, -) -> Result -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::

().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 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

( - state: Rc>, - args: MakeTempArgs, -) -> Result -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::

().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 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

( - state: &mut OpState, - args: MakeTempArgs, -) -> Result -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::

().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 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

( - state: Rc>, - args: MakeTempArgs, -) -> Result -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::

().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 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>, - 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

( - 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::

().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

( - state: Rc>, - 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::

() - .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

(state: &mut OpState) -> Result -where - P: FsPermissions + 'static, -{ - let path = current_dir()?; - state - .borrow_mut::

() - .check_read_blind(&path, "CWD", "Deno.cwd()")?; - let path_str = into_string(path.into_os_string())?; - Ok(path_str) -} - -#[op] -fn op_readfile_sync

( - state: &mut OpState, - path: String, -) -> Result -where - P: FsPermissions + 'static, -{ - let path = Path::new(&path); - state - .borrow_mut::

() - .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

( - state: &mut OpState, - path: String, -) -> Result -where - P: FsPermissions + 'static, -{ - let path = Path::new(&path); - state - .borrow_mut::

() - .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

( - state: Rc>, - path: String, - cancel_rid: Option, -) -> Result -where - P: FsPermissions + 'static, -{ - { - let path = Path::new(&path); - let mut state = state.borrow_mut(); - state - .borrow_mut::

() - .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::(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

( - state: Rc>, - path: String, - cancel_rid: Option, -) -> Result -where - P: FsPermissions + 'static, -{ - { - let path = Path::new(&path); - let mut state = state.borrow_mut(); - state - .borrow_mut::

() - .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::(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) -> 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 0000000000..c1381d89c4 --- /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(state: &mut OpState) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let fs = state.borrow::(); + let path = fs.cwd()?; + state + .borrow_mut::

() + .check_read_blind(&path, "CWD", "Deno.cwd()")?; + let path_str = path_into_string(path.into_os_string())?; + Ok(path_str) +} + +#[op] +fn op_chdir(state: &mut OpState, directory: &str) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let d = PathBuf::from(&directory); + state.borrow_mut::

().check_read(&d, "Deno.chdir()")?; + state.borrow::().chdir(&d).context_path("chdir", &d) +} + +#[op] +fn op_umask(state: &mut OpState, mask: Option) -> Result +where + Fs: FileSystem + 'static, +{ + check_unstable(state, "Deno.umask"); + state.borrow::().umask(mask).context("umask") +} + +#[op] +fn op_open_sync( + state: &mut OpState, + path: String, + options: Option, +) -> Result +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::

(); + options.check(permissions, &path, "Deno.openSync()")?; + + let fs = state.borrow::(); + 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( + state: Rc>, + path: String, + options: Option, +) -> Result +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::

(); + options.check(permissions, &path, "Deno.open()")?; + state.borrow::().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( + state: &mut OpState, + path: String, + recursive: bool, + mode: Option, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + let mode = mode.unwrap_or(0o777) & 0o777; + + state + .borrow_mut::

() + .check_write(&path, "Deno.mkdirSync()")?; + + let fs = state.borrow::(); + fs.mkdir_sync(&path, recursive, mode) + .context_path("mkdir", &path)?; + + Ok(()) +} + +#[op] +async fn op_mkdir_async( + state: Rc>, + path: String, + recursive: bool, + mode: Option, +) -> 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::

().check_write(&path, "Deno.mkdir()")?; + state.borrow::().clone() + }; + + fs.mkdir_async(path.clone(), recursive, mode) + .await + .context_path("mkdir", &path)?; + + Ok(()) +} + +#[op] +fn op_chmod_sync( + state: &mut OpState, + path: String, + mode: u32, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + state + .borrow_mut::

() + .check_write(&path, "Deno.chmodSync()")?; + let fs = state.borrow::(); + fs.chmod_sync(&path, mode).context_path("chmod", &path)?; + Ok(()) +} + +#[op] +async fn op_chmod_async( + state: Rc>, + 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::

().check_write(&path, "Deno.chmod()")?; + state.borrow::().clone() + }; + fs.chmod_async(path.clone(), mode) + .await + .context_path("chmod", &path)?; + Ok(()) +} + +#[op] +fn op_chown_sync( + state: &mut OpState, + path: String, + uid: Option, + gid: Option, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + state + .borrow_mut::

() + .check_write(&path, "Deno.chownSync()")?; + let fs = state.borrow::(); + fs.chown_sync(&path, uid, gid) + .context_path("chown", &path)?; + Ok(()) +} + +#[op] +async fn op_chown_async( + state: Rc>, + path: String, + uid: Option, + gid: Option, +) -> 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::

().check_write(&path, "Deno.chown()")?; + state.borrow::().clone() + }; + fs.chown_async(path.clone(), uid, gid) + .await + .context_path("chown", &path)?; + Ok(()) +} + +#[op] +fn op_remove_sync( + state: &mut OpState, + path: &str, + recursive: bool, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + state + .borrow_mut::

() + .check_write(&path, "Deno.removeSync()")?; + + let fs = state.borrow::(); + fs.remove_sync(&path, recursive) + .context_path("remove", &path)?; + + Ok(()) +} + +#[op] +async fn op_remove_async( + state: Rc>, + 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::

() + .check_write(&path, "Deno.remove()")?; + state.borrow::().clone() + }; + + fs.remove_async(path.clone(), recursive) + .await + .context_path("remove", &path)?; + + Ok(()) +} + +#[op] +fn op_copy_file_sync( + 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::

(); + permissions.check_read(&from, "Deno.copyFileSync()")?; + permissions.check_write(&to, "Deno.copyFileSync()")?; + + let fs = state.borrow::(); + fs.copy_file_sync(&from, &to) + .context_two_path("copy", &from, &to)?; + + Ok(()) +} + +#[op] +async fn op_copy_file_async( + state: Rc>, + 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::

(); + permissions.check_read(&from, "Deno.copyFile()")?; + permissions.check_write(&to, "Deno.copyFile()")?; + state.borrow::().clone() + }; + + fs.copy_file_async(from.clone(), to.clone()) + .await + .context_two_path("copy", &from, &to)?; + + Ok(()) +} + +#[op] +fn op_stat_sync( + 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::

() + .check_read(&path, "Deno.statSync()")?; + let fs = state.borrow::(); + 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( + state: Rc>, + path: String, +) -> Result +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::

(); + permissions.check_read(&path, "Deno.stat()")?; + state.borrow::().clone() + }; + let stat = fs + .stat_async(path.clone()) + .await + .context_path("stat", &path)?; + Ok(SerializableStat::from(stat)) +} + +#[op] +fn op_lstat_sync( + 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::

() + .check_read(&path, "Deno.lstatSync()")?; + let fs = state.borrow::(); + 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( + state: Rc>, + path: String, +) -> Result +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::

(); + permissions.check_read(&path, "Deno.lstat()")?; + state.borrow::().clone() + }; + let stat = fs + .lstat_async(path.clone()) + .await + .context_path("lstat", &path)?; + Ok(SerializableStat::from(stat)) +} + +#[op] +fn op_realpath_sync( + state: &mut OpState, + path: String, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + let fs = state.borrow::().clone(); + let permissions = state.borrow_mut::

(); + 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( + state: Rc>, + path: String, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + let fs; + { + let mut state = state.borrow_mut(); + fs = state.borrow::().clone(); + let permissions = state.borrow_mut::

(); + 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( + state: &mut OpState, + path: String, +) -> Result, AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + state + .borrow_mut::

() + .check_read(&path, "Deno.readDirSync()")?; + + let fs = state.borrow::(); + let entries = fs.read_dir_sync(&path).context_path("readdir", &path)?; + + Ok(entries) +} + +#[op] +async fn op_read_dir_async( + state: Rc>, + path: String, +) -> 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::

() + .check_read(&path, "Deno.readDir()")?; + state.borrow::().clone() + }; + + let entries = fs + .read_dir_async(path.clone()) + .await + .context_path("readdir", &path)?; + + Ok(entries) +} + +#[op] +fn op_rename_sync( + 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::

(); + permissions.check_read(&oldpath, "Deno.renameSync()")?; + permissions.check_write(&oldpath, "Deno.renameSync()")?; + permissions.check_write(&newpath, "Deno.renameSync()")?; + + let fs = state.borrow::(); + fs.rename_sync(&oldpath, &newpath) + .context_two_path("rename", &oldpath, &newpath)?; + + Ok(()) +} + +#[op] +async fn op_rename_async( + state: Rc>, + 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::

(); + permissions.check_read(&oldpath, "Deno.rename()")?; + permissions.check_write(&oldpath, "Deno.rename()")?; + permissions.check_write(&newpath, "Deno.rename()")?; + state.borrow::().clone() + }; + + fs.rename_async(oldpath.clone(), newpath.clone()) + .await + .context_two_path("rename", &oldpath, &newpath)?; + + Ok(()) +} + +#[op] +fn op_link_sync( + 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::

(); + 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.link_sync(&oldpath, &newpath) + .context_two_path("link", &oldpath, &newpath)?; + + Ok(()) +} + +#[op] +async fn op_link_async( + state: Rc>, + 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::

(); + 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::().clone() + }; + + fs.link_async(oldpath.clone(), newpath.clone()) + .await + .context_two_path("link", &oldpath, &newpath)?; + + Ok(()) +} + +#[op] +fn op_symlink_sync( + state: &mut OpState, + oldpath: &str, + newpath: &str, + file_type: Option, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let oldpath = PathBuf::from(oldpath); + let newpath = PathBuf::from(newpath); + + let permissions = state.borrow_mut::

(); + permissions.check_write_all("Deno.symlinkSync()")?; + permissions.check_read_all("Deno.symlinkSync()")?; + + let fs = state.borrow::(); + fs.symlink_sync(&oldpath, &newpath, file_type) + .context_two_path("symlink", &oldpath, &newpath)?; + + Ok(()) +} + +#[op] +async fn op_symlink_async( + state: Rc>, + oldpath: String, + newpath: String, + file_type: Option, +) -> 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::

(); + permissions.check_write_all("Deno.symlink()")?; + permissions.check_read_all("Deno.symlink()")?; + state.borrow::().clone() + }; + + fs.symlink_async(oldpath.clone(), newpath.clone(), file_type) + .await + .context_two_path("symlink", &oldpath, &newpath)?; + + Ok(()) +} + +#[op] +fn op_read_link_sync( + state: &mut OpState, + path: String, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + state + .borrow_mut::

() + .check_read(&path, "Deno.readLink()")?; + + let fs = state.borrow::(); + + 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( + state: Rc>, + path: String, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + let fs = { + let mut state = state.borrow_mut(); + state + .borrow_mut::

() + .check_read(&path, "Deno.readLink()")?; + state.borrow::().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( + state: &mut OpState, + path: &str, + len: u64, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + state + .borrow_mut::

() + .check_write(&path, "Deno.truncateSync()")?; + + let fs = state.borrow::(); + fs.truncate_sync(&path, len) + .context_path("truncate", &path)?; + + Ok(()) +} + +#[op] +async fn op_truncate_async( + state: Rc>, + 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::

() + .check_write(&path, "Deno.truncate()")?; + state.borrow::().clone() + }; + + fs.truncate_async(path.clone(), len) + .await + .context_path("truncate", &path)?; + + Ok(()) +} + +#[op] +fn op_utime_sync( + 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::

().check_write(&path, "Deno.utime()")?; + + let fs = state.borrow::(); + fs.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos) + .context_path("utime", &path)?; + + Ok(()) +} + +#[op] +async fn op_utime_async( + state: Rc>, + 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::

().check_write(&path, "Deno.utime()")?; + state.borrow::().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( + state: &mut OpState, + dir: Option, + prefix: Option, + suffix: Option, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let (dir, fs) = make_temp_check_sync::(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( + state: Rc>, + dir: Option, + prefix: Option, + suffix: Option, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let (dir, fs) = make_temp_check_async::(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( + state: &mut OpState, + dir: Option, + prefix: Option, + suffix: Option, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let (dir, fs) = make_temp_check_sync::(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( + state: Rc>, + dir: Option, + prefix: Option, + suffix: Option, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let (dir, fs) = make_temp_check_async::(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( + state: &mut OpState, + dir: Option, +) -> Result<(PathBuf, Fs), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let fs = state.borrow::().clone(); + let dir = match dir { + Some(dir) => { + let dir = PathBuf::from(dir); + state + .borrow_mut::

() + .check_write(&dir, "Deno.makeTempFile()")?; + dir + } + None => { + let dir = fs.tmp_dir().context("tmpdir")?; + state.borrow_mut::

().check_write_blind( + &dir, + "TMP", + "Deno.makeTempFile()", + )?; + dir + } + }; + Ok((dir, fs)) +} + +fn make_temp_check_async( + state: Rc>, + dir: Option, +) -> Result<(PathBuf, Fs), AnyError> +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let mut state = state.borrow_mut(); + let fs = state.borrow::().clone(); + let dir = match dir { + Some(dir) => { + let dir = PathBuf::from(dir); + state + .borrow_mut::

() + .check_write(&dir, "Deno.makeTempFile()")?; + dir + } + None => { + let dir = fs.tmp_dir().context("tmpdir")?; + state.borrow_mut::

().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 { + let prefix = prefix.unwrap_or(""); + let suffix = suffix.unwrap_or(""); + + let mut path = dir.join("_"); + + let unique = rng.gen::(); + path.set_file_name(format!("{prefix}{unique:08x}{suffix}")); + + Ok(path) +} + +#[op] +fn op_write_file_sync( + state: &mut OpState, + path: String, + mode: Option, + 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::

(); + let options = OpenOptions::write(create, append, create_new, mode); + options.check(permissions, &path, "Deno.writeFileSync()")?; + + let fs = state.borrow::(); + + fs.write_file_sync(&path, options, &data) + .context_path("writefile", &path)?; + + Ok(()) +} + +#[op] +async fn op_write_file_async( + state: Rc>, + path: String, + mode: Option, + append: bool, + create: bool, + create_new: bool, + data: ZeroCopyBuf, + cancel_rid: Option, +) -> 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::

(); + options.check(permissions, &path, "Deno.writeFile()")?; + let cancel_handle = cancel_rid + .and_then(|rid| state.resource_table.get::(rid).ok()); + (state.borrow::().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( + state: &mut OpState, + path: String, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + let permissions = state.borrow_mut::

(); + permissions.check_read(&path, "Deno.readFileSync()")?; + + let fs = state.borrow::(); + let buf = fs.read_file_sync(path).context("readfile")?; + + Ok(buf.into()) +} + +#[op] +async fn op_read_file_async( + state: Rc>, + path: String, + cancel_rid: Option, +) -> Result +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::

(); + permissions.check_read(&path, "Deno.readFile()")?; + let cancel_handle = cancel_rid + .and_then(|rid| state.resource_table.get::(rid).ok()); + (state.borrow::().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( + state: &mut OpState, + path: String, +) -> Result +where + Fs: FileSystem + 'static, + P: FsPermissions + 'static, +{ + let path = PathBuf::from(path); + + let permissions = state.borrow_mut::

(); + permissions.check_read(&path, "Deno.readFileSync()")?; + + let fs = state.borrow::(); + let buf = fs.read_file_sync(path).context("readfile")?; + + Ok(string_from_utf8_lossy(buf)) +} + +#[op] +async fn op_read_file_text_async( + state: Rc>, + path: String, + cancel_rid: Option, +) -> Result +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::

(); + permissions.check_read(&path, "Deno.readFile()")?; + let cancel_handle = cancel_rid + .and_then(|rid| state.resource_table.get::(rid).ok()); + (state.borrow::().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) -> 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 { + 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( + state: &mut OpState, + rid: ResourceId, + offset: i64, + whence: i32, +) -> Result +where + Fs: FileSystem + 'static, +{ + let pos = to_seek_from(offset, whence)?; + let file = state.resource_table.get::(rid)?; + let cursor = file.seek_sync(pos)?; + Ok(cursor) +} + +#[op] +async fn op_seek_async( + state: Rc>, + rid: ResourceId, + offset: i64, + whence: i32, +) -> Result +where + Fs: FileSystem + 'static, +{ + let pos = to_seek_from(offset, whence)?; + let file = state.borrow().resource_table.get::(rid)?; + let cursor = file.seek_async(pos).await?; + Ok(cursor) +} + +#[op] +fn op_fdatasync_sync( + state: &mut OpState, + rid: ResourceId, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + let file = state.resource_table.get::(rid)?; + file.datasync_sync()?; + Ok(()) +} + +#[op] +async fn op_fdatasync_async( + state: Rc>, + rid: ResourceId, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + let file = state.borrow().resource_table.get::(rid)?; + file.datasync_async().await?; + Ok(()) +} + +#[op] +fn op_fsync_sync( + state: &mut OpState, + rid: ResourceId, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + let file = state.resource_table.get::(rid)?; + file.sync_sync()?; + Ok(()) +} + +#[op] +async fn op_fsync_async( + state: Rc>, + rid: ResourceId, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + let file = state.borrow().resource_table.get::(rid)?; + file.sync_async().await?; + Ok(()) +} + +#[op] +fn op_fstat_sync( + state: &mut OpState, + rid: ResourceId, + stat_out_buf: &mut [u32], +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + let file = state.resource_table.get::(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( + state: Rc>, + rid: ResourceId, +) -> Result +where + Fs: FileSystem + 'static, +{ + let file = state.borrow().resource_table.get::(rid)?; + let stat = file.stat_async().await?; + Ok(stat.into()) +} + +#[op] +fn op_flock_sync( + state: &mut OpState, + rid: ResourceId, + exclusive: bool, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + check_unstable(state, "Deno.flockSync"); + let file = state.resource_table.get::(rid)?; + file.lock_sync(exclusive)?; + Ok(()) +} + +#[op] +async fn op_flock_async( + state: Rc>, + rid: ResourceId, + exclusive: bool, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + check_unstable2(&state, "Deno.flock"); + let file = state.borrow().resource_table.get::(rid)?; + file.lock_async(exclusive).await?; + Ok(()) +} + +#[op] +fn op_funlock_sync( + state: &mut OpState, + rid: ResourceId, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + check_unstable(state, "Deno.funlockSync"); + let file = state.resource_table.get::(rid)?; + file.unlock_sync()?; + Ok(()) +} + +#[op] +async fn op_funlock_async( + state: Rc>, + rid: ResourceId, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + check_unstable2(&state, "Deno.funlock"); + let file = state.borrow().resource_table.get::(rid)?; + file.unlock_async().await?; + Ok(()) +} + +#[op] +fn op_ftruncate_sync( + state: &mut OpState, + rid: ResourceId, + len: u64, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + let file = state.resource_table.get::(rid)?; + file.truncate_sync(len)?; + Ok(()) +} + +#[op] +async fn op_ftruncate_async( + state: Rc>, + rid: ResourceId, + len: u64, +) -> Result<(), AnyError> +where + Fs: FileSystem + 'static, +{ + let file = state.borrow().resource_table.get::(rid)?; + file.truncate_async(len).await?; + Ok(()) +} + +#[op] +fn op_futime_sync( + 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::(rid)?; + file.utime_sync(atime_secs, atime_nanos, mtime_secs, mtime_nanos)?; + Ok(()) +} + +#[op] +async fn op_futime_async( + state: Rc>, + 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::(rid)?; + file + .utime_async(atime_secs, atime_nanos, mtime_secs, mtime_nanos) + .await?; + Ok(()) +} + +trait WithContext { + fn context>>( + self, + desc: E, + ) -> AnyError; +} + +impl WithContext for FsError { + fn context>>( + self, + desc: E, + ) -> AnyError { + match self { + FsError::Io(io) => { + AnyError::new(io::Error::new(io.kind(), desc)).context(io) + } + _ => self.into(), + } + } +} + +trait MapErrContext { + type R; + + fn context_fn(self, f: F) -> Self::R + where + F: FnOnce() -> E, + E: Into>; + + 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 MapErrContext for Result { + type R = Result; + + fn context_fn(self, f: F) -> Self::R + where + F: FnOnce() -> E, + E: Into>, + { + 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 { + 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 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 0000000000..28c375ff12 --- /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 { + std::env::current_dir().map_err(Into::into) + } + + fn tmp_dir(&self) -> FsResult { + Ok(std::env::temp_dir()) + } + + fn chdir(&self, path: impl AsRef) -> FsResult<()> { + std::env::set_current_dir(path).map_err(Into::into) + } + + #[cfg(not(unix))] + fn umask(&self, _mask: Option) -> FsResult { + // 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) -> FsResult { + 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, + options: OpenOptions, + ) -> FsResult { + 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 { + 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, + 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, 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, + uid: Option, + gid: Option, + ) -> FsResult<()> { + chown(path, uid, gid) + } + async fn chown_async( + &self, + path: PathBuf, + uid: Option, + gid: Option, + ) -> FsResult<()> { + tokio::task::spawn_blocking(move || chown(path, uid, gid)).await? + } + + fn remove_sync( + &self, + path: impl AsRef, + 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, + to: impl AsRef, + ) -> 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) -> FsResult { + stat(path).map(Into::into) + } + async fn stat_async(&self, path: PathBuf) -> FsResult { + tokio::task::spawn_blocking(move || stat(path)) + .await? + .map(Into::into) + } + + fn lstat_sync(&self, path: impl AsRef) -> FsResult { + lstat(path).map(Into::into) + } + async fn lstat_async(&self, path: PathBuf) -> FsResult { + tokio::task::spawn_blocking(move || lstat(path)) + .await? + .map(Into::into) + } + + fn realpath_sync(&self, path: impl AsRef) -> FsResult { + realpath(path) + } + async fn realpath_async(&self, path: PathBuf) -> FsResult { + tokio::task::spawn_blocking(move || realpath(path)).await? + } + + fn read_dir_sync(&self, path: impl AsRef) -> FsResult> { + read_dir(path) + } + async fn read_dir_async(&self, path: PathBuf) -> FsResult> { + tokio::task::spawn_blocking(move || read_dir(path)).await? + } + + fn rename_sync( + &self, + oldpath: impl AsRef, + newpath: impl AsRef, + ) -> 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, + newpath: impl AsRef, + ) -> 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, + newpath: impl AsRef, + file_type: Option, + ) -> FsResult<()> { + symlink(oldpath, newpath, file_type) + } + async fn symlink_async( + &self, + oldpath: PathBuf, + newpath: PathBuf, + file_type: Option, + ) -> FsResult<()> { + tokio::task::spawn_blocking(move || symlink(oldpath, newpath, file_type)) + .await? + } + + fn read_link_sync(&self, path: impl AsRef) -> FsResult { + fs::read_link(path).map_err(Into::into) + } + async fn read_link_async(&self, path: PathBuf) -> FsResult { + tokio::task::spawn_blocking(move || fs::read_link(path)) + .await? + .map_err(Into::into) + } + + fn truncate_sync(&self, path: impl AsRef, 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, + 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, + 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, + ) -> 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) -> FsResult> { + fs::read(path).map_err(Into::into) + } + async fn read_file_async(&self, path: PathBuf) -> FsResult> { + tokio::task::spawn_blocking(move || fs::read(path)) + .await? + .map_err(Into::into) + } +} + +fn mkdir(path: impl AsRef, 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, 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, _mode: u32) -> FsResult<()> { + // Still check file/dir exists on Windows + std::fs::metadata(path)?; + Err(FsError::NotSupported) +} + +#[cfg(unix)] +fn chown( + path: impl AsRef, + uid: Option, + gid: Option, +) -> 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, + _uid: Option, + _gid: Option, +) -> FsResult<()> { + Err(FsError::NotSupported) +} + +fn remove(path: impl AsRef, 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, to: impl AsRef) -> 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) -> FsResult { + let metadata = fs::metadata(path)?; + Ok(metadata_to_fsstat(metadata)) +} + +#[cfg(windows)] +fn stat(path: impl AsRef) -> FsResult { + 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) -> FsResult { + let metadata = fs::symlink_metadata(path)?; + Ok(metadata_to_fsstat(metadata)) +} + +#[cfg(windows)] +fn lstat(path: impl AsRef) -> FsResult { + 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 { + 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::::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) -> Option { + 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) -> FsResult { + 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) -> FsResult> { + 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, + newpath: impl AsRef, + _file_type: Option, +) -> FsResult<()> { + std::os::unix::fs::symlink(oldpath.as_ref(), newpath.as_ref())?; + Ok(()) +} + +#[cfg(windows)] +fn symlink( + oldpath: impl AsRef, + newpath: impl AsRef, + file_type: Option, +) -> 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, 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( + resource: Rc, + f: impl FnOnce(&mut fs::File) -> io::Result, +) -> FsResult { + let res = resource + .with_file2(|file| f(file)) + .ok_or(FsError::FileBusy)??; + Ok(res) +} + +async fn nonblocking( + resource: Rc, + f: impl FnOnce(&mut fs::File) -> io::Result + Send + 'static, +) -> FsResult { + 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, buf: &[u8]) -> FsResult<()> { + sync(self, |file| file.write_all(buf)) + } + async fn write_all_async(self: Rc, buf: Vec) -> FsResult<()> { + nonblocking(self, move |file| file.write_all(&buf)).await + } + + fn read_all_sync(self: Rc) -> FsResult> { + sync(self, |file| { + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok(buf) + }) + } + async fn read_all_async(self: Rc) -> FsResult> { + nonblocking(self, |file| { + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok(buf) + }) + .await + } + + fn chmod_sync(self: Rc, 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, 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, pos: io::SeekFrom) -> FsResult { + sync(self, |file| file.seek(pos)) + } + async fn seek_async(self: Rc, pos: io::SeekFrom) -> FsResult { + nonblocking(self, move |file| file.seek(pos)).await + } + + fn datasync_sync(self: Rc) -> FsResult<()> { + sync(self, |file| file.sync_data()) + } + async fn datasync_async(self: Rc) -> FsResult<()> { + nonblocking(self, |file| file.sync_data()).await + } + + fn sync_sync(self: Rc) -> FsResult<()> { + sync(self, |file| file.sync_all()) + } + async fn sync_async(self: Rc) -> FsResult<()> { + nonblocking(self, |file| file.sync_all()).await + } + + fn stat_sync(self: Rc) -> FsResult { + sync(self, |file| file.metadata().map(metadata_to_fsstat)) + } + async fn stat_async(self: Rc) -> FsResult { + nonblocking(self, |file| file.metadata().map(metadata_to_fsstat)).await + } + + fn lock_sync(self: Rc, exclusive: bool) -> FsResult<()> { + sync(self, |file| { + if exclusive { + file.lock_exclusive() + } else { + file.lock_shared() + } + }) + } + async fn lock_async(self: Rc, exclusive: bool) -> FsResult<()> { + nonblocking(self, move |file| { + if exclusive { + file.lock_exclusive() + } else { + file.lock_shared() + } + }) + .await + } + + fn unlock_sync(self: Rc) -> FsResult<()> { + sync(self, |file| file.unlock()) + } + async fn unlock_async(self: Rc) -> FsResult<()> { + nonblocking(self, |file| file.unlock()).await + } + + fn truncate_sync(self: Rc, len: u64) -> FsResult<()> { + sync(self, |file| file.set_len(len)) + } + async fn truncate_async(self: Rc, len: u64) -> FsResult<()> { + nonblocking(self, move |file| file.set_len(len)).await + } + + fn utime_sync( + self: Rc, + 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, + 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 69f8c9da5e..c85b4baf6d 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( + fn with_inner_and_metadata( &self, action: impl FnOnce( &mut StdFileResourceInner, &Arc>, - ) -> Result, - ) -> Result { + ) -> Result, + ) -> Option> { 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 { - 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 { - 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( @@ -565,10 +571,19 @@ impl StdFileResource { F: FnOnce(&mut StdFile) -> Result, { 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(self: Rc, f: F) -> Option> + where + F: FnOnce(&mut StdFile) -> Result, + { + self.with_inner_and_metadata(move |inner, _| inner.with_file(f)) + } + pub fn with_file_and_metadata( state: &mut OpState, rid: ResourceId, @@ -578,9 +593,11 @@ impl StdFileResource { F: FnOnce(&mut StdFile, &Arc>) -> Result, { 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( + self: Rc, + f: F, + ) -> Result + where + F: (FnOnce(&mut StdFile) -> Result) + 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 { 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 eb8cc34a66..d47bee9419 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::(), 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::(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 7e1772ee3a..b15750313e 100644 --- a/runtime/permissions/mod.rs +++ b/runtime/permissions/mod.rs @@ -667,6 +667,40 @@ impl UnaryPermission { } 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 { @@ -1792,6 +1826,16 @@ impl PermissionsContainer { self.0.lock().write.check_all(Some(api_name)) } + #[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, @@ -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 4be40c9b06..0d743cfc62 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::(), deno_http::deno_http::init_ops(), deno_io::deno_io::init_ops(Some(options.stdio)), - deno_fs::deno_fs::init_ops::(unstable), + deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs), deno_node::deno_node::init_ops::( options.npm_resolver, ), diff --git a/runtime/worker.rs b/runtime/worker.rs index ea1e5e0469..14abd12b55 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::(), deno_http::deno_http::init_ops(), deno_io::deno_io::init_ops(Some(options.stdio)), - deno_fs::deno_fs::init_ops::(unstable), + deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs), deno_node::deno_node::init_ops::( options.npm_resolver, ),