// Copyright 2018-2022 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::StdFileResource; use super::utils::into_string; 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::op; use deno_core::CancelFuture; use deno_core::CancelHandle; use deno_core::ZeroCopyBuf; use deno_core::Extension; use deno_core::OpState; use deno_core::ResourceId; use deno_crypto::rand::thread_rng; use deno_crypto::rand::Rng; 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; #[cfg(not(unix))] use deno_core::error::generic_error; #[cfg(not(unix))] use deno_core::error::not_supported; pub fn init() -> Extension { Extension::builder() .ops(vec![ op_open_sync::decl(), op_open_async::decl(), op_write_file_sync::decl(), op_write_file_async::decl(), op_seek_sync::decl(), op_seek_async::decl(), op_fdatasync_sync::decl(), op_fdatasync_async::decl(), op_fsync_sync::decl(), op_fsync_async::decl(), op_fstat_sync::decl(), op_fstat_async::decl(), op_flock_sync::decl(), op_flock_async::decl(), op_funlock_sync::decl(), op_funlock_async::decl(), op_umask::decl(), op_chdir::decl(), op_mkdir_sync::decl(), op_mkdir_async::decl(), op_chmod_sync::decl(), op_chmod_async::decl(), op_chown_sync::decl(), op_chown_async::decl(), op_remove_sync::decl(), op_remove_async::decl(), op_copy_file_sync::decl(), op_copy_file_async::decl(), op_stat_sync::decl(), op_stat_async::decl(), op_realpath_sync::decl(), op_realpath_async::decl(), op_read_dir_sync::decl(), op_read_dir_async::decl(), op_rename_sync::decl(), op_rename_async::decl(), op_link_sync::decl(), op_link_async::decl(), op_symlink_sync::decl(), op_symlink_async::decl(), op_read_link_sync::decl(), op_read_link_async::decl(), op_ftruncate_sync::decl(), op_ftruncate_async::decl(), op_truncate_sync::decl(), op_truncate_async::decl(), op_make_temp_dir_sync::decl(), op_make_temp_dir_async::decl(), op_make_temp_file_sync::decl(), op_make_temp_file_async::decl(), op_cwd::decl(), op_futime_sync::decl(), op_futime_async::decl(), op_utime_sync::decl(), op_utime_async::decl(), op_readfile_sync::decl(), op_readfile_text_sync::decl(), op_readfile_async::decl(), op_readfile_text_async::decl(), ]) .build() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct OpenArgs { path: String, mode: Option, options: OpenOptions, } #[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, } fn open_helper( state: &mut OpState, args: &OpenArgs, ) -> Result<(PathBuf, std::fs::OpenOptions), AnyError> { 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_mut::(); let options = &args.options; if options.read { permissions.read.check(&path)?; } if options.write || options.append { permissions.write.check(&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)) } #[op] fn op_open_sync(state: &mut OpState, args: OpenArgs) -> Result { let (path, open_options) = open_helper(state, &args)?; let std_file = open_options .open(&path) .map_err(|err| Error::new(err.kind(), format!("{}, open '{}'", err, path.display())))?; let resource = StdFileResource::fs_file(std_file); let rid = state.resource_table.add(resource); Ok(rid) } #[op] async fn op_open_async( state: Rc>, args: OpenArgs, ) -> Result { let (path, open_options) = open_helper(&mut state.borrow_mut(), &args)?; let std_file = tokio::task::spawn_blocking(move || { open_options .open(path.clone()) .map_err(|err| Error::new(err.kind(), format!("{}, open '{}'", err, path.display()))) }) .await?; let resource = StdFileResource::fs_file(std_file?); let rid = state.borrow_mut().resource_table.add(resource); Ok(rid) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct WriteFileArgs { path: String, mode: Option, append: bool, create: bool, data: ZeroCopyBuf, cancel_rid: Option, } impl WriteFileArgs { fn into_open_args_and_data(self) -> (OpenArgs, ZeroCopyBuf) { ( OpenArgs { path: self.path, mode: self.mode, options: OpenOptions { read: false, write: true, create: self.create, truncate: !self.append, append: self.append, create_new: false, }, }, self.data, ) } } #[op] fn op_write_file_sync(state: &mut OpState, args: WriteFileArgs) -> Result<(), AnyError> { let (open_args, data) = args.into_open_args_and_data(); let (path, open_options) = open_helper(state, &open_args)?; write_file(&path, open_options, &open_args, data) } #[op] async fn op_write_file_async( state: Rc>, args: WriteFileArgs, ) -> Result<(), AnyError> { let cancel_handle = match args.cancel_rid { Some(cancel_rid) => state .borrow_mut() .resource_table .get::(cancel_rid) .ok(), None => None, }; let (open_args, data) = args.into_open_args_and_data(); let (path, open_options) = open_helper(&mut *state.borrow_mut(), &open_args)?; let write_future = tokio::task::spawn_blocking(move || write_file(&path, open_options, &open_args, data)); if let Some(cancel_handle) = cancel_handle { write_future.or_cancel(cancel_handle).await???; } else { write_future.await??; } Ok(()) } fn write_file( path: &Path, open_options: std::fs::OpenOptions, _open_args: &OpenArgs, data: ZeroCopyBuf, ) -> Result<(), AnyError> { let mut std_file = open_options .open(path) .map_err(|err| Error::new(err.kind(), format!("{}, open '{}'", err, path.display())))?; // need to chmod the file if it already exists and a mode is specified #[cfg(unix)] if let Some(mode) = &_open_args.mode { use std::os::unix::fs::PermissionsExt; let permissions = PermissionsExt::from_mode(mode & 0o777); std_file .set_permissions(permissions) .map_err(|err: Error| { Error::new(err.kind(), format!("{}, chmod '{}'", err, 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 { 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>, args: SeekArgs) -> Result { 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>, 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>, 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) -> Result { let metadata = StdFileResource::with_file(state, rid, |std_file| { std_file.metadata().map_err(AnyError::from) })?; Ok(get_stat(metadata)) } #[op] async fn op_fstat_async(state: Rc>, rid: ResourceId) -> Result { 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; super::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>, rid: ResourceId, exclusive: bool, ) -> Result<(), AnyError> { use fs3::FileExt; super::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; super::check_unstable(state, "Deno.funlockSync"); StdFileResource::with_file(state, rid, |std_file| { std_file.unlock()?; Ok(()) }) } #[op] async fn op_funlock_async(state: Rc>, rid: ResourceId) -> Result<(), AnyError> { use fs3::FileExt; super::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) -> Result { super::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 }; Ok(r.bits() as u32) } } #[op] fn op_chdir(state: &mut OpState, directory: String) -> Result<(), AnyError> { let d = PathBuf::from(&directory); state.borrow_mut::().read.check(&d)?; set_current_dir(&d) .map_err(|err| Error::new(err.kind(), format!("{}, chdir '{}'", err, directory)))?; Ok(()) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct MkdirArgs { path: String, recursive: bool, mode: Option, } #[op] fn op_mkdir_sync(state: &mut OpState, args: MkdirArgs) -> Result<(), AnyError> { let path = Path::new(&args.path).to_path_buf(); let mode = args.mode.unwrap_or(0o777) & 0o777; state.borrow_mut::().write.check(&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) .map_err(|err| Error::new(err.kind(), format!("{}, mkdir '{}'", err, path.display())))?; Ok(()) } #[op] async fn op_mkdir_async(state: Rc>, args: MkdirArgs) -> Result<(), AnyError> { 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::().write.check(&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).map_err(|err| { Error::new(err.kind(), format!("{}, mkdir '{}'", err, path.display())) })?; Ok(()) }) .await .unwrap() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChmodArgs { path: String, mode: u32, } #[op] fn op_chmod_sync(state: &mut OpState, args: ChmodArgs) -> Result<(), AnyError> { let path = Path::new(&args.path); let mode = args.mode & 0o777; state.borrow_mut::().write.check(path)?; debug!("op_chmod_sync {} {:o}", path.display(), mode); raw_chmod(path, mode) } #[op] async fn op_chmod_async(state: Rc>, args: ChmodArgs) -> Result<(), AnyError> { let path = Path::new(&args.path).to_path_buf(); let mode = args.mode & 0o777; { let mut state = state.borrow_mut(); state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_chmod_async {} {:o}", path.display(), mode); raw_chmod(&path, mode) }) .await .unwrap() } fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> { let err_mapper = |err: Error| Error::new(err.kind(), format!("{}, chmod '{}'", err, 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()) } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChownArgs { path: String, uid: Option, gid: Option, } #[op] fn op_chown_sync(state: &mut OpState, args: ChownArgs) -> Result<(), AnyError> { let path = Path::new(&args.path).to_path_buf(); state.borrow_mut::().write.check(&path)?; debug!( "op_chown_sync {} {:?} {:?}", path.display(), args.uid, args.gid, ); #[cfg(unix)] { use crate::errors::get_nix_error_class; 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).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(state: Rc>, args: ChownArgs) -> Result<(), AnyError> { let path = Path::new(&args.path).to_path_buf(); { let mut state = state.borrow_mut(); state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { debug!( "op_chown_async {} {:?} {:?}", path.display(), args.uid, args.gid, ); #[cfg(unix)] { use crate::errors::get_nix_error_class; 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).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() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct RemoveArgs { path: String, recursive: bool, } #[op] fn op_remove_sync(state: &mut OpState, args: RemoveArgs) -> Result<(), AnyError> { let path = PathBuf::from(&args.path); let recursive = args.recursive; state.borrow_mut::().write.check(&path)?; #[cfg(not(unix))] use std::os::windows::prelude::MetadataExt; let err_mapper = |err: Error| Error::new(err.kind(), format!("{}, remove '{}'", err, path.display())); let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?; debug!("op_remove_sync {} {}", 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(()) } #[op] async fn op_remove_async(state: Rc>, args: RemoveArgs) -> Result<(), AnyError> { let path = PathBuf::from(&args.path); let recursive = args.recursive; { let mut state = state.borrow_mut(); state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { #[cfg(not(unix))] use std::os::windows::prelude::MetadataExt; let err_mapper = |err: Error| Error::new(err.kind(), format!("{}, remove '{}'", err, 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() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct CopyFileArgs { from: String, to: String, } #[op] fn op_copy_file_sync(state: &mut OpState, args: CopyFileArgs) -> Result<(), AnyError> { let from = PathBuf::from(&args.from); let to = PathBuf::from(&args.to); let permissions = state.borrow_mut::(); permissions.read.check(&from)?; permissions.write.check(&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", format!( "File not found, copy '{}' -> '{}'", from.display(), to.display() ), )); } let err_mapper = |err: Error| { Error::new( err.kind(), format!("{}, copy '{}' -> '{}'", err, from.display(), to.display()), ) }; // returns size of from as u64 (we ignore) std::fs::copy(&from, &to).map_err(err_mapper)?; Ok(()) } #[op] async fn op_copy_file_async( state: Rc>, args: CopyFileArgs, ) -> Result<(), AnyError> { let from = PathBuf::from(&args.from); let to = PathBuf::from(&args.to); { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::(); permissions.read.check(&from)?; permissions.write.check(&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", format!( "File not found, copy '{}' -> '{}'", from.display(), to.display() ), )); } let err_mapper = |err: Error| { Error::new( err.kind(), format!("{}, copy '{}' -> '{}'", err, from.display(), to.display()), ) }; // returns size of from as u64 (we ignore) std::fs::copy(&from, &to).map_err(err_mapper)?; Ok(()) }) .await .unwrap() } fn to_msec(maybe_time: Result) -> Option { match maybe_time { Ok(time) => { let msec = time .duration_since(UNIX_EPOCH) .map(|t| t.as_millis() as u64) .unwrap_or_else(|err| err.duration().as_millis() as u64); Some(msec) } Err(_) => None, } } #[derive(Serialize)] #[serde(rename_all = "camelCase")] 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: Option, atime: Option, birthtime: Option, // 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; 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: 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), blksize: usm!(blksize), blocks: usm!(blocks), } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct StatArgs { path: String, lstat: bool, } #[op] fn op_stat_sync(state: &mut OpState, args: StatArgs) -> Result { let path = PathBuf::from(&args.path); let lstat = args.lstat; state.borrow_mut::().read.check(&path)?; debug!("op_stat_sync {} {}", path.display(), lstat); let err_mapper = |err: Error| Error::new(err.kind(), format!("{}, stat '{}'", err, 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_stat(metadata)) } #[op] async fn op_stat_async(state: Rc>, args: StatArgs) -> Result { let path = PathBuf::from(&args.path); let lstat = args.lstat; { let mut state = state.borrow_mut(); state.borrow_mut::().read.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_stat_async {} {}", path.display(), lstat); let err_mapper = |err: Error| Error::new(err.kind(), format!("{}, stat '{}'", err, 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_stat(metadata)) }) .await .unwrap() } #[op] fn op_realpath_sync(state: &mut OpState, path: String) -> Result { let path = PathBuf::from(&path); let permissions = state.borrow_mut::(); permissions.read.check(&path)?; if path.is_relative() { permissions.read.check_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(realpath_str) } #[op] async fn op_realpath_async(state: Rc>, path: String) -> Result { let path = PathBuf::from(&path); { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::(); permissions.read.check(&path)?; if path.is_relative() { permissions.read.check_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(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(state: &mut OpState, path: String) -> Result, AnyError> { let path = PathBuf::from(&path); state.borrow_mut::().read.check(&path)?; debug!("op_read_dir_sync {}", path.display()); let err_mapper = |err: Error| Error::new(err.kind(), format!("{}, readdir '{}'", err, path.display())); let entries: Vec<_> = std::fs::read_dir(&path) .map_err(err_mapper)? .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_or(false, |file_type| file_type.is_file()), is_directory: entry .file_type() .map_or(false, |file_type| file_type.is_dir()), is_symlink: entry .file_type() .map_or(false, |file_type| file_type.is_symlink()), }) } else { None } }) .collect(); Ok(entries) } #[op] async fn op_read_dir_async( state: Rc>, path: String, ) -> Result, AnyError> { let path = PathBuf::from(&path); { let mut state = state.borrow_mut(); state.borrow_mut::().read.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_read_dir_async {}", path.display()); let err_mapper = |err: Error| Error::new(err.kind(), format!("{}, readdir '{}'", err, path.display())); let entries: Vec<_> = std::fs::read_dir(&path) .map_err(err_mapper)? .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_or(false, |file_type| file_type.is_file()), is_directory: entry .file_type() .map_or(false, |file_type| file_type.is_dir()), is_symlink: entry .file_type() .map_or(false, |file_type| file_type.is_symlink()), }) } else { None } }) .collect(); Ok(entries) }) .await .unwrap() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct RenameArgs { oldpath: String, newpath: String, } #[op] fn op_rename_sync(state: &mut OpState, args: RenameArgs) -> Result<(), AnyError> { let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.write.check(&newpath)?; debug!("op_rename_sync {} {}", oldpath.display(), newpath.display()); let err_mapper = |err: Error| { Error::new( err.kind(), format!( "{}, rename '{}' -> '{}'", err, oldpath.display(), newpath.display() ), ) }; std::fs::rename(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) } #[op] async fn op_rename_async(state: Rc>, args: RenameArgs) -> Result<(), AnyError> { let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.write.check(&newpath)?; } tokio::task::spawn_blocking(move || { debug!( "op_rename_async {} {}", oldpath.display(), newpath.display() ); let err_mapper = |err: Error| { Error::new( err.kind(), format!( "{}, rename '{}' -> '{}'", err, oldpath.display(), newpath.display() ), ) }; std::fs::rename(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) }) .await .unwrap() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct LinkArgs { oldpath: String, newpath: String, } #[op] fn op_link_sync(state: &mut OpState, args: LinkArgs) -> Result<(), AnyError> { let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.read.check(&newpath)?; permissions.write.check(&newpath)?; debug!("op_link_sync {} {}", oldpath.display(), newpath.display()); let err_mapper = |err: Error| { Error::new( err.kind(), format!( "{}, link '{}' -> '{}'", err, oldpath.display(), newpath.display() ), ) }; std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) } #[op] async fn op_link_async(state: Rc>, args: LinkArgs) -> Result<(), AnyError> { let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.read.check(&newpath)?; permissions.write.check(&newpath)?; } tokio::task::spawn_blocking(move || { debug!("op_link_async {} {}", oldpath.display(), newpath.display()); let err_mapper = |err: Error| { Error::new( err.kind(), format!( "{}, link '{}' -> '{}'", err, oldpath.display(), newpath.display() ), ) }; std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?; Ok(()) }) .await .unwrap() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct SymlinkArgs { oldpath: String, newpath: String, #[cfg(not(unix))] options: Option, } #[cfg(not(unix))] #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct SymlinkOptions { _type: String, } #[op] fn op_symlink_sync(state: &mut OpState, args: SymlinkArgs) -> Result<(), AnyError> { let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); state.borrow_mut::().write.check_all()?; state.borrow_mut::().read.check_all()?; debug!( "op_symlink_sync {} {}", oldpath.display(), newpath.display() ); let err_mapper = |err: Error| { Error::new( err.kind(), format!( "{}, symlink '{}' -> '{}'", err, 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 args.options { Some(options) => match options._type.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(state: Rc>, args: SymlinkArgs) -> Result<(), AnyError> { let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); { let mut state = state.borrow_mut(); state.borrow_mut::().write.check_all()?; state.borrow_mut::().read.check_all()?; } tokio::task::spawn_blocking(move || { debug!("op_symlink_async {} {}", oldpath.display(), newpath.display()); let err_mapper = |err: Error| { Error::new( err.kind(), format!( "{}, symlink '{}' -> '{}'", err, 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 args.options { Some(options) => match options._type.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(state: &mut OpState, path: String) -> Result { let path = PathBuf::from(&path); state.borrow_mut::().read.check(&path)?; debug!("op_read_link_value {}", path.display()); let err_mapper = |err: Error| { Error::new( err.kind(), format!("{}, readlink '{}'", err, path.display()), ) }; let target = std::fs::read_link(&path) .map_err(err_mapper)? .into_os_string(); let targetstr = into_string(target)?; Ok(targetstr) } #[op] async fn op_read_link_async(state: Rc>, path: String) -> Result { let path = PathBuf::from(&path); { let mut state = state.borrow_mut(); state.borrow_mut::().read.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_read_link_async {}", path.display()); let err_mapper = |err: Error| { Error::new( err.kind(), format!("{}, readlink '{}'", err, path.display()), ) }; let target = std::fs::read_link(&path) .map_err(err_mapper)? .into_os_string(); let targetstr = into_string(target)?; Ok(targetstr) }) .await .unwrap() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct FtruncateArgs { rid: ResourceId, len: i32, } #[op] fn op_ftruncate_sync(state: &mut OpState, args: FtruncateArgs) -> Result<(), AnyError> { let rid = args.rid; let len = args.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>, args: FtruncateArgs, ) -> Result<(), AnyError> { let rid = args.rid; let len = args.len as u64; StdFileResource::with_file_blocking_task(state, rid, move |std_file| { std_file.set_len(len)?; Ok(()) }) .await } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct TruncateArgs { path: String, len: u64, } #[op] fn op_truncate_sync(state: &mut OpState, args: TruncateArgs) -> Result<(), AnyError> { let path = PathBuf::from(&args.path); let len = args.len; state.borrow_mut::().write.check(&path)?; debug!("op_truncate_sync {} {}", path.display(), len); let err_mapper = |err: Error| { Error::new( err.kind(), format!("{}, truncate '{}'", err, 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( state: Rc>, args: TruncateArgs, ) -> Result<(), AnyError> { let path = PathBuf::from(&args.path); let len = args.len; { let mut state = state.borrow_mut(); state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_truncate_async {} {}", path.display(), len); let err_mapper = |err: Error| { Error::new( err.kind(), format!("{}, truncate '{}'", err, 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 { 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(); loop { let unique = rng.gen::(); 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")] pub struct MakeTempArgs { dir: Option, prefix: Option, suffix: Option, } #[op] fn op_make_temp_dir_sync(state: &mut OpState, args: MakeTempArgs) -> Result { 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::() .write .check(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 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( state: Rc>, args: MakeTempArgs, ) -> Result { 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::() .write .check(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 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(state: &mut OpState, args: MakeTempArgs) -> Result { 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::() .write .check(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 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( state: Rc>, args: MakeTempArgs, ) -> Result { 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::() .write .check(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 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() } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct FutimeArgs { rid: ResourceId, atime: (i64, u32), mtime: (i64, u32), } #[op] fn op_futime_sync(state: &mut OpState, args: FutimeArgs) -> Result<(), AnyError> { super::check_unstable(state, "Deno.futimeSync"); let rid = args.rid; 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); 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>, args: FutimeArgs) -> Result<(), AnyError> { super::check_unstable2(&state, "Deno.futime"); let rid = args.rid; 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); StdFileResource::with_file_blocking_task(state, rid, move |std_file| { filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))?; Ok(()) }) .await } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct UtimeArgs { path: String, atime: (i64, u32), mtime: (i64, u32), } #[op] fn op_utime_sync(state: &mut OpState, args: UtimeArgs) -> Result<(), AnyError> { super::check_unstable(state, "Deno.utime"); 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_mut::().write.check(&path)?; filetime::set_file_times(&path, atime, mtime) .map_err(|err| Error::new(err.kind(), format!("{}, utime '{}'", err, path.display())))?; Ok(()) } #[op] async fn op_utime_async(state: Rc>, args: UtimeArgs) -> Result<(), AnyError> { super::check_unstable(&state.borrow(), "Deno.utime"); 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_mut() .borrow_mut::() .write .check(&path)?; tokio::task::spawn_blocking(move || { filetime::set_file_times(&path, atime, mtime).map_err(|err| { Error::new(err.kind(), format!("{}, utime '{}'", err, path.display())) })?; Ok(()) }) .await .unwrap() } #[op] fn op_cwd(state: &mut OpState) -> Result { let path = current_dir()?; state .borrow_mut::() .read .check_blind(&path, "CWD")?; let path_str = into_string(path.into_os_string())?; Ok(path_str) } #[op] fn op_readfile_sync(state: &mut OpState, path: String) -> Result { let permissions = state.borrow_mut::(); let path = Path::new(&path); permissions.read.check(path)?; Ok(std::fs::read(path)?.into()) } #[op] fn op_readfile_text_sync(state: &mut OpState, path: String) -> Result { let permissions = state.borrow_mut::(); let path = Path::new(&path); permissions.read.check(path)?; Ok(string_from_utf8_lossy(std::fs::read(path)?)) } #[op] async fn op_readfile_async( state: Rc>, path: String, cancel_rid: Option, ) -> Result { { let path = Path::new(&path); let mut state = state.borrow_mut(); state.borrow_mut::().read.check(path)?; } let fut = tokio::task::spawn_blocking(move || { let path = Path::new(&path); Ok(std::fs::read(path).map(ZeroCopyBuf::from)?) }); if let Some(cancel_rid) = cancel_rid { let cancel_handle = state .borrow_mut() .resource_table .get::(cancel_rid); if let Ok(cancel_handle) = cancel_handle { return fut.or_cancel(cancel_handle).await??; } } fut.await? } #[op] async fn op_readfile_text_async( state: Rc>, path: String, cancel_rid: Option, ) -> Result { { let path = Path::new(&path); let mut state = state.borrow_mut(); state.borrow_mut::().read.check(path)?; } let fut = tokio::task::spawn_blocking(move || { let path = Path::new(&path); Ok(string_from_utf8_lossy(std::fs::read(path)?)) }); if let Some(cancel_rid) = cancel_rid { let cancel_handle = state .borrow_mut() .resource_table .get::(cancel_rid); if let Ok(cancel_handle) = cancel_handle { return fut.or_cancel(cancel_handle).await??; } } fut.await? } // Like String::from_utf8_lossy but operates on owned values fn string_from_utf8_lossy(buf: Vec) -> 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) }, } }