0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-10-29 08:58:01 -04:00
denoland-deno/cli/fs.rs
Bartek Iwańczuk 4e1abb4f3a
refactor: use OpError instead of ErrBox for errors in ops (#4058)
To better reflect changes in error types in JS from #3662 this PR changes 
default error type used in ops from "ErrBox" to "OpError".

"OpError" is a type that can be sent over to JSON; it has all 
information needed to construct error in JavaScript. That
made "GetErrorKind" trait useless and so it was removed altogether.

To provide compatibility with previous use of "ErrBox" an implementation of
"From<ErrBox> for OpError" was added, however, it is an escape hatch and
ops implementors should strive to use "OpError" directly.
2020-02-23 14:51:29 -05:00

216 lines
5.6 KiB
Rust

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use std;
use std::fs::{create_dir, DirBuilder, File, OpenOptions};
use std::io::ErrorKind;
use std::io::Write;
use std::path::{Path, PathBuf};
use deno_core::ErrBox;
use rand;
use rand::Rng;
use url::Url;
use walkdir::WalkDir;
#[cfg(unix)]
use nix::unistd::{chown as unix_chown, Gid, Uid};
#[cfg(any(unix))]
use std::os::unix::fs::DirBuilderExt;
#[cfg(any(unix))]
use std::os::unix::fs::PermissionsExt;
pub fn write_file<T: AsRef<[u8]>>(
filename: &Path,
data: T,
perm: u32,
) -> std::io::Result<()> {
write_file_2(filename, data, true, perm, true, false)
}
pub fn write_file_2<T: AsRef<[u8]>>(
filename: &Path,
data: T,
update_perm: bool,
perm: u32,
is_create: bool,
is_append: bool,
) -> std::io::Result<()> {
let mut file = OpenOptions::new()
.read(false)
.write(true)
.append(is_append)
.truncate(!is_append)
.create(is_create)
.open(filename)?;
if update_perm {
set_permissions(&mut file, perm)?;
}
file.write_all(data.as_ref())
}
#[cfg(any(unix))]
fn set_permissions(file: &mut File, perm: u32) -> std::io::Result<()> {
debug!("set file perm to {}", perm);
file.set_permissions(PermissionsExt::from_mode(perm & 0o777))
}
#[cfg(not(any(unix)))]
fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> {
// NOOP on windows
Ok(())
}
pub 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 => std::env::temp_dir(),
}
.join("_");
let mut rng = rand::thread_rng();
loop {
let unique = rng.gen::<u32>();
buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_));
// TODO: on posix, set mode flags to 0o700.
let r = if is_dir {
create_dir(buf.as_path())
} else {
OpenOptions::new()
.write(true)
.create_new(true)
.open(buf.as_path())
.map(|_| ())
};
match r {
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue,
Ok(_) => return Ok(buf),
Err(e) => return Err(e),
}
}
}
pub fn mkdir(path: &Path, perm: u32, recursive: bool) -> std::io::Result<()> {
debug!("mkdir -p {}", path.display());
let mut builder = DirBuilder::new();
builder.recursive(recursive);
set_dir_permission(&mut builder, perm);
builder.create(path)
}
#[cfg(any(unix))]
fn set_dir_permission(builder: &mut DirBuilder, perm: u32) {
debug!("set dir perm to {}", perm);
builder.mode(perm & 0o777);
}
#[cfg(not(any(unix)))]
fn set_dir_permission(_builder: &mut DirBuilder, _perm: u32) {
// NOOP on windows
}
pub fn normalize_path(path: &Path) -> String {
let s = String::from(path.to_str().unwrap());
if cfg!(windows) {
// TODO This isn't correct. Probbly should iterate over components.
s.replace("\\", "/")
} else {
s
}
}
#[cfg(unix)]
pub fn chown(path: &str, uid: u32, gid: u32) -> Result<(), ErrBox> {
let nix_uid = Uid::from_raw(uid);
let nix_gid = Gid::from_raw(gid);
unix_chown(path, Option::Some(nix_uid), Option::Some(nix_gid))
.map_err(ErrBox::from)
}
#[cfg(not(unix))]
pub fn chown(_path: &str, _uid: u32, _gid: u32) -> Result<(), ErrBox> {
// Noop
// TODO: implement chown for Windows
let e = std::io::Error::new(
std::io::ErrorKind::Other,
"Op not implemented".to_string(),
);
Err(ErrBox::from(e))
}
pub fn resolve_from_cwd(path: &Path) -> Result<PathBuf, ErrBox> {
let resolved_path = if path.is_absolute() {
path.to_owned()
} else {
let cwd = std::env::current_dir().unwrap();
cwd.join(path)
};
// HACK: `Url::parse` is used here because it normalizes the path.
// Joining `/dev/deno/" with "./tests" using `PathBuf` yields `/deno/dev/./tests/`.
// On the other hand joining `/dev/deno/" with "./tests" using `Url` yields "/dev/deno/tests"
// - and that's what we want.
// There exists similar method on `PathBuf` - `PathBuf.canonicalize`, but the problem
// is `canonicalize` resolves symlinks and we don't want that.
// We just want to normalize the path...
// This only works on absolute paths - not worth extracting as a public utility.
let resolved_url =
Url::from_file_path(resolved_path).expect("Path should be absolute");
let normalized_url = Url::parse(resolved_url.as_str())
.expect("String from a URL should parse to a URL");
let normalized_path = normalized_url
.to_file_path()
.expect("URL from a path should contain a valid path");
Ok(normalized_path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_from_cwd_child() {
let cwd = std::env::current_dir().unwrap();
assert_eq!(resolve_from_cwd(Path::new("a")).unwrap(), cwd.join("a"));
}
#[test]
fn resolve_from_cwd_dot() {
let cwd = std::env::current_dir().unwrap();
assert_eq!(resolve_from_cwd(Path::new(".")).unwrap(), cwd);
}
#[test]
fn resolve_from_cwd_parent() {
let cwd = std::env::current_dir().unwrap();
assert_eq!(resolve_from_cwd(Path::new("a/..")).unwrap(), cwd);
}
// TODO: Get a good expected value here for Windows.
#[cfg(not(windows))]
#[test]
fn resolve_from_cwd_absolute() {
let expected = Path::new("/a");
assert_eq!(resolve_from_cwd(expected).unwrap(), expected);
}
}
pub fn files_in_subtree<F>(root: PathBuf, filter: F) -> Vec<PathBuf>
where
F: Fn(&Path) -> bool,
{
assert!(root.is_dir());
WalkDir::new(root)
.into_iter()
.filter_map(|e| e.ok())
.map(|e| e.path().to_owned())
.filter(|p| if p.is_dir() { false } else { filter(&p) })
.collect()
}