// 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::dispatch_json::{blocking_json, Deserialize, JsonOp, Value}; use crate::fs as deno_fs; use crate::op_error::OpError; use crate::ops::dispatch_json::JsonResult; use crate::ops::json_op; use crate::state::State; use deno_core::*; use remove_dir_all::remove_dir_all; use std::convert::From; use std::fs; use std::path::{Path, PathBuf}; use std::time::UNIX_EPOCH; #[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; pub fn init(i: &mut Isolate, s: &State) { i.register_op("chdir", s.core_op(json_op(s.stateful_op(op_chdir)))); i.register_op("mkdir", s.core_op(json_op(s.stateful_op(op_mkdir)))); i.register_op("chmod", s.core_op(json_op(s.stateful_op(op_chmod)))); i.register_op("chown", s.core_op(json_op(s.stateful_op(op_chown)))); i.register_op("remove", s.core_op(json_op(s.stateful_op(op_remove)))); i.register_op("copy_file", s.core_op(json_op(s.stateful_op(op_copy_file)))); i.register_op("stat", s.core_op(json_op(s.stateful_op(op_stat)))); i.register_op("realpath", s.core_op(json_op(s.stateful_op(op_realpath)))); i.register_op("read_dir", s.core_op(json_op(s.stateful_op(op_read_dir)))); i.register_op("rename", s.core_op(json_op(s.stateful_op(op_rename)))); i.register_op("link", s.core_op(json_op(s.stateful_op(op_link)))); i.register_op("symlink", s.core_op(json_op(s.stateful_op(op_symlink)))); i.register_op("read_link", s.core_op(json_op(s.stateful_op(op_read_link)))); i.register_op("truncate", s.core_op(json_op(s.stateful_op(op_truncate)))); i.register_op( "make_temp_dir", s.core_op(json_op(s.stateful_op(op_make_temp_dir))), ); i.register_op( "make_temp_file", s.core_op(json_op(s.stateful_op(op_make_temp_file))), ); i.register_op("cwd", s.core_op(json_op(s.stateful_op(op_cwd)))); i.register_op("utime", s.core_op(json_op(s.stateful_op(op_utime)))); } #[derive(Deserialize)] struct ChdirArgs { directory: String, } fn op_chdir( _state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: ChdirArgs = serde_json::from_value(args)?; std::env::set_current_dir(&args.directory)?; Ok(JsonOp::Sync(json!({}))) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct MkdirArgs { promise_id: Option, path: String, recursive: bool, mode: u32, } fn op_mkdir( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: MkdirArgs = serde_json::from_value(args)?; let path = deno_fs::resolve_from_cwd(Path::new(&args.path))?; state.check_write(&path)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_mkdir {}", path.display()); deno_fs::mkdir(&path, args.mode, args.recursive)?; Ok(json!({})) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ChmodArgs { promise_id: Option, path: String, #[allow(unused)] mode: u32, } fn op_chmod( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: ChmodArgs = serde_json::from_value(args)?; let path = deno_fs::resolve_from_cwd(Path::new(&args.path))?; state.check_write(&path)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_chmod {}", path.display()); // Still check file/dir exists on windows let _metadata = fs::metadata(&path)?; #[cfg(any(unix))] { let mut permissions = _metadata.permissions(); permissions.set_mode(args.mode); fs::set_permissions(&path, permissions)?; } Ok(json!({})) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ChownArgs { promise_id: Option, path: String, uid: u32, gid: u32, } fn op_chown( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: ChownArgs = serde_json::from_value(args)?; let path = deno_fs::resolve_from_cwd(Path::new(&args.path))?; state.check_write(&path)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_chown {}", path.display()); match deno_fs::chown(args.path.as_ref(), args.uid, args.gid) { Ok(_) => Ok(json!({})), Err(e) => Err(OpError::from(e)), } }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct RemoveArgs { promise_id: Option, path: String, recursive: bool, } fn op_remove( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: RemoveArgs = serde_json::from_value(args)?; let path = deno_fs::resolve_from_cwd(Path::new(&args.path))?; let recursive = args.recursive; state.check_write(&path)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_remove {}", path.display()); let metadata = fs::symlink_metadata(&path)?; let file_type = metadata.file_type(); if file_type.is_file() || file_type.is_symlink() { fs::remove_file(&path)?; } else if recursive { remove_dir_all(&path)?; } else { fs::remove_dir(&path)?; } Ok(json!({})) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct CopyFileArgs { promise_id: Option, from: String, to: String, } fn op_copy_file( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: CopyFileArgs = serde_json::from_value(args)?; let from = deno_fs::resolve_from_cwd(Path::new(&args.from))?; let to = deno_fs::resolve_from_cwd(Path::new(&args.to))?; state.check_read(&from)?; state.check_write(&to)?; debug!("op_copy_file {} {}", from.display(), to.display()); let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { // On *nix, Rust deem non-existent path as invalid input // See https://github.com/rust-lang/rust/issues/54800 // Once the issue is reolved, we should remove this workaround. if cfg!(unix) && !from.is_file() { return Err(OpError::not_found("File not found".to_string())); } fs::copy(&from, &to)?; Ok(json!({})) }) } macro_rules! to_seconds { ($time:expr) => {{ // Unwrap is safe here as if the file is before the unix epoch // something is very wrong. $time .and_then(|t| Ok(t.duration_since(UNIX_EPOCH).unwrap().as_secs())) .unwrap_or(0) }}; } #[inline(always)] fn get_stat_json( metadata: fs::Metadata, maybe_name: Option, ) -> JsonResult { // Unix stat member (number types only). 0 if not on unix. macro_rules! usm { ($member: ident) => {{ #[cfg(unix)] { metadata.$member() } #[cfg(not(unix))] { 0 } }}; } let mut json_val = json!({ "isFile": metadata.is_file(), "isSymlink": metadata.file_type().is_symlink(), "len": metadata.len(), // In seconds. Available on both Unix or Windows. "modified":to_seconds!(metadata.modified()), "accessed":to_seconds!(metadata.accessed()), "created":to_seconds!(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), }); // "name" is an optional field by our design. if let Some(name) = maybe_name { if let serde_json::Value::Object(ref mut m) = json_val { m.insert("name".to_owned(), json!(name)); } } Ok(json_val) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct StatArgs { promise_id: Option, filename: String, lstat: bool, } fn op_stat( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: StatArgs = serde_json::from_value(args)?; let filename = deno_fs::resolve_from_cwd(Path::new(&args.filename))?; let lstat = args.lstat; state.check_read(&filename)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_stat {} {}", filename.display(), lstat); let metadata = if lstat { fs::symlink_metadata(&filename)? } else { fs::metadata(&filename)? }; get_stat_json(metadata, None) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct RealpathArgs { promise_id: Option, path: String, } fn op_realpath( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: RealpathArgs = serde_json::from_value(args)?; let path = deno_fs::resolve_from_cwd(Path::new(&args.path))?; state.check_read(&path)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_realpath {}", path.display()); // corresponds to the realpath on Unix and // CreateFile and GetFinalPathNameByHandle on Windows let realpath = fs::canonicalize(&path)?; let mut realpath_str = realpath.to_str().unwrap().to_owned().replace("\\", "/"); if cfg!(windows) { realpath_str = realpath_str.trim_start_matches("//?/").to_string(); } Ok(json!(realpath_str)) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ReadDirArgs { promise_id: Option, path: String, } fn op_read_dir( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: ReadDirArgs = serde_json::from_value(args)?; let path = deno_fs::resolve_from_cwd(Path::new(&args.path))?; state.check_read(&path)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_read_dir {}", path.display()); let entries: Vec<_> = fs::read_dir(path)? .filter_map(|entry| { let entry = entry.unwrap(); let metadata = entry.metadata().unwrap(); // Not all filenames can be encoded as UTF-8. Skip those for now. if let Some(filename) = entry.file_name().to_str() { let filename = Some(filename.to_owned()); Some(get_stat_json(metadata, filename).unwrap()) } else { None } }) .collect(); Ok(json!({ "entries": entries })) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct RenameArgs { promise_id: Option, oldpath: String, newpath: String, } fn op_rename( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: RenameArgs = serde_json::from_value(args)?; let oldpath = deno_fs::resolve_from_cwd(Path::new(&args.oldpath))?; let newpath = deno_fs::resolve_from_cwd(Path::new(&args.newpath))?; state.check_read(&oldpath)?; state.check_write(&oldpath)?; state.check_write(&newpath)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_rename {} {}", oldpath.display(), newpath.display()); fs::rename(&oldpath, &newpath)?; Ok(json!({})) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct LinkArgs { promise_id: Option, oldname: String, newname: String, } fn op_link( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: LinkArgs = serde_json::from_value(args)?; let oldname = deno_fs::resolve_from_cwd(Path::new(&args.oldname))?; let newname = deno_fs::resolve_from_cwd(Path::new(&args.newname))?; state.check_read(&oldname)?; state.check_write(&newname)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_link {} {}", oldname.display(), newname.display()); std::fs::hard_link(&oldname, &newname)?; Ok(json!({})) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct SymlinkArgs { promise_id: Option, oldname: String, newname: String, } fn op_symlink( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: SymlinkArgs = serde_json::from_value(args)?; let oldname = deno_fs::resolve_from_cwd(Path::new(&args.oldname))?; let newname = deno_fs::resolve_from_cwd(Path::new(&args.newname))?; state.check_write(&newname)?; // TODO Use type for Windows. if cfg!(windows) { return Err(OpError::other("Not implemented".to_string())); } let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_symlink {} {}", oldname.display(), newname.display()); #[cfg(any(unix))] std::os::unix::fs::symlink(&oldname, &newname)?; Ok(json!({})) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ReadLinkArgs { promise_id: Option, name: String, } fn op_read_link( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: ReadLinkArgs = serde_json::from_value(args)?; let name = deno_fs::resolve_from_cwd(Path::new(&args.name))?; state.check_read(&name)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_read_link {}", name.display()); let path = fs::read_link(&name)?; let path_str = path.to_str().unwrap(); Ok(json!(path_str)) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct TruncateArgs { promise_id: Option, name: String, len: u64, } fn op_truncate( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: TruncateArgs = serde_json::from_value(args)?; let filename = deno_fs::resolve_from_cwd(Path::new(&args.name))?; let len = args.len; state.check_write(&filename)?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_truncate {} {}", filename.display(), len); let f = fs::OpenOptions::new().write(true).open(&filename)?; f.set_len(len)?; Ok(json!({})) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct MakeTempArgs { promise_id: Option, dir: Option, prefix: Option, suffix: Option, } fn op_make_temp_dir( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: MakeTempArgs = serde_json::from_value(args)?; let dir = args.dir.map(PathBuf::from); let prefix = args.prefix.map(String::from); let suffix = args.suffix.map(String::from); state .check_write(dir.clone().unwrap_or_else(std::env::temp_dir).as_path())?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, 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 = deno_fs::make_temp( // Converting Option to Option<&str> dir.as_ref().map(|x| &**x), prefix.as_ref().map(|x| &**x), suffix.as_ref().map(|x| &**x), true, )?; let path_str = path.to_str().unwrap(); Ok(json!(path_str)) }) } fn op_make_temp_file( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: MakeTempArgs = serde_json::from_value(args)?; let dir = args.dir.map(PathBuf::from); let prefix = args.prefix.map(String::from); let suffix = args.suffix.map(String::from); state .check_write(dir.clone().unwrap_or_else(std::env::temp_dir).as_path())?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, 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 = deno_fs::make_temp( // Converting Option to Option<&str> dir.as_ref().map(|x| &**x), prefix.as_ref().map(|x| &**x), suffix.as_ref().map(|x| &**x), false, )?; let path_str = path.to_str().unwrap(); Ok(json!(path_str)) }) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct Utime { promise_id: Option, filename: String, atime: u64, mtime: u64, } fn op_utime( state: &State, args: Value, _zero_copy: Option, ) -> Result { let args: Utime = serde_json::from_value(args)?; state.check_write(Path::new(&args.filename))?; let is_sync = args.promise_id.is_none(); blocking_json(is_sync, move || { debug!("op_utimes {} {} {}", args.filename, args.atime, args.mtime); utime::set_file_times(args.filename, args.atime, args.mtime)?; Ok(json!({})) }) } fn op_cwd( _state: &State, _args: Value, _zero_copy: Option, ) -> Result { let path = std::env::current_dir()?; let path_str = path.into_os_string().into_string().unwrap(); Ok(JsonOp::Sync(json!(path_str))) }