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

refactor(ext/fs): abstract FS via FileSystem trait (#18599)

This commit abstracts out the specifics of the underlying system calls
FS operations behind a new `FileSystem` and `File` trait in the
`ext/fs` extension.

This allows other embedders to re-use ext/fs, but substituting in a
different FS backend.

This is likely not the final form of these traits. Eventually they will
be entirely `deno_core::Resource` agnostic, and will live in a seperate
crate.

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
Luca Casonato 2023-04-12 15:13:32 +02:00 committed by GitHub
parent 0e3f62d444
commit f90caa821c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 3350 additions and 2488 deletions

3
Cargo.lock generated
View file

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

View file

@ -8,6 +8,7 @@ use deno_core::Extension;
use deno_core::ExtensionFileSource;
use deno_core::ExtensionFileSourceCode;
use deno_runtime::deno_cache::SqliteBackedCache;
use deno_runtime::deno_fs::StdFs;
use deno_runtime::deno_kv::sqlite::SqliteDbHandler;
use deno_runtime::permissions::PermissionsContainer;
use deno_runtime::*;
@ -361,7 +362,7 @@ fn create_cli_snapshot(snapshot_path: PathBuf) {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Default::default()),
deno_fs::deno_fs::init_ops::<PermissionsContainer>(false),
deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(false, StdFs),
deno_node::deno_node::init_ops::<deno_runtime::RuntimeNodeEnv>(None),
cli::init_ops_and_esm(), // NOTE: This needs to be init_ops_and_esm!
];

View file

@ -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()
}

View file

@ -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,
);

View file

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

45
ext/fs/clippy.toml Normal file
View file

@ -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" },
]

395
ext/fs/interface.rs Normal file
View file

@ -0,0 +1,395 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use deno_core::error::not_supported;
use deno_core::error::resource_unavailable;
use deno_core::error::AnyError;
use deno_core::Resource;
use serde::Deserialize;
use serde::Serialize;
use tokio::task::JoinError;
pub trait FsPermissions {
fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_read_blind(
&mut self,
p: &Path,
display: &str,
api_name: &str,
) -> Result<(), AnyError>;
fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>;
fn check_write_blind(
&mut self,
p: &Path,
display: &str,
api_name: &str,
) -> Result<(), AnyError>;
}
#[derive(Deserialize, Default, Debug, Clone, Copy)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct OpenOptions {
pub read: bool,
pub write: bool,
pub create: bool,
pub truncate: bool,
pub append: bool,
pub create_new: bool,
pub mode: Option<u32>,
}
impl OpenOptions {
pub fn read() -> Self {
Self {
read: true,
write: false,
create: false,
truncate: false,
append: false,
create_new: false,
mode: None,
}
}
pub fn write(
create: bool,
append: bool,
create_new: bool,
mode: Option<u32>,
) -> Self {
Self {
read: false,
write: true,
create,
truncate: !append,
append,
create_new,
mode,
}
}
pub(crate) fn check<P: FsPermissions>(
&self,
permissions: &mut P,
path: &Path,
api_name: &str,
) -> Result<(), AnyError> {
if self.read {
permissions.check_read(path, api_name)?;
}
if self.write || self.append {
permissions.check_write(path, api_name)?;
}
Ok(())
}
}
pub struct FsStat {
pub is_file: bool,
pub is_directory: bool,
pub is_symlink: bool,
pub size: u64,
pub mtime: Option<u64>,
pub atime: Option<u64>,
pub birthtime: Option<u64>,
pub dev: u64,
pub ino: u64,
pub mode: u32,
pub nlink: u64,
pub uid: u32,
pub gid: u32,
pub rdev: u64,
pub blksize: u64,
pub blocks: u64,
}
#[derive(Deserialize)]
pub enum FsFileType {
#[serde(rename = "file")]
File,
#[serde(rename = "dir")]
Directory,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FsDirEntry {
pub name: String,
pub is_file: bool,
pub is_directory: bool,
pub is_symlink: bool,
}
pub enum FsError {
Io(io::Error),
FileBusy,
NotSupported,
}
impl From<io::Error> for FsError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<JoinError> for FsError {
fn from(err: JoinError) -> Self {
if err.is_cancelled() {
todo!("async tasks must not be cancelled")
}
if err.is_panic() {
std::panic::resume_unwind(err.into_panic()); // resume the panic on the main thread
}
unreachable!()
}
}
impl From<FsError> for AnyError {
fn from(err: FsError) -> Self {
match err {
FsError::Io(err) => AnyError::from(err),
FsError::FileBusy => resource_unavailable(),
FsError::NotSupported => not_supported(),
}
}
}
pub type FsResult<T> = Result<T, FsError>;
#[async_trait::async_trait(?Send)]
pub trait File {
fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()>;
async fn write_all_async(self: Rc<Self>, buf: Vec<u8>) -> FsResult<()>;
fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>>;
async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>>;
fn chmod_sync(self: Rc<Self>, pathmode: u32) -> FsResult<()>;
async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()>;
fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64>;
fn datasync_sync(self: Rc<Self>) -> FsResult<()>;
async fn datasync_async(self: Rc<Self>) -> FsResult<()>;
fn sync_sync(self: Rc<Self>) -> FsResult<()>;
async fn sync_async(self: Rc<Self>) -> FsResult<()>;
fn stat_sync(self: Rc<Self>) -> FsResult<FsStat>;
async fn stat_async(self: Rc<Self>) -> FsResult<FsStat>;
fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()>;
fn unlock_sync(self: Rc<Self>) -> FsResult<()>;
async fn unlock_async(self: Rc<Self>) -> FsResult<()>;
fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()>;
async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()>;
fn utime_sync(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
async fn utime_async(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
}
#[async_trait::async_trait(?Send)]
pub trait FileSystem: Clone {
type File: File + Resource;
fn cwd(&self) -> FsResult<PathBuf>;
fn tmp_dir(&self) -> FsResult<PathBuf>;
fn chdir(&self, path: impl AsRef<Path>) -> FsResult<()>;
fn umask(&self, mask: Option<u32>) -> FsResult<u32>;
fn open_sync(
&self,
path: impl AsRef<Path>,
options: OpenOptions,
) -> FsResult<Self::File>;
async fn open_async(
&self,
path: PathBuf,
options: OpenOptions,
) -> FsResult<Self::File>;
fn mkdir_sync(
&self,
path: impl AsRef<Path>,
recusive: bool,
mode: u32,
) -> FsResult<()>;
async fn mkdir_async(
&self,
path: PathBuf,
recusive: bool,
mode: u32,
) -> FsResult<()>;
fn chmod_sync(&self, path: impl AsRef<Path>, mode: u32) -> FsResult<()>;
async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()>;
fn chown_sync(
&self,
path: impl AsRef<Path>,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()>;
async fn chown_async(
&self,
path: PathBuf,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()>;
fn remove_sync(
&self,
path: impl AsRef<Path>,
recursive: bool,
) -> FsResult<()>;
async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()>;
fn copy_file_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()>;
async fn copy_file_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
) -> FsResult<()>;
fn stat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat>;
async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat>;
fn lstat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat>;
async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat>;
fn realpath_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf>;
async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf>;
fn read_dir_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>>;
async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>>;
fn rename_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()>;
async fn rename_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
) -> FsResult<()>;
fn link_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()>;
async fn link_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
) -> FsResult<()>;
fn symlink_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
file_type: Option<FsFileType>,
) -> FsResult<()>;
async fn symlink_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
file_type: Option<FsFileType>,
) -> FsResult<()>;
fn read_link_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf>;
async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf>;
fn truncate_sync(&self, path: impl AsRef<Path>, len: u64) -> FsResult<()>;
async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()>;
fn utime_sync(
&self,
path: impl AsRef<Path>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
async fn utime_async(
&self,
path: PathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()>;
fn write_file_sync(
&self,
path: impl AsRef<Path>,
options: OpenOptions,
data: &[u8],
) -> FsResult<()> {
let file = self.open_sync(path, options)?;
let file = Rc::new(file);
if let Some(mode) = options.mode {
file.clone().chmod_sync(mode)?;
}
file.write_all_sync(data)?;
Ok(())
}
async fn write_file_async(
&self,
path: PathBuf,
options: OpenOptions,
data: Vec<u8>,
) -> FsResult<()> {
let file = self.open_async(path, options).await?;
let file = Rc::new(file);
if let Some(mode) = options.mode {
file.clone().chmod_async(mode).await?;
}
file.write_all_async(data).await?;
Ok(())
}
fn read_file_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
let file = self.open_sync(path, options)?;
let file = Rc::new(file);
let buf = file.read_all_sync()?;
Ok(buf)
}
async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
let options = OpenOptions::read();
let file = self.clone().open_async(path, options).await?;
let file = Rc::new(file);
let buf = file.read_all_async().await?;
Ok(buf)
}
}

File diff suppressed because it is too large Load diff

1712
ext/fs/ops.rs Normal file

File diff suppressed because it is too large Load diff

929
ext/fs/std_fs.rs Normal file
View file

@ -0,0 +1,929 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
#![allow(clippy::disallowed_methods)]
use std::fs;
use std::io;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use deno_io::StdFileResource;
use fs3::FileExt;
use crate::interface::FsDirEntry;
use crate::interface::FsError;
use crate::interface::FsFileType;
use crate::interface::FsResult;
use crate::interface::FsStat;
use crate::File;
use crate::FileSystem;
use crate::OpenOptions;
#[derive(Clone)]
pub struct StdFs;
#[async_trait::async_trait(?Send)]
impl FileSystem for StdFs {
type File = StdFileResource;
fn cwd(&self) -> FsResult<PathBuf> {
std::env::current_dir().map_err(Into::into)
}
fn tmp_dir(&self) -> FsResult<PathBuf> {
Ok(std::env::temp_dir())
}
fn chdir(&self, path: impl AsRef<Path>) -> FsResult<()> {
std::env::set_current_dir(path).map_err(Into::into)
}
#[cfg(not(unix))]
fn umask(&self, _mask: Option<u32>) -> FsResult<u32> {
// TODO implement umask for Windows
// see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc
// and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019
Err(FsError::NotSupported)
}
#[cfg(unix)]
fn umask(&self, mask: Option<u32>) -> FsResult<u32> {
use nix::sys::stat::mode_t;
use nix::sys::stat::umask;
use nix::sys::stat::Mode;
let r = if let Some(mask) = mask {
// If mask provided, return previous.
umask(Mode::from_bits_truncate(mask as mode_t))
} else {
// If no mask provided, we query the current. Requires two syscalls.
let prev = umask(Mode::from_bits_truncate(0o777));
let _ = umask(prev);
prev
};
#[cfg(target_os = "linux")]
{
Ok(r.bits())
}
#[cfg(target_os = "macos")]
{
Ok(r.bits() as u32)
}
}
fn open_sync(
&self,
path: impl AsRef<Path>,
options: OpenOptions,
) -> FsResult<Self::File> {
let opts = open_options(options);
let std_file = opts.open(path)?;
Ok(StdFileResource::fs_file(std_file))
}
async fn open_async(
&self,
path: PathBuf,
options: OpenOptions,
) -> FsResult<Self::File> {
let opts = open_options(options);
let std_file =
tokio::task::spawn_blocking(move || opts.open(path)).await??;
Ok(StdFileResource::fs_file(std_file))
}
fn mkdir_sync(
&self,
path: impl AsRef<Path>,
recursive: bool,
mode: u32,
) -> FsResult<()> {
mkdir(path, recursive, mode)
}
async fn mkdir_async(
&self,
path: PathBuf,
recursive: bool,
mode: u32,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || mkdir(path, recursive, mode)).await?
}
fn chmod_sync(&self, path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
chmod(path, mode)
}
async fn chmod_async(&self, path: PathBuf, mode: u32) -> FsResult<()> {
tokio::task::spawn_blocking(move || chmod(path, mode)).await?
}
fn chown_sync(
&self,
path: impl AsRef<Path>,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()> {
chown(path, uid, gid)
}
async fn chown_async(
&self,
path: PathBuf,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || chown(path, uid, gid)).await?
}
fn remove_sync(
&self,
path: impl AsRef<Path>,
recursive: bool,
) -> FsResult<()> {
remove(path, recursive)
}
async fn remove_async(&self, path: PathBuf, recursive: bool) -> FsResult<()> {
tokio::task::spawn_blocking(move || remove(path, recursive)).await?
}
fn copy_file_sync(
&self,
from: impl AsRef<Path>,
to: impl AsRef<Path>,
) -> FsResult<()> {
copy_file(from, to)
}
async fn copy_file_async(&self, from: PathBuf, to: PathBuf) -> FsResult<()> {
tokio::task::spawn_blocking(move || copy_file(from, to)).await?
}
fn stat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
stat(path).map(Into::into)
}
async fn stat_async(&self, path: PathBuf) -> FsResult<FsStat> {
tokio::task::spawn_blocking(move || stat(path))
.await?
.map(Into::into)
}
fn lstat_sync(&self, path: impl AsRef<Path>) -> FsResult<FsStat> {
lstat(path).map(Into::into)
}
async fn lstat_async(&self, path: PathBuf) -> FsResult<FsStat> {
tokio::task::spawn_blocking(move || lstat(path))
.await?
.map(Into::into)
}
fn realpath_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
realpath(path)
}
async fn realpath_async(&self, path: PathBuf) -> FsResult<PathBuf> {
tokio::task::spawn_blocking(move || realpath(path)).await?
}
fn read_dir_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
read_dir(path)
}
async fn read_dir_async(&self, path: PathBuf) -> FsResult<Vec<FsDirEntry>> {
tokio::task::spawn_blocking(move || read_dir(path)).await?
}
fn rename_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()> {
fs::rename(oldpath, newpath).map_err(Into::into)
}
async fn rename_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || fs::rename(oldpath, newpath))
.await?
.map_err(Into::into)
}
fn link_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
) -> FsResult<()> {
fs::hard_link(oldpath, newpath).map_err(Into::into)
}
async fn link_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || fs::hard_link(oldpath, newpath))
.await?
.map_err(Into::into)
}
fn symlink_sync(
&self,
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
file_type: Option<FsFileType>,
) -> FsResult<()> {
symlink(oldpath, newpath, file_type)
}
async fn symlink_async(
&self,
oldpath: PathBuf,
newpath: PathBuf,
file_type: Option<FsFileType>,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || symlink(oldpath, newpath, file_type))
.await?
}
fn read_link_sync(&self, path: impl AsRef<Path>) -> FsResult<PathBuf> {
fs::read_link(path).map_err(Into::into)
}
async fn read_link_async(&self, path: PathBuf) -> FsResult<PathBuf> {
tokio::task::spawn_blocking(move || fs::read_link(path))
.await?
.map_err(Into::into)
}
fn truncate_sync(&self, path: impl AsRef<Path>, len: u64) -> FsResult<()> {
truncate(path, len)
}
async fn truncate_async(&self, path: PathBuf, len: u64) -> FsResult<()> {
tokio::task::spawn_blocking(move || truncate(path, len)).await?
}
fn utime_sync(
&self,
path: impl AsRef<Path>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
filetime::set_file_times(path, atime, mtime).map_err(Into::into)
}
async fn utime_async(
&self,
path: PathBuf,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
tokio::task::spawn_blocking(move || {
filetime::set_file_times(path, atime, mtime).map_err(Into::into)
})
.await?
}
fn write_file_sync(
&self,
path: impl AsRef<Path>,
options: OpenOptions,
data: &[u8],
) -> FsResult<()> {
let opts = open_options(options);
let mut file = opts.open(path)?;
#[cfg(unix)]
if let Some(mode) = options.mode {
use std::os::unix::fs::PermissionsExt;
file.set_permissions(fs::Permissions::from_mode(mode))?;
}
file.write_all(data)?;
Ok(())
}
async fn write_file_async(
&self,
path: PathBuf,
options: OpenOptions,
data: Vec<u8>,
) -> FsResult<()> {
tokio::task::spawn_blocking(move || {
let opts = open_options(options);
let mut file = opts.open(path)?;
#[cfg(unix)]
if let Some(mode) = options.mode {
use std::os::unix::fs::PermissionsExt;
file.set_permissions(fs::Permissions::from_mode(mode))?;
}
file.write_all(&data)?;
Ok(())
})
.await?
}
fn read_file_sync(&self, path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
fs::read(path).map_err(Into::into)
}
async fn read_file_async(&self, path: PathBuf) -> FsResult<Vec<u8>> {
tokio::task::spawn_blocking(move || fs::read(path))
.await?
.map_err(Into::into)
}
}
fn mkdir(path: impl AsRef<Path>, recursive: bool, mode: u32) -> FsResult<()> {
let mut builder = fs::DirBuilder::new();
builder.recursive(recursive);
#[cfg(unix)]
{
use std::os::unix::fs::DirBuilderExt;
builder.mode(mode);
}
#[cfg(not(unix))]
{
_ = mode;
}
builder.create(path).map_err(Into::into)
}
#[cfg(unix)]
fn chmod(path: impl AsRef<Path>, mode: u32) -> FsResult<()> {
use std::os::unix::fs::PermissionsExt;
let permissions = fs::Permissions::from_mode(mode);
fs::set_permissions(path, permissions)?;
Ok(())
}
// TODO: implement chmod for Windows (#4357)
#[cfg(not(unix))]
fn chmod(path: impl AsRef<Path>, _mode: u32) -> FsResult<()> {
// Still check file/dir exists on Windows
std::fs::metadata(path)?;
Err(FsError::NotSupported)
}
#[cfg(unix)]
fn chown(
path: impl AsRef<Path>,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()> {
use nix::unistd::chown;
use nix::unistd::Gid;
use nix::unistd::Uid;
let owner = uid.map(Uid::from_raw);
let group = gid.map(Gid::from_raw);
let res = chown(path.as_ref(), owner, group);
if let Err(err) = res {
return Err(io::Error::from_raw_os_error(err as i32).into());
}
Ok(())
}
// TODO: implement chown for Windows
#[cfg(not(unix))]
fn chown(
_path: impl AsRef<Path>,
_uid: Option<u32>,
_gid: Option<u32>,
) -> FsResult<()> {
Err(FsError::NotSupported)
}
fn remove(path: impl AsRef<Path>, recursive: bool) -> FsResult<()> {
// TODO: this is racy. This should open fds, and then `unlink` those.
let metadata = fs::symlink_metadata(&path)?;
let file_type = metadata.file_type();
let res = if file_type.is_dir() {
if recursive {
fs::remove_dir_all(&path)
} else {
fs::remove_dir(&path)
}
} else if file_type.is_symlink() {
#[cfg(unix)]
{
fs::remove_file(&path)
}
#[cfg(not(unix))]
{
use std::os::windows::prelude::MetadataExt;
use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
fs::remove_dir(&path)
} else {
fs::remove_file(&path)
}
}
} else {
fs::remove_file(&path)
};
res.map_err(Into::into)
}
fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> FsResult<()> {
#[cfg(target_os = "macos")]
{
use libc::clonefile;
use libc::stat;
use libc::unlink;
use std::ffi::CString;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::prelude::OsStrExt;
let from_str = CString::new(from.as_ref().as_os_str().as_bytes()).unwrap();
let to_str = CString::new(to.as_ref().as_os_str().as_bytes()).unwrap();
// SAFETY: `from` and `to` are valid C strings.
// std::fs::copy does open() + fcopyfile() on macOS. We try to use
// clonefile() instead, which is more efficient.
unsafe {
let mut st = std::mem::zeroed();
let ret = stat(from_str.as_ptr(), &mut st);
if ret != 0 {
return Err(io::Error::last_os_error().into());
}
if st.st_size > 128 * 1024 {
// Try unlink. If it fails, we are going to try clonefile() anyway.
let _ = unlink(to_str.as_ptr());
// Matches rust stdlib behavior for io::copy.
// https://github.com/rust-lang/rust/blob/3fdd578d72a24d4efc2fe2ad18eec3b6ba72271e/library/std/src/sys/unix/fs.rs#L1613-L1616
if clonefile(from_str.as_ptr(), to_str.as_ptr(), 0) == 0 {
return Ok(());
}
} else {
// Do a regular copy. fcopyfile() is an overkill for < 128KB
// files.
let mut buf = [0u8; 128 * 1024];
let mut from_file = fs::File::open(&from)?;
let perm = from_file.metadata()?.permissions();
let mut to_file = fs::OpenOptions::new()
// create the file with the correct mode right away
.mode(perm.mode())
.write(true)
.create(true)
.truncate(true)
.open(&to)?;
let writer_metadata = to_file.metadata()?;
if writer_metadata.is_file() {
// Set the correct file permissions, in case the file already existed.
// Don't set the permissions on already existing non-files like
// pipes/FIFOs or device nodes.
to_file.set_permissions(perm)?;
}
loop {
let nread = from_file.read(&mut buf)?;
if nread == 0 {
break;
}
to_file.write_all(&buf[..nread])?;
}
return Ok(());
}
}
// clonefile() failed, fall back to std::fs::copy().
}
fs::copy(from, to)?;
Ok(())
}
#[cfg(not(windows))]
fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
let metadata = fs::metadata(path)?;
Ok(metadata_to_fsstat(metadata))
}
#[cfg(windows)]
fn stat(path: impl AsRef<Path>) -> FsResult<FsStat> {
let metadata = fs::metadata(path.as_ref())?;
let mut fsstat = metadata_to_fsstat(metadata);
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
let path = path.as_ref().canonicalize()?;
stat_extra(&mut fsstat, &path, FILE_FLAG_BACKUP_SEMANTICS)?;
Ok(fsstat)
}
#[cfg(not(windows))]
fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
let metadata = fs::symlink_metadata(path)?;
Ok(metadata_to_fsstat(metadata))
}
#[cfg(windows)]
fn lstat(path: impl AsRef<Path>) -> FsResult<FsStat> {
let metadata = fs::symlink_metadata(path.as_ref())?;
let mut fsstat = metadata_to_fsstat(metadata);
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
stat_extra(
&mut fsstat,
path.as_ref(),
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
)?;
Ok(fsstat)
}
#[cfg(windows)]
fn stat_extra(
fsstat: &mut FsStat,
path: &Path,
file_flags: winapi::shared::minwindef::DWORD,
) -> FsResult<()> {
use std::os::windows::prelude::OsStrExt;
use winapi::um::fileapi::CreateFileW;
use winapi::um::fileapi::OPEN_EXISTING;
use winapi::um::handleapi::CloseHandle;
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::winnt::FILE_SHARE_DELETE;
use winapi::um::winnt::FILE_SHARE_READ;
use winapi::um::winnt::FILE_SHARE_WRITE;
unsafe fn get_dev(
handle: winapi::shared::ntdef::HANDLE,
) -> std::io::Result<u64> {
use winapi::shared::minwindef::FALSE;
use winapi::um::fileapi::GetFileInformationByHandle;
use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION;
let info = {
let mut info =
std::mem::MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::zeroed();
if GetFileInformationByHandle(handle, info.as_mut_ptr()) == FALSE {
return Err(std::io::Error::last_os_error());
}
info.assume_init()
};
Ok(info.dwVolumeSerialNumber as u64)
}
// SAFETY: winapi calls
unsafe {
let mut path: Vec<_> = path.as_os_str().encode_wide().collect();
path.push(0);
let file_handle = CreateFileW(
path.as_ptr(),
0,
FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
std::ptr::null_mut(),
OPEN_EXISTING,
file_flags,
std::ptr::null_mut(),
);
if file_handle == INVALID_HANDLE_VALUE {
return Err(std::io::Error::last_os_error().into());
}
let result = get_dev(file_handle);
CloseHandle(file_handle);
fsstat.dev = result?;
Ok(())
}
}
#[inline(always)]
fn metadata_to_fsstat(metadata: fs::Metadata) -> FsStat {
macro_rules! unix_or_zero {
($member:ident) => {{
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
metadata.$member()
}
#[cfg(not(unix))]
{
0
}
}};
}
#[inline(always)]
fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Option<u64> {
match maybe_time {
Ok(time) => Some(
time
.duration_since(UNIX_EPOCH)
.map(|t| t.as_millis() as u64)
.unwrap_or_else(|err| err.duration().as_millis() as u64),
),
Err(_) => None,
}
}
FsStat {
is_file: metadata.is_file(),
is_directory: metadata.is_dir(),
is_symlink: metadata.file_type().is_symlink(),
size: metadata.len(),
mtime: to_msec(metadata.modified()),
atime: to_msec(metadata.accessed()),
birthtime: to_msec(metadata.created()),
dev: unix_or_zero!(dev),
ino: unix_or_zero!(ino),
mode: unix_or_zero!(mode),
nlink: unix_or_zero!(nlink),
uid: unix_or_zero!(uid),
gid: unix_or_zero!(gid),
rdev: unix_or_zero!(rdev),
blksize: unix_or_zero!(blksize),
blocks: unix_or_zero!(blocks),
}
}
fn realpath(path: impl AsRef<Path>) -> FsResult<PathBuf> {
let canonicalized_path = path.as_ref().canonicalize()?;
#[cfg(windows)]
let canonicalized_path = PathBuf::from(
canonicalized_path
.display()
.to_string()
.trim_start_matches("\\\\?\\"),
);
Ok(canonicalized_path)
}
fn read_dir(path: impl AsRef<Path>) -> FsResult<Vec<FsDirEntry>> {
let entries = fs::read_dir(path)?
.filter_map(|entry| {
let entry = entry.ok()?;
let name = entry.file_name().into_string().ok()?;
let metadata = entry.file_type();
macro_rules! method_or_false {
($method:ident) => {
if let Ok(metadata) = &metadata {
metadata.$method()
} else {
false
}
};
}
Some(FsDirEntry {
name,
is_file: method_or_false!(is_file),
is_directory: method_or_false!(is_dir),
is_symlink: method_or_false!(is_symlink),
})
})
.collect();
Ok(entries)
}
#[cfg(not(windows))]
fn symlink(
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
_file_type: Option<FsFileType>,
) -> FsResult<()> {
std::os::unix::fs::symlink(oldpath.as_ref(), newpath.as_ref())?;
Ok(())
}
#[cfg(windows)]
fn symlink(
oldpath: impl AsRef<Path>,
newpath: impl AsRef<Path>,
file_type: Option<FsFileType>,
) -> FsResult<()> {
let file_type = match file_type {
Some(file_type) => file_type,
None => {
let old_meta = fs::metadata(&oldpath);
match old_meta {
Ok(metadata) => {
if metadata.is_file() {
FsFileType::File
} else if metadata.is_dir() {
FsFileType::Directory
} else {
return Err(FsError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"On Windows the target must be a file or directory",
)));
}
}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
return Err(FsError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
"On Windows an `options` argument is required if the target does not exist",
)))
}
Err(err) => return Err(err.into()),
}
}
};
match file_type {
FsFileType::File => {
std::os::windows::fs::symlink_file(&oldpath, &newpath)?;
}
FsFileType::Directory => {
std::os::windows::fs::symlink_dir(&oldpath, &newpath)?;
}
};
Ok(())
}
fn truncate(path: impl AsRef<Path>, len: u64) -> FsResult<()> {
let file = fs::OpenOptions::new().write(true).open(path)?;
file.set_len(len)?;
Ok(())
}
fn open_options(options: OpenOptions) -> fs::OpenOptions {
let mut open_options = fs::OpenOptions::new();
if let Some(mode) = options.mode {
// mode only used if creating the file on Unix
// if not specified, defaults to 0o666
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
open_options.mode(mode & 0o777);
}
#[cfg(not(unix))]
let _ = mode; // avoid unused warning
}
open_options.read(options.read);
open_options.create(options.create);
open_options.write(options.write);
open_options.truncate(options.truncate);
open_options.append(options.append);
open_options.create_new(options.create_new);
open_options
}
fn sync<T>(
resource: Rc<StdFileResource>,
f: impl FnOnce(&mut fs::File) -> io::Result<T>,
) -> FsResult<T> {
let res = resource
.with_file2(|file| f(file))
.ok_or(FsError::FileBusy)??;
Ok(res)
}
async fn nonblocking<T: Send + 'static>(
resource: Rc<StdFileResource>,
f: impl FnOnce(&mut fs::File) -> io::Result<T> + Send + 'static,
) -> FsResult<T> {
let res = resource.with_file_blocking_task2(f).await?;
Ok(res)
}
#[async_trait::async_trait(?Send)]
impl File for StdFileResource {
fn write_all_sync(self: Rc<Self>, buf: &[u8]) -> FsResult<()> {
sync(self, |file| file.write_all(buf))
}
async fn write_all_async(self: Rc<Self>, buf: Vec<u8>) -> FsResult<()> {
nonblocking(self, move |file| file.write_all(&buf)).await
}
fn read_all_sync(self: Rc<Self>) -> FsResult<Vec<u8>> {
sync(self, |file| {
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
})
}
async fn read_all_async(self: Rc<Self>) -> FsResult<Vec<u8>> {
nonblocking(self, |file| {
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
})
.await
}
fn chmod_sync(self: Rc<Self>, mode: u32) -> FsResult<()> {
#[cfg(unix)]
{
sync(self, |file| {
use std::os::unix::prelude::PermissionsExt;
file.set_permissions(fs::Permissions::from_mode(mode))
})
}
#[cfg(not(unix))]
Err(FsError::NotSupported)
}
async fn chmod_async(self: Rc<Self>, mode: u32) -> FsResult<()> {
#[cfg(unix)]
{
nonblocking(self, move |file| {
use std::os::unix::prelude::PermissionsExt;
file.set_permissions(fs::Permissions::from_mode(mode))
})
.await
}
#[cfg(not(unix))]
Err(FsError::NotSupported)
}
fn seek_sync(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
sync(self, |file| file.seek(pos))
}
async fn seek_async(self: Rc<Self>, pos: io::SeekFrom) -> FsResult<u64> {
nonblocking(self, move |file| file.seek(pos)).await
}
fn datasync_sync(self: Rc<Self>) -> FsResult<()> {
sync(self, |file| file.sync_data())
}
async fn datasync_async(self: Rc<Self>) -> FsResult<()> {
nonblocking(self, |file| file.sync_data()).await
}
fn sync_sync(self: Rc<Self>) -> FsResult<()> {
sync(self, |file| file.sync_all())
}
async fn sync_async(self: Rc<Self>) -> FsResult<()> {
nonblocking(self, |file| file.sync_all()).await
}
fn stat_sync(self: Rc<Self>) -> FsResult<FsStat> {
sync(self, |file| file.metadata().map(metadata_to_fsstat))
}
async fn stat_async(self: Rc<Self>) -> FsResult<FsStat> {
nonblocking(self, |file| file.metadata().map(metadata_to_fsstat)).await
}
fn lock_sync(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
sync(self, |file| {
if exclusive {
file.lock_exclusive()
} else {
file.lock_shared()
}
})
}
async fn lock_async(self: Rc<Self>, exclusive: bool) -> FsResult<()> {
nonblocking(self, move |file| {
if exclusive {
file.lock_exclusive()
} else {
file.lock_shared()
}
})
.await
}
fn unlock_sync(self: Rc<Self>) -> FsResult<()> {
sync(self, |file| file.unlock())
}
async fn unlock_async(self: Rc<Self>) -> FsResult<()> {
nonblocking(self, |file| file.unlock()).await
}
fn truncate_sync(self: Rc<Self>, len: u64) -> FsResult<()> {
sync(self, |file| file.set_len(len))
}
async fn truncate_async(self: Rc<Self>, len: u64) -> FsResult<()> {
nonblocking(self, move |file| file.set_len(len)).await
}
fn utime_sync(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
sync(self, |file| {
filetime::set_file_handle_times(file, Some(atime), Some(mtime))
})
}
async fn utime_async(
self: Rc<Self>,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> FsResult<()> {
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
nonblocking(self, move |file| {
filetime::set_file_handle_times(file, Some(atime), Some(mtime))
})
.await
}
}

View file

@ -20,6 +20,7 @@ use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fs::File as StdFile;
use std::io;
use std::io::ErrorKind;
use std::io::Read;
use std::io::Write;
@ -452,21 +453,21 @@ impl StdFileResource {
}
}
fn with_inner_and_metadata<TResult>(
fn with_inner_and_metadata<TResult, E>(
&self,
action: impl FnOnce(
&mut StdFileResourceInner,
&Arc<Mutex<FileMetadata>>,
) -> Result<TResult, AnyError>,
) -> Result<TResult, AnyError> {
) -> Result<TResult, E>,
) -> Option<Result<TResult, E>> {
match self.cell.try_borrow_mut() {
Ok(mut cell) => {
let mut file = cell.take().unwrap();
let result = action(&mut file.inner, &file.meta_data);
cell.replace(file);
result
Some(result)
}
Err(_) => Err(resource_unavailable()),
Err(_) => None,
}
}
@ -537,11 +538,16 @@ impl StdFileResource {
}
fn read_byob_sync(&self, buf: &mut [u8]) -> Result<usize, AnyError> {
self.with_inner_and_metadata(|inner, _| inner.read(buf).map_err(Into::into))
self
.with_inner_and_metadata(|inner, _| inner.read(buf))
.ok_or_else(resource_unavailable)?
.map_err(Into::into)
}
fn write_sync(&self, data: &[u8]) -> Result<usize, AnyError> {
self.with_inner_and_metadata(|inner, _| inner.write_and_maybe_flush(data))
self
.with_inner_and_metadata(|inner, _| inner.write_and_maybe_flush(data))
.ok_or_else(resource_unavailable)?
}
fn with_resource<F, R>(
@ -565,10 +571,19 @@ impl StdFileResource {
F: FnOnce(&mut StdFile) -> Result<R, AnyError>,
{
Self::with_resource(state, rid, move |resource| {
resource.with_inner_and_metadata(move |inner, _| inner.with_file(f))
resource
.with_inner_and_metadata(move |inner, _| inner.with_file(f))
.ok_or_else(resource_unavailable)?
})
}
pub fn with_file2<F, R>(self: Rc<Self>, f: F) -> Option<Result<R, io::Error>>
where
F: FnOnce(&mut StdFile) -> Result<R, io::Error>,
{
self.with_inner_and_metadata(move |inner, _| inner.with_file(f))
}
pub fn with_file_and_metadata<F, R>(
state: &mut OpState,
rid: ResourceId,
@ -578,9 +593,11 @@ impl StdFileResource {
F: FnOnce(&mut StdFile, &Arc<Mutex<FileMetadata>>) -> Result<R, AnyError>,
{
Self::with_resource(state, rid, move |resource| {
resource.with_inner_and_metadata(move |inner, metadata| {
inner.with_file(move |file| f(file, metadata))
})
resource
.with_inner_and_metadata(move |inner, metadata| {
inner.with_file(move |file| f(file, metadata))
})
.ok_or_else(resource_unavailable)?
})
}
@ -602,6 +619,18 @@ impl StdFileResource {
.await
}
pub async fn with_file_blocking_task2<F, R: Send + 'static>(
self: Rc<Self>,
f: F,
) -> Result<R, io::Error>
where
F: (FnOnce(&mut StdFile) -> Result<R, io::Error>) + Send + 'static,
{
self
.with_inner_blocking_task(move |inner| inner.with_file(f))
.await
}
pub fn clone_file(
state: &mut OpState,
rid: ResourceId,
@ -616,13 +645,15 @@ impl StdFileResource {
rid: u32,
) -> Result<std::process::Stdio, AnyError> {
Self::with_resource(state, rid, |resource| {
resource.with_inner_and_metadata(|inner, _| match inner.kind {
StdFileResourceKind::File => {
let file = inner.file.try_clone()?;
Ok(file.into())
}
_ => Ok(std::process::Stdio::inherit()),
})
resource
.with_inner_and_metadata(|inner, _| match inner.kind {
StdFileResourceKind::File => {
let file = inner.file.try_clone()?;
Ok(file.into())
}
_ => Ok(std::process::Stdio::inherit()),
})
.ok_or_else(resource_unavailable)?
})
}
}
@ -679,8 +710,8 @@ impl Resource for StdFileResource {
use std::os::unix::io::AsRawFd;
self
.with_inner_and_metadata(move |std_file, _| {
Ok(std_file.with_file(|f| f.as_raw_fd()))
})
Ok::<_, ()>(std_file.with_file(|f| f.as_raw_fd()))
})?
.ok()
}
}
@ -694,9 +725,11 @@ pub fn op_print(
) -> Result<(), AnyError> {
let rid = if is_err { 2 } else { 1 };
StdFileResource::with_resource(state, rid, move |resource| {
resource.with_inner_and_metadata(|inner, _| {
inner.write_all_and_maybe_flush(msg.as_bytes())?;
Ok(())
})
resource
.with_inner_and_metadata(|inner, _| {
inner.write_all_and_maybe_flush(msg.as_bytes())?;
Ok(())
})
.ok_or_else(resource_unavailable)?
})
}

View file

@ -18,6 +18,7 @@ mod startup_snapshot {
use deno_core::Extension;
use deno_core::ExtensionFileSource;
use deno_core::ModuleCode;
use deno_fs::StdFs;
use std::path::Path;
fn transpile_ts_for_snapshotting(
@ -164,6 +165,10 @@ mod startup_snapshot {
unreachable!("snapshotting!")
}
fn check_read_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
unreachable!("snapshotting!")
}
fn check_read_blind(
&mut self,
_path: &Path,
@ -181,11 +186,16 @@ mod startup_snapshot {
unreachable!("snapshotting!")
}
fn check_read_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
unreachable!("snapshotting!")
}
fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> {
fn check_write_blind(
&mut self,
_path: &Path,
_display: &str,
_api_name: &str,
) -> Result<(), AnyError> {
unreachable!("snapshotting!")
}
}
@ -310,7 +320,7 @@ mod startup_snapshot {
deno_napi::deno_napi::init_ops_and_esm::<Permissions>(),
deno_http::deno_http::init_ops_and_esm(),
deno_io::deno_io::init_ops_and_esm(Default::default()),
deno_fs::deno_fs::init_ops_and_esm::<Permissions>(false),
deno_fs::deno_fs::init_ops_and_esm::<_, Permissions>(false, StdFs),
runtime::init_ops_and_esm(),
// FIXME(bartlomieju): these extensions are specified last, because they
// depend on `runtime`, even though it should be other way around

View file

@ -667,6 +667,40 @@ impl UnaryPermission<WriteDescriptor> {
}
result
}
/// As `check()`, but permission error messages will anonymize the path
/// by replacing it with the given `display`.
pub fn check_blind(
&mut self,
path: &Path,
display: &str,
api_name: &str,
) -> Result<(), AnyError> {
let resolved_path = resolve_from_cwd(path)?;
let (result, prompted, is_allow_all) =
self.query(Some(&resolved_path)).check(
self.name,
Some(api_name),
Some(&format!("<{display}>")),
self.prompt,
);
if prompted {
if result.is_ok() {
if is_allow_all {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
} else {
self.granted_list.insert(WriteDescriptor(resolved_path));
}
} else {
self.global_state = PermissionState::Denied;
if !is_allow_all {
self.denied_list.insert(WriteDescriptor(resolved_path));
}
}
}
result
}
}
impl Default for UnaryPermission<WriteDescriptor> {
@ -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))
}

View file

@ -34,6 +34,7 @@ use deno_core::RuntimeOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::Snapshot;
use deno_core::SourceMapGetter;
use deno_fs::StdFs;
use deno_io::Stdio;
use deno_kv::sqlite::SqliteDbHandler;
use deno_node::RequireNpmResolver;
@ -440,7 +441,7 @@ impl WebWorker {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Some(options.stdio)),
deno_fs::deno_fs::init_ops::<PermissionsContainer>(unstable),
deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs),
deno_node::deno_node::init_ops::<crate::RuntimeNodeEnv>(
options.npm_resolver,
),

View file

@ -30,6 +30,7 @@ use deno_core::RuntimeOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::Snapshot;
use deno_core::SourceMapGetter;
use deno_fs::StdFs;
use deno_io::Stdio;
use deno_kv::sqlite::SqliteDbHandler;
use deno_node::RequireNpmResolver;
@ -264,7 +265,7 @@ impl MainWorker {
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
deno_http::deno_http::init_ops(),
deno_io::deno_io::init_ops(Some(options.stdio)),
deno_fs::deno_fs::init_ops::<PermissionsContainer>(unstable),
deno_fs::deno_fs::init_ops::<_, PermissionsContainer>(unstable, StdFs),
deno_node::deno_node::init_ops::<crate::RuntimeNodeEnv>(
options.npm_resolver,
),