diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2020-12-13 19:45:53 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-13 19:45:53 +0100 |
commit | 2e74f164b6dcf0ecbf8dd38fba9fae550d784bd0 (patch) | |
tree | 61abe8e09d5331ace5d9de529f0e2737a8e05dbb /runtime/ops/fs.rs | |
parent | 84ef9bd21fb48fb6b5fbc8dafc3de9f361bade3b (diff) |
refactor: deno_runtime crate (#8640)
This commit moves Deno JS runtime, ops, permissions and
inspector implementation to new "deno_runtime" crate located
in "runtime/" directory.
Details in "runtime/README.md".
Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
Diffstat (limited to 'runtime/ops/fs.rs')
-rw-r--r-- | runtime/ops/fs.rs | 1702 |
1 files changed, 1702 insertions, 0 deletions
diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs new file mode 100644 index 000000000..865c5bcca --- /dev/null +++ b/runtime/ops/fs.rs @@ -0,0 +1,1702 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +// Some deserializer fields are only used on Unix and Windows build fails without it +use super::io::std_file_resource; +use super::io::{FileMetadata, StreamResource, StreamResourceHolder}; +use crate::fs_util::canonicalize_path; +use crate::permissions::Permissions; +use deno_core::error::custom_error; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::serde_json; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::BufVec; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use deno_crypto::rand::thread_rng; +use deno_crypto::rand::Rng; +use serde::Deserialize; +use std::cell::RefCell; +use std::convert::From; +use std::env::{current_dir, set_current_dir, temp_dir}; +use std::io; +use std::io::{Seek, SeekFrom}; +use std::path::{Path, PathBuf}; +use std::rc::Rc; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; + +#[cfg(not(unix))] +use deno_core::error::generic_error; +#[cfg(not(unix))] +use deno_core::error::not_supported; + +pub fn init(rt: &mut deno_core::JsRuntime) { + super::reg_json_sync(rt, "op_open_sync", op_open_sync); + super::reg_json_async(rt, "op_open_async", op_open_async); + + super::reg_json_sync(rt, "op_seek_sync", op_seek_sync); + super::reg_json_async(rt, "op_seek_async", op_seek_async); + + super::reg_json_sync(rt, "op_fdatasync_sync", op_fdatasync_sync); + super::reg_json_async(rt, "op_fdatasync_async", op_fdatasync_async); + + super::reg_json_sync(rt, "op_fsync_sync", op_fsync_sync); + super::reg_json_async(rt, "op_fsync_async", op_fsync_async); + + super::reg_json_sync(rt, "op_fstat_sync", op_fstat_sync); + super::reg_json_async(rt, "op_fstat_async", op_fstat_async); + + super::reg_json_sync(rt, "op_umask", op_umask); + super::reg_json_sync(rt, "op_chdir", op_chdir); + + super::reg_json_sync(rt, "op_mkdir_sync", op_mkdir_sync); + super::reg_json_async(rt, "op_mkdir_async", op_mkdir_async); + + super::reg_json_sync(rt, "op_chmod_sync", op_chmod_sync); + super::reg_json_async(rt, "op_chmod_async", op_chmod_async); + + super::reg_json_sync(rt, "op_chown_sync", op_chown_sync); + super::reg_json_async(rt, "op_chown_async", op_chown_async); + + super::reg_json_sync(rt, "op_remove_sync", op_remove_sync); + super::reg_json_async(rt, "op_remove_async", op_remove_async); + + super::reg_json_sync(rt, "op_copy_file_sync", op_copy_file_sync); + super::reg_json_async(rt, "op_copy_file_async", op_copy_file_async); + + super::reg_json_sync(rt, "op_stat_sync", op_stat_sync); + super::reg_json_async(rt, "op_stat_async", op_stat_async); + + super::reg_json_sync(rt, "op_realpath_sync", op_realpath_sync); + super::reg_json_async(rt, "op_realpath_async", op_realpath_async); + + super::reg_json_sync(rt, "op_read_dir_sync", op_read_dir_sync); + super::reg_json_async(rt, "op_read_dir_async", op_read_dir_async); + + super::reg_json_sync(rt, "op_rename_sync", op_rename_sync); + super::reg_json_async(rt, "op_rename_async", op_rename_async); + + super::reg_json_sync(rt, "op_link_sync", op_link_sync); + super::reg_json_async(rt, "op_link_async", op_link_async); + + super::reg_json_sync(rt, "op_symlink_sync", op_symlink_sync); + super::reg_json_async(rt, "op_symlink_async", op_symlink_async); + + super::reg_json_sync(rt, "op_read_link_sync", op_read_link_sync); + super::reg_json_async(rt, "op_read_link_async", op_read_link_async); + + super::reg_json_sync(rt, "op_ftruncate_sync", op_ftruncate_sync); + super::reg_json_async(rt, "op_ftruncate_async", op_ftruncate_async); + + super::reg_json_sync(rt, "op_truncate_sync", op_truncate_sync); + super::reg_json_async(rt, "op_truncate_async", op_truncate_async); + + super::reg_json_sync(rt, "op_make_temp_dir_sync", op_make_temp_dir_sync); + super::reg_json_async(rt, "op_make_temp_dir_async", op_make_temp_dir_async); + + super::reg_json_sync(rt, "op_make_temp_file_sync", op_make_temp_file_sync); + super::reg_json_async(rt, "op_make_temp_file_async", op_make_temp_file_async); + + super::reg_json_sync(rt, "op_cwd", op_cwd); + + super::reg_json_sync(rt, "op_futime_sync", op_futime_sync); + super::reg_json_async(rt, "op_futime_async", op_futime_async); + + super::reg_json_sync(rt, "op_utime_sync", op_utime_sync); + super::reg_json_async(rt, "op_utime_async", op_utime_async); +} + +fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> { + s.into_string().map_err(|s| { + let message = format!("File name or path {:?} is not valid UTF-8", s); + custom_error("InvalidData", message) + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct OpenArgs { + path: String, + mode: Option<u32>, + options: OpenOptions, +} + +#[derive(Deserialize, Default, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +struct OpenOptions { + read: bool, + write: bool, + create: bool, + truncate: bool, + append: bool, + create_new: bool, +} + +fn open_helper( + state: &mut OpState, + args: Value, +) -> Result<(PathBuf, std::fs::OpenOptions), AnyError> { + let args: OpenArgs = serde_json::from_value(args)?; + let path = Path::new(&args.path).to_path_buf(); + + let mut open_options = std::fs::OpenOptions::new(); + + if let Some(mode) = args.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::<Permissions>(); + let options = args.options; + + if options.read { + permissions.check_read(&path)?; + } + + if options.write || options.append { + permissions.check_write(&path)?; + } + + 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)) +} + +fn op_open_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let (path, open_options) = open_helper(state, args)?; + let std_file = open_options.open(path)?; + let tokio_file = tokio::fs::File::from_std(std_file); + let rid = state.resource_table.add( + "fsFile", + Box::new(StreamResourceHolder::new(StreamResource::FsFile(Some(( + tokio_file, + FileMetadata::default(), + ))))), + ); + Ok(json!(rid)) +} + +async fn op_open_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let (path, open_options) = open_helper(&mut state.borrow_mut(), args)?; + let tokio_file = tokio::fs::OpenOptions::from(open_options) + .open(path) + .await?; + let rid = state.borrow_mut().resource_table.add( + "fsFile", + Box::new(StreamResourceHolder::new(StreamResource::FsFile(Some(( + tokio_file, + FileMetadata::default(), + ))))), + ); + Ok(json!(rid)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SeekArgs { + rid: i32, + offset: i64, + whence: i32, +} + +fn seek_helper(args: Value) -> Result<(u32, SeekFrom), AnyError> { + let args: SeekArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + 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)) +} + +fn op_seek_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let (rid, seek_from) = seek_helper(args)?; + let pos = std_file_resource(state, rid, |r| match r { + Ok(std_file) => std_file.seek(seek_from).map_err(AnyError::from), + Err(_) => Err(type_error( + "cannot seek on this type of resource".to_string(), + )), + })?; + Ok(json!(pos)) +} + +async fn op_seek_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let (rid, seek_from) = seek_helper(args)?; + // TODO(ry) This is a fake async op. We need to use poll_fn, + // tokio::fs::File::start_seek and tokio::fs::File::poll_complete + let pos = std_file_resource(&mut state.borrow_mut(), rid, |r| match r { + Ok(std_file) => std_file.seek(seek_from).map_err(AnyError::from), + Err(_) => Err(type_error( + "cannot seek on this type of resource".to_string(), + )), + })?; + Ok(json!(pos)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FdatasyncArgs { + rid: i32, +} + +fn op_fdatasync_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: FdatasyncArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + std_file_resource(state, rid, |r| match r { + Ok(std_file) => std_file.sync_data().map_err(AnyError::from), + Err(_) => Err(type_error("cannot sync this type of resource".to_string())), + })?; + Ok(json!({})) +} + +async fn op_fdatasync_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: FdatasyncArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + std_file_resource(&mut state.borrow_mut(), rid, |r| match r { + Ok(std_file) => std_file.sync_data().map_err(AnyError::from), + Err(_) => Err(type_error("cannot sync this type of resource".to_string())), + })?; + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FsyncArgs { + rid: i32, +} + +fn op_fsync_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: FsyncArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + std_file_resource(state, rid, |r| match r { + Ok(std_file) => std_file.sync_all().map_err(AnyError::from), + Err(_) => Err(type_error("cannot sync this type of resource".to_string())), + })?; + Ok(json!({})) +} + +async fn op_fsync_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: FsyncArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + std_file_resource(&mut state.borrow_mut(), rid, |r| match r { + Ok(std_file) => std_file.sync_all().map_err(AnyError::from), + Err(_) => Err(type_error("cannot sync this type of resource".to_string())), + })?; + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FstatArgs { + rid: i32, +} + +fn op_fstat_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + super::check_unstable(state, "Deno.fstat"); + let args: FstatArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let metadata = std_file_resource(state, rid, |r| match r { + Ok(std_file) => std_file.metadata().map_err(AnyError::from), + Err(_) => Err(type_error("cannot stat this type of resource".to_string())), + })?; + Ok(get_stat_json(metadata)) +} + +async fn op_fstat_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + super::check_unstable2(&state, "Deno.fstat"); + + let args: FstatArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let metadata = + std_file_resource(&mut state.borrow_mut(), rid, |r| match r { + Ok(std_file) => std_file.metadata().map_err(AnyError::from), + Err(_) => { + Err(type_error("cannot stat this type of resource".to_string())) + } + })?; + Ok(get_stat_json(metadata)) +} + +#[derive(Deserialize)] +struct UmaskArgs { + mask: Option<u32>, +} + +fn op_umask( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + super::check_unstable(state, "Deno.umask"); + let args: UmaskArgs = serde_json::from_value(args)?; + // 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 _ = args.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) = args.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 + }; + Ok(json!(r.bits() as u32)) + } +} + +#[derive(Deserialize)] +struct ChdirArgs { + directory: String, +} + +fn op_chdir( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: ChdirArgs = serde_json::from_value(args)?; + let d = PathBuf::from(&args.directory); + state.borrow::<Permissions>().check_read(&d)?; + set_current_dir(&d)?; + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MkdirArgs { + path: String, + recursive: bool, + mode: Option<u32>, +} + +fn op_mkdir_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: MkdirArgs = serde_json::from_value(args)?; + let path = Path::new(&args.path).to_path_buf(); + let mode = args.mode.unwrap_or(0o777) & 0o777; + state.borrow::<Permissions>().check_write(&path)?; + 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)?; + Ok(json!({})) +} + +async fn op_mkdir_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: MkdirArgs = serde_json::from_value(args)?; + let path = Path::new(&args.path).to_path_buf(); + let mode = args.mode.unwrap_or(0o777) & 0o777; + + { + let state = state.borrow(); + state.borrow::<Permissions>().check_write(&path)?; + } + + 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)?; + Ok(json!({})) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ChmodArgs { + path: String, + mode: u32, +} + +fn op_chmod_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: ChmodArgs = serde_json::from_value(args)?; + let path = Path::new(&args.path).to_path_buf(); + let mode = args.mode & 0o777; + + state.borrow::<Permissions>().check_write(&path)?; + debug!("op_chmod_sync {} {:o}", path.display(), mode); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let permissions = PermissionsExt::from_mode(mode); + std::fs::set_permissions(&path, permissions)?; + Ok(json!({})) + } + // TODO Implement chmod for Windows (#4357) + #[cfg(not(unix))] + { + // Still check file/dir exists on Windows + let _metadata = std::fs::metadata(&path)?; + Err(generic_error("Not implemented")) + } +} + +async fn op_chmod_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: ChmodArgs = serde_json::from_value(args)?; + let path = Path::new(&args.path).to_path_buf(); + let mode = args.mode & 0o777; + + { + let state = state.borrow(); + state.borrow::<Permissions>().check_write(&path)?; + } + + tokio::task::spawn_blocking(move || { + debug!("op_chmod_async {} {:o}", path.display(), mode); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let permissions = PermissionsExt::from_mode(mode); + std::fs::set_permissions(&path, permissions)?; + Ok(json!({})) + } + // TODO Implement chmod for Windows (#4357) + #[cfg(not(unix))] + { + // Still check file/dir exists on Windows + let _metadata = std::fs::metadata(&path)?; + Err(not_supported()) + } + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ChownArgs { + path: String, + uid: Option<u32>, + gid: Option<u32>, +} + +fn op_chown_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: ChownArgs = serde_json::from_value(args)?; + let path = Path::new(&args.path).to_path_buf(); + state.borrow::<Permissions>().check_write(&path)?; + debug!( + "op_chown_sync {} {:?} {:?}", + path.display(), + args.uid, + args.gid, + ); + #[cfg(unix)] + { + use nix::unistd::{chown, Gid, Uid}; + let nix_uid = args.uid.map(Uid::from_raw); + let nix_gid = args.gid.map(Gid::from_raw); + chown(&path, nix_uid, nix_gid)?; + Ok(json!({})) + } + // TODO Implement chown for Windows + #[cfg(not(unix))] + { + Err(generic_error("Not implemented")) + } +} + +async fn op_chown_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: ChownArgs = serde_json::from_value(args)?; + let path = Path::new(&args.path).to_path_buf(); + + { + let state = state.borrow(); + state.borrow::<Permissions>().check_write(&path)?; + } + + tokio::task::spawn_blocking(move || { + debug!( + "op_chown_async {} {:?} {:?}", + path.display(), + args.uid, + args.gid, + ); + #[cfg(unix)] + { + use nix::unistd::{chown, Gid, Uid}; + let nix_uid = args.uid.map(Uid::from_raw); + let nix_gid = args.gid.map(Gid::from_raw); + chown(&path, nix_uid, nix_gid)?; + Ok(json!({})) + } + // TODO Implement chown for Windows + #[cfg(not(unix))] + Err(not_supported()) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RemoveArgs { + path: String, + recursive: bool, +} + +fn op_remove_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: RemoveArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let recursive = args.recursive; + + state.borrow::<Permissions>().check_write(&path)?; + + #[cfg(not(unix))] + use std::os::windows::prelude::MetadataExt; + + let metadata = std::fs::symlink_metadata(&path)?; + + debug!("op_remove_sync {} {}", path.display(), recursive); + let file_type = metadata.file_type(); + if file_type.is_file() { + std::fs::remove_file(&path)?; + } else if recursive { + std::fs::remove_dir_all(&path)?; + } else if file_type.is_symlink() { + #[cfg(unix)] + std::fs::remove_file(&path)?; + #[cfg(not(unix))] + { + use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; + if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { + std::fs::remove_dir(&path)?; + } else { + std::fs::remove_file(&path)?; + } + } + } else if file_type.is_dir() { + std::fs::remove_dir(&path)?; + } else { + // pipes, sockets, etc... + std::fs::remove_file(&path)?; + } + Ok(json!({})) +} + +async fn op_remove_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: RemoveArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let recursive = args.recursive; + + { + let state = state.borrow(); + state.borrow::<Permissions>().check_write(&path)?; + } + + tokio::task::spawn_blocking(move || { + #[cfg(not(unix))] + use std::os::windows::prelude::MetadataExt; + + let metadata = std::fs::symlink_metadata(&path)?; + + debug!("op_remove_async {} {}", path.display(), recursive); + let file_type = metadata.file_type(); + if file_type.is_file() { + std::fs::remove_file(&path)?; + } else if recursive { + std::fs::remove_dir_all(&path)?; + } else if file_type.is_symlink() { + #[cfg(unix)] + std::fs::remove_file(&path)?; + #[cfg(not(unix))] + { + use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; + if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { + std::fs::remove_dir(&path)?; + } else { + std::fs::remove_file(&path)?; + } + } + } else if file_type.is_dir() { + std::fs::remove_dir(&path)?; + } else { + // pipes, sockets, etc... + std::fs::remove_file(&path)?; + } + Ok(json!({})) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CopyFileArgs { + from: String, + to: String, +} + +fn op_copy_file_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: CopyFileArgs = serde_json::from_value(args)?; + let from = PathBuf::from(&args.from); + let to = PathBuf::from(&args.to); + + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&from)?; + permissions.check_write(&to)?; + + debug!("op_copy_file_sync {} {}", from.display(), to.display()); + // 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", "File not found")); + } + + // returns size of from as u64 (we ignore) + std::fs::copy(&from, &to)?; + Ok(json!({})) +} + +async fn op_copy_file_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: CopyFileArgs = serde_json::from_value(args)?; + let from = PathBuf::from(&args.from); + let to = PathBuf::from(&args.to); + + { + let state = state.borrow(); + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&from)?; + permissions.check_write(&to)?; + } + + debug!("op_copy_file_async {} {}", from.display(), to.display()); + 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", "File not found")); + } + + // returns size of from as u64 (we ignore) + std::fs::copy(&from, &to)?; + Ok(json!({})) + }) + .await + .unwrap() +} + +fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Value { + match maybe_time { + Ok(time) => { + let msec = time + .duration_since(UNIX_EPOCH) + .map(|t| t.as_secs_f64() * 1000f64) + .unwrap_or_else(|err| err.duration().as_secs_f64() * -1000f64); + serde_json::Number::from_f64(msec) + .map(Value::Number) + .unwrap_or(Value::Null) + } + Err(_) => Value::Null, + } +} + +#[inline(always)] +fn get_stat_json(metadata: std::fs::Metadata) -> Value { + // 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 json_val = json!({ + "isFile": metadata.is_file(), + "isDirectory": metadata.is_dir(), + "isSymlink": metadata.file_type().is_symlink(), + "size": metadata.len(), + // In milliseconds, like JavaScript. Available on both Unix or Windows. + "mtime": to_msec(metadata.modified()), + "atime": to_msec(metadata.accessed()), + "birthtime": to_msec(metadata.created()), + // 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), + // TODO(kevinkassimo): *time_nsec requires BigInt. + // Probably should be treated as String if we need to add them. + "blksize": usm!(blksize), + "blocks": usm!(blocks), + }); + json_val +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct StatArgs { + path: String, + lstat: bool, +} + +fn op_stat_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: StatArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let lstat = args.lstat; + state.borrow::<Permissions>().check_read(&path)?; + debug!("op_stat_sync {} {}", path.display(), lstat); + let metadata = if lstat { + std::fs::symlink_metadata(&path)? + } else { + std::fs::metadata(&path)? + }; + Ok(get_stat_json(metadata)) +} + +async fn op_stat_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: StatArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let lstat = args.lstat; + + { + let state = state.borrow(); + state.borrow::<Permissions>().check_read(&path)?; + } + + tokio::task::spawn_blocking(move || { + debug!("op_stat_async {} {}", path.display(), lstat); + let metadata = if lstat { + std::fs::symlink_metadata(&path)? + } else { + std::fs::metadata(&path)? + }; + Ok(get_stat_json(metadata)) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RealpathArgs { + path: String, +} + +fn op_realpath_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: RealpathArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&path)?; + if path.is_relative() { + permissions.check_read_blind(¤t_dir()?, "CWD")?; + } + + debug!("op_realpath_sync {}", path.display()); + // corresponds to the realpath on Unix and + // CreateFile and GetFinalPathNameByHandle on Windows + let realpath = canonicalize_path(&path)?; + let realpath_str = into_string(realpath.into_os_string())?; + Ok(json!(realpath_str)) +} + +async fn op_realpath_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: RealpathArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + + { + let state = state.borrow(); + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&path)?; + if path.is_relative() { + permissions.check_read_blind(¤t_dir()?, "CWD")?; + } + } + + 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)?; + let realpath_str = into_string(realpath.into_os_string())?; + Ok(json!(realpath_str)) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ReadDirArgs { + path: String, +} + +fn op_read_dir_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: ReadDirArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + + state.borrow::<Permissions>().check_read(&path)?; + + debug!("op_read_dir_sync {}", path.display()); + let entries: Vec<_> = std::fs::read_dir(path)? + .filter_map(|entry| { + let entry = entry.unwrap(); + let file_type = entry.file_type().unwrap(); + // Not all filenames can be encoded as UTF-8. Skip those for now. + if let Ok(name) = into_string(entry.file_name()) { + Some(json!({ + "name": name, + "isFile": file_type.is_file(), + "isDirectory": file_type.is_dir(), + "isSymlink": file_type.is_symlink() + })) + } else { + None + } + }) + .collect(); + + Ok(json!({ "entries": entries })) +} + +async fn op_read_dir_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: ReadDirArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + { + let state = state.borrow(); + state.borrow::<Permissions>().check_read(&path)?; + } + tokio::task::spawn_blocking(move || { + debug!("op_read_dir_async {}", path.display()); + let entries: Vec<_> = std::fs::read_dir(path)? + .filter_map(|entry| { + let entry = entry.unwrap(); + let file_type = entry.file_type().unwrap(); + // Not all filenames can be encoded as UTF-8. Skip those for now. + if let Ok(name) = into_string(entry.file_name()) { + Some(json!({ + "name": name, + "isFile": file_type.is_file(), + "isDirectory": file_type.is_dir(), + "isSymlink": file_type.is_symlink() + })) + } else { + None + } + }) + .collect(); + + Ok(json!({ "entries": entries })) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RenameArgs { + oldpath: String, + newpath: String, +} + +fn op_rename_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: RenameArgs = serde_json::from_value(args)?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); + + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&oldpath)?; + permissions.check_write(&oldpath)?; + permissions.check_write(&newpath)?; + debug!("op_rename_sync {} {}", oldpath.display(), newpath.display()); + std::fs::rename(&oldpath, &newpath)?; + Ok(json!({})) +} + +async fn op_rename_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: RenameArgs = serde_json::from_value(args)?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); + { + let state = state.borrow(); + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&oldpath)?; + permissions.check_write(&oldpath)?; + permissions.check_write(&newpath)?; + } + tokio::task::spawn_blocking(move || { + debug!( + "op_rename_async {} {}", + oldpath.display(), + newpath.display() + ); + std::fs::rename(&oldpath, &newpath)?; + Ok(json!({})) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct LinkArgs { + oldpath: String, + newpath: String, +} + +fn op_link_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + super::check_unstable(state, "Deno.link"); + let args: LinkArgs = serde_json::from_value(args)?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); + + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&oldpath)?; + permissions.check_write(&newpath)?; + + debug!("op_link_sync {} {}", oldpath.display(), newpath.display()); + std::fs::hard_link(&oldpath, &newpath)?; + Ok(json!({})) +} + +async fn op_link_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + super::check_unstable2(&state, "Deno.link"); + + let args: LinkArgs = serde_json::from_value(args)?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); + + { + let state = state.borrow(); + let permissions = state.borrow::<Permissions>(); + permissions.check_read(&oldpath)?; + permissions.check_write(&newpath)?; + } + + tokio::task::spawn_blocking(move || { + debug!("op_link_async {} {}", oldpath.display(), newpath.display()); + std::fs::hard_link(&oldpath, &newpath)?; + Ok(json!({})) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SymlinkArgs { + oldpath: String, + newpath: String, + #[cfg(not(unix))] + options: Option<SymlinkOptions>, +} + +#[cfg(not(unix))] +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SymlinkOptions { + _type: String, +} + +fn op_symlink_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + super::check_unstable(state, "Deno.symlink"); + let args: SymlinkArgs = serde_json::from_value(args)?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); + + state.borrow::<Permissions>().check_write(&newpath)?; + + debug!( + "op_symlink_sync {} {}", + oldpath.display(), + newpath.display() + ); + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + symlink(&oldpath, &newpath)?; + Ok(json!({})) + } + #[cfg(not(unix))] + { + use std::os::windows::fs::{symlink_dir, symlink_file}; + + match args.options { + Some(options) => match options._type.as_ref() { + "file" => symlink_file(&oldpath, &newpath)?, + "dir" => symlink_dir(&oldpath, &newpath)?, + _ => 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)? + } else if metadata.is_dir() { + symlink_dir(&oldpath, &newpath)? + } + } + Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())), + } + } + }; + Ok(json!({})) + } +} + +async fn op_symlink_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + super::check_unstable2(&state, "Deno.symlink"); + + let args: SymlinkArgs = serde_json::from_value(args)?; + let oldpath = PathBuf::from(&args.oldpath); + let newpath = PathBuf::from(&args.newpath); + + { + let state = state.borrow(); + state.borrow::<Permissions>().check_write(&newpath)?; + } + + tokio::task::spawn_blocking(move || { + debug!("op_symlink_async {} {}", oldpath.display(), newpath.display()); + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + symlink(&oldpath, &newpath)?; + Ok(json!({})) + } + #[cfg(not(unix))] + { + use std::os::windows::fs::{symlink_dir, symlink_file}; + + match args.options { + Some(options) => match options._type.as_ref() { + "file" => symlink_file(&oldpath, &newpath)?, + "dir" => symlink_dir(&oldpath, &newpath)?, + _ => 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)? + } else if metadata.is_dir() { + symlink_dir(&oldpath, &newpath)? + } + } + Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())), + } + } + }; + Ok(json!({})) + } + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ReadLinkArgs { + path: String, +} + +fn op_read_link_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: ReadLinkArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + + state.borrow::<Permissions>().check_read(&path)?; + + debug!("op_read_link_value {}", path.display()); + let target = std::fs::read_link(&path)?.into_os_string(); + let targetstr = into_string(target)?; + Ok(json!(targetstr)) +} + +async fn op_read_link_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: ReadLinkArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + { + let state = state.borrow(); + state.borrow::<Permissions>().check_read(&path)?; + } + tokio::task::spawn_blocking(move || { + debug!("op_read_link_async {}", path.display()); + let target = std::fs::read_link(&path)?.into_os_string(); + let targetstr = into_string(target)?; + Ok(json!(targetstr)) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FtruncateArgs { + rid: i32, + len: i32, +} + +fn op_ftruncate_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + super::check_unstable(state, "Deno.ftruncate"); + let args: FtruncateArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let len = args.len as u64; + std_file_resource(state, rid, |r| match r { + Ok(std_file) => std_file.set_len(len).map_err(AnyError::from), + Err(_) => Err(type_error("cannot truncate this type of resource")), + })?; + Ok(json!({})) +} + +async fn op_ftruncate_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + super::check_unstable2(&state, "Deno.ftruncate"); + let args: FtruncateArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let len = args.len as u64; + std_file_resource(&mut state.borrow_mut(), rid, |r| match r { + Ok(std_file) => std_file.set_len(len).map_err(AnyError::from), + Err(_) => Err(type_error("cannot truncate this type of resource")), + })?; + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct TruncateArgs { + path: String, + len: u64, +} + +fn op_truncate_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: TruncateArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let len = args.len; + + state.borrow::<Permissions>().check_write(&path)?; + + debug!("op_truncate_sync {} {}", path.display(), len); + let f = std::fs::OpenOptions::new().write(true).open(&path)?; + f.set_len(len)?; + Ok(json!({})) +} + +async fn op_truncate_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: TruncateArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let len = args.len; + { + let state = state.borrow(); + state.borrow::<Permissions>().check_write(&path)?; + } + tokio::task::spawn_blocking(move || { + debug!("op_truncate_async {} {}", path.display(), len); + let f = std::fs::OpenOptions::new().write(true).open(&path)?; + f.set_len(len)?; + Ok(json!({})) + }) + .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(ref p) => p.to_path_buf(), + None => temp_dir(), + } + .join("_"); + let mut rng = thread_rng(); + loop { + let unique = rng.gen::<u32>(); + buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, 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())?; + Ok(()) + }; + match r { + Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue, + Ok(_) => return Ok(buf), + Err(e) => return Err(e), + } + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct MakeTempArgs { + dir: Option<String>, + prefix: Option<String>, + suffix: Option<String>, +} + +fn op_make_temp_dir_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: MakeTempArgs = serde_json::from_value(args)?; + + 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::<Permissions>() + .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?; + + // 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(json!(path_str)) +} + +async fn op_make_temp_dir_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: MakeTempArgs = serde_json::from_value(args)?; + + let dir = args.dir.map(|s| PathBuf::from(&s)); + let prefix = args.prefix.map(String::from); + let suffix = args.suffix.map(String::from); + { + let state = state.borrow(); + state + .borrow::<Permissions>() + .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?; + } + 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(json!(path_str)) + }) + .await + .unwrap() +} + +fn op_make_temp_file_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let args: MakeTempArgs = serde_json::from_value(args)?; + + 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::<Permissions>() + .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?; + + // 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(json!(path_str)) +} + +async fn op_make_temp_file_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let args: MakeTempArgs = serde_json::from_value(args)?; + + let dir = args.dir.map(|s| PathBuf::from(&s)); + let prefix = args.prefix.map(String::from); + let suffix = args.suffix.map(String::from); + { + let state = state.borrow(); + state + .borrow::<Permissions>() + .check_write(dir.clone().unwrap_or_else(temp_dir).as_path())?; + } + 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(json!(path_str)) + }) + .await + .unwrap() +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FutimeArgs { + rid: i32, + atime: (i64, u32), + mtime: (i64, u32), +} + +fn op_futime_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + super::check_unstable(state, "Deno.futimeSync"); + let args: FutimeArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); + let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); + + std_file_resource(state, rid, |r| match r { + Ok(std_file) => { + filetime::set_file_handle_times(std_file, Some(atime), Some(mtime)) + .map_err(AnyError::from) + } + Err(_) => Err(type_error( + "cannot futime on this type of resource".to_string(), + )), + })?; + + Ok(json!({})) +} + +async fn op_futime_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let mut state = state.borrow_mut(); + super::check_unstable(&state, "Deno.futime"); + let args: FutimeArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); + let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); + // TODO Not actually async! https://github.com/denoland/deno/issues/7400 + std_file_resource(&mut state, rid, |r| match r { + Ok(std_file) => { + filetime::set_file_handle_times(std_file, Some(atime), Some(mtime)) + .map_err(AnyError::from) + } + Err(_) => Err(type_error( + "cannot futime on this type of resource".to_string(), + )), + })?; + + Ok(json!({})) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct UtimeArgs { + path: String, + atime: (i64, u32), + mtime: (i64, u32), +} + +fn op_utime_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + super::check_unstable(state, "Deno.utime"); + + let args: UtimeArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); + let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); + + state.borrow::<Permissions>().check_write(&path)?; + filetime::set_file_times(path, atime, mtime)?; + Ok(json!({})) +} + +async fn op_utime_async( + state: Rc<RefCell<OpState>>, + args: Value, + _zero_copy: BufVec, +) -> Result<Value, AnyError> { + let state = state.borrow(); + super::check_unstable(&state, "Deno.utime"); + + let args: UtimeArgs = serde_json::from_value(args)?; + let path = PathBuf::from(&args.path); + let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); + let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); + + state.borrow::<Permissions>().check_write(&path)?; + + tokio::task::spawn_blocking(move || { + filetime::set_file_times(path, atime, mtime)?; + Ok(json!({})) + }) + .await + .unwrap() +} + +fn op_cwd( + state: &mut OpState, + _args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result<Value, AnyError> { + let path = current_dir()?; + state + .borrow::<Permissions>() + .check_read_blind(&path, "CWD")?; + let path_str = into_string(path.into_os_string())?; + Ok(json!(path_str)) +} |