mirror of
https://github.com/denoland/deno.git
synced 2024-12-27 09:39:08 -05:00
2438 lines
59 KiB
Rust
2438 lines
59 KiB
Rust
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
// Some deserializer fields are only used on Unix and Windows build fails without it
|
|
|
|
use deno_core::error::custom_error;
|
|
use deno_core::error::type_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::op;
|
|
use deno_core::CancelFuture;
|
|
use deno_core::CancelHandle;
|
|
use deno_core::OpState;
|
|
use deno_core::ResourceId;
|
|
use deno_core::ZeroCopyBuf;
|
|
use deno_crypto::rand::thread_rng;
|
|
use deno_crypto::rand::Rng;
|
|
use deno_io::StdFileResource;
|
|
use log::debug;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::borrow::Cow;
|
|
use std::cell::RefCell;
|
|
use std::convert::From;
|
|
use std::env::current_dir;
|
|
use std::env::set_current_dir;
|
|
use std::env::temp_dir;
|
|
use std::io;
|
|
use std::io::Error;
|
|
use std::io::Seek;
|
|
use std::io::SeekFrom;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use std::rc::Rc;
|
|
use std::time::SystemTime;
|
|
use std::time::UNIX_EPOCH;
|
|
|
|
/// Similar to `std::fs::canonicalize()` but strips UNC prefixes on Windows.
|
|
fn canonicalize_path(path: &Path) -> Result<PathBuf, Error> {
|
|
let mut canonicalized_path = path.canonicalize()?;
|
|
if cfg!(windows) {
|
|
canonicalized_path = PathBuf::from(
|
|
canonicalized_path
|
|
.display()
|
|
.to_string()
|
|
.trim_start_matches("\\\\?\\"),
|
|
);
|
|
}
|
|
Ok(canonicalized_path)
|
|
}
|
|
|
|
/// A utility function to map OsStrings to Strings
|
|
fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> {
|
|
s.into_string().map_err(|s| {
|
|
let message = format!("File name or path {s:?} is not valid UTF-8");
|
|
custom_error("InvalidData", message)
|
|
})
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
pub fn get_nix_error_class(error: &nix::Error) -> &'static str {
|
|
match error {
|
|
nix::Error::ECHILD => "NotFound",
|
|
nix::Error::EINVAL => "TypeError",
|
|
nix::Error::ENOENT => "NotFound",
|
|
nix::Error::ENOTTY => "BadResource",
|
|
nix::Error::EPERM => "PermissionDenied",
|
|
nix::Error::ESRCH => "NotFound",
|
|
nix::Error::UnknownErrno => "Error",
|
|
&nix::Error::ENOTSUP => unreachable!(),
|
|
_ => "Error",
|
|
}
|
|
}
|
|
|
|
struct UnstableChecker {
|
|
pub unstable: bool,
|
|
}
|
|
|
|
impl UnstableChecker {
|
|
// NOTE(bartlomieju): keep in sync with `cli/program_state.rs`
|
|
pub fn check_unstable(&self, api_name: &str) {
|
|
if !self.unstable {
|
|
eprintln!(
|
|
"Unstable API '{api_name}'. The --unstable flag must be provided."
|
|
);
|
|
std::process::exit(70);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Helper for checking unstable features. Used for sync ops.
|
|
fn check_unstable(state: &OpState, api_name: &str) {
|
|
state.borrow::<UnstableChecker>().check_unstable(api_name)
|
|
}
|
|
|
|
/// Helper for checking unstable features. Used for async ops.
|
|
fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
|
|
let state = state.borrow();
|
|
state.borrow::<UnstableChecker>().check_unstable(api_name)
|
|
}
|
|
|
|
pub trait FsPermissions {
|
|
fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
|
|
fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>;
|
|
fn check_read_blind(
|
|
&mut self,
|
|
p: &Path,
|
|
display: &str,
|
|
api_name: &str,
|
|
) -> Result<(), AnyError>;
|
|
fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>;
|
|
fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>;
|
|
}
|
|
|
|
#[cfg(not(unix))]
|
|
use deno_core::error::generic_error;
|
|
#[cfg(not(unix))]
|
|
use deno_core::error::not_supported;
|
|
|
|
deno_core::extension!(deno_fs,
|
|
deps = [ deno_web, deno_io ],
|
|
parameters = [P: FsPermissions],
|
|
ops = [
|
|
op_open_sync<P>,
|
|
op_open_async<P>,
|
|
op_write_file_sync<P>,
|
|
op_write_file_async<P>,
|
|
op_seek_sync,
|
|
op_seek_async,
|
|
op_fdatasync_sync,
|
|
op_fdatasync_async,
|
|
op_fsync_sync,
|
|
op_fsync_async,
|
|
op_fstat_sync,
|
|
op_fstat_async,
|
|
op_flock_sync,
|
|
op_flock_async,
|
|
op_funlock_sync,
|
|
op_funlock_async,
|
|
op_umask,
|
|
op_chdir<P>,
|
|
op_mkdir_sync<P>,
|
|
op_mkdir_async<P>,
|
|
op_chmod_sync<P>,
|
|
op_chmod_async<P>,
|
|
op_chown_sync<P>,
|
|
op_chown_async<P>,
|
|
op_remove_sync<P>,
|
|
op_remove_async<P>,
|
|
op_copy_file_sync<P>,
|
|
op_copy_file_async<P>,
|
|
op_stat_sync<P>,
|
|
op_stat_async<P>,
|
|
op_realpath_sync<P>,
|
|
op_realpath_async<P>,
|
|
op_read_dir_sync<P>,
|
|
op_read_dir_async<P>,
|
|
op_rename_sync<P>,
|
|
op_rename_async<P>,
|
|
op_link_sync<P>,
|
|
op_link_async<P>,
|
|
op_symlink_sync<P>,
|
|
op_symlink_async<P>,
|
|
op_read_link_sync<P>,
|
|
op_read_link_async<P>,
|
|
op_ftruncate_sync,
|
|
op_ftruncate_async,
|
|
op_truncate_sync<P>,
|
|
op_truncate_async<P>,
|
|
op_make_temp_dir_sync<P>,
|
|
op_make_temp_dir_async<P>,
|
|
op_make_temp_file_sync<P>,
|
|
op_make_temp_file_async<P>,
|
|
op_cwd<P>,
|
|
op_futime_sync,
|
|
op_futime_async,
|
|
op_utime_sync<P>,
|
|
op_utime_async<P>,
|
|
op_readfile_sync<P>,
|
|
op_readfile_text_sync<P>,
|
|
op_readfile_async<P>,
|
|
op_readfile_text_async<P>,
|
|
],
|
|
esm = [ "30_fs.js" ],
|
|
options = {
|
|
unstable: bool
|
|
},
|
|
state = |state, options| {
|
|
state.put(UnstableChecker { unstable: options.unstable });
|
|
},
|
|
);
|
|
|
|
fn default_err_mapper(err: Error, desc: String) -> AnyError {
|
|
AnyError::new(Error::new(err.kind(), desc)).context(err)
|
|
}
|
|
|
|
#[derive(Deserialize, Default, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[serde(default)]
|
|
pub struct OpenOptions {
|
|
read: bool,
|
|
write: bool,
|
|
create: bool,
|
|
truncate: bool,
|
|
append: bool,
|
|
create_new: bool,
|
|
}
|
|
|
|
#[inline]
|
|
fn open_helper<P>(
|
|
state: &mut OpState,
|
|
path: &str,
|
|
mode: Option<u32>,
|
|
options: Option<&OpenOptions>,
|
|
api_name: &str,
|
|
) -> Result<(PathBuf, std::fs::OpenOptions), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(path).to_path_buf();
|
|
|
|
let mut open_options = std::fs::OpenOptions::new();
|
|
|
|
if let Some(mode) = mode {
|
|
// mode only used if creating the file on Unix
|
|
// if not specified, defaults to 0o666
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::OpenOptionsExt;
|
|
open_options.mode(mode & 0o777);
|
|
}
|
|
#[cfg(not(unix))]
|
|
let _ = mode; // avoid unused warning
|
|
}
|
|
|
|
let permissions = state.borrow_mut::<P>();
|
|
|
|
match options {
|
|
None => {
|
|
permissions.check_read(&path, api_name)?;
|
|
open_options
|
|
.read(true)
|
|
.create(false)
|
|
.write(false)
|
|
.truncate(false)
|
|
.append(false)
|
|
.create_new(false);
|
|
}
|
|
Some(options) => {
|
|
if options.read {
|
|
permissions.check_read(&path, api_name)?;
|
|
}
|
|
|
|
if options.write || options.append {
|
|
permissions.check_write(&path, api_name)?;
|
|
}
|
|
|
|
open_options
|
|
.read(options.read)
|
|
.create(options.create)
|
|
.write(options.write)
|
|
.truncate(options.truncate)
|
|
.append(options.append)
|
|
.create_new(options.create_new);
|
|
}
|
|
}
|
|
|
|
Ok((path, open_options))
|
|
}
|
|
|
|
#[op]
|
|
fn op_open_sync<P>(
|
|
state: &mut OpState,
|
|
path: String,
|
|
options: Option<OpenOptions>,
|
|
mode: Option<u32>,
|
|
) -> Result<ResourceId, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let (path, open_options) =
|
|
open_helper::<P>(state, &path, mode, options.as_ref(), "Deno.openSync()")?;
|
|
let std_file = open_options.open(&path).map_err(|err| {
|
|
default_err_mapper(err, format!("open '{}'", path.display()))
|
|
})?;
|
|
let resource = StdFileResource::fs_file(std_file);
|
|
let rid = state.resource_table.add(resource);
|
|
Ok(rid)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_open_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
options: Option<OpenOptions>,
|
|
mode: Option<u32>,
|
|
) -> Result<ResourceId, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let (path, open_options) = open_helper::<P>(
|
|
&mut state.borrow_mut(),
|
|
&path,
|
|
mode,
|
|
options.as_ref(),
|
|
"Deno.open()",
|
|
)?;
|
|
let std_file = tokio::task::spawn_blocking(move || {
|
|
open_options.open(&path).map_err(|err| {
|
|
default_err_mapper(err, format!("open '{}'", path.display()))
|
|
})
|
|
})
|
|
.await?;
|
|
let resource = StdFileResource::fs_file(std_file?);
|
|
let rid = state.borrow_mut().resource_table.add(resource);
|
|
Ok(rid)
|
|
}
|
|
|
|
#[inline]
|
|
fn write_open_options(
|
|
create: bool,
|
|
append: bool,
|
|
create_new: bool,
|
|
) -> OpenOptions {
|
|
OpenOptions {
|
|
read: false,
|
|
write: true,
|
|
create,
|
|
truncate: !append,
|
|
append,
|
|
create_new,
|
|
}
|
|
}
|
|
|
|
#[op]
|
|
fn op_write_file_sync<P>(
|
|
state: &mut OpState,
|
|
path: String,
|
|
mode: Option<u32>,
|
|
append: bool,
|
|
create: bool,
|
|
create_new: bool,
|
|
data: ZeroCopyBuf,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let (path, open_options) = open_helper::<P>(
|
|
state,
|
|
&path,
|
|
mode,
|
|
Some(&write_open_options(create, append, create_new)),
|
|
"Deno.writeFileSync()",
|
|
)?;
|
|
write_file(&path, open_options, mode, data)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_write_file_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
mode: Option<u32>,
|
|
append: bool,
|
|
create: bool,
|
|
create_new: bool,
|
|
data: ZeroCopyBuf,
|
|
cancel_rid: Option<ResourceId>,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let (path, open_options) = open_helper::<P>(
|
|
&mut state.borrow_mut(),
|
|
&path,
|
|
mode,
|
|
Some(&write_open_options(create, append, create_new)),
|
|
"Deno.writeFile()",
|
|
)?;
|
|
|
|
let write_future = tokio::task::spawn_blocking(move || {
|
|
write_file(&path, open_options, mode, data)
|
|
});
|
|
|
|
let cancel_handle = cancel_rid.and_then(|rid| {
|
|
state
|
|
.borrow_mut()
|
|
.resource_table
|
|
.get::<CancelHandle>(rid)
|
|
.ok()
|
|
});
|
|
|
|
if let Some(cancel_handle) = cancel_handle {
|
|
let write_future_rv = write_future.or_cancel(cancel_handle).await;
|
|
|
|
if let Some(cancel_rid) = cancel_rid {
|
|
state.borrow_mut().resource_table.close(cancel_rid).ok();
|
|
};
|
|
|
|
return write_future_rv??;
|
|
}
|
|
|
|
write_future.await?
|
|
}
|
|
|
|
fn write_file(
|
|
path: &Path,
|
|
open_options: std::fs::OpenOptions,
|
|
_mode: Option<u32>,
|
|
data: ZeroCopyBuf,
|
|
) -> Result<(), AnyError> {
|
|
let mut std_file = open_options.open(path).map_err(|err| {
|
|
default_err_mapper(err, format!("open '{}'", path.display()))
|
|
})?;
|
|
|
|
// need to chmod the file if it already exists and a mode is specified
|
|
#[cfg(unix)]
|
|
if let Some(mode) = _mode {
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let permissions = PermissionsExt::from_mode(mode & 0o777);
|
|
std_file.set_permissions(permissions).map_err(|err| {
|
|
default_err_mapper(err, format!("chmod '{}'", path.display()))
|
|
})?;
|
|
}
|
|
|
|
std_file.write_all(&data)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SeekArgs {
|
|
rid: ResourceId,
|
|
offset: i64,
|
|
whence: i32,
|
|
}
|
|
|
|
fn seek_helper(args: SeekArgs) -> Result<(u32, SeekFrom), AnyError> {
|
|
let rid = args.rid;
|
|
let offset = args.offset;
|
|
let whence = args.whence as u32;
|
|
// Translate seek mode to Rust repr.
|
|
let seek_from = match whence {
|
|
0 => SeekFrom::Start(offset as u64),
|
|
1 => SeekFrom::Current(offset),
|
|
2 => SeekFrom::End(offset),
|
|
_ => {
|
|
return Err(type_error(format!("Invalid seek mode: {whence}")));
|
|
}
|
|
};
|
|
|
|
Ok((rid, seek_from))
|
|
}
|
|
|
|
#[op]
|
|
fn op_seek_sync(state: &mut OpState, args: SeekArgs) -> Result<u64, AnyError> {
|
|
let (rid, seek_from) = seek_helper(args)?;
|
|
StdFileResource::with_file(state, rid, |std_file| {
|
|
std_file.seek(seek_from).map_err(AnyError::from)
|
|
})
|
|
}
|
|
|
|
#[op]
|
|
async fn op_seek_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
args: SeekArgs,
|
|
) -> Result<u64, AnyError> {
|
|
let (rid, seek_from) = seek_helper(args)?;
|
|
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
std_file.seek(seek_from).map_err(AnyError::from)
|
|
})
|
|
.await
|
|
}
|
|
|
|
#[op]
|
|
fn op_fdatasync_sync(
|
|
state: &mut OpState,
|
|
rid: ResourceId,
|
|
) -> Result<(), AnyError> {
|
|
StdFileResource::with_file(state, rid, |std_file| {
|
|
std_file.sync_data().map_err(AnyError::from)
|
|
})
|
|
}
|
|
|
|
#[op]
|
|
async fn op_fdatasync_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
) -> Result<(), AnyError> {
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
std_file.sync_data().map_err(AnyError::from)
|
|
})
|
|
.await
|
|
}
|
|
|
|
#[op]
|
|
fn op_fsync_sync(state: &mut OpState, rid: ResourceId) -> Result<(), AnyError> {
|
|
StdFileResource::with_file(state, rid, |std_file| {
|
|
std_file.sync_all().map_err(AnyError::from)
|
|
})
|
|
}
|
|
|
|
#[op]
|
|
async fn op_fsync_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
) -> Result<(), AnyError> {
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
std_file.sync_all().map_err(AnyError::from)
|
|
})
|
|
.await
|
|
}
|
|
|
|
#[op]
|
|
fn op_fstat_sync(
|
|
state: &mut OpState,
|
|
rid: ResourceId,
|
|
out_buf: &mut [u32],
|
|
) -> Result<(), AnyError> {
|
|
let metadata = StdFileResource::with_file(state, rid, |std_file| {
|
|
std_file.metadata().map_err(AnyError::from)
|
|
})?;
|
|
let stat = get_stat(metadata);
|
|
stat.write(out_buf);
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_fstat_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
) -> Result<FsStat, AnyError> {
|
|
let metadata =
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
std_file.metadata().map_err(AnyError::from)
|
|
})
|
|
.await?;
|
|
Ok(get_stat(metadata))
|
|
}
|
|
|
|
#[op]
|
|
fn op_flock_sync(
|
|
state: &mut OpState,
|
|
rid: ResourceId,
|
|
exclusive: bool,
|
|
) -> Result<(), AnyError> {
|
|
use fs3::FileExt;
|
|
check_unstable(state, "Deno.flockSync");
|
|
|
|
StdFileResource::with_file(state, rid, |std_file| {
|
|
if exclusive {
|
|
std_file.lock_exclusive()?;
|
|
} else {
|
|
std_file.lock_shared()?;
|
|
}
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[op]
|
|
async fn op_flock_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
exclusive: bool,
|
|
) -> Result<(), AnyError> {
|
|
use fs3::FileExt;
|
|
check_unstable2(&state, "Deno.flock");
|
|
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
if exclusive {
|
|
std_file.lock_exclusive()?;
|
|
} else {
|
|
std_file.lock_shared()?;
|
|
}
|
|
Ok(())
|
|
})
|
|
.await
|
|
}
|
|
|
|
#[op]
|
|
fn op_funlock_sync(
|
|
state: &mut OpState,
|
|
rid: ResourceId,
|
|
) -> Result<(), AnyError> {
|
|
use fs3::FileExt;
|
|
check_unstable(state, "Deno.funlockSync");
|
|
|
|
StdFileResource::with_file(state, rid, |std_file| {
|
|
std_file.unlock()?;
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[op]
|
|
async fn op_funlock_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
) -> Result<(), AnyError> {
|
|
use fs3::FileExt;
|
|
check_unstable2(&state, "Deno.funlock");
|
|
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
std_file.unlock()?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
}
|
|
|
|
#[op]
|
|
fn op_umask(state: &mut OpState, mask: Option<u32>) -> Result<u32, AnyError> {
|
|
check_unstable(state, "Deno.umask");
|
|
// TODO implement umask for Windows
|
|
// see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc
|
|
// and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019
|
|
#[cfg(not(unix))]
|
|
{
|
|
let _ = mask; // avoid unused warning.
|
|
Err(not_supported())
|
|
}
|
|
#[cfg(unix)]
|
|
{
|
|
use nix::sys::stat::mode_t;
|
|
use nix::sys::stat::umask;
|
|
use nix::sys::stat::Mode;
|
|
let r = if let Some(mask) = mask {
|
|
// If mask provided, return previous.
|
|
umask(Mode::from_bits_truncate(mask as mode_t))
|
|
} else {
|
|
// If no mask provided, we query the current. Requires two syscalls.
|
|
let prev = umask(Mode::from_bits_truncate(0o777));
|
|
let _ = umask(prev);
|
|
prev
|
|
};
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
Ok(r.bits())
|
|
}
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
Ok(r.bits() as u32)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[op]
|
|
fn op_chdir<P>(state: &mut OpState, directory: &str) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let d = PathBuf::from(&directory);
|
|
state.borrow_mut::<P>().check_read(&d, "Deno.chdir()")?;
|
|
set_current_dir(&d)
|
|
.map_err(|err| default_err_mapper(err, format!("chdir '{directory}'")))?;
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct MkdirArgs {
|
|
path: String,
|
|
recursive: bool,
|
|
mode: Option<u32>,
|
|
}
|
|
|
|
#[op]
|
|
fn op_mkdir_sync<P>(
|
|
state: &mut OpState,
|
|
args: MkdirArgs,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(&args.path).to_path_buf();
|
|
let mode = args.mode.unwrap_or(0o777) & 0o777;
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write(&path, "Deno.mkdirSync()")?;
|
|
debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive);
|
|
let mut builder = std::fs::DirBuilder::new();
|
|
builder.recursive(args.recursive);
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::DirBuilderExt;
|
|
builder.mode(mode);
|
|
}
|
|
builder.create(&path).map_err(|err| {
|
|
default_err_mapper(err, format!("mkdir '{}'", path.display()))
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_mkdir_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
args: MkdirArgs,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(&args.path).to_path_buf();
|
|
let mode = args.mode.unwrap_or(0o777) & 0o777;
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state.borrow_mut::<P>().check_write(&path, "Deno.mkdir()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive);
|
|
let mut builder = std::fs::DirBuilder::new();
|
|
builder.recursive(args.recursive);
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::DirBuilderExt;
|
|
builder.mode(mode);
|
|
}
|
|
builder.create(&path).map_err(|err| {
|
|
default_err_mapper(err, format!("mkdir '{}'", path.display()))
|
|
})?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_chmod_sync<P>(
|
|
state: &mut OpState,
|
|
path: &str,
|
|
mode: u32,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(path);
|
|
let mode = mode & 0o777;
|
|
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write(path, "Deno.chmodSync()")?;
|
|
raw_chmod(path, mode)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_chmod_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
mode: u32,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(&path).to_path_buf();
|
|
let mode = mode & 0o777;
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state.borrow_mut::<P>().check_write(&path, "Deno.chmod()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || raw_chmod(&path, mode))
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> {
|
|
let err_mapper =
|
|
|err| default_err_mapper(err, format!("chmod '{}'", path.display()));
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let permissions = PermissionsExt::from_mode(_raw_mode);
|
|
std::fs::set_permissions(path, permissions).map_err(err_mapper)?;
|
|
Ok(())
|
|
}
|
|
// TODO Implement chmod for Windows (#4357)
|
|
#[cfg(not(unix))]
|
|
{
|
|
// Still check file/dir exists on Windows
|
|
let _metadata = std::fs::metadata(path).map_err(err_mapper)?;
|
|
Err(not_supported())
|
|
}
|
|
}
|
|
|
|
#[op]
|
|
fn op_chown_sync<P>(
|
|
state: &mut OpState,
|
|
path: &str,
|
|
#[cfg_attr(windows, allow(unused_variables))] uid: Option<u32>,
|
|
#[cfg_attr(windows, allow(unused_variables))] gid: Option<u32>,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(path).to_path_buf();
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write(&path, "Deno.chownSync()")?;
|
|
#[cfg(unix)]
|
|
{
|
|
use nix::unistd::chown;
|
|
use nix::unistd::Gid;
|
|
use nix::unistd::Uid;
|
|
let nix_uid = uid.map(Uid::from_raw);
|
|
let nix_gid = gid.map(Gid::from_raw);
|
|
chown(&path, nix_uid, nix_gid).map_err(|err| {
|
|
custom_error(
|
|
get_nix_error_class(&err),
|
|
format!("{}, chown '{}'", err.desc(), path.display()),
|
|
)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
// TODO Implement chown for Windows
|
|
#[cfg(not(unix))]
|
|
{
|
|
Err(generic_error("Not implemented"))
|
|
}
|
|
}
|
|
|
|
#[op]
|
|
async fn op_chown_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
#[cfg_attr(windows, allow(unused_variables))] uid: Option<u32>,
|
|
#[cfg_attr(windows, allow(unused_variables))] gid: Option<u32>,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(&path).to_path_buf();
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state.borrow_mut::<P>().check_write(&path, "Deno.chown()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
#[cfg(unix)]
|
|
{
|
|
use nix::unistd::chown;
|
|
use nix::unistd::Gid;
|
|
use nix::unistd::Uid;
|
|
let nix_uid = uid.map(Uid::from_raw);
|
|
let nix_gid = gid.map(Gid::from_raw);
|
|
chown(&path, nix_uid, nix_gid).map_err(|err| {
|
|
custom_error(
|
|
get_nix_error_class(&err),
|
|
format!("{}, chown '{}'", err.desc(), path.display()),
|
|
)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
// TODO Implement chown for Windows
|
|
#[cfg(not(unix))]
|
|
Err(not_supported())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_remove_sync<P>(
|
|
state: &mut OpState,
|
|
path: &str,
|
|
recursive: bool,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(path);
|
|
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write(&path, "Deno.removeSync()")?;
|
|
|
|
#[cfg(not(unix))]
|
|
use std::os::windows::prelude::MetadataExt;
|
|
|
|
let err_mapper =
|
|
|err| default_err_mapper(err, format!("remove '{}'", path.display()));
|
|
let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?;
|
|
|
|
let file_type = metadata.file_type();
|
|
if file_type.is_file() {
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
} else if recursive {
|
|
std::fs::remove_dir_all(&path).map_err(err_mapper)?;
|
|
} else if file_type.is_symlink() {
|
|
#[cfg(unix)]
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
#[cfg(not(unix))]
|
|
{
|
|
use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
|
|
if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
std::fs::remove_dir(&path).map_err(err_mapper)?;
|
|
} else {
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
}
|
|
}
|
|
} else if file_type.is_dir() {
|
|
std::fs::remove_dir(&path).map_err(err_mapper)?;
|
|
} else {
|
|
// pipes, sockets, etc...
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_remove_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
recursive: bool,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write(&path, "Deno.remove()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
#[cfg(not(unix))]
|
|
use std::os::windows::prelude::MetadataExt;
|
|
let err_mapper =
|
|
|err| default_err_mapper(err, format!("remove '{}'", path.display()));
|
|
let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?;
|
|
|
|
debug!("op_remove_async {} {}", path.display(), recursive);
|
|
let file_type = metadata.file_type();
|
|
if file_type.is_file() {
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
} else if recursive {
|
|
std::fs::remove_dir_all(&path).map_err(err_mapper)?;
|
|
} else if file_type.is_symlink() {
|
|
#[cfg(unix)]
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
#[cfg(not(unix))]
|
|
{
|
|
use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
|
|
if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
std::fs::remove_dir(&path).map_err(err_mapper)?;
|
|
} else {
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
}
|
|
}
|
|
} else if file_type.is_dir() {
|
|
std::fs::remove_dir(&path).map_err(err_mapper)?;
|
|
} else {
|
|
// pipes, sockets, etc...
|
|
std::fs::remove_file(&path).map_err(err_mapper)?;
|
|
}
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_copy_file_sync<P>(
|
|
state: &mut OpState,
|
|
from: &str,
|
|
to: &str,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let from_path = PathBuf::from(from);
|
|
let to_path = PathBuf::from(to);
|
|
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&from_path, "Deno.copyFileSync()")?;
|
|
permissions.check_write(&to_path, "Deno.copyFileSync()")?;
|
|
|
|
// On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
|
|
// See https://github.com/rust-lang/rust/issues/54800
|
|
// Once the issue is resolved, we should remove this workaround.
|
|
if cfg!(unix) && !from_path.is_file() {
|
|
return Err(custom_error(
|
|
"NotFound",
|
|
format!(
|
|
"File not found, copy '{}' -> '{}'",
|
|
from_path.display(),
|
|
to_path.display()
|
|
),
|
|
));
|
|
}
|
|
|
|
let err_mapper = |err| {
|
|
default_err_mapper(
|
|
err,
|
|
format!("copy '{}' -> '{}'", from_path.display(), to_path.display()),
|
|
)
|
|
};
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
use libc::clonefile;
|
|
use libc::stat;
|
|
use libc::unlink;
|
|
use std::ffi::CString;
|
|
use std::io::Read;
|
|
use std::os::unix::fs::OpenOptionsExt;
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
let from = CString::new(from).unwrap();
|
|
let to = CString::new(to).unwrap();
|
|
|
|
// SAFETY: `from` and `to` are valid C strings.
|
|
// std::fs::copy does open() + fcopyfile() on macOS. We try to use
|
|
// clonefile() instead, which is more efficient.
|
|
unsafe {
|
|
let mut st = std::mem::zeroed();
|
|
let ret = stat(from.as_ptr(), &mut st);
|
|
if ret != 0 {
|
|
return Err(err_mapper(Error::last_os_error()));
|
|
}
|
|
|
|
if st.st_size > 128 * 1024 {
|
|
// Try unlink. If it fails, we are going to try clonefile() anyway.
|
|
let _ = unlink(to.as_ptr());
|
|
// Matches rust stdlib behavior for io::copy.
|
|
// https://github.com/rust-lang/rust/blob/3fdd578d72a24d4efc2fe2ad18eec3b6ba72271e/library/std/src/sys/unix/fs.rs#L1613-L1616
|
|
if clonefile(from.as_ptr(), to.as_ptr(), 0) == 0 {
|
|
return Ok(());
|
|
}
|
|
} else {
|
|
// Do a regular copy. fcopyfile() is an overkill for < 128KB
|
|
// files.
|
|
let mut buf = [0u8; 128 * 1024];
|
|
let mut from_file =
|
|
std::fs::File::open(&from_path).map_err(err_mapper)?;
|
|
let perm = from_file.metadata().map_err(err_mapper)?.permissions();
|
|
|
|
let mut to_file = std::fs::OpenOptions::new()
|
|
// create the file with the correct mode right away
|
|
.mode(perm.mode())
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&to_path)
|
|
.map_err(err_mapper)?;
|
|
let writer_metadata = to_file.metadata()?;
|
|
if writer_metadata.is_file() {
|
|
// Set the correct file permissions, in case the file already existed.
|
|
// Don't set the permissions on already existing non-files like
|
|
// pipes/FIFOs or device nodes.
|
|
to_file.set_permissions(perm)?;
|
|
}
|
|
loop {
|
|
let nread = from_file.read(&mut buf).map_err(err_mapper)?;
|
|
if nread == 0 {
|
|
break;
|
|
}
|
|
to_file.write_all(&buf[..nread]).map_err(err_mapper)?;
|
|
}
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
// clonefile() failed, fall back to std::fs::copy().
|
|
}
|
|
|
|
// returns size of from as u64 (we ignore)
|
|
std::fs::copy(&from_path, &to_path).map_err(err_mapper)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_copy_file_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
from: String,
|
|
to: String,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let from = PathBuf::from(&from);
|
|
let to = PathBuf::from(&to);
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&from, "Deno.copyFile()")?;
|
|
permissions.check_write(&to, "Deno.copyFile()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
// On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
|
|
// See https://github.com/rust-lang/rust/issues/54800
|
|
// Once the issue is resolved, we should remove this workaround.
|
|
if cfg!(unix) && !from.is_file() {
|
|
return Err(custom_error(
|
|
"NotFound",
|
|
format!(
|
|
"File not found, copy '{}' -> '{}'",
|
|
from.display(),
|
|
to.display()
|
|
),
|
|
));
|
|
}
|
|
// returns size of from as u64 (we ignore)
|
|
std::fs::copy(&from, &to).map_err(|err| {
|
|
default_err_mapper(
|
|
err,
|
|
format!("copy '{}' -> '{}'", from.display(), to.display()),
|
|
)
|
|
})?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> (u64, bool) {
|
|
match maybe_time {
|
|
Ok(time) => (
|
|
time
|
|
.duration_since(UNIX_EPOCH)
|
|
.map(|t| t.as_millis() as u64)
|
|
.unwrap_or_else(|err| err.duration().as_millis() as u64),
|
|
true,
|
|
),
|
|
Err(_) => (0, false),
|
|
}
|
|
}
|
|
|
|
macro_rules! create_struct_writer {
|
|
(pub struct $name:ident { $($field:ident: $type:ty),* $(,)? }) => {
|
|
impl $name {
|
|
fn write(self, buf: &mut [u32]) {
|
|
let mut offset = 0;
|
|
$(
|
|
let value = self.$field as u64;
|
|
buf[offset] = value as u32;
|
|
buf[offset + 1] = (value >> 32) as u32;
|
|
#[allow(unused_assignments)]
|
|
{
|
|
offset += 2;
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct $name {
|
|
$($field: $type),*
|
|
}
|
|
};
|
|
}
|
|
|
|
create_struct_writer! {
|
|
pub struct FsStat {
|
|
is_file: bool,
|
|
is_directory: bool,
|
|
is_symlink: bool,
|
|
size: u64,
|
|
// In milliseconds, like JavaScript. Available on both Unix or Windows.
|
|
mtime_set: bool,
|
|
mtime: u64,
|
|
atime_set: bool,
|
|
atime: u64,
|
|
birthtime_set: bool,
|
|
birthtime: u64,
|
|
// Following are only valid under Unix.
|
|
dev: u64,
|
|
ino: u64,
|
|
mode: u32,
|
|
nlink: u64,
|
|
uid: u32,
|
|
gid: u32,
|
|
rdev: u64,
|
|
blksize: u64,
|
|
blocks: u64,
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn get_stat(metadata: std::fs::Metadata) -> FsStat {
|
|
// Unix stat member (number types only). 0 if not on unix.
|
|
macro_rules! usm {
|
|
($member:ident) => {{
|
|
#[cfg(unix)]
|
|
{
|
|
metadata.$member()
|
|
}
|
|
#[cfg(not(unix))]
|
|
{
|
|
0
|
|
}
|
|
}};
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
use std::os::unix::fs::MetadataExt;
|
|
let (mtime, mtime_set) = to_msec(metadata.modified());
|
|
let (atime, atime_set) = to_msec(metadata.accessed());
|
|
let (birthtime, birthtime_set) = to_msec(metadata.created());
|
|
|
|
FsStat {
|
|
is_file: metadata.is_file(),
|
|
is_directory: metadata.is_dir(),
|
|
is_symlink: metadata.file_type().is_symlink(),
|
|
size: metadata.len(),
|
|
// In milliseconds, like JavaScript. Available on both Unix or Windows.
|
|
mtime_set,
|
|
mtime,
|
|
atime_set,
|
|
atime,
|
|
birthtime_set,
|
|
birthtime,
|
|
// Following are only valid under Unix.
|
|
dev: usm!(dev),
|
|
ino: usm!(ino),
|
|
mode: usm!(mode),
|
|
nlink: usm!(nlink),
|
|
uid: usm!(uid),
|
|
gid: usm!(gid),
|
|
rdev: usm!(rdev),
|
|
blksize: usm!(blksize),
|
|
blocks: usm!(blocks),
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[inline(always)]
|
|
fn get_stat2(metadata: std::fs::Metadata, dev: u64) -> FsStat {
|
|
let (mtime, mtime_set) = to_msec(metadata.modified());
|
|
let (atime, atime_set) = to_msec(metadata.accessed());
|
|
let (birthtime, birthtime_set) = to_msec(metadata.created());
|
|
|
|
FsStat {
|
|
is_file: metadata.is_file(),
|
|
is_directory: metadata.is_dir(),
|
|
is_symlink: metadata.file_type().is_symlink(),
|
|
size: metadata.len(),
|
|
mtime_set,
|
|
mtime,
|
|
atime_set,
|
|
atime,
|
|
birthtime_set,
|
|
birthtime,
|
|
dev,
|
|
ino: 0,
|
|
mode: 0,
|
|
nlink: 0,
|
|
uid: 0,
|
|
gid: 0,
|
|
rdev: 0,
|
|
blksize: 0,
|
|
blocks: 0,
|
|
}
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
#[inline(always)]
|
|
fn get_stat2(metadata: std::fs::Metadata) -> FsStat {
|
|
#[cfg(unix)]
|
|
use std::os::unix::fs::MetadataExt;
|
|
let (mtime, mtime_set) = to_msec(metadata.modified());
|
|
let (atime, atime_set) = to_msec(metadata.accessed());
|
|
let (birthtime, birthtime_set) = to_msec(metadata.created());
|
|
|
|
FsStat {
|
|
is_file: metadata.is_file(),
|
|
is_directory: metadata.is_dir(),
|
|
is_symlink: metadata.file_type().is_symlink(),
|
|
size: metadata.len(),
|
|
mtime_set,
|
|
mtime,
|
|
atime_set,
|
|
atime,
|
|
birthtime_set,
|
|
birthtime,
|
|
dev: metadata.dev(),
|
|
ino: metadata.ino(),
|
|
mode: metadata.mode(),
|
|
nlink: metadata.nlink(),
|
|
uid: metadata.uid(),
|
|
gid: metadata.gid(),
|
|
rdev: metadata.rdev(),
|
|
blksize: metadata.blksize(),
|
|
blocks: metadata.blocks(),
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StatArgs {
|
|
path: String,
|
|
lstat: bool,
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
fn do_stat(path: PathBuf, lstat: bool) -> Result<FsStat, AnyError> {
|
|
let err_mapper =
|
|
|err| default_err_mapper(err, format!("stat '{}'", path.display()));
|
|
let metadata = if lstat {
|
|
std::fs::symlink_metadata(&path).map_err(err_mapper)?
|
|
} else {
|
|
std::fs::metadata(&path).map_err(err_mapper)?
|
|
};
|
|
|
|
Ok(get_stat2(metadata))
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn do_stat(path: PathBuf, lstat: bool) -> Result<FsStat, AnyError> {
|
|
use std::os::windows::prelude::OsStrExt;
|
|
|
|
use winapi::um::fileapi::CreateFileW;
|
|
use winapi::um::fileapi::OPEN_EXISTING;
|
|
use winapi::um::handleapi::CloseHandle;
|
|
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
|
use winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS;
|
|
use winapi::um::winbase::FILE_FLAG_OPEN_REPARSE_POINT;
|
|
use winapi::um::winnt::FILE_SHARE_DELETE;
|
|
use winapi::um::winnt::FILE_SHARE_READ;
|
|
use winapi::um::winnt::FILE_SHARE_WRITE;
|
|
|
|
let err_mapper =
|
|
|err| default_err_mapper(err, format!("stat '{}'", path.display()));
|
|
let metadata = if lstat {
|
|
std::fs::symlink_metadata(&path).map_err(err_mapper)?
|
|
} else {
|
|
std::fs::metadata(&path).map_err(err_mapper)?
|
|
};
|
|
|
|
let (p, file_flags) = if lstat {
|
|
(
|
|
path,
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
|
|
)
|
|
} else {
|
|
(path.canonicalize()?, FILE_FLAG_BACKUP_SEMANTICS)
|
|
};
|
|
// SAFETY: winapi calls
|
|
unsafe {
|
|
let mut path: Vec<_> = p.as_os_str().encode_wide().collect();
|
|
path.push(0);
|
|
let file_handle = CreateFileW(
|
|
path.as_ptr(),
|
|
0,
|
|
FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
|
|
std::ptr::null_mut(),
|
|
OPEN_EXISTING,
|
|
file_flags,
|
|
std::ptr::null_mut(),
|
|
);
|
|
if file_handle == INVALID_HANDLE_VALUE {
|
|
return Err(std::io::Error::last_os_error().into());
|
|
}
|
|
|
|
let result = get_dev(file_handle);
|
|
CloseHandle(file_handle);
|
|
let dev = result?;
|
|
|
|
Ok(get_stat2(metadata, dev))
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
use winapi::um::fileapi::GetFileInformationByHandle;
|
|
#[cfg(windows)]
|
|
use winapi::um::fileapi::BY_HANDLE_FILE_INFORMATION;
|
|
|
|
#[cfg(windows)]
|
|
unsafe fn get_dev(
|
|
handle: winapi::shared::ntdef::HANDLE,
|
|
) -> std::io::Result<u64> {
|
|
use winapi::shared::minwindef::FALSE;
|
|
|
|
let info = {
|
|
let mut info =
|
|
std::mem::MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::zeroed();
|
|
if GetFileInformationByHandle(handle, info.as_mut_ptr()) == FALSE {
|
|
return Err(std::io::Error::last_os_error());
|
|
}
|
|
|
|
info.assume_init()
|
|
};
|
|
|
|
Ok(info.dwVolumeSerialNumber as u64)
|
|
}
|
|
|
|
#[op]
|
|
fn op_stat_sync<P>(
|
|
state: &mut OpState,
|
|
path: &str,
|
|
lstat: bool,
|
|
out_buf: &mut [u32],
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(path);
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(&path, "Deno.statSync()")?;
|
|
|
|
let stat = do_stat(path, lstat)?;
|
|
stat.write(out_buf);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_stat_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
args: StatArgs,
|
|
) -> Result<FsStat, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&args.path);
|
|
let lstat = args.lstat;
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state.borrow_mut::<P>().check_read(&path, "Deno.stat()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
debug!("op_stat_async {} {}", path.display(), lstat);
|
|
do_stat(path, lstat)
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_realpath_sync<P>(
|
|
state: &mut OpState,
|
|
path: String,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&path, "Deno.realPathSync()")?;
|
|
if path.is_relative() {
|
|
permissions.check_read_blind(
|
|
¤t_dir()?,
|
|
"CWD",
|
|
"Deno.realPathSync()",
|
|
)?;
|
|
}
|
|
|
|
debug!("op_realpath_sync {}", path.display());
|
|
// corresponds to the realpath on Unix and
|
|
// CreateFile and GetFinalPathNameByHandle on Windows
|
|
let realpath = canonicalize_path(&path).map_err(|error| {
|
|
default_err_mapper(error, format!("op_realpath_sync '{}'", path.display()))
|
|
})?;
|
|
let realpath_str = into_string(realpath.into_os_string())?;
|
|
Ok(realpath_str)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_realpath_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&path, "Deno.realPath()")?;
|
|
if path.is_relative() {
|
|
permissions.check_read_blind(
|
|
¤t_dir()?,
|
|
"CWD",
|
|
"Deno.realPath()",
|
|
)?;
|
|
}
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
debug!("op_realpath_async {}", path.display());
|
|
// corresponds to the realpath on Unix and
|
|
// CreateFile and GetFinalPathNameByHandle on Windows
|
|
let realpath = canonicalize_path(&path).map_err(|error| {
|
|
default_err_mapper(
|
|
error,
|
|
format!("op_realpath_async '{}'", path.display()),
|
|
)
|
|
})?;
|
|
let realpath_str = into_string(realpath.into_os_string())?;
|
|
Ok(realpath_str)
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct DirEntry {
|
|
name: String,
|
|
is_file: bool,
|
|
is_directory: bool,
|
|
is_symlink: bool,
|
|
}
|
|
|
|
#[op]
|
|
fn op_read_dir_sync<P>(
|
|
state: &mut OpState,
|
|
path: String,
|
|
) -> Result<Vec<DirEntry>, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(&path, "Deno.readDirSync()")?;
|
|
|
|
debug!("op_read_dir_sync {}", path.display());
|
|
|
|
let entries: Vec<_> = std::fs::read_dir(&path)
|
|
.map_err(|err| {
|
|
default_err_mapper(err, format!("readdir '{}'", path.display()))
|
|
})?
|
|
.filter_map(|entry| {
|
|
let entry = entry.unwrap();
|
|
// Not all filenames can be encoded as UTF-8. Skip those for now.
|
|
if let Ok(name) = into_string(entry.file_name()) {
|
|
Some(DirEntry {
|
|
name,
|
|
is_file: entry
|
|
.file_type()
|
|
.map(|file_type| file_type.is_file())
|
|
.unwrap_or(false),
|
|
is_directory: entry
|
|
.file_type()
|
|
.map(|file_type| file_type.is_dir())
|
|
.unwrap_or(false),
|
|
is_symlink: entry
|
|
.file_type()
|
|
.map(|file_type| file_type.is_symlink())
|
|
.unwrap_or(false),
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_read_dir_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
) -> Result<Vec<DirEntry>, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(&path, "Deno.readDir()")?;
|
|
}
|
|
tokio::task::spawn_blocking(move || {
|
|
debug!("op_read_dir_async {}", path.display());
|
|
|
|
let entries: Vec<_> = std::fs::read_dir(&path)
|
|
.map_err(|err| {
|
|
default_err_mapper(err, format!("readdir '{}'", path.display()))
|
|
})?
|
|
.filter_map(|entry| {
|
|
let entry = entry.unwrap();
|
|
// Not all filenames can be encoded as UTF-8. Skip those for now.
|
|
if let Ok(name) = into_string(entry.file_name()) {
|
|
Some(DirEntry {
|
|
name,
|
|
is_file: entry
|
|
.file_type()
|
|
.map(|file_type| file_type.is_file())
|
|
.unwrap_or(false),
|
|
is_directory: entry
|
|
.file_type()
|
|
.map(|file_type| file_type.is_dir())
|
|
.unwrap_or(false),
|
|
is_symlink: entry
|
|
.file_type()
|
|
.map(|file_type| file_type.is_symlink())
|
|
.unwrap_or(false),
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
Ok(entries)
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_rename_sync<P>(
|
|
state: &mut OpState,
|
|
oldpath: &str,
|
|
newpath: &str,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let oldpath = PathBuf::from(oldpath);
|
|
let newpath = PathBuf::from(newpath);
|
|
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&oldpath, "Deno.renameSync()")?;
|
|
permissions.check_write(&oldpath, "Deno.renameSync()")?;
|
|
permissions.check_write(&newpath, "Deno.renameSync()")?;
|
|
|
|
std::fs::rename(&oldpath, &newpath).map_err(|err| {
|
|
default_err_mapper(
|
|
err,
|
|
format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()),
|
|
)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_rename_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
oldpath: String,
|
|
newpath: String,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let oldpath = PathBuf::from(&oldpath);
|
|
let newpath = PathBuf::from(&newpath);
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&oldpath, "Deno.rename()")?;
|
|
permissions.check_write(&oldpath, "Deno.rename()")?;
|
|
permissions.check_write(&newpath, "Deno.rename()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
std::fs::rename(&oldpath, &newpath).map_err(|err| {
|
|
default_err_mapper(
|
|
err,
|
|
format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()),
|
|
)
|
|
})?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_link_sync<P>(
|
|
state: &mut OpState,
|
|
oldpath: &str,
|
|
newpath: &str,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let oldpath = PathBuf::from(oldpath);
|
|
let newpath = PathBuf::from(newpath);
|
|
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&oldpath, "Deno.linkSync()")?;
|
|
permissions.check_write(&oldpath, "Deno.linkSync()")?;
|
|
permissions.check_read(&newpath, "Deno.linkSync()")?;
|
|
permissions.check_write(&newpath, "Deno.linkSync()")?;
|
|
|
|
std::fs::hard_link(&oldpath, &newpath).map_err(|err| {
|
|
default_err_mapper(
|
|
err,
|
|
format!("link '{}' -> '{}'", oldpath.display(), newpath.display()),
|
|
)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_link_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
oldpath: String,
|
|
newpath: String,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let oldpath = PathBuf::from(&oldpath);
|
|
let newpath = PathBuf::from(&newpath);
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<P>();
|
|
permissions.check_read(&oldpath, "Deno.link()")?;
|
|
permissions.check_write(&oldpath, "Deno.link()")?;
|
|
permissions.check_read(&newpath, "Deno.link()")?;
|
|
permissions.check_write(&newpath, "Deno.link()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
let err_mapper = |err| {
|
|
default_err_mapper(
|
|
err,
|
|
format!("link '{}' -> '{}'", oldpath.display(), newpath.display()),
|
|
)
|
|
};
|
|
std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_symlink_sync<P>(
|
|
state: &mut OpState,
|
|
oldpath: &str,
|
|
newpath: &str,
|
|
_type: Option<String>,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let oldpath = PathBuf::from(oldpath);
|
|
let newpath = PathBuf::from(newpath);
|
|
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write_all("Deno.symlinkSync()")?;
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read_all("Deno.symlinkSync()")?;
|
|
|
|
let err_mapper = |err| {
|
|
default_err_mapper(
|
|
err,
|
|
format!("symlink '{}' -> '{}'", oldpath.display(), newpath.display()),
|
|
)
|
|
};
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::symlink;
|
|
symlink(&oldpath, &newpath).map_err(err_mapper)?;
|
|
Ok(())
|
|
}
|
|
#[cfg(not(unix))]
|
|
{
|
|
use std::os::windows::fs::symlink_dir;
|
|
use std::os::windows::fs::symlink_file;
|
|
|
|
match _type {
|
|
Some(ty) => match ty.as_ref() {
|
|
"file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
|
|
"dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
|
|
_ => return Err(type_error("unsupported type")),
|
|
},
|
|
None => {
|
|
let old_meta = std::fs::metadata(&oldpath);
|
|
match old_meta {
|
|
Ok(metadata) => {
|
|
if metadata.is_file() {
|
|
symlink_file(&oldpath, &newpath).map_err(err_mapper)?
|
|
} else if metadata.is_dir() {
|
|
symlink_dir(&oldpath, &newpath).map_err(err_mapper)?
|
|
}
|
|
}
|
|
Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())),
|
|
}
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[op]
|
|
async fn op_symlink_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
oldpath: String,
|
|
newpath: String,
|
|
_type: Option<String>,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let oldpath = PathBuf::from(&oldpath);
|
|
let newpath = PathBuf::from(&newpath);
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state.borrow_mut::<P>().check_write_all("Deno.symlink()")?;
|
|
state.borrow_mut::<P>().check_read_all("Deno.symlink()")?;
|
|
}
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
let err_mapper = |err| default_err_mapper(err, format!(
|
|
"symlink '{}' -> '{}'",
|
|
oldpath.display(),
|
|
newpath.display()
|
|
));
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::symlink;
|
|
symlink(&oldpath, &newpath).map_err(err_mapper)?;
|
|
Ok(())
|
|
}
|
|
#[cfg(not(unix))]
|
|
{
|
|
use std::os::windows::fs::{symlink_dir, symlink_file};
|
|
|
|
match _type {
|
|
Some(ty) => match ty.as_ref() {
|
|
"file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
|
|
"dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
|
|
_ => return Err(type_error("unsupported type")),
|
|
},
|
|
None => {
|
|
let old_meta = std::fs::metadata(&oldpath);
|
|
match old_meta {
|
|
Ok(metadata) => {
|
|
if metadata.is_file() {
|
|
symlink_file(&oldpath, &newpath).map_err(err_mapper)?
|
|
} else if metadata.is_dir() {
|
|
symlink_dir(&oldpath, &newpath).map_err(err_mapper)?
|
|
}
|
|
}
|
|
Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())),
|
|
}
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_read_link_sync<P>(
|
|
state: &mut OpState,
|
|
path: String,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(&path, "Deno.readLink()")?;
|
|
|
|
debug!("op_read_link_value {}", path.display());
|
|
let target = std::fs::read_link(&path)
|
|
.map_err(|err| {
|
|
default_err_mapper(err, format!("readlink '{}'", path.display()))
|
|
})?
|
|
.into_os_string();
|
|
let targetstr = into_string(target)?;
|
|
Ok(targetstr)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_read_link_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(&path, "Deno.readLink()")?;
|
|
}
|
|
tokio::task::spawn_blocking(move || {
|
|
debug!("op_read_link_async {}", path.display());
|
|
let target = std::fs::read_link(&path)
|
|
.map_err(|err| {
|
|
default_err_mapper(err, format!("readlink '{}'", path.display()))
|
|
})?
|
|
.into_os_string();
|
|
let targetstr = into_string(target)?;
|
|
Ok(targetstr)
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_ftruncate_sync(
|
|
state: &mut OpState,
|
|
rid: u32,
|
|
len: i32,
|
|
) -> Result<(), AnyError> {
|
|
let len = len as u64;
|
|
StdFileResource::with_file(state, rid, |std_file| {
|
|
std_file.set_len(len).map_err(AnyError::from)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_ftruncate_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
len: i32,
|
|
) -> Result<(), AnyError> {
|
|
let len = len as u64;
|
|
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
std_file.set_len(len)?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
}
|
|
|
|
#[op]
|
|
fn op_truncate_sync<P>(
|
|
state: &mut OpState,
|
|
path: &str,
|
|
len: u64,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(path);
|
|
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write(&path, "Deno.truncateSync()")?;
|
|
|
|
debug!("op_truncate_sync {} {}", path.display(), len);
|
|
let err_mapper =
|
|
|err| default_err_mapper(err, format!("truncate '{}'", path.display()));
|
|
let f = std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.open(&path)
|
|
.map_err(err_mapper)?;
|
|
f.set_len(len).map_err(err_mapper)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_truncate_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
len: u64,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_write(&path, "Deno.truncate()")?;
|
|
}
|
|
tokio::task::spawn_blocking(move || {
|
|
debug!("op_truncate_async {} {}", path.display(), len);
|
|
let err_mapper =
|
|
|err| default_err_mapper(err, format!("truncate '{}'", path.display()));
|
|
let f = std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.open(&path)
|
|
.map_err(err_mapper)?;
|
|
f.set_len(len).map_err(err_mapper)?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
fn make_temp(
|
|
dir: Option<&Path>,
|
|
prefix: Option<&str>,
|
|
suffix: Option<&str>,
|
|
is_dir: bool,
|
|
) -> std::io::Result<PathBuf> {
|
|
let prefix_ = prefix.unwrap_or("");
|
|
let suffix_ = suffix.unwrap_or("");
|
|
let mut buf: PathBuf = match dir {
|
|
Some(p) => p.to_path_buf(),
|
|
None => temp_dir(),
|
|
}
|
|
.join("_");
|
|
let mut rng = thread_rng();
|
|
const MAX_TRIES: u32 = 10;
|
|
for _ in 0..MAX_TRIES {
|
|
let unique = rng.gen::<u32>();
|
|
buf.set_file_name(format!("{prefix_}{unique:08x}{suffix_}"));
|
|
let r = if is_dir {
|
|
#[allow(unused_mut)]
|
|
let mut builder = std::fs::DirBuilder::new();
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::DirBuilderExt;
|
|
builder.mode(0o700);
|
|
}
|
|
builder.create(buf.as_path())
|
|
} else {
|
|
let mut open_options = std::fs::OpenOptions::new();
|
|
open_options.write(true).create_new(true);
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::OpenOptionsExt;
|
|
open_options.mode(0o600);
|
|
}
|
|
open_options.open(buf.as_path()).map(drop)
|
|
};
|
|
match r {
|
|
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
|
|
Ok(_) => return Ok(buf),
|
|
Err(e) => return Err(e),
|
|
}
|
|
}
|
|
Err(io::Error::new(
|
|
io::ErrorKind::AlreadyExists,
|
|
"too many temp files exist",
|
|
))
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct MakeTempArgs {
|
|
dir: Option<String>,
|
|
prefix: Option<String>,
|
|
suffix: Option<String>,
|
|
}
|
|
|
|
#[op]
|
|
fn op_make_temp_dir_sync<P>(
|
|
state: &mut OpState,
|
|
args: MakeTempArgs,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let dir = args.dir.map(|s| PathBuf::from(&s));
|
|
let prefix = args.prefix.map(String::from);
|
|
let suffix = args.suffix.map(String::from);
|
|
|
|
state.borrow_mut::<P>().check_write(
|
|
dir.clone().unwrap_or_else(temp_dir).as_path(),
|
|
"Deno.makeTempDirSync()",
|
|
)?;
|
|
|
|
// TODO(piscisaureus): use byte vector for paths, not a string.
|
|
// See https://github.com/denoland/deno/issues/627.
|
|
// We can't assume that paths are always valid utf8 strings.
|
|
let path = make_temp(
|
|
// Converting Option<String> to Option<&str>
|
|
dir.as_deref(),
|
|
prefix.as_deref(),
|
|
suffix.as_deref(),
|
|
true,
|
|
)?;
|
|
let path_str = into_string(path.into_os_string())?;
|
|
|
|
Ok(path_str)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_make_temp_dir_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
args: MakeTempArgs,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let dir = args.dir.map(|s| PathBuf::from(&s));
|
|
let prefix = args.prefix.map(String::from);
|
|
let suffix = args.suffix.map(String::from);
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state.borrow_mut::<P>().check_write(
|
|
dir.clone().unwrap_or_else(temp_dir).as_path(),
|
|
"Deno.makeTempDir()",
|
|
)?;
|
|
}
|
|
tokio::task::spawn_blocking(move || {
|
|
// TODO(piscisaureus): use byte vector for paths, not a string.
|
|
// See https://github.com/denoland/deno/issues/627.
|
|
// We can't assume that paths are always valid utf8 strings.
|
|
let path = make_temp(
|
|
// Converting Option<String> to Option<&str>
|
|
dir.as_deref(),
|
|
prefix.as_deref(),
|
|
suffix.as_deref(),
|
|
true,
|
|
)?;
|
|
let path_str = into_string(path.into_os_string())?;
|
|
|
|
Ok(path_str)
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_make_temp_file_sync<P>(
|
|
state: &mut OpState,
|
|
args: MakeTempArgs,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let dir = args.dir.map(|s| PathBuf::from(&s));
|
|
let prefix = args.prefix.map(String::from);
|
|
let suffix = args.suffix.map(String::from);
|
|
|
|
state.borrow_mut::<P>().check_write(
|
|
dir.clone().unwrap_or_else(temp_dir).as_path(),
|
|
"Deno.makeTempFileSync()",
|
|
)?;
|
|
|
|
// TODO(piscisaureus): use byte vector for paths, not a string.
|
|
// See https://github.com/denoland/deno/issues/627.
|
|
// We can't assume that paths are always valid utf8 strings.
|
|
let path = make_temp(
|
|
// Converting Option<String> to Option<&str>
|
|
dir.as_deref(),
|
|
prefix.as_deref(),
|
|
suffix.as_deref(),
|
|
false,
|
|
)?;
|
|
let path_str = into_string(path.into_os_string())?;
|
|
|
|
Ok(path_str)
|
|
}
|
|
|
|
#[op]
|
|
async fn op_make_temp_file_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
args: MakeTempArgs,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let dir = args.dir.map(|s| PathBuf::from(&s));
|
|
let prefix = args.prefix.map(String::from);
|
|
let suffix = args.suffix.map(String::from);
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
state.borrow_mut::<P>().check_write(
|
|
dir.clone().unwrap_or_else(temp_dir).as_path(),
|
|
"Deno.makeTempFile()",
|
|
)?;
|
|
}
|
|
tokio::task::spawn_blocking(move || {
|
|
// TODO(piscisaureus): use byte vector for paths, not a string.
|
|
// See https://github.com/denoland/deno/issues/627.
|
|
// We can't assume that paths are always valid utf8 strings.
|
|
let path = make_temp(
|
|
// Converting Option<String> to Option<&str>
|
|
dir.as_deref(),
|
|
prefix.as_deref(),
|
|
suffix.as_deref(),
|
|
false,
|
|
)?;
|
|
let path_str = into_string(path.into_os_string())?;
|
|
|
|
Ok(path_str)
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_futime_sync(
|
|
state: &mut OpState,
|
|
rid: ResourceId,
|
|
atime_secs: i64,
|
|
atime_nanos: u32,
|
|
mtime_secs: i64,
|
|
mtime_nanos: u32,
|
|
) -> Result<(), AnyError> {
|
|
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
|
|
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
|
|
|
|
StdFileResource::with_file(state, rid, |std_file| {
|
|
filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))
|
|
.map_err(AnyError::from)
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_futime_async(
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
atime_secs: i64,
|
|
atime_nanos: u32,
|
|
mtime_secs: i64,
|
|
mtime_nanos: u32,
|
|
) -> Result<(), AnyError> {
|
|
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
|
|
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
|
|
|
|
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
|
|
filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
}
|
|
|
|
#[op]
|
|
fn op_utime_sync<P>(
|
|
state: &mut OpState,
|
|
path: &str,
|
|
atime_secs: i64,
|
|
atime_nanos: u32,
|
|
mtime_secs: i64,
|
|
mtime_nanos: u32,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(path);
|
|
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
|
|
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
|
|
|
|
state.borrow_mut::<P>().check_write(&path, "Deno.utime()")?;
|
|
filetime::set_file_times(&path, atime, mtime).map_err(|err| {
|
|
default_err_mapper(err, format!("utime '{}'", path.display()))
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
#[op]
|
|
async fn op_utime_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
atime_secs: i64,
|
|
atime_nanos: u32,
|
|
mtime_secs: i64,
|
|
mtime_nanos: u32,
|
|
) -> Result<(), AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = PathBuf::from(&path);
|
|
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
|
|
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
|
|
|
|
state
|
|
.borrow_mut()
|
|
.borrow_mut::<P>()
|
|
.check_write(&path, "Deno.utime()")?;
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
filetime::set_file_times(&path, atime, mtime).map_err(|err| {
|
|
default_err_mapper(err, format!("utime '{}'", path.display()))
|
|
})?;
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[op]
|
|
fn op_cwd<P>(state: &mut OpState) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = current_dir()?;
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read_blind(&path, "CWD", "Deno.cwd()")?;
|
|
let path_str = into_string(path.into_os_string())?;
|
|
Ok(path_str)
|
|
}
|
|
|
|
#[op]
|
|
fn op_readfile_sync<P>(
|
|
state: &mut OpState,
|
|
path: String,
|
|
) -> Result<ZeroCopyBuf, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(&path);
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(path, "Deno.readFileSync()")?;
|
|
Ok(
|
|
std::fs::read(path)
|
|
.map_err(|err| {
|
|
default_err_mapper(err, format!("readfile '{}'", path.display()))
|
|
})?
|
|
.into(),
|
|
)
|
|
}
|
|
|
|
#[op]
|
|
fn op_readfile_text_sync<P>(
|
|
state: &mut OpState,
|
|
path: String,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
let path = Path::new(&path);
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(path, "Deno.readTextFileSync()")?;
|
|
Ok(string_from_utf8_lossy(std::fs::read(path).map_err(
|
|
|err| default_err_mapper(err, format!("readfile '{}'", path.display())),
|
|
)?))
|
|
}
|
|
|
|
#[op]
|
|
async fn op_readfile_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
cancel_rid: Option<ResourceId>,
|
|
) -> Result<ZeroCopyBuf, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
{
|
|
let path = Path::new(&path);
|
|
let mut state = state.borrow_mut();
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(path, "Deno.readFile()")?;
|
|
}
|
|
|
|
let read_future = tokio::task::spawn_blocking(move || {
|
|
let path = Path::new(&path);
|
|
std::fs::read(path).map(ZeroCopyBuf::from).map_err(|err| {
|
|
default_err_mapper(err, format!("readfile '{}'", path.display()))
|
|
})
|
|
});
|
|
|
|
let cancel_handle = cancel_rid.and_then(|rid| {
|
|
state
|
|
.borrow_mut()
|
|
.resource_table
|
|
.get::<CancelHandle>(rid)
|
|
.ok()
|
|
});
|
|
|
|
if let Some(cancel_handle) = cancel_handle {
|
|
let read_future_rv = read_future.or_cancel(cancel_handle).await;
|
|
|
|
if let Some(cancel_rid) = cancel_rid {
|
|
state.borrow_mut().resource_table.close(cancel_rid).ok();
|
|
};
|
|
|
|
return read_future_rv??;
|
|
}
|
|
|
|
read_future.await?
|
|
}
|
|
|
|
#[op]
|
|
async fn op_readfile_text_async<P>(
|
|
state: Rc<RefCell<OpState>>,
|
|
path: String,
|
|
cancel_rid: Option<ResourceId>,
|
|
) -> Result<String, AnyError>
|
|
where
|
|
P: FsPermissions + 'static,
|
|
{
|
|
{
|
|
let path = Path::new(&path);
|
|
let mut state = state.borrow_mut();
|
|
state
|
|
.borrow_mut::<P>()
|
|
.check_read(path, "Deno.readTextFile()")?;
|
|
}
|
|
|
|
let read_future = tokio::task::spawn_blocking(move || {
|
|
let path = Path::new(&path);
|
|
Ok(string_from_utf8_lossy(std::fs::read(path).map_err(
|
|
|err| default_err_mapper(err, format!("readfile '{}'", path.display())),
|
|
)?))
|
|
});
|
|
|
|
let cancel_handle = cancel_rid.and_then(|rid| {
|
|
state
|
|
.borrow_mut()
|
|
.resource_table
|
|
.get::<CancelHandle>(rid)
|
|
.ok()
|
|
});
|
|
|
|
if let Some(cancel_handle) = cancel_handle {
|
|
let read_future_rv = read_future.or_cancel(cancel_handle).await;
|
|
|
|
if let Some(cancel_rid) = cancel_rid {
|
|
state.borrow_mut().resource_table.close(cancel_rid).ok();
|
|
};
|
|
|
|
return read_future_rv??;
|
|
}
|
|
|
|
read_future.await?
|
|
}
|
|
|
|
// Like String::from_utf8_lossy but operates on owned values
|
|
fn string_from_utf8_lossy(buf: Vec<u8>) -> String {
|
|
match String::from_utf8_lossy(&buf) {
|
|
// buf contained non-utf8 chars than have been patched
|
|
Cow::Owned(s) => s,
|
|
// SAFETY: if Borrowed then the buf only contains utf8 chars,
|
|
// we do this instead of .into_owned() to avoid copying the input buf
|
|
Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) },
|
|
}
|
|
}
|