1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

refactor(ext/fs): boxed deno_fs::FileSystem (#18945)

1. Boxed `File` and `FileSystem` to allow more easily passing this
through the CLI code (as shown within this pr).
2. `StdFileResource` is now `FileResource`. `FileResource` now contains
an `Rc<dyn File>`.
This commit is contained in:
David Sherret 2023-05-04 14:28:42 -04:00 committed by GitHub
parent 4b645676d6
commit 5270c43e41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1190 additions and 1258 deletions

3
Cargo.lock generated
View file

@ -1059,7 +1059,10 @@ dependencies = [
name = "deno_io"
version = "0.13.0"
dependencies = [
"async-trait",
"deno_core",
"filetime",
"fs3",
"nix",
"once_cell",
"tokio",

View file

@ -92,6 +92,7 @@ dlopen = "0.1.8"
encoding_rs = "=0.8.31"
ecb = "=0.1.1"
fastwebsockets = "=0.3.1"
filetime = "0.2.16"
flate2 = "=1.0.24"
fs3 = "0.5.0"
futures = "0.3.21"

View file

@ -9,7 +9,6 @@ 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,11 +360,11 @@ fn create_cli_snapshot(snapshot_path: PathBuf) {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Default::default()),
deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(false, StdFs),
deno_node::deno_node::init_ops::<PermissionsContainer>(
None,
Some(Arc::new(deno_node::RealFs)),
deno_fs::deno_fs::init_ops::<PermissionsContainer>(
false,
Arc::new(deno_fs::RealFs),
),
deno_node::deno_node::init_ops::<PermissionsContainer>(None, None),
cli::init_ops_and_esm(), // NOTE: This needs to be init_ops_and_esm!
];

View file

@ -42,6 +42,7 @@ use crate::worker::HasNodeSpecifierChecker;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
use deno_runtime::deno_fs;
use deno_runtime::deno_node;
use deno_runtime::deno_node::analyze::NodeCodeTranslator;
use deno_runtime::deno_node::NodeResolver;
@ -553,6 +554,7 @@ impl CliFactory {
let node_code_translator = self.node_code_translator().await?.clone();
let options = self.cli_options().clone();
let main_worker_options = self.create_cli_main_worker_options()?;
let fs = Arc::new(deno_fs::RealFs);
let node_fs = self.node_fs().clone();
let root_cert_store_provider = self.root_cert_store_provider().clone();
let node_resolver = self.node_resolver().await?.clone();
@ -579,6 +581,7 @@ impl CliFactory {
),
)),
root_cert_store_provider.clone(),
fs.clone(),
node_fs.clone(),
maybe_inspector_server.clone(),
main_worker_options.clone(),
@ -610,6 +613,7 @@ impl CliFactory {
),
)),
self.root_cert_store_provider().clone(),
Arc::new(deno_fs::RealFs),
self.node_fs().clone(),
self.maybe_inspector_server().clone(),
self.create_cli_main_worker_options()?,

View file

@ -30,6 +30,7 @@ use deno_core::ModuleSpecifier;
use deno_core::ModuleType;
use deno_core::ResolutionKind;
use deno_graph::source::Resolver;
use deno_runtime::deno_fs;
use deno_runtime::deno_node;
use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_tls::rustls::RootCertStore;
@ -253,6 +254,7 @@ pub async fn run(
BlobStore::default(),
Box::new(module_loader_factory),
root_cert_store_provider,
Arc::new(deno_fs::RealFs),
node_fs,
None,
CliMainWorkerOptions {

View file

@ -18,6 +18,7 @@ use deno_core::SharedArrayBufferStore;
use deno_core::SourceMapGetter;
use deno_runtime::colors;
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
use deno_runtime::deno_fs;
use deno_runtime::deno_node;
use deno_runtime::deno_node::NodeResolution;
use deno_runtime::deno_node::NodeResolver;
@ -97,6 +98,7 @@ struct SharedWorkerState {
compiled_wasm_module_store: CompiledWasmModuleStore,
module_loader_factory: Box<dyn ModuleLoaderFactory>,
root_cert_store_provider: Arc<dyn RootCertStoreProvider>,
fs: Arc<dyn deno_fs::FileSystem>,
node_fs: Arc<dyn deno_node::NodeFs>,
maybe_inspector_server: Option<Arc<InspectorServer>>,
}
@ -308,6 +310,7 @@ impl CliMainWorkerFactory {
blob_store: BlobStore,
module_loader_factory: Box<dyn ModuleLoaderFactory>,
root_cert_store_provider: Arc<dyn RootCertStoreProvider>,
fs: Arc<dyn deno_fs::FileSystem>,
node_fs: Arc<dyn deno_node::NodeFs>,
maybe_inspector_server: Option<Arc<InspectorServer>>,
options: CliMainWorkerOptions,
@ -325,6 +328,7 @@ impl CliMainWorkerFactory {
compiled_wasm_module_store: Default::default(),
module_loader_factory,
root_cert_store_provider,
fs,
node_fs,
maybe_inspector_server,
}),
@ -445,6 +449,7 @@ impl CliMainWorkerFactory {
should_break_on_first_statement: shared.options.inspect_brk,
should_wait_for_inspector_session: shared.options.inspect_wait,
module_loader,
fs: shared.fs.clone(),
node_fs: Some(shared.node_fs.clone()),
npm_resolver: Some(shared.npm_resolver.clone()),
get_error_class_fn: Some(&errors::get_error_class_name),
@ -570,6 +575,7 @@ fn create_web_worker_callback(
format_js_error_fn: Some(Arc::new(format_js_error)),
source_map_getter: maybe_source_map_getter,
module_loader,
fs: shared.fs.clone(),
node_fs: Some(shared.node_fs.clone()),
npm_resolver: Some(shared.npm_resolver.clone()),
worker_type: args.worker_type,
@ -597,13 +603,8 @@ fn create_web_worker_callback(
#[cfg(test)]
mod tests {
use std::rc::Rc;
use super::*;
use deno_core::resolve_path;
use deno_core::FsModuleLoader;
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
use deno_runtime::deno_web::BlobStore;
use deno_runtime::permissions::Permissions;
fn create_test_worker() -> MainWorker {
@ -612,31 +613,8 @@ mod tests {
let permissions = PermissionsContainer::new(Permissions::default());
let options = WorkerOptions {
bootstrap: BootstrapOptions::default(),
extensions: vec![],
startup_snapshot: Some(crate::js::deno_isolate_init()),
unsafely_ignore_certificate_errors: None,
root_cert_store_provider: None,
seed: None,
format_js_error_fn: None,
source_map_getter: None,
web_worker_preload_module_cb: Arc::new(|_| unreachable!()),
web_worker_pre_execute_module_cb: Arc::new(|_| unreachable!()),
create_web_worker_cb: Arc::new(|_| unreachable!()),
maybe_inspector_server: None,
should_break_on_first_statement: false,
should_wait_for_inspector_session: false,
module_loader: Rc::new(FsModuleLoader),
node_fs: Some(Arc::new(deno_node::RealFs)),
npm_resolver: None,
get_error_class_fn: None,
cache_storage_dir: None,
origin_storage_dir: None,
blob_store: BlobStore::default(),
broadcast_channel: InMemoryBroadcastChannel::default(),
shared_array_buffer_store: None,
compiled_wasm_module_store: None,
stdio: Default::default(),
..Default::default()
};
MainWorker::bootstrap_from_options(main_module, permissions, options)

View file

@ -187,6 +187,13 @@ pub trait Resource: Any + 'static {
None
}
/// Resources backed by a file descriptor can let ops know to allow for
/// low-level optimizations.
#[cfg(windows)]
fn backing_fd(self: Rc<Self>) -> Option<std::os::windows::io::RawHandle> {
None
}
fn size_hint(&self) -> (u64, Option<u64>) {
(0, None)
}

View file

@ -17,8 +17,8 @@ path = "lib.rs"
async-trait.workspace = true
deno_core.workspace = true
deno_io.workspace = true
filetime = "0.2.16"
fs3 = "0.5.0"
filetime.workspace = true
fs3.workspace = true
libc.workspace = true
log.workspace = true
rand.workspace = true

View file

@ -1,6 +1,5 @@
// 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;
@ -8,6 +7,10 @@ use std::rc::Rc;
use serde::Deserialize;
use serde::Serialize;
use deno_io::fs::File;
use deno_io::fs::FsResult;
use deno_io::fs::FsStat;
#[derive(Deserialize, Default, Debug, Clone, Copy)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
@ -52,27 +55,6 @@ impl OpenOptions {
}
}
pub struct FsStat {
pub is_file: bool,
pub is_directory: bool,
pub is_symlink: bool,
pub size: u64,
pub mtime: Option<u64>,
pub atime: Option<u64>,
pub birthtime: Option<u64>,
pub dev: u64,
pub ino: u64,
pub mode: u32,
pub nlink: u64,
pub uid: u32,
pub gid: u32,
pub rdev: u64,
pub blksize: u64,
pub blocks: u64,
}
#[derive(Deserialize)]
pub enum FsFileType {
#[serde(rename = "file")]
@ -90,93 +72,25 @@ pub struct FsDirEntry {
pub is_symlink: bool,
}
pub enum FsError {
Io(io::Error),
FileBusy,
NotSupported,
}
impl From<io::Error> for FsError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
pub type FsResult<T> = Result<T, FsError>;
#[async_trait::async_trait(?Send)]
pub trait File {
fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()>;
async fn write_all_async(self: Rc<Self>, buf: Vec<u8>) -> FsResult<()>;
fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>>;
async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>>;
fn chmod_sync(self: Rc<Self>, pathmode: u32) -> FsResult<()>;
async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()>;
fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
fn datasync_sync(self: Rc<Self>) -> FsResult<()>;
async fn datasync_async(self: Rc<Self>) -> FsResult<()>;
fn sync_sync(self: Rc<Self>) -> FsResult<()>;
async fn sync_async(self: Rc<Self>) -> FsResult<()>;
fn stat_sync(self: Rc<Self>) -> FsResult<FsStat>;
async fn stat_async(self: Rc<Self>) -> FsResult<FsStat>;
fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
fn unlock_sync(self: Rc<Self>) -> FsResult<()>;
async fn unlock_async(self: Rc<Self>) -> FsResult<()>;
fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()>;
async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()>;
fn utime_sync(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
async fn utime_async(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
}
#[async_trait::async_trait(?Send)]
pub trait FileSystem: Clone {
type File: File;
pub trait FileSystem: Send + Sync {
fn cwd(&self) -> FsResult<PathBuf>;
fn tmp_dir(&self) -> FsResult<PathBuf>;
fn chdir(&self, path: impl AsRef<Path>) -> FsResult<()>;
fn chdir(&self, path: &Path) -> FsResult<()>;
fn umask(&self, mask: Option<u32>) -> FsResult<u32>;
fn open_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
options: OpenOptions,
) -> FsResult<Self::File>;
) -> FsResult<Rc<dyn File>>;
async fn open_async(
&self,
path: PathBuf,
options: OpenOptions,
) -> FsResult<Self::File>;
) -> FsResult<Rc<dyn File>>;
fn mkdir_sync(
&self,
path: impl AsRef<Path>,
recusive: bool,
mode: u32,
) -> FsResult<()>;
fn mkdir_sync(&self, path: &Path, recusive: bool, mode: u32) -> FsResult<()>;
async fn mkdir_async(
&self,
path: PathBuf,
@ -184,12 +98,12 @@ pub trait FileSystem: Clone {
mode: u32,
) -> FsResult<()>;
fn chmod_sync(&self, path: impl AsRef<Path>, mode: u32) -> FsResult<()>;
fn chmod_sync(&self, path: &Path, mode: u32) -> FsResult<()>;
async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()>;
fn chown_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()>;
@ -200,52 +114,36 @@ pub trait FileSystem: Clone {
gid: Option<u32>,
) -> FsResult<()>;
fn remove_sync(
&self,
path: impl AsRef<Path>,
recursive: bool,
) -> FsResult<()>;
fn remove_sync(&self, path: &Path, recursive: bool) -> FsResult<()>;
async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()>;
fn copy_file_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()>;
fn copy_file_sync(&self, oldpath: &Path, newpath: &Path) -> FsResult<()>;
async fn copy_file_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
) -> FsResult<()>;
fn stat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat>;
fn stat_sync(&self, path: &Path) -> FsResult<FsStat>;
async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat>;
fn lstat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat>;
fn lstat_sync(&self, path: &Path) -> FsResult<FsStat>;
async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat>;
fn realpath_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf>;
fn realpath_sync(&self, path: &Path) -> FsResult<PathBuf>;
async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf>;
fn read_dir_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>>;
fn read_dir_sync(&self, path: &Path) -> FsResult<Vec<FsDirEntry>>;
async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>>;
fn rename_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()>;
fn rename_sync(&self, oldpath: &Path, newpath: &Path) -> FsResult<()>;
async fn rename_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
) -> FsResult<()>;
fn link_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()>;
fn link_sync(&self, oldpath: &Path, newpath: &Path) -> FsResult<()>;
async fn link_async(
&self,
oldpath: PathBuf,
@ -254,8 +152,8 @@ pub trait FileSystem: Clone {
fn symlink_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
oldpath: &Path,
newpath: &Path,
file_type: Option<FsFileType>,
) -> FsResult<()>;
async fn symlink_async(
@ -265,15 +163,15 @@ pub trait FileSystem: Clone {
file_type: Option<FsFileType>,
) -> FsResult<()>;
fn read_link_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf>;
fn read_link_sync(&self, path: &Path) -> FsResult<PathBuf>;
async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf>;
fn truncate_sync(&self, path: impl AsRef<Path>, len: u64) -> FsResult<()>;
fn truncate_sync(&self, path: &Path, len: u64) -> FsResult<()>;
async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()>;
fn utime_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
@ -290,12 +188,11 @@ pub trait FileSystem: Clone {
fn write_file_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
options: OpenOptions,
data: &[u8],
) -> FsResult<()> {
let file = self.open_sync(path, options)?;
let file = Rc::new(file);
if let Some(mode) = options.mode {
file.clone().chmod_sync(mode)?;
}
@ -309,25 +206,22 @@ pub trait FileSystem: Clone {
data: Vec<u8>,
) -> FsResult<()> {
let file = self.open_async(path, options).await?;
let file = Rc::new(file);
if let Some(mode) = options.mode {
file.clone().chmod_async(mode).await?;
}
file.write_all_async(data).await?;
file.write_all(data.into()).await?;
Ok(())
}
fn read_file_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
let file = self.open_sync(path, options)?;
let file = Rc::new(file);
let buf = file.read_all_sync()?;
Ok(buf)
}
async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
let file = self.clone().open_async(path, options).await?;
let file = Rc::new(file);
let file = self.open_async(path, options).await?;
let buf = file.read_all_async().await?;
Ok(buf)
}

View file

@ -4,25 +4,21 @@ 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::FsResult;
pub use crate::interface::FsStat;
pub use crate::interface::OpenOptions;
use crate::ops::*;
pub use crate::std_fs::StdFs;
pub use crate::std_fs::RealFs;
use deno_core::error::AnyError;
use deno_core::OpState;
use deno_core::Resource;
use std::cell::RefCell;
use std::convert::From;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
pub trait FsPermissions {
fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
@ -87,78 +83,77 @@ pub(crate) fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
deno_core::extension!(deno_fs,
deps = [ deno_web ],
parameters = [Fs: FileSystem, P: FsPermissions],
bounds = [Fs::File: Resource],
parameters = [P: FsPermissions],
ops = [
op_cwd<Fs, P>,
op_umask<Fs>,
op_chdir<Fs, P>,
op_cwd<P>,
op_umask,
op_chdir<P>,
op_open_sync<Fs, P>,
op_open_async<Fs, P>,
op_mkdir_sync<Fs, P>,
op_mkdir_async<Fs, P>,
op_chmod_sync<Fs, P>,
op_chmod_async<Fs, P>,
op_chown_sync<Fs, P>,
op_chown_async<Fs, P>,
op_remove_sync<Fs, P>,
op_remove_async<Fs, P>,
op_copy_file_sync<Fs, P>,
op_copy_file_async<Fs, P>,
op_stat_sync<Fs, P>,
op_stat_async<Fs, P>,
op_lstat_sync<Fs, P>,
op_lstat_async<Fs, P>,
op_realpath_sync<Fs, P>,
op_realpath_async<Fs, P>,
op_read_dir_sync<Fs, P>,
op_read_dir_async<Fs, P>,
op_rename_sync<Fs, P>,
op_rename_async<Fs, P>,
op_link_sync<Fs, P>,
op_link_async<Fs, P>,
op_symlink_sync<Fs, P>,
op_symlink_async<Fs, P>,
op_read_link_sync<Fs, P>,
op_read_link_async<Fs, P>,
op_truncate_sync<Fs, P>,
op_truncate_async<Fs, P>,
op_utime_sync<Fs, P>,
op_utime_async<Fs, P>,
op_make_temp_dir_sync<Fs, P>,
op_make_temp_dir_async<Fs, P>,
op_make_temp_file_sync<Fs, P>,
op_make_temp_file_async<Fs, P>,
op_write_file_sync<Fs, P>,
op_write_file_async<Fs, P>,
op_read_file_sync<Fs, P>,
op_read_file_async<Fs, P>,
op_read_file_text_sync<Fs, P>,
op_read_file_text_async<Fs, P>,
op_open_sync<P>,
op_open_async<P>,
op_mkdir_sync<P>,
op_mkdir_async<P>,
op_chmod_sync<P>,
op_chmod_async<P>,
op_chown_sync<P>,
op_chown_async<P>,
op_remove_sync<P>,
op_remove_async<P>,
op_copy_file_sync<P>,
op_copy_file_async<P>,
op_stat_sync<P>,
op_stat_async<P>,
op_lstat_sync<P>,
op_lstat_async<P>,
op_realpath_sync<P>,
op_realpath_async<P>,
op_read_dir_sync<P>,
op_read_dir_async<P>,
op_rename_sync<P>,
op_rename_async<P>,
op_link_sync<P>,
op_link_async<P>,
op_symlink_sync<P>,
op_symlink_async<P>,
op_read_link_sync<P>,
op_read_link_async<P>,
op_truncate_sync<P>,
op_truncate_async<P>,
op_utime_sync<P>,
op_utime_async<P>,
op_make_temp_dir_sync<P>,
op_make_temp_dir_async<P>,
op_make_temp_file_sync<P>,
op_make_temp_file_async<P>,
op_write_file_sync<P>,
op_write_file_async<P>,
op_read_file_sync<P>,
op_read_file_async<P>,
op_read_file_text_sync<P>,
op_read_file_text_async<P>,
op_seek_sync<Fs>,
op_seek_async<Fs>,
op_fdatasync_sync<Fs>,
op_fdatasync_async<Fs>,
op_fsync_sync<Fs>,
op_fsync_async<Fs>,
op_fstat_sync<Fs>,
op_fstat_async<Fs>,
op_flock_sync<Fs>,
op_flock_async<Fs>,
op_funlock_sync<Fs>,
op_funlock_async<Fs>,
op_ftruncate_sync<Fs>,
op_ftruncate_async<Fs>,
op_futime_sync<Fs>,
op_futime_async<Fs>,
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,
fs: Fs,
fs: Arc<dyn FileSystem>,
},
state = |state, options| {
state.put(UnstableChecker { unstable: options.unstable });

File diff suppressed because it is too large Load diff

View file

@ -4,34 +4,29 @@
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 deno_io::fs::File;
use deno_io::fs::FsResult;
use deno_io::fs::FsStat;
use deno_io::StdFileResourceInner;
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;
#[cfg(not(unix))]
use deno_io::fs::FsError;
#[derive(Clone)]
pub struct StdFs;
pub struct RealFs;
#[async_trait::async_trait(?Send)]
impl FileSystem for StdFs {
type File = StdFileResource;
impl FileSystem for RealFs {
fn cwd(&self) -> FsResult<PathBuf> {
std::env::current_dir().map_err(Into::into)
}
@ -40,7 +35,7 @@ impl FileSystem for StdFs {
Ok(std::env::temp_dir())
}
fn chdir(&self, path: impl AsRef<Path>) -> FsResult<()> {
fn chdir(&self, path: &Path) -> FsResult<()> {
std::env::set_current_dir(path).map_err(Into::into)
}
@ -78,27 +73,27 @@ impl FileSystem for StdFs {
fn open_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
options: OpenOptions,
) -> FsResult<Self::File> {
) -> FsResult<Rc<dyn File>> {
let opts = open_options(options);
let std_file = opts.open(path)?;
Ok(StdFileResource::fs_file(std_file))
Ok(Rc::new(StdFileResourceInner::file(std_file)))
}
async fn open_async(
&self,
path: PathBuf,
options: OpenOptions,
) -> FsResult<Self::File> {
) -> FsResult<Rc<dyn File>> {
let opts = open_options(options);
let std_file =
tokio::task::spawn_blocking(move || opts.open(path)).await??;
Ok(StdFileResource::fs_file(std_file))
Ok(Rc::new(StdFileResourceInner::file(std_file)))
}
fn mkdir_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
recursive: bool,
mode: u32,
) -> FsResult<()> {
@ -110,19 +105,19 @@ impl FileSystem for StdFs {
recursive: bool,
mode: u32,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || mkdir(path, recursive, mode)).await?
tokio::task::spawn_blocking(move || mkdir(&path, recursive, mode)).await?
}
fn chmod_sync(&self, path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
fn chmod_sync(&self, path: &Path, mode: u32) -> FsResult<()> {
chmod(path, mode)
}
async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()> {
tokio::task::spawn_blocking(move || chmod(path, mode)).await?
tokio::task::spawn_blocking(move || chmod(&path, mode)).await?
}
fn chown_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()> {
@ -134,68 +129,56 @@ impl FileSystem for StdFs {
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || chown(path, uid, gid)).await?
tokio::task::spawn_blocking(move || chown(&path, uid, gid)).await?
}
fn remove_sync(
&self,
path: impl AsRef<Path>,
recursive: bool,
) -> FsResult<()> {
fn remove_sync(&self, path: &Path, recursive: bool) -> FsResult<()> {
remove(path, recursive)
}
async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()> {
tokio::task::spawn_blocking(move || remove(path, recursive)).await?
tokio::task::spawn_blocking(move || remove(&path, recursive)).await?
}
fn copy_file_sync(
&self,
from: impl AsRef<Path>,
to: impl AsRef<Path>,
) -> FsResult<()> {
fn copy_file_sync(&self, from: &Path, to: &Path) -> FsResult<()> {
copy_file(from, to)
}
async fn copy_file_async(&self, from: PathBuf, to: PathBuf) -> FsResult<()> {
tokio::task::spawn_blocking(move || copy_file(from, to)).await?
tokio::task::spawn_blocking(move || copy_file(&from, &to)).await?
}
fn stat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
fn stat_sync(&self, path: &Path) -> FsResult<FsStat> {
stat(path).map(Into::into)
}
async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat> {
tokio::task::spawn_blocking(move || stat(path))
tokio::task::spawn_blocking(move || stat(&path))
.await?
.map(Into::into)
}
fn lstat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
fn lstat_sync(&self, path: &Path) -> FsResult<FsStat> {
lstat(path).map(Into::into)
}
async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat> {
tokio::task::spawn_blocking(move || lstat(path))
tokio::task::spawn_blocking(move || lstat(&path))
.await?
.map(Into::into)
}
fn realpath_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
fn realpath_sync(&self, path: &Path) -> FsResult<PathBuf> {
realpath(path)
}
async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf> {
tokio::task::spawn_blocking(move || realpath(path)).await?
tokio::task::spawn_blocking(move || realpath(&path)).await?
}
fn read_dir_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
fn read_dir_sync(&self, path: &Path) -> FsResult<Vec<FsDirEntry>> {
read_dir(path)
}
async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>> {
tokio::task::spawn_blocking(move || read_dir(path)).await?
tokio::task::spawn_blocking(move || read_dir(&path)).await?
}
fn rename_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()> {
fn rename_sync(&self, oldpath: &Path, newpath: &Path) -> FsResult<()> {
fs::rename(oldpath, newpath).map_err(Into::into)
}
async fn rename_async(
@ -208,11 +191,7 @@ impl FileSystem for StdFs {
.map_err(Into::into)
}
fn link_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()> {
fn link_sync(&self, oldpath: &Path, newpath: &Path) -> FsResult<()> {
fs::hard_link(oldpath, newpath).map_err(Into::into)
}
async fn link_async(
@ -227,8 +206,8 @@ impl FileSystem for StdFs {
fn symlink_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
oldpath: &Path,
newpath: &Path,
file_type: Option<FsFileType>,
) -> FsResult<()> {
symlink(oldpath, newpath, file_type)
@ -239,11 +218,11 @@ impl FileSystem for StdFs {
newpath: PathBuf,
file_type: Option<FsFileType>,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || symlink(oldpath, newpath, file_type))
tokio::task::spawn_blocking(move || symlink(&oldpath, &newpath, file_type))
.await?
}
fn read_link_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
fn read_link_sync(&self, path: &Path) -> FsResult<PathBuf> {
fs::read_link(path).map_err(Into::into)
}
async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf> {
@ -252,16 +231,16 @@ impl FileSystem for StdFs {
.map_err(Into::into)
}
fn truncate_sync(&self, path: impl AsRef<Path>, len: u64) -> FsResult<()> {
fn truncate_sync(&self, path: &Path, len: u64) -> FsResult<()> {
truncate(path, len)
}
async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()> {
tokio::task::spawn_blocking(move || truncate(path, len)).await?
tokio::task::spawn_blocking(move || truncate(&path, len)).await?
}
fn utime_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
@ -289,7 +268,7 @@ impl FileSystem for StdFs {
fn write_file_sync(
&self,
path: impl AsRef<Path>,
path: &Path,
options: OpenOptions,
data: &[u8],
) -> FsResult<()> {
@ -324,7 +303,7 @@ impl FileSystem for StdFs {
.await?
}
fn read_file_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
fn read_file_sync(&self, path: &Path) -> FsResult<Vec<u8>> {
fs::read(path).map_err(Into::into)
}
async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
@ -334,7 +313,7 @@ impl FileSystem for StdFs {
}
}
fn mkdir(path: impl AsRef<Path>, recursive: bool, mode: u32) -> FsResult<()> {
fn mkdir(path: &Path, recursive: bool, mode: u32) -> FsResult<()> {
let mut builder = fs::DirBuilder::new();
builder.recursive(recursive);
#[cfg(unix)]
@ -350,7 +329,7 @@ fn mkdir(path: impl AsRef<Path>, recursive: bool, mode: u32) -> FsResult<()> {
}
#[cfg(unix)]
fn chmod(path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
fn chmod(path: &Path, mode: u32) -> FsResult<()> {
use std::os::unix::fs::PermissionsExt;
let permissions = fs::Permissions::from_mode(mode);
fs::set_permissions(path, permissions)?;
@ -359,24 +338,20 @@ fn chmod(path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
// TODO: implement chmod for Windows (#4357)
#[cfg(not(unix))]
fn chmod(path: impl AsRef<Path>, _mode: u32) -> FsResult<()> {
fn chmod(path: &Path, _mode: u32) -> FsResult<()> {
// Still check file/dir exists on Windows
std::fs::metadata(path)?;
Err(FsError::NotSupported)
}
#[cfg(unix)]
fn chown(
path: impl AsRef<Path>,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()> {
fn chown(path: &Path, uid: Option<u32>, gid: Option<u32>) -> FsResult<()> {
use nix::unistd::chown;
use nix::unistd::Gid;
use nix::unistd::Uid;
let owner = uid.map(Uid::from_raw);
let group = gid.map(Gid::from_raw);
let res = chown(path.as_ref(), owner, group);
let res = chown(path, owner, group);
if let Err(err) = res {
return Err(io::Error::from_raw_os_error(err as i32).into());
}
@ -385,60 +360,57 @@ fn chown(
// TODO: implement chown for Windows
#[cfg(not(unix))]
fn chown(
_path: impl AsRef<Path>,
_uid: Option<u32>,
_gid: Option<u32>,
) -> FsResult<()> {
fn chown(_path: &Path, _uid: Option<u32>, _gid: Option<u32>) -> FsResult<()> {
Err(FsError::NotSupported)
}
fn remove(path: impl AsRef<Path>, recursive: bool) -> FsResult<()> {
fn remove(path: &Path, recursive: bool) -> FsResult<()> {
// TODO: this is racy. This should open fds, and then `unlink` those.
let metadata = fs::symlink_metadata(&path)?;
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)
fs::remove_dir_all(path)
} else {
fs::remove_dir(&path)
fs::remove_dir(path)
}
} else if file_type.is_symlink() {
#[cfg(unix)]
{
fs::remove_file(&path)
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)
fs::remove_dir(path)
} else {
fs::remove_file(&path)
fs::remove_file(path)
}
}
} else {
fs::remove_file(&path)
fs::remove_file(path)
};
res.map_err(Into::into)
}
fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> FsResult<()> {
fn copy_file(from: &Path, to: &Path) -> FsResult<()> {
#[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;
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();
let from_str = CString::new(from.as_os_str().as_bytes()).unwrap();
let to_str = CString::new(to.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
@ -499,36 +471,37 @@ fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> FsResult<()> {
}
#[cfg(not(windows))]
fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
fn stat(path: &Path) -> FsResult<FsStat> {
let metadata = fs::metadata(path)?;
Ok(metadata_to_fsstat(metadata))
Ok(FsStat::from_std(metadata))
}
#[cfg(windows)]
fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
let metadata = fs::metadata(path.as_ref())?;
let mut fsstat = metadata_to_fsstat(metadata);
fn stat(path: &Path) -> FsResult<FsStat> {
let metadata = fs::metadata(path)?;
let mut fsstat = FsStat::from_std(metadata);
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
let path = path.as_ref().canonicalize()?;
let path = path.canonicalize()?;
stat_extra(&mut fsstat, &path, FILE_FLAG_BACKUP_SEMANTICS)?;
Ok(fsstat)
}
#[cfg(not(windows))]
fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
fn lstat(path: &Path) -> FsResult<FsStat> {
let metadata = fs::symlink_metadata(path)?;
Ok(metadata_to_fsstat(metadata))
Ok(FsStat::from_std(metadata))
}
#[cfg(windows)]
fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
let metadata = fs::symlink_metadata(path.as_ref())?;
let mut fsstat = metadata_to_fsstat(metadata);
fn lstat(path: &Path) -> FsResult<FsStat> {
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
let metadata = fs::symlink_metadata(path)?;
let mut fsstat = FsStat::from_std(metadata);
stat_extra(
&mut fsstat,
path.as_ref(),
path,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
)?;
Ok(fsstat)
@ -595,62 +568,11 @@ fn stat_extra(
}
}
#[inline(always)]
fn metadata_to_fsstat(metadata: fs::Metadata) -> FsStat {
macro_rules! unix_or_zero {
($member:ident) => {{
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
metadata.$member()
}
#[cfg(not(unix))]
{
0
}
}};
}
#[inline(always)]
fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Option<u64> {
match maybe_time {
Ok(time) => Some(
time
.duration_since(UNIX_EPOCH)
.map(|t| t.as_millis() as u64)
.unwrap_or_else(|err| err.duration().as_millis() as u64),
),
Err(_) => None,
}
}
FsStat {
is_file: metadata.is_file(),
is_directory: metadata.is_dir(),
is_symlink: metadata.file_type().is_symlink(),
size: metadata.len(),
mtime: to_msec(metadata.modified()),
atime: to_msec(metadata.accessed()),
birthtime: to_msec(metadata.created()),
dev: unix_or_zero!(dev),
ino: unix_or_zero!(ino),
mode: unix_or_zero!(mode),
nlink: unix_or_zero!(nlink),
uid: unix_or_zero!(uid),
gid: unix_or_zero!(gid),
rdev: unix_or_zero!(rdev),
blksize: unix_or_zero!(blksize),
blocks: unix_or_zero!(blocks),
}
fn realpath(path: &Path) -> FsResult<PathBuf> {
Ok(deno_core::strip_unc_prefix(path.canonicalize()?))
}
fn realpath(path: impl AsRef<Path>) -> FsResult<PathBuf> {
Ok(deno_core::strip_unc_prefix(path.as_ref().canonicalize()?))
}
fn read_dir(path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
fn read_dir(path: &Path) -> FsResult<Vec<FsDirEntry>> {
let entries = fs::read_dir(path)?
.filter_map(|entry| {
let entry = entry.ok()?;
@ -679,24 +601,24 @@ fn read_dir(path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
#[cfg(not(windows))]
fn symlink(
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
oldpath: &Path,
newpath: &Path,
_file_type: Option<FsFileType>,
) -> FsResult<()> {
std::os::unix::fs::symlink(oldpath.as_ref(), newpath.as_ref())?;
std::os::unix::fs::symlink(oldpath, newpath)?;
Ok(())
}
#[cfg(windows)]
fn symlink(
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
oldpath: &Path,
newpath: &Path,
file_type: Option<FsFileType>,
) -> FsResult<()> {
let file_type = match file_type {
Some(file_type) => file_type,
None => {
let old_meta = fs::metadata(&oldpath);
let old_meta = fs::metadata(oldpath);
match old_meta {
Ok(metadata) => {
if metadata.is_file() {
@ -723,17 +645,17 @@ fn symlink(
match file_type {
FsFileType::File => {
std::os::windows::fs::symlink_file(&oldpath, &newpath)?;
std::os::windows::fs::symlink_file(oldpath, newpath)?;
}
FsFileType::Directory => {
std::os::windows::fs::symlink_dir(&oldpath, &newpath)?;
std::os::windows::fs::symlink_dir(oldpath, newpath)?;
}
};
Ok(())
}
fn truncate(path: impl AsRef<Path>, len: u64) -> FsResult<()> {
fn truncate(path: &Path, len: u64) -> FsResult<()> {
let file = fs::OpenOptions::new().write(true).open(path)?;
file.set_len(len)?;
Ok(())
@ -760,162 +682,3 @@ fn open_options(options: OpenOptions) -> fs::OpenOptions {
open_options.create_new(options.create_new);
open_options
}
fn sync<T>(
resource: Rc<StdFileResource>,
f: impl FnOnce(&mut fs::File) -> io::Result<T>,
) -> FsResult<T> {
let res = resource
.with_file2(|file| f(file))
.ok_or(FsError::FileBusy)??;
Ok(res)
}
async fn nonblocking<T: Send + 'static>(
resource: Rc<StdFileResource>,
f: impl FnOnce(&mut fs::File) -> io::Result<T> + Send + 'static,
) -> FsResult<T> {
let res = resource.with_file_blocking_task2(f).await?;
Ok(res)
}
#[async_trait::async_trait(?Send)]
impl File for StdFileResource {
fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()> {
sync(self, |file| file.write_all(buf))
}
async fn write_all_async(self: Rc<Self>, buf: Vec<u8>) -> FsResult<()> {
nonblocking(self, move |file| file.write_all(&buf)).await
}
fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>> {
sync(self, |file| {
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
})
}
async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>> {
nonblocking(self, |file| {
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
})
.await
}
fn chmod_sync(self: Rc<Self>, _mode: u32) -> FsResult<()> {
#[cfg(unix)]
{
sync(self, |file| {
use std::os::unix::prelude::PermissionsExt;
file.set_permissions(fs::Permissions::from_mode(_mode))
})
}
#[cfg(not(unix))]
Err(FsError::NotSupported)
}
async fn chmod_async(self: Rc<Self>, _mode: u32) -> FsResult<()> {
#[cfg(unix)]
{
nonblocking(self, move |file| {
use std::os::unix::prelude::PermissionsExt;
file.set_permissions(fs::Permissions::from_mode(_mode))
})
.await
}
#[cfg(not(unix))]
Err(FsError::NotSupported)
}
fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
sync(self, |file| file.seek(pos))
}
async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
nonblocking(self, move |file| file.seek(pos)).await
}
fn datasync_sync(self: Rc<Self>) -> FsResult<()> {
sync(self, |file| file.sync_data())
}
async fn datasync_async(self: Rc<Self>) -> FsResult<()> {
nonblocking(self, |file| file.sync_data()).await
}
fn sync_sync(self: Rc<Self>) -> FsResult<()> {
sync(self, |file| file.sync_all())
}
async fn sync_async(self: Rc<Self>) -> FsResult<()> {
nonblocking(self, |file| file.sync_all()).await
}
fn stat_sync(self: Rc<Self>) -> FsResult<FsStat> {
sync(self, |file| file.metadata().map(metadata_to_fsstat))
}
async fn stat_async(self: Rc<Self>) -> FsResult<FsStat> {
nonblocking(self, |file| file.metadata().map(metadata_to_fsstat)).await
}
fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
sync(self, |file| {
if exclusive {
file.lock_exclusive()
} else {
file.lock_shared()
}
})
}
async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
nonblocking(self, move |file| {
if exclusive {
file.lock_exclusive()
} else {
file.lock_shared()
}
})
.await
}
fn unlock_sync(self: Rc<Self>) -> FsResult<()> {
sync(self, |file| file.unlock())
}
async fn unlock_async(self: Rc<Self>) -> FsResult<()> {
nonblocking(self, |file| file.unlock()).await
}
fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()> {
sync(self, |file| file.set_len(len))
}
async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()> {
nonblocking(self, move |file| file.set_len(len)).await
}
fn utime_sync(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
sync(self, |file| {
filetime::set_file_handle_times(file, Some(atime), Some(mtime))
})
}
async fn utime_async(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
nonblocking(self, move |file| {
filetime::set_file_handle_times(file, Some(atime), Some(mtime))
})
.await
}
}

View file

@ -14,7 +14,10 @@ description = "IO promitives for Deno extensions"
path = "lib.rs"
[dependencies]
async-trait.workspace = true
deno_core.workspace = true
filetime.workspace = true
fs3.workspace = true
once_cell.workspace = true
tokio.workspace = true

330
ext/io/fs.rs Normal file
View file

@ -0,0 +1,330 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::borrow::Cow;
use std::io;
use std::rc::Rc;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use deno_core::error::not_supported;
use deno_core::error::resource_unavailable;
use deno_core::error::AnyError;
use deno_core::BufMutView;
use deno_core::BufView;
use deno_core::OpState;
use deno_core::ResourceId;
use tokio::task::JoinError;
pub enum FsError {
Io(io::Error),
FileBusy,
NotSupported,
}
impl From<io::Error> for FsError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<FsError> for AnyError {
fn from(err: FsError) -> Self {
match err {
FsError::Io(err) => AnyError::from(err),
FsError::FileBusy => resource_unavailable(),
FsError::NotSupported => not_supported(),
}
}
}
impl From<JoinError> for FsError {
fn from(err: JoinError) -> Self {
if err.is_cancelled() {
todo!("async tasks must not be cancelled")
}
if err.is_panic() {
std::panic::resume_unwind(err.into_panic()); // resume the panic on the main thread
}
unreachable!()
}
}
pub type FsResult<T> = Result<T, FsError>;
pub struct FsStat {
pub is_file: bool,
pub is_directory: bool,
pub is_symlink: bool,
pub size: u64,
pub mtime: Option<u64>,
pub atime: Option<u64>,
pub birthtime: Option<u64>,
pub dev: u64,
pub ino: u64,
pub mode: u32,
pub nlink: u64,
pub uid: u32,
pub gid: u32,
pub rdev: u64,
pub blksize: u64,
pub blocks: u64,
}
impl FsStat {
pub fn from_std(metadata: std::fs::Metadata) -> Self {
macro_rules! unix_or_zero {
($member:ident) => {{
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
metadata.$member()
}
#[cfg(not(unix))]
{
0
}
}};
}
#[inline(always)]
fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Option<u64> {
match maybe_time {
Ok(time) => Some(
time
.duration_since(UNIX_EPOCH)
.map(|t| t.as_millis() as u64)
.unwrap_or_else(|err| err.duration().as_millis() as u64),
),
Err(_) => None,
}
}
Self {
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),
}
}
}
#[async_trait::async_trait(?Send)]
pub trait File {
fn read_sync(self: Rc<Self>, buf: &mut [u8]) -> FsResult<usize>;
async fn read(self: Rc<Self>, limit: usize) -> FsResult<BufView> {
let vec = vec![0; limit];
let buf = BufMutView::from(vec);
let (nread, buf) = self.read_byob(buf).await?;
let mut vec = buf.unwrap_vec();
if vec.len() != nread {
vec.truncate(nread);
}
Ok(BufView::from(vec))
}
async fn read_byob(
self: Rc<Self>,
buf: BufMutView,
) -> FsResult<(usize, BufMutView)>;
fn write_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<usize>;
async fn write(
self: Rc<Self>,
buf: BufView,
) -> FsResult<deno_core::WriteOutcome>;
fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()>;
async fn write_all(self: Rc<Self>, buf: BufView) -> FsResult<()>;
fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>>;
async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>>;
fn chmod_sync(self: Rc<Self>, pathmode: u32) -> FsResult<()>;
async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()>;
fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
fn datasync_sync(self: Rc<Self>) -> FsResult<()>;
async fn datasync_async(self: Rc<Self>) -> FsResult<()>;
fn sync_sync(self: Rc<Self>) -> FsResult<()>;
async fn sync_async(self: Rc<Self>) -> FsResult<()>;
fn stat_sync(self: Rc<Self>) -> FsResult<FsStat>;
async fn stat_async(self: Rc<Self>) -> FsResult<FsStat>;
fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
fn unlock_sync(self: Rc<Self>) -> FsResult<()>;
async fn unlock_async(self: Rc<Self>) -> FsResult<()>;
fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()>;
async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()>;
fn utime_sync(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
async fn utime_async(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
// lower level functionality
fn as_stdio(self: Rc<Self>) -> FsResult<std::process::Stdio>;
#[cfg(unix)]
fn backing_fd(self: Rc<Self>) -> Option<std::os::unix::prelude::RawFd>;
#[cfg(windows)]
fn backing_fd(self: Rc<Self>) -> Option<std::os::windows::io::RawHandle>;
fn try_clone_inner(self: Rc<Self>) -> FsResult<Rc<dyn File>>;
}
pub struct FileResource {
name: String,
file: Rc<dyn File>,
}
impl FileResource {
pub fn new(file: Rc<dyn File>, name: String) -> Self {
Self { name, file }
}
pub fn with_resource<F, R>(
state: &OpState,
rid: ResourceId,
f: F,
) -> Result<R, AnyError>
where
F: FnOnce(Rc<FileResource>) -> Result<R, AnyError>,
{
let resource = state.resource_table.get::<FileResource>(rid)?;
f(resource)
}
pub fn get_file(
state: &OpState,
rid: ResourceId,
) -> Result<Rc<dyn File>, AnyError> {
let resource = state.resource_table.get::<FileResource>(rid)?;
Ok(resource.file())
}
pub fn with_file<F, R>(
state: &OpState,
rid: ResourceId,
f: F,
) -> Result<R, AnyError>
where
F: FnOnce(Rc<dyn File>) -> Result<R, AnyError>,
{
Self::with_resource(state, rid, |r| f(r.file.clone()))
}
pub fn file(&self) -> Rc<dyn File> {
self.file.clone()
}
}
impl deno_core::Resource for FileResource {
fn name(&self) -> Cow<str> {
Cow::Borrowed(&self.name)
}
fn read(
self: Rc<Self>,
limit: usize,
) -> deno_core::AsyncResult<deno_core::BufView> {
Box::pin(async move {
self
.file
.clone()
.read(limit)
.await
.map_err(|err| err.into())
})
}
fn read_byob(
self: Rc<Self>,
buf: deno_core::BufMutView,
) -> deno_core::AsyncResult<(usize, deno_core::BufMutView)> {
Box::pin(async move {
self
.file
.clone()
.read_byob(buf)
.await
.map_err(|err| err.into())
})
}
fn write(
self: Rc<Self>,
buf: deno_core::BufView,
) -> deno_core::AsyncResult<deno_core::WriteOutcome> {
Box::pin(async move {
self.file.clone().write(buf).await.map_err(|err| err.into())
})
}
fn write_all(
self: Rc<Self>,
buf: deno_core::BufView,
) -> deno_core::AsyncResult<()> {
Box::pin(async move {
self
.file
.clone()
.write_all(buf)
.await
.map_err(|err| err.into())
})
}
fn read_byob_sync(
self: Rc<Self>,
data: &mut [u8],
) -> Result<usize, deno_core::anyhow::Error> {
self.file.clone().read_sync(data).map_err(|err| err.into())
}
fn write_sync(
self: Rc<Self>,
data: &[u8],
) -> Result<usize, deno_core::anyhow::Error> {
self.file.clone().write_sync(data).map_err(|err| err.into())
}
#[cfg(unix)]
fn backing_fd(self: Rc<Self>) -> Option<std::os::unix::prelude::RawFd> {
self.file.clone().backing_fd()
}
#[cfg(windows)]
fn backing_fd(self: Rc<Self>) -> Option<std::os::windows::io::RawHandle> {
self.file.clone().backing_fd()
}
}

View file

@ -1,6 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use deno_core::error::resource_unavailable;
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::AsyncMutFuture;
@ -13,8 +12,12 @@ use deno_core::CancelTryFuture;
use deno_core::OpState;
use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::TaskQueue;
use fs::FileResource;
use fs::FsError;
use fs::FsResult;
use fs::FsStat;
use fs3::FileExt;
use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::cell::RefCell;
@ -22,6 +25,7 @@ use std::fs::File as StdFile;
use std::io;
use std::io::ErrorKind;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
use std::rc::Rc;
use tokio::io::AsyncRead;
@ -40,6 +44,8 @@ use winapi::um::processenv::GetStdHandle;
#[cfg(windows)]
use winapi::um::winbase;
pub mod fs;
// Store the stdio fd/handles in global statics in order to keep them
// alive for the duration of the application since the last handle/fd
// being dropped will close the corresponding pipe.
@ -89,39 +95,39 @@ deno_core::extension!(deno_io,
if let Some(stdio) = options.stdio {
let t = &mut state.resource_table;
let rid = t.add(StdFileResource::stdio(
match stdio.stdin {
StdioPipe::Inherit => StdFileResourceInner {
kind: StdFileResourceKind::Stdin,
file: STDIN_HANDLE.try_clone().unwrap(),
},
let rid = t.add(fs::FileResource::new(
Rc::new(match stdio.stdin {
StdioPipe::Inherit => StdFileResourceInner::new(
StdFileResourceKind::Stdin,
STDIN_HANDLE.try_clone().unwrap(),
),
StdioPipe::File(pipe) => StdFileResourceInner::file(pipe),
},
"stdin",
}),
"stdin".to_string(),
));
assert_eq!(rid, 0, "stdin must have ResourceId 0");
let rid = t.add(StdFileResource::stdio(
match stdio.stdout {
StdioPipe::Inherit => StdFileResourceInner {
kind: StdFileResourceKind::Stdout,
file: STDOUT_HANDLE.try_clone().unwrap(),
},
let rid = t.add(FileResource::new(
Rc::new(match stdio.stdout {
StdioPipe::Inherit => StdFileResourceInner::new(
StdFileResourceKind::Stdout,
STDOUT_HANDLE.try_clone().unwrap(),
),
StdioPipe::File(pipe) => StdFileResourceInner::file(pipe),
},
"stdout",
}),
"stdout".to_string(),
));
assert_eq!(rid, 1, "stdout must have ResourceId 1");
let rid = t.add(StdFileResource::stdio(
match stdio.stderr {
StdioPipe::Inherit => StdFileResourceInner {
kind: StdFileResourceKind::Stderr,
file: STDERR_HANDLE.try_clone().unwrap(),
},
let rid = t.add(FileResource::new(
Rc::new(match stdio.stderr {
StdioPipe::Inherit => StdFileResourceInner::new(
StdFileResourceKind::Stderr,
STDERR_HANDLE.try_clone().unwrap(),
),
StdioPipe::File(pipe) => StdFileResourceInner::file(pipe),
},
"stderr",
}),
"stderr".to_string(),
));
assert_eq!(rid, 2, "stderr must have ResourceId 2");
}
@ -291,150 +297,43 @@ enum StdFileResourceKind {
Stderr,
}
struct StdFileResourceInner {
pub struct StdFileResourceInner {
kind: StdFileResourceKind,
file: StdFile,
}
impl StdFileResourceInner {
pub fn file(fs_file: StdFile) -> Self {
StdFileResourceInner {
kind: StdFileResourceKind::File,
file: fs_file,
}
}
pub fn with_file<R>(&mut self, f: impl FnOnce(&mut StdFile) -> R) -> R {
f(&mut self.file)
}
pub fn try_clone(&self) -> Result<Self, std::io::Error> {
Ok(Self {
kind: self.kind,
file: self.file.try_clone()?,
})
}
pub fn write_and_maybe_flush(
&mut self,
buf: &[u8],
) -> Result<usize, AnyError> {
// Rust will line buffer and we don't want that behavior
// (see https://github.com/denoland/deno/issues/948), so flush stdout and stderr.
// Although an alternative solution could be to bypass Rust's std by
// using the raw fds/handles, it will cause encoding issues on Windows
// that we get solved for free by using Rust's stdio wrappers (see
// std/src/sys/windows/stdio.rs in Rust's source code).
match self.kind {
StdFileResourceKind::File => Ok(self.file.write(buf)?),
StdFileResourceKind::Stdin => {
Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
}
StdFileResourceKind::Stdout => {
// bypass the file and use std::io::stdout()
let mut stdout = std::io::stdout().lock();
let nwritten = stdout.write(buf)?;
stdout.flush()?;
Ok(nwritten)
}
StdFileResourceKind::Stderr => {
// bypass the file and use std::io::stderr()
let mut stderr = std::io::stderr().lock();
let nwritten = stderr.write(buf)?;
stderr.flush()?;
Ok(nwritten)
}
}
}
pub fn write_all_and_maybe_flush(
&mut self,
buf: &[u8],
) -> Result<(), AnyError> {
// this method exists instead of using a `Write` implementation
// so that we can acquire the locks once and do both actions
match self.kind {
StdFileResourceKind::File => Ok(self.file.write_all(buf)?),
StdFileResourceKind::Stdin => {
Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
}
StdFileResourceKind::Stdout => {
// bypass the file and use std::io::stdout()
let mut stdout = std::io::stdout().lock();
stdout.write_all(buf)?;
stdout.flush()?;
Ok(())
}
StdFileResourceKind::Stderr => {
// bypass the file and use std::io::stderr()
let mut stderr = std::io::stderr().lock();
stderr.write_all(buf)?;
stderr.flush()?;
Ok(())
}
}
}
}
impl Read for StdFileResourceInner {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self.kind {
StdFileResourceKind::File | StdFileResourceKind::Stdin => {
self.file.read(buf)
}
StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
Err(ErrorKind::Unsupported.into())
}
}
}
}
pub struct StdFileResource {
name: String,
// We can't use an AsyncRefCell here because we need to allow
// access to the resource synchronously at any time and
// asynchronously one at a time in order
cell: RefCell<Option<StdFileResourceInner>>,
cell: RefCell<Option<StdFile>>,
// Used to keep async actions in order and only allow one
// to occur at a time
cell_async_task_queue: TaskQueue,
}
impl StdFileResource {
fn stdio(inner: StdFileResourceInner, name: &str) -> Self {
Self {
cell: RefCell::new(Some(inner)),
impl StdFileResourceInner {
pub fn file(fs_file: StdFile) -> Self {
StdFileResourceInner::new(StdFileResourceKind::File, fs_file)
}
fn new(kind: StdFileResourceKind, fs_file: StdFile) -> Self {
StdFileResourceInner {
kind,
cell: RefCell::new(Some(fs_file)),
cell_async_task_queue: Default::default(),
name: name.to_string(),
}
}
pub fn fs_file(fs_file: StdFile) -> Self {
Self {
cell: RefCell::new(Some(StdFileResourceInner::file(fs_file))),
cell_async_task_queue: Default::default(),
name: "fsFile".to_string(),
}
}
fn with_inner<TResult, E>(
&self,
action: impl FnOnce(&mut StdFileResourceInner) -> Result<TResult, E>,
) -> Option<Result<TResult, E>> {
match self.cell.try_borrow_mut() {
Ok(mut cell) if cell.is_some() => {
let mut file = cell.take().unwrap();
let result = action(&mut file);
cell.replace(file);
Some(result)
}
_ => None,
}
}
async fn with_inner_blocking_task<F, R: Send + 'static>(&self, action: F) -> R
fn with_sync<F, R>(&self, action: F) -> FsResult<R>
where
F: FnOnce(&mut StdFileResourceInner) -> R + Send + 'static,
F: FnOnce(&mut StdFile) -> FsResult<R>,
{
match self.cell.try_borrow_mut() {
Ok(mut cell) if cell.is_some() => action(cell.as_mut().unwrap()),
_ => Err(fs::FsError::FileBusy),
}
}
async fn with_inner_blocking_task<F, R: 'static + Send>(&self, action: F) -> R
where
F: FnOnce(&mut StdFile) -> R + Send + 'static,
{
// we want to restrict this to one async action at a time
let _permit = self.cell_async_task_queue.acquire().await;
@ -443,9 +342,9 @@ impl StdFileResource {
let mut did_take = false;
let mut cell_value = {
let mut cell = self.cell.borrow_mut();
match cell.as_mut().unwrap().try_clone() {
Ok(value) => value,
Err(_) => {
match cell.as_mut().unwrap().try_clone().ok() {
Some(value) => value,
None => {
did_take = true;
cell.take().unwrap()
}
@ -466,200 +365,369 @@ impl StdFileResource {
result
}
async fn read_byob(
self: Rc<Self>,
mut buf: BufMutView,
) -> Result<(usize, BufMutView), AnyError> {
self
.with_inner_blocking_task(move |inner| {
let nread = inner.read(&mut buf)?;
Ok((nread, buf))
})
.await
async fn with_blocking_task<F, R: 'static + Send>(&self, action: F) -> R
where
F: FnOnce() -> R + Send + 'static,
{
// we want to restrict this to one async action at a time
let _permit = self.cell_async_task_queue.acquire().await;
tokio::task::spawn_blocking(action).await.unwrap()
}
}
#[async_trait::async_trait(?Send)]
impl crate::fs::File for StdFileResourceInner {
fn write_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<usize> {
// Rust will line buffer and we don't want that behavior
// (see https://github.com/denoland/deno/issues/948), so flush stdout and stderr.
// Although an alternative solution could be to bypass Rust's std by
// using the raw fds/handles, it will cause encoding issues on Windows
// that we get solved for free by using Rust's stdio wrappers (see
// std/src/sys/windows/stdio.rs in Rust's source code).
match self.kind {
StdFileResourceKind::File => self.with_sync(|file| Ok(file.write(buf)?)),
StdFileResourceKind::Stdin => {
Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
}
StdFileResourceKind::Stdout => {
// bypass the file and use std::io::stdout()
let mut stdout = std::io::stdout().lock();
let nwritten = stdout.write(buf)?;
stdout.flush()?;
Ok(nwritten)
}
StdFileResourceKind::Stderr => {
// bypass the file and use std::io::stderr()
let mut stderr = std::io::stderr().lock();
let nwritten = stderr.write(buf)?;
stderr.flush()?;
Ok(nwritten)
}
}
}
fn read_sync(self: Rc<Self>, buf: &mut [u8]) -> FsResult<usize> {
match self.kind {
StdFileResourceKind::File | StdFileResourceKind::Stdin => {
self.with_sync(|file| Ok(file.read(buf)?))
}
StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
Err(FsError::NotSupported)
}
}
}
fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()> {
match self.kind {
StdFileResourceKind::File => {
self.with_sync(|file| Ok(file.write_all(buf)?))
}
StdFileResourceKind::Stdin => {
Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
}
StdFileResourceKind::Stdout => {
// bypass the file and use std::io::stdout()
let mut stdout = std::io::stdout().lock();
stdout.write_all(buf)?;
stdout.flush()?;
Ok(())
}
StdFileResourceKind::Stderr => {
// bypass the file and use std::io::stderr()
let mut stderr = std::io::stderr().lock();
stderr.write_all(buf)?;
stderr.flush()?;
Ok(())
}
}
}
async fn write_all(self: Rc<Self>, buf: BufView) -> FsResult<()> {
match self.kind {
StdFileResourceKind::File => {
self
.with_inner_blocking_task(move |file| Ok(file.write_all(&buf)?))
.await
}
StdFileResourceKind::Stdin => {
Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
}
StdFileResourceKind::Stdout => {
self
.with_blocking_task(move || {
// bypass the file and use std::io::stdout()
let mut stdout = std::io::stdout().lock();
stdout.write_all(&buf)?;
stdout.flush()?;
Ok(())
})
.await
}
StdFileResourceKind::Stderr => {
self
.with_blocking_task(move || {
// bypass the file and use std::io::stderr()
let mut stderr = std::io::stderr().lock();
stderr.write_all(&buf)?;
stderr.flush()?;
Ok(())
})
.await
}
}
}
async fn write(
self: Rc<Self>,
view: BufView,
) -> Result<deno_core::WriteOutcome, AnyError> {
self
.with_inner_blocking_task(move |inner| {
let nwritten = inner.write_and_maybe_flush(&view)?;
Ok(deno_core::WriteOutcome::Partial { nwritten, view })
})
.await
}
async fn write_all(self: Rc<Self>, view: BufView) -> Result<(), AnyError> {
self
.with_inner_blocking_task(move |inner| {
inner.write_all_and_maybe_flush(&view)
})
.await
}
fn read_byob_sync(self: Rc<Self>, buf: &mut [u8]) -> Result<usize, AnyError> {
self
.with_inner(|inner| inner.read(buf))
.ok_or_else(resource_unavailable)?
.map_err(Into::into)
}
fn write_sync(self: Rc<Self>, data: &[u8]) -> Result<usize, AnyError> {
self
.with_inner(|inner| inner.write_and_maybe_flush(data))
.ok_or_else(resource_unavailable)?
}
fn with_resource<F, R>(
state: &mut OpState,
rid: ResourceId,
f: F,
) -> Result<R, AnyError>
where
F: FnOnce(Rc<StdFileResource>) -> Result<R, AnyError>,
{
let resource = state.resource_table.get::<StdFileResource>(rid)?;
f(resource)
}
pub fn with_file<F, R>(
state: &mut OpState,
rid: ResourceId,
f: F,
) -> Result<R, AnyError>
where
F: FnOnce(&mut StdFile) -> Result<R, AnyError>,
{
Self::with_resource(state, rid, move |resource| {
resource
.with_inner(move |inner| inner.with_file(f))
.ok_or_else(resource_unavailable)?
})
}
pub fn with_file2<F, R>(self: Rc<Self>, f: F) -> Option<Result<R, io::Error>>
where
F: FnOnce(&mut StdFile) -> Result<R, io::Error>,
{
self.with_inner(move |inner| inner.with_file(f))
}
pub async fn with_file_blocking_task<F, R: Send + 'static>(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
f: F,
) -> Result<R, AnyError>
where
F: (FnOnce(&mut StdFile) -> Result<R, AnyError>) + Send + 'static,
{
let resource = state
.borrow_mut()
.resource_table
.get::<StdFileResource>(rid)?;
resource
.with_inner_blocking_task(move |inner| inner.with_file(f))
.await
}
pub async fn with_file_blocking_task2<F, R: Send + 'static>(
self: Rc<Self>,
f: F,
) -> Result<R, io::Error>
where
F: (FnOnce(&mut StdFile) -> Result<R, io::Error>) + Send + 'static,
{
self
.with_inner_blocking_task(move |inner| inner.with_file(f))
.await
}
pub fn clone_file(
state: &mut OpState,
rid: ResourceId,
) -> Result<StdFile, AnyError> {
Self::with_file(state, rid, move |std_file| {
std_file.try_clone().map_err(AnyError::from)
})
}
pub fn as_stdio(
state: &mut OpState,
rid: u32,
) -> Result<std::process::Stdio, AnyError> {
Self::with_resource(state, rid, |resource| {
resource
.with_inner(|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)?
})
}
}
impl Resource for StdFileResource {
fn name(&self) -> Cow<str> {
self.name.as_str().into()
}
fn read(self: Rc<Self>, limit: usize) -> AsyncResult<deno_core::BufView> {
Box::pin(async move {
let vec = vec![0; limit];
let buf = BufMutView::from(vec);
let (nread, buf) = StdFileResource::read_byob(self, buf).await?;
let mut vec = buf.unwrap_vec();
if vec.len() != nread {
vec.truncate(nread);
) -> FsResult<deno_core::WriteOutcome> {
match self.kind {
StdFileResourceKind::File => {
self
.with_inner_blocking_task(|file| {
let nwritten = file.write(&view)?;
Ok(deno_core::WriteOutcome::Partial { nwritten, view })
})
.await
}
Ok(BufView::from(vec))
StdFileResourceKind::Stdin => {
Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into())
}
StdFileResourceKind::Stdout => {
self
.with_blocking_task(|| {
// bypass the file and use std::io::stdout()
let mut stdout = std::io::stdout().lock();
let nwritten = stdout.write(&view)?;
stdout.flush()?;
Ok(deno_core::WriteOutcome::Partial { nwritten, view })
})
.await
}
StdFileResourceKind::Stderr => {
self
.with_blocking_task(|| {
// bypass the file and use std::io::stderr()
let mut stderr = std::io::stderr().lock();
let nwritten = stderr.write(&view)?;
stderr.flush()?;
Ok(deno_core::WriteOutcome::Partial { nwritten, view })
})
.await
}
}
}
fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>> {
match self.kind {
StdFileResourceKind::File | StdFileResourceKind::Stdin => {
let mut buf = Vec::new();
self.with_sync(|file| Ok(file.read_to_end(&mut buf)?))?;
Ok(buf)
}
StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
Err(FsError::NotSupported)
}
}
}
async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>> {
match self.kind {
StdFileResourceKind::File | StdFileResourceKind::Stdin => {
self
.with_inner_blocking_task(|file| {
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
})
.await
}
StdFileResourceKind::Stdout | StdFileResourceKind::Stderr => {
Err(FsError::NotSupported)
}
}
}
fn chmod_sync(self: Rc<Self>, _mode: u32) -> FsResult<()> {
#[cfg(unix)]
{
use std::os::unix::prelude::PermissionsExt;
self.with_sync(|file| {
Ok(file.set_permissions(std::fs::Permissions::from_mode(_mode))?)
})
}
#[cfg(not(unix))]
Err(FsError::NotSupported)
}
async fn chmod_async(self: Rc<Self>, _mode: u32) -> FsResult<()> {
#[cfg(unix)]
{
use std::os::unix::prelude::PermissionsExt;
self
.with_inner_blocking_task(move |file| {
Ok(file.set_permissions(std::fs::Permissions::from_mode(_mode))?)
})
.await
}
#[cfg(not(unix))]
Err(FsError::NotSupported)
}
fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
self.with_sync(|file| Ok(file.seek(pos)?))
}
async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
self
.with_inner_blocking_task(move |file| Ok(file.seek(pos)?))
.await
}
fn datasync_sync(self: Rc<Self>) -> FsResult<()> {
self.with_sync(|file| Ok(file.sync_data()?))
}
async fn datasync_async(self: Rc<Self>) -> FsResult<()> {
self
.with_inner_blocking_task(|file| Ok(file.sync_data()?))
.await
}
fn sync_sync(self: Rc<Self>) -> FsResult<()> {
self.with_sync(|file| Ok(file.sync_all()?))
}
async fn sync_async(self: Rc<Self>) -> FsResult<()> {
self
.with_inner_blocking_task(|file| Ok(file.sync_all()?))
.await
}
fn stat_sync(self: Rc<Self>) -> FsResult<FsStat> {
self.with_sync(|file| Ok(file.metadata().map(FsStat::from_std)?))
}
async fn stat_async(self: Rc<Self>) -> FsResult<FsStat> {
self
.with_inner_blocking_task(|file| {
Ok(file.metadata().map(FsStat::from_std)?)
})
.await
}
fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
self.with_sync(|file| {
if exclusive {
file.lock_exclusive()?;
} else {
file.lock_shared()?;
}
Ok(())
})
}
fn read_byob(
self: Rc<Self>,
buf: deno_core::BufMutView,
) -> AsyncResult<(usize, deno_core::BufMutView)> {
Box::pin(StdFileResource::read_byob(self, buf))
async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
self
.with_inner_blocking_task(move |file| {
if exclusive {
file.lock_exclusive()?;
} else {
file.lock_shared()?;
}
Ok(())
})
.await
}
fn write(
self: Rc<Self>,
view: deno_core::BufView,
) -> AsyncResult<deno_core::WriteOutcome> {
Box::pin(StdFileResource::write(self, view))
fn unlock_sync(self: Rc<Self>) -> FsResult<()> {
self.with_sync(|file| Ok(file.unlock()?))
}
async fn unlock_async(self: Rc<Self>) -> FsResult<()> {
self
.with_inner_blocking_task(|file| Ok(file.unlock()?))
.await
}
fn write_all(self: Rc<Self>, view: deno_core::BufView) -> AsyncResult<()> {
Box::pin(StdFileResource::write_all(self, view))
fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()> {
self.with_sync(|file| Ok(file.set_len(len)?))
}
async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()> {
self
.with_inner_blocking_task(move |file| Ok(file.set_len(len)?))
.await
}
fn write_sync(
fn utime_sync(
self: Rc<Self>,
data: &[u8],
) -> Result<usize, deno_core::anyhow::Error> {
StdFileResource::write_sync(self, data)
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);
self.with_sync(|file| {
filetime::set_file_handle_times(file, Some(atime), Some(mtime))?;
Ok(())
})
}
async fn utime_async(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
self
.with_inner_blocking_task(move |file| {
filetime::set_file_handle_times(file, Some(atime), Some(mtime))?;
Ok(())
})
.await
}
fn read_byob_sync(
async fn read_byob(
self: Rc<Self>,
data: &mut [u8],
) -> Result<usize, deno_core::anyhow::Error> {
StdFileResource::read_byob_sync(self, data)
mut buf: BufMutView,
) -> FsResult<(usize, BufMutView)> {
self
.with_inner_blocking_task(|file| {
let nread = file.read(&mut buf)?;
Ok((nread, buf))
})
.await
}
fn try_clone_inner(self: Rc<Self>) -> FsResult<Rc<dyn fs::File>> {
let inner: &Option<_> = &self.cell.borrow();
match inner {
Some(inner) => Ok(Rc::new(StdFileResourceInner {
kind: self.kind,
cell: RefCell::new(Some(inner.try_clone()?)),
cell_async_task_queue: Default::default(),
})),
None => Err(FsError::FileBusy),
}
}
fn as_stdio(self: Rc<Self>) -> FsResult<std::process::Stdio> {
match self.kind {
StdFileResourceKind::File => self.with_sync(|file| {
let file = file.try_clone()?;
Ok(file.into())
}),
_ => Ok(std::process::Stdio::inherit()),
}
}
#[cfg(unix)]
fn backing_fd(self: Rc<Self>) -> Option<std::os::unix::prelude::RawFd> {
use std::os::unix::io::AsRawFd;
self
.with_inner(move |std_file| {
Ok::<_, ()>(std_file.with_file(|f| f.as_raw_fd()))
})?
.ok()
self.with_sync(|file| Ok(file.as_raw_fd())).ok()
}
#[cfg(windows)]
fn backing_fd(self: Rc<Self>) -> Option<std::os::windows::io::RawHandle> {
use std::os::windows::prelude::AsRawHandle;
self.with_sync(|file| Ok(file.as_raw_handle())).ok()
}
}
@ -671,12 +739,7 @@ pub fn op_print(
is_err: bool,
) -> Result<(), AnyError> {
let rid = if is_err { 2 } else { 1 };
StdFileResource::with_resource(state, rid, move |resource| {
resource
.with_inner(|inner| {
inner.write_all_and_maybe_flush(msg.as_bytes())?;
Ok(())
})
.ok_or_else(resource_unavailable)?
FileResource::with_file(state, rid, move |file| {
Ok(file.write_all_sync(msg.as_bytes())?)
})
}

View file

@ -18,7 +18,6 @@ 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(
@ -310,7 +309,10 @@ mod startup_snapshot {
deno_napi::deno_napi::init_ops_and_esm::<Permissions>(),
deno_http::deno_http::init_ops_and_esm(),
deno_io::deno_io::init_ops_and_esm(Default::default()),
deno_fs::deno_fs::init_ops_and_esm::<_, Permissions>(false, StdFs),
deno_fs::deno_fs::init_ops_and_esm::<Permissions>(
false,
std::sync::Arc::new(deno_fs::RealFs),
),
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

View file

@ -12,10 +12,10 @@ use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use deno_io::fs::FileResource;
use deno_io::ChildStderrResource;
use deno_io::ChildStdinResource;
use deno_io::ChildStdoutResource;
use deno_io::StdFileResource;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
@ -93,7 +93,9 @@ impl StdioOrRid {
) -> Result<std::process::Stdio, AnyError> {
match &self {
StdioOrRid::Stdio(val) => Ok(val.as_stdio()),
StdioOrRid::Rid(rid) => StdFileResource::as_stdio(state, *rid),
StdioOrRid::Rid(rid) => {
FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?))
}
}
}
}

View file

@ -1,10 +1,14 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::io::Error;
use std::rc::Rc;
use deno_core::error::resource_unavailable;
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::OpState;
use deno_io::StdFileResource;
use std::io::Error;
use deno_core::Resource;
use deno_io::fs::FileResource;
#[cfg(unix)]
use deno_core::ResourceId;
@ -14,8 +18,6 @@ use nix::sys::termios;
use std::cell::RefCell;
#[cfg(unix)]
use std::collections::HashMap;
#[cfg(unix)]
use std::rc::Rc;
#[cfg(unix)]
#[derive(Default, Clone)]
@ -44,13 +46,14 @@ use winapi::shared::minwindef::DWORD;
use winapi::um::wincon;
#[cfg(windows)]
fn get_windows_handle(
f: &std::fs::File,
fn get_fd_from_resource(
resource: Rc<FileResource>,
) -> Result<std::os::windows::io::RawHandle, AnyError> {
use std::os::windows::io::AsRawHandle;
use winapi::um::handleapi;
let handle = f.as_raw_handle();
let Some(handle) = resource.backing_fd() else {
return Err(resource_unavailable());
};
if handle == handleapi::INVALID_HANDLE_VALUE {
return Err(Error::last_os_error().into());
} else if handle.is_null() {
@ -59,6 +62,16 @@ fn get_windows_handle(
Ok(handle)
}
#[cfg(not(windows))]
fn get_fd_from_resource(
resource: Rc<FileResource>,
) -> Result<std::os::unix::prelude::RawFd, AnyError> {
match resource.backing_fd() {
Some(fd) => Ok(fd),
None => Err(resource_unavailable()),
}
}
deno_core::extension!(
deno_tty,
ops = [op_stdin_set_raw, op_isatty, op_console_size],
@ -106,23 +119,15 @@ fn op_stdin_set_raw(
// Copyright (c) 2019 Timon. MIT license.
#[cfg(windows)]
{
use std::os::windows::io::AsRawHandle;
use winapi::shared::minwindef::FALSE;
use winapi::um::consoleapi;
use winapi::um::handleapi;
if cbreak {
return Err(deno_core::error::not_supported());
}
StdFileResource::with_file(state, rid, move |std_file| {
let handle = std_file.as_raw_handle();
if handle == handleapi::INVALID_HANDLE_VALUE {
return Err(Error::last_os_error().into());
} else if handle.is_null() {
return Err(custom_error("ReferenceError", "null handle"));
}
FileResource::with_resource(state, rid, move |resource| {
let handle = get_fd_from_resource(resource)?;
let mut original_mode: DWORD = 0;
// SAFETY: winapi call
if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) }
@ -147,13 +152,11 @@ fn op_stdin_set_raw(
}
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let tty_mode_store = state.borrow::<TtyModeStore>().clone();
let previous_mode = tty_mode_store.get(rid);
StdFileResource::with_file(state, rid, move |std_file| {
let raw_fd = std_file.as_raw_fd();
FileResource::with_resource(state, rid, move |resource| {
let raw_fd = get_fd_from_resource(resource)?;
if is_raw {
let mut raw = match previous_mode {
@ -201,13 +204,14 @@ fn op_isatty(
rid: u32,
out: &mut [u8],
) -> Result<(), AnyError> {
StdFileResource::with_file(state, rid, move |std_file| {
FileResource::with_resource(state, rid, move |resource| {
let raw_fd = get_fd_from_resource(resource)?;
#[cfg(windows)]
{
use winapi::shared::minwindef::FALSE;
use winapi::um::consoleapi;
let handle = get_windows_handle(std_file)?;
let handle = raw_fd;
let mut test_mode: DWORD = 0;
// If I cannot get mode out of console, it is not a console.
// TODO(bartlomieju):
@ -220,8 +224,6 @@ fn op_isatty(
}
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let raw_fd = std_file.as_raw_fd();
// TODO(bartlomieju):
#[allow(clippy::undocumented_unsafe_blocks)]
{
@ -242,8 +244,9 @@ fn op_console_size(
result: &mut [u32],
rid: u32,
) -> Result<(), AnyError> {
StdFileResource::with_file(state, rid, move |std_file| {
let size = console_size(std_file)?;
FileResource::with_resource(state, rid, move |resource| {
let fd = get_fd_from_resource(resource)?;
let size = console_size_from_fd(fd)?;
result[0] = size.cols;
result[1] = size.rows;
Ok(())
@ -276,40 +279,50 @@ pub fn console_size(
{
use std::os::windows::io::AsRawHandle;
let handle = std_file.as_raw_handle();
// SAFETY: winapi calls
unsafe {
let mut bufinfo: winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO =
std::mem::zeroed();
if winapi::um::wincon::GetConsoleScreenBufferInfo(handle, &mut bufinfo)
== 0
{
return Err(Error::last_os_error());
}
Ok(ConsoleSize {
cols: bufinfo.dwSize.X as u32,
rows: bufinfo.dwSize.Y as u32,
})
}
console_size_from_fd(handle)
}
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let fd = std_file.as_raw_fd();
// SAFETY: libc calls
unsafe {
let mut size: libc::winsize = std::mem::zeroed();
if libc::ioctl(fd, libc::TIOCGWINSZ, &mut size as *mut _) != 0 {
return Err(Error::last_os_error());
}
Ok(ConsoleSize {
cols: size.ws_col as u32,
rows: size.ws_row as u32,
})
console_size_from_fd(fd)
}
}
#[cfg(windows)]
fn console_size_from_fd(
handle: std::os::windows::io::RawHandle,
) -> Result<ConsoleSize, std::io::Error> {
// SAFETY: winapi calls
unsafe {
let mut bufinfo: winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO =
std::mem::zeroed();
if winapi::um::wincon::GetConsoleScreenBufferInfo(handle, &mut bufinfo) == 0
{
return Err(Error::last_os_error());
}
Ok(ConsoleSize {
cols: bufinfo.dwSize.X as u32,
rows: bufinfo.dwSize.Y as u32,
})
}
}
#[cfg(not(windows))]
fn console_size_from_fd(
fd: std::os::unix::prelude::RawFd,
) -> Result<ConsoleSize, std::io::Error> {
// SAFETY: libc calls
unsafe {
let mut size: libc::winsize = std::mem::zeroed();
if libc::ioctl(fd, libc::TIOCGWINSZ, &mut size as *mut _) != 0 {
return Err(Error::last_os_error());
}
Ok(ConsoleSize {
cols: size.ws_col as u32,
rows: size.ws_row as u32,
})
}
}

View file

@ -34,7 +34,7 @@ use deno_core::RuntimeOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::Snapshot;
use deno_core::SourceMapGetter;
use deno_fs::StdFs;
use deno_fs::FileSystem;
use deno_io::Stdio;
use deno_kv::sqlite::SqliteDbHandler;
use deno_tls::RootCertStoreProvider;
@ -331,6 +331,7 @@ pub struct WebWorkerOptions {
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>,
pub seed: Option<u64>,
pub fs: Arc<dyn FileSystem>,
pub module_loader: Rc<dyn ModuleLoader>,
pub node_fs: Option<Arc<dyn deno_node::NodeFs>>,
pub npm_resolver: Option<Arc<dyn deno_node::NpmResolver>>,
@ -441,7 +442,7 @@ impl WebWorker {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Some(options.stdio)),
deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs),
deno_fs::deno_fs::init_ops::<PermissionsContainer>(unstable, options.fs),
deno_node::deno_node::init_ops::<PermissionsContainer>(
options.npm_resolver,
options.node_fs,

View file

@ -30,7 +30,7 @@ use deno_core::RuntimeOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::Snapshot;
use deno_core::SourceMapGetter;
use deno_fs::StdFs;
use deno_fs::FileSystem;
use deno_io::Stdio;
use deno_kv::sqlite::SqliteDbHandler;
use deno_tls::RootCertStoreProvider;
@ -87,6 +87,7 @@ pub struct WorkerOptions {
pub root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>,
pub seed: Option<u64>,
pub fs: Arc<dyn FileSystem>,
/// Implementation of `ModuleLoader` which will be
/// called when V8 requests to load ES modules.
///
@ -149,6 +150,7 @@ impl Default for WorkerOptions {
create_web_worker_cb: Arc::new(|_| {
unimplemented!("web workers are not supported")
}),
fs: Arc::new(deno_fs::RealFs),
module_loader: Rc::new(FsModuleLoader),
seed: None,
unsafely_ignore_certificate_errors: Default::default(),
@ -266,7 +268,7 @@ impl MainWorker {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Some(options.stdio)),
deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs),
deno_fs::deno_fs::init_ops::<PermissionsContainer>(unstable, options.fs),
deno_node::deno_node::init_ops::<PermissionsContainer>(
options.npm_resolver,
options.node_fs,