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:
parent
0e3f62d444
commit
f90caa821c
15 changed files with 3350 additions and 2488 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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!
|
||||
];
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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
45
ext/fs/clippy.toml
Normal 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
395
ext/fs/interface.rs
Normal 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)
|
||||
}
|
||||
}
|
2484
ext/fs/lib.rs
2484
ext/fs/lib.rs
File diff suppressed because it is too large
Load diff
1712
ext/fs/ops.rs
Normal file
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
929
ext/fs/std_fs.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)?
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue