perf: fs optimizations - part 1 (#15873)

This commit is contained in:
Divy Srivastava 2022-09-22 14:39:25 +05:30 committed by GitHub
parent 11ced3c10e
commit 698a340ad7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 595 additions and 320 deletions

1
cli/bench/fs/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.json

26
cli/bench/fs/README.md Normal file
View file

@ -0,0 +1,26 @@
## `fs` benchmarks
### adding new benchmarks
```js
const copyFileSync = getFunction("copyFileSync");
bench(() => copyFileSync("test", "test2"));
// For functions with side-effects, clean up after `bench` like so:
const removeSync = getFunction("removeSync");
removeSync("test2");
```
### running
```bash
deno run -A --unstable run.mjs
node run.js
```
### view report
```bash
deno run --allow-net=127.0.0.1:9000 serve.jsx
# View rendered report at http://127.0.0.1:9000/
```

66
cli/bench/fs/run.mjs Normal file
View file

@ -0,0 +1,66 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
let total = 5;
let current = "";
const values = {};
const runtime = typeof Deno !== "undefined" ? "deno" : "node";
function bench(fun, count = 100000) {
if (total === 5) console.log(fun.toString());
const start = Date.now();
for (let i = 0; i < count; i++) fun();
const elapsed = Date.now() - start;
const rate = Math.floor(count / (elapsed / 1000));
console.log(`time ${elapsed} ms rate ${rate}`);
values[current] = values[current] || [];
values[current].push(rate);
if (--total) bench(fun, count);
else total = 5;
}
let fs;
if (runtime === "node") {
fs = await import("fs");
}
const getFunction = runtime === "deno"
? (name) => {
current = name;
return Deno[name];
}
: (name) => {
current = name;
return fs[name];
};
const writeFileSync = getFunction("writeFileSync");
writeFileSync("test", new Uint8Array(1024 * 1024), { truncate: true });
const copyFileSync = getFunction("copyFileSync");
bench(() => copyFileSync("test", "test2"), 10000);
const truncateSync = getFunction("truncateSync");
bench(() => truncateSync("test", 0));
const lstatSync = getFunction("lstatSync");
bench(() => lstatSync("test"));
const { uid, gid } = lstatSync("test");
const chownSync = getFunction("chownSync");
bench(() => chownSync("test", uid, gid));
const chmodSync = getFunction("chmodSync");
bench(() => chmodSync("test", 0o666));
// const cwd = getFunction("cwd");
// bench(() => cwd());
// const chdir = getFunction("chdir");
// bench(() => chdir("/"));
const readFileSync = getFunction("readFileSync");
writeFileSync("test", new Uint8Array(1024), { truncate: true });
bench(() => readFileSync("test"));
writeFileSync(new URL(`./${runtime}.json`, import.meta.url), new TextEncoder().encode(JSON.stringify(values, null, 2)), { truncate: true });

57
cli/bench/fs/serve.jsx Normal file
View file

@ -0,0 +1,57 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
/** @jsx h */
import results from "./deno.json" assert { type: "json" };
import nodeResults from "./node.json" assert { type: "json" };
import { h, ssr } from "https://crux.land/nanossr@0.0.4";
import { router } from "https://crux.land/router@0.0.11";
function once(fn) {
let called = false;
let result;
return function () {
if (!called) {
called = true;
result = fn();
return result;
}
return result;
};
}
const body = once(() =>
Object.entries(results).map(([name, data]) => (
<tr>
<td class="border px-4 py-2">{name}</td>
<td class="border px-4 py-2">
{data.reduce((a, b) => a + b, 0) / data.length} ops/sec
</td>
<td class="border px-4 py-2">
{nodeResults[name].reduce((a, b) => a + b, 0) /
nodeResults[name].length} ops/sec
</td>
</tr>
))
);
function App() {
return (
<table class="table-auto">
<thead>
<tr>
<th class="px-4 py-2">Benchmark</th>
<th class="px-4 py-2">Deno</th>
<th class="px-4 py-2">Node</th>
</tr>
</thead>
<tbody>
{body()}
</tbody>
</table>
);
}
const { serve } = Deno;
serve(router({
"/": () => ssr(() => <App />),
}));

View file

@ -11,16 +11,19 @@
ObjectPrototypeIsPrototypeOf,
SymbolAsyncIterator,
SymbolIterator,
Function,
ObjectEntries,
Uint32Array,
} = window.__bootstrap.primordials;
const { pathFromURL } = window.__bootstrap.util;
const build = window.__bootstrap.build.build;
function chmodSync(path, mode) {
ops.op_chmod_sync({ path: pathFromURL(path), mode });
ops.op_chmod_sync(pathFromURL(path), mode);
}
async function chmod(path, mode) {
await core.opAsync("op_chmod_async", { path: pathFromURL(path), mode });
await core.opAsync("op_chmod_async", pathFromURL(path), mode);
}
function chownSync(
@ -28,7 +31,7 @@
uid,
gid,
) {
ops.op_chown_sync({ path: pathFromURL(path), uid, gid });
ops.op_chown_sync(pathFromURL(path), uid, gid);
}
async function chown(
@ -38,7 +41,9 @@
) {
await core.opAsync(
"op_chown_async",
{ path: pathFromURL(path), uid, gid },
pathFromURL(path),
uid,
gid,
);
}
@ -46,20 +51,21 @@
fromPath,
toPath,
) {
ops.op_copy_file_sync({
from: pathFromURL(fromPath),
to: pathFromURL(toPath),
});
ops.op_copy_file_sync(
pathFromURL(fromPath),
pathFromURL(toPath),
);
}
async function copyFile(
fromPath,
toPath,
) {
await core.opAsync("op_copy_file_async", {
from: pathFromURL(fromPath),
to: pathFromURL(toPath),
});
await core.opAsync(
"op_copy_file_async",
pathFromURL(fromPath),
pathFromURL(toPath),
);
}
function cwd() {
@ -148,36 +154,111 @@
path,
options = {},
) {
ops.op_remove_sync({
path: pathFromURL(path),
recursive: !!options.recursive,
});
ops.op_remove_sync(
pathFromURL(path),
!!options.recursive,
);
}
async function remove(
path,
options = {},
) {
await core.opAsync("op_remove_async", {
path: pathFromURL(path),
recursive: !!options.recursive,
});
await core.opAsync(
"op_remove_async",
pathFromURL(path),
!!options.recursive,
);
}
function renameSync(oldpath, newpath) {
ops.op_rename_sync({
oldpath: pathFromURL(oldpath),
newpath: pathFromURL(newpath),
});
ops.op_rename_sync(
pathFromURL(oldpath),
pathFromURL(newpath),
);
}
async function rename(oldpath, newpath) {
await core.opAsync("op_rename_async", {
oldpath: pathFromURL(oldpath),
newpath: pathFromURL(newpath),
});
await core.opAsync(
"op_rename_async",
pathFromURL(oldpath),
pathFromURL(newpath),
);
}
// Extract the FsStat object from the encoded buffer.
// See `runtime/ops/fs.rs` for the encoder.
//
// This is not a general purpose decoder. There are 4 types:
//
// 1. date
// offset += 4
// 1/0 | extra padding | high u32 | low u32
// if date[0] == 1, new Date(u64) else null
//
// 2. bool
// offset += 2
// 1/0 | extra padding
//
// 3. u64
// offset += 2
// high u32 | low u32
//
// 4. ?u64 converts a zero u64 value to JS null on Windows.
function createByteStruct(types) {
// types can be "date", "bool" or "u64".
// `?` prefix means optional on windows.
let offset = 0;
let str =
'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {';
for (let [name, type] of ObjectEntries(types)) {
const optional = type.startsWith("?");
if (optional) type = type.slice(1);
if (type == "u64") {
if (!optional) {
str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`;
} else {
str += `${name}: (unix ? (view[${offset}] + view[${
offset + 1
}] * 2**32) : (view[${offset}] + view[${
offset + 1
}] * 2**32) || null),`;
}
} else if (type == "date") {
str += `${name}: view[${offset}] === 0 ? null : new Date(view[${
offset + 2
}] + view[${offset + 3}] * 2**32),`;
offset += 2;
} else {
str += `${name}: !!(view[${offset}] + view[${offset + 1}] * 2**32),`;
}
offset += 2;
}
str += "};";
// ...so you don't like eval huh? don't worry, it only executes during snapshot :)
return [new Function("view", str), new Uint32Array(offset)];
}
const [statStruct, statBuf] = createByteStruct({
isFile: "bool",
isDirectory: "bool",
isSymlink: "bool",
size: "u64",
mtime: "date",
atime: "date",
birthtime: "date",
dev: "?u64",
ino: "?u64",
mode: "?u64",
nlink: "?u64",
uid: "?u64",
gid: "?u64",
rdev: "?u64",
blksize: "?u64",
blocks: "?u64",
});
function parseFileInfo(response) {
const unix = build.os === "darwin" || build.os === "linux";
return {
@ -185,9 +266,9 @@
isDirectory: response.isDirectory,
isSymlink: response.isSymlink,
size: response.size,
mtime: response.mtime != null ? new Date(response.mtime) : null,
atime: response.atime != null ? new Date(response.atime) : null,
birthtime: response.birthtime != null
mtime: response.mtimeSet !== null ? new Date(response.mtime) : null,
atime: response.atimeSet !== null ? new Date(response.atime) : null,
birthtime: response.birthtimeSet !== null
? new Date(response.birthtime)
: null,
// Only non-null if on Unix
@ -204,7 +285,8 @@
}
function fstatSync(rid) {
return parseFileInfo(ops.op_fstat_sync(rid));
ops.op_fstat_sync(rid, statBuf);
return statStruct(statBuf);
}
async function fstat(rid) {
@ -220,11 +302,12 @@
}
function lstatSync(path) {
const res = ops.op_stat_sync({
path: pathFromURL(path),
lstat: true,
});
return parseFileInfo(res);
ops.op_stat_sync(
pathFromURL(path),
true,
statBuf,
);
return statStruct(statBuf);
}
async function stat(path) {
@ -236,11 +319,12 @@
}
function statSync(path) {
const res = ops.op_stat_sync({
path: pathFromURL(path),
lstat: false,
});
return parseFileInfo(res);
ops.op_stat_sync(
pathFromURL(path),
false,
statBuf,
);
return statStruct(statBuf);
}
function coerceLen(len) {
@ -252,19 +336,19 @@
}
function ftruncateSync(rid, len) {
ops.op_ftruncate_sync({ rid, len: coerceLen(len) });
ops.op_ftruncate_sync(rid, coerceLen(len));
}
async function ftruncate(rid, len) {
await core.opAsync("op_ftruncate_async", { rid, len: coerceLen(len) });
await core.opAsync("op_ftruncate_async", rid, coerceLen(len));
}
function truncateSync(path, len) {
ops.op_truncate_sync({ path, len: coerceLen(len) });
ops.op_truncate_sync(path, coerceLen(len));
}
async function truncate(path, len) {
await core.opAsync("op_truncate_async", { path, len: coerceLen(len) });
await core.opAsync("op_truncate_async", path, coerceLen(len));
}
function umask(mask) {
@ -272,11 +356,11 @@
}
function linkSync(oldpath, newpath) {
ops.op_link_sync({ oldpath, newpath });
ops.op_link_sync(oldpath, newpath);
}
async function link(oldpath, newpath) {
await core.opAsync("op_link_async", { oldpath, newpath });
await core.opAsync("op_link_async", oldpath, newpath);
}
function toUnixTimeFromEpoch(value) {
@ -305,11 +389,9 @@
atime,
mtime,
) {
ops.op_futime_sync({
rid,
atime: toUnixTimeFromEpoch(atime),
mtime: toUnixTimeFromEpoch(mtime),
});
const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec);
}
async function futime(
@ -317,11 +399,16 @@
atime,
mtime,
) {
await core.opAsync("op_futime_async", {
const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
await core.opAsync(
"op_futime_async",
rid,
atime: toUnixTimeFromEpoch(atime),
mtime: toUnixTimeFromEpoch(mtime),
});
atimeSec,
atimeNsec,
mtimeSec,
mtimeNsec,
);
}
function utimeSync(
@ -329,11 +416,15 @@
atime,
mtime,
) {
ops.op_utime_sync({
path: pathFromURL(path),
atime: toUnixTimeFromEpoch(atime),
mtime: toUnixTimeFromEpoch(mtime),
});
const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
ops.op_utime_sync(
pathFromURL(path),
atimeSec,
atimeNsec,
mtimeSec,
mtimeNsec,
);
}
async function utime(
@ -341,11 +432,16 @@
atime,
mtime,
) {
await core.opAsync("op_utime_async", {
path: pathFromURL(path),
atime: toUnixTimeFromEpoch(atime),
mtime: toUnixTimeFromEpoch(mtime),
});
const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
await core.opAsync(
"op_utime_async",
pathFromURL(path),
atimeSec,
atimeNsec,
mtimeSec,
mtimeNsec,
);
}
function symlinkSync(
@ -353,11 +449,11 @@
newpath,
options,
) {
ops.op_symlink_sync({
oldpath: pathFromURL(oldpath),
newpath: pathFromURL(newpath),
options,
});
ops.op_symlink_sync(
pathFromURL(oldpath),
pathFromURL(newpath),
options?.type,
);
}
async function symlink(
@ -365,11 +461,12 @@
newpath,
options,
) {
await core.opAsync("op_symlink_async", {
oldpath: pathFromURL(oldpath),
newpath: pathFromURL(newpath),
options,
});
await core.opAsync(
"op_symlink_async",
pathFromURL(oldpath),
pathFromURL(newpath),
options?.type,
);
}
function fdatasyncSync(rid) {

View file

@ -394,11 +394,14 @@ async fn op_fsync_async(
fn op_fstat_sync(
state: &mut OpState,
rid: ResourceId,
) -> Result<FsStat, AnyError> {
out_buf: &mut [u32],
) -> Result<(), AnyError> {
let metadata = StdFileResource::with_file(state, rid, |std_file| {
std_file.metadata().map_err(AnyError::from)
})?;
Ok(get_stat(metadata))
let stat = get_stat(metadata);
stat.write(out_buf);
Ok(())
}
#[op]
@ -579,42 +582,36 @@ async fn op_mkdir_async(
.unwrap()
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChmodArgs {
#[op]
fn op_chmod_sync(
state: &mut OpState,
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;
) -> Result<(), AnyError> {
let path = Path::new(&path);
let mode = mode & 0o777;
state.borrow_mut::<Permissions>().write.check(path)?;
debug!("op_chmod_sync {} {:o}", path.display(), mode);
raw_chmod(path, mode)
}
#[op]
async fn op_chmod_async(
state: Rc<RefCell<OpState>>,
args: ChmodArgs,
path: String,
mode: u32,
) -> Result<(), AnyError> {
let path = Path::new(&args.path).to_path_buf();
let mode = args.mode & 0o777;
let path = Path::new(&path).to_path_buf();
let mode = mode & 0o777;
{
let mut state = state.borrow_mut();
state.borrow_mut::<Permissions>().write.check(&path)?;
}
tokio::task::spawn_blocking(move || {
debug!("op_chmod_async {} {:o}", path.display(), mode);
raw_chmod(&path, mode)
})
.await
.unwrap()
tokio::task::spawn_blocking(move || raw_chmod(&path, mode))
.await
.unwrap()
}
fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> {
@ -637,30 +634,21 @@ fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> {
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChownArgs {
#[op]
fn op_chown_sync(
state: &mut OpState,
path: String,
uid: Option<u32>,
gid: Option<u32>,
}
#[op]
fn op_chown_sync(state: &mut OpState, args: ChownArgs) -> Result<(), AnyError> {
let path = Path::new(&args.path).to_path_buf();
) -> Result<(), AnyError> {
let path = Path::new(&path).to_path_buf();
state.borrow_mut::<Permissions>().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);
let nix_uid = uid.map(Uid::from_raw);
let nix_gid = gid.map(Gid::from_raw);
chown(&path, nix_uid, nix_gid).map_err(|err| {
custom_error(
get_nix_error_class(&err),
@ -679,9 +667,11 @@ fn op_chown_sync(state: &mut OpState, args: ChownArgs) -> Result<(), AnyError> {
#[op]
async fn op_chown_async(
state: Rc<RefCell<OpState>>,
args: ChownArgs,
path: String,
uid: Option<u32>,
gid: Option<u32>,
) -> Result<(), AnyError> {
let path = Path::new(&args.path).to_path_buf();
let path = Path::new(&path).to_path_buf();
{
let mut state = state.borrow_mut();
@ -689,18 +679,12 @@ async fn op_chown_async(
}
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);
let nix_uid = uid.map(Uid::from_raw);
let nix_gid = gid.map(Gid::from_raw);
chown(&path, nix_uid, nix_gid).map_err(|err| {
custom_error(
get_nix_error_class(&err),
@ -717,20 +701,13 @@ async fn op_chown_async(
.unwrap()
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveArgs {
path: String,
recursive: bool,
}
#[op]
fn op_remove_sync(
state: &mut OpState,
args: RemoveArgs,
path: String,
recursive: bool,
) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path);
let recursive = args.recursive;
let path = PathBuf::from(&path);
state.borrow_mut::<Permissions>().write.check(&path)?;
@ -742,7 +719,6 @@ fn op_remove_sync(
};
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)?;
@ -772,10 +748,10 @@ fn op_remove_sync(
#[op]
async fn op_remove_async(
state: Rc<RefCell<OpState>>,
args: RemoveArgs,
path: String,
recursive: bool,
) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path);
let recursive = args.recursive;
let path = PathBuf::from(&path);
{
let mut state = state.borrow_mut();
@ -820,36 +796,29 @@ async fn op_remove_async(
.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,
from: String,
to: String,
) -> Result<(), AnyError> {
let from = PathBuf::from(&args.from);
let to = PathBuf::from(&args.to);
let from_path = PathBuf::from(&from);
let to_path = PathBuf::from(&to);
let permissions = state.borrow_mut::<Permissions>();
permissions.read.check(&from)?;
permissions.write.check(&to)?;
permissions.read.check(&from_path)?;
permissions.write.check(&to_path)?;
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() {
if cfg!(unix) && !from_path.is_file() {
return Err(custom_error(
"NotFound",
format!(
"File not found, copy '{}' -> '{}'",
from.display(),
to.display()
from_path.display(),
to_path.display()
),
));
}
@ -857,21 +826,80 @@ fn op_copy_file_sync(
let err_mapper = |err: Error| {
Error::new(
err.kind(),
format!("{}, copy '{}' -> '{}'", err, from.display(), to.display()),
format!(
"{}, copy '{}' -> '{}'",
err,
from_path.display(),
to_path.display()
),
)
};
#[cfg(target_os = "macos")]
{
use libc::chmod;
use libc::clonefile;
use libc::stat;
use libc::unlink;
use std::ffi::CString;
use std::io::Read;
let from = CString::new(from).unwrap();
let to = CString::new(to).unwrap();
// SAFETY: `from` and `to` are valid C strings.
// std::fs::copy does open() + fcopyfile() on macOS. We try to use
// clonefile() instead, which is more efficient.
unsafe {
let mut st = std::mem::zeroed();
let ret = stat(from.as_ptr(), &mut st);
if ret != 0 {
return Err(err_mapper(Error::last_os_error()).into());
}
if st.st_size > 128 * 1024 {
// Try unlink. If it fails, we are going to try clonefile() anyway.
let _ = unlink(to.as_ptr());
// Matches rust stdlib behavior for io::copy.
// https://github.com/rust-lang/rust/blob/3fdd578d72a24d4efc2fe2ad18eec3b6ba72271e/library/std/src/sys/unix/fs.rs#L1613-L1616
if clonefile(from.as_ptr(), to.as_ptr(), 0) == 0 {
return Ok(());
}
} else {
// Do a regular copy. fcopyfile() is an overkill for < 128KB
// files.
let mut buf = [0u8; 128 * 1024];
let mut from_file =
std::fs::File::open(&from_path).map_err(err_mapper)?;
let mut to_file =
std::fs::File::create(&to_path).map_err(err_mapper)?;
loop {
let nread = from_file.read(&mut buf).map_err(err_mapper)?;
if nread == 0 {
break;
}
to_file.write_all(&buf[..nread]).map_err(err_mapper)?;
}
return Ok(());
}
}
// clonefile() failed, fall back to std::fs::copy().
}
// returns size of from as u64 (we ignore)
std::fs::copy(&from, &to).map_err(err_mapper)?;
std::fs::copy(&from_path, &to_path).map_err(err_mapper)?;
Ok(())
}
#[op]
async fn op_copy_file_async(
state: Rc<RefCell<OpState>>,
args: CopyFileArgs,
from: String,
to: String,
) -> Result<(), AnyError> {
let from = PathBuf::from(&args.from);
let to = PathBuf::from(&args.to);
let from = PathBuf::from(&from);
let to = PathBuf::from(&to);
{
let mut state = state.borrow_mut();
@ -880,7 +908,6 @@ async fn op_copy_file_async(
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
@ -910,40 +937,68 @@ async fn op_copy_file_async(
.unwrap()
}
fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> Option<u64> {
fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> (u64, bool) {
match maybe_time {
Ok(time) => {
let msec = time
Ok(time) => (
time
.duration_since(UNIX_EPOCH)
.map(|t| t.as_millis() as u64)
.unwrap_or_else(|err| err.duration().as_millis() as u64);
Some(msec)
}
Err(_) => None,
.unwrap_or_else(|err| err.duration().as_millis() as u64),
true,
),
Err(_) => (0, false),
}
}
#[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<u64>,
atime: Option<u64>,
birthtime: Option<u64>,
// Following are only valid under Unix.
dev: u64,
ino: u64,
mode: u32,
nlink: u64,
uid: u32,
gid: u32,
rdev: u64,
blksize: u64,
blocks: u64,
macro_rules! create_struct_writer {
(pub struct $name:ident { $($field:ident: $type:ty),* $(,)? }) => {
impl $name {
fn write(self, buf: &mut [u32]) {
let mut offset = 0;
$(
let value = self.$field as u64;
buf[offset] = value as u32;
buf[offset + 1] = (value >> 32) as u32;
#[allow(unused_assignments)]
{
offset += 2;
}
)*
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct $name {
$($field: $type),*
}
};
}
create_struct_writer! {
pub struct FsStat {
is_file: bool,
is_directory: bool,
is_symlink: bool,
size: u64,
// In milliseconds, like JavaScript. Available on both Unix or Windows.
mtime_set: bool,
mtime: u64,
atime_set: bool,
atime: u64,
birthtime_set: bool,
birthtime: u64,
// Following are only valid under Unix.
dev: u64,
ino: u64,
mode: u32,
nlink: u64,
uid: u32,
gid: u32,
rdev: u64,
blksize: u64,
blocks: u64,
}
}
#[inline(always)]
@ -964,15 +1019,22 @@ fn get_stat(metadata: std::fs::Metadata) -> FsStat {
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
let (mtime, mtime_set) = to_msec(metadata.modified());
let (atime, atime_set) = to_msec(metadata.accessed());
let (birthtime, birthtime_set) = to_msec(metadata.created());
FsStat {
is_file: metadata.is_file(),
is_directory: metadata.is_dir(),
is_symlink: metadata.file_type().is_symlink(),
size: metadata.len(),
// 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()),
mtime_set,
mtime,
atime_set,
atime,
birthtime_set,
birthtime,
// Following are only valid under Unix.
dev: usm!(dev),
ino: usm!(ino),
@ -996,12 +1058,12 @@ pub struct StatArgs {
#[op]
fn op_stat_sync(
state: &mut OpState,
args: StatArgs,
) -> Result<FsStat, AnyError> {
let path = PathBuf::from(&args.path);
let lstat = args.lstat;
path: String,
lstat: bool,
out_buf: &mut [u32],
) -> Result<(), AnyError> {
let path = PathBuf::from(&path);
state.borrow_mut::<Permissions>().read.check(&path)?;
debug!("op_stat_sync {} {}", path.display(), lstat);
let err_mapper = |err: Error| {
Error::new(err.kind(), format!("{}, stat '{}'", err, path.display()))
};
@ -1010,7 +1072,11 @@ fn op_stat_sync(
} else {
std::fs::metadata(&path).map_err(err_mapper)?
};
Ok(get_stat(metadata))
let stat = get_stat(metadata);
stat.write(out_buf);
Ok(())
}
#[op]
@ -1185,26 +1251,20 @@ async fn op_read_dir_async(
.unwrap()
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RenameArgs {
oldpath: String,
newpath: String,
}
#[op]
fn op_rename_sync(
state: &mut OpState,
args: RenameArgs,
oldpath: String,
newpath: String,
) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath);
let newpath = PathBuf::from(&args.newpath);
let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&newpath);
let permissions = state.borrow_mut::<Permissions>();
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(),
@ -1223,10 +1283,11 @@ fn op_rename_sync(
#[op]
async fn op_rename_async(
state: Rc<RefCell<OpState>>,
args: RenameArgs,
oldpath: String,
newpath: String,
) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath);
let newpath = PathBuf::from(&args.newpath);
let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&newpath);
{
let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<Permissions>();
@ -1235,11 +1296,6 @@ async fn op_rename_async(
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(),
@ -1258,17 +1314,14 @@ async fn op_rename_async(
.unwrap()
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LinkArgs {
#[op]
fn op_link_sync(
state: &mut OpState,
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);
) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&newpath);
let permissions = state.borrow_mut::<Permissions>();
permissions.read.check(&oldpath)?;
@ -1276,7 +1329,6 @@ fn op_link_sync(state: &mut OpState, args: LinkArgs) -> Result<(), AnyError> {
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(),
@ -1295,10 +1347,11 @@ fn op_link_sync(state: &mut OpState, args: LinkArgs) -> Result<(), AnyError> {
#[op]
async fn op_link_async(
state: Rc<RefCell<OpState>>,
args: LinkArgs,
oldpath: String,
newpath: String,
) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath);
let newpath = PathBuf::from(&args.newpath);
let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&newpath);
{
let mut state = state.borrow_mut();
@ -1310,7 +1363,6 @@ async fn op_link_async(
}
tokio::task::spawn_blocking(move || {
debug!("op_link_async {} {}", oldpath.display(), newpath.display());
let err_mapper = |err: Error| {
Error::new(
err.kind(),
@ -1329,38 +1381,19 @@ async fn op_link_async(
.unwrap()
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SymlinkArgs {
oldpath: String,
newpath: String,
#[cfg(not(unix))]
options: Option<SymlinkOptions>,
}
#[cfg(not(unix))]
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SymlinkOptions {
_type: String,
}
#[op]
fn op_symlink_sync(
state: &mut OpState,
args: SymlinkArgs,
oldpath: String,
newpath: String,
_type: Option<String>,
) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath);
let newpath = PathBuf::from(&args.newpath);
let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&newpath);
state.borrow_mut::<Permissions>().write.check_all()?;
state.borrow_mut::<Permissions>().read.check_all()?;
debug!(
"op_symlink_sync {} {}",
oldpath.display(),
newpath.display()
);
let err_mapper = |err: Error| {
Error::new(
err.kind(),
@ -1382,8 +1415,8 @@ fn op_symlink_sync(
{
use std::os::windows::fs::{symlink_dir, symlink_file};
match args.options {
Some(options) => match options._type.as_ref() {
match _type {
Some(ty) => match ty.as_ref() {
"file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
"dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
_ => return Err(type_error("unsupported type")),
@ -1409,10 +1442,12 @@ fn op_symlink_sync(
#[op]
async fn op_symlink_async(
state: Rc<RefCell<OpState>>,
args: SymlinkArgs,
oldpath: String,
newpath: String,
_type: Option<String>,
) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath);
let newpath = PathBuf::from(&args.newpath);
let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&newpath);
{
let mut state = state.borrow_mut();
@ -1421,7 +1456,6 @@ async fn op_symlink_async(
}
tokio::task::spawn_blocking(move || {
debug!("op_symlink_async {} {}", oldpath.display(), newpath.display());
let err_mapper = |err: Error| {
Error::new(
err.kind(),
@ -1443,8 +1477,8 @@ async fn op_symlink_async(
{
use std::os::windows::fs::{symlink_dir, symlink_file};
match args.options {
Some(options) => match options._type.as_ref() {
match _type {
Some(ty) => match ty.as_ref() {
"file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
"dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
_ => return Err(type_error("unsupported type")),
@ -1521,20 +1555,13 @@ async fn op_read_link_async(
.unwrap()
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FtruncateArgs {
rid: ResourceId,
len: i32,
}
#[op]
fn op_ftruncate_sync(
state: &mut OpState,
args: FtruncateArgs,
rid: u32,
len: i32,
) -> Result<(), AnyError> {
let rid = args.rid;
let len = args.len as u64;
let len = len as u64;
StdFileResource::with_file(state, rid, |std_file| {
std_file.set_len(len).map_err(AnyError::from)
})?;
@ -1544,10 +1571,10 @@ fn op_ftruncate_sync(
#[op]
async fn op_ftruncate_async(
state: Rc<RefCell<OpState>>,
args: FtruncateArgs,
rid: ResourceId,
len: i32,
) -> Result<(), AnyError> {
let rid = args.rid;
let len = args.len as u64;
let len = len as u64;
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
std_file.set_len(len)?;
@ -1556,20 +1583,13 @@ async fn op_ftruncate_async(
.await
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TruncateArgs {
path: String,
len: u64,
}
#[op]
fn op_truncate_sync(
state: &mut OpState,
args: TruncateArgs,
path: String,
len: u64,
) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path);
let len = args.len;
let path = PathBuf::from(&path);
state.borrow_mut::<Permissions>().write.check(&path)?;
@ -1591,10 +1611,11 @@ fn op_truncate_sync(
#[op]
async fn op_truncate_async(
state: Rc<RefCell<OpState>>,
args: TruncateArgs,
path: String,
len: u64,
) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path);
let len = args.len;
let path = PathBuf::from(&path);
{
let mut state = state.borrow_mut();
state.borrow_mut::<Permissions>().write.check(&path)?;
@ -1797,23 +1818,18 @@ async fn op_make_temp_file_async(
.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,
rid: ResourceId,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> 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);
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
StdFileResource::with_file(state, rid, |std_file| {
filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))
@ -1826,12 +1842,15 @@ fn op_futime_sync(
#[op]
async fn op_futime_async(
state: Rc<RefCell<OpState>>,
args: FutimeArgs,
rid: ResourceId,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> 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);
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))?;
@ -1840,21 +1859,20 @@ async fn op_futime_async(
.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> {
fn op_utime_sync(
state: &mut OpState,
path: String,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> 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);
let path = PathBuf::from(&path);
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
state.borrow_mut::<Permissions>().write.check(&path)?;
filetime::set_file_times(&path, atime, mtime).map_err(|err| {
@ -1866,13 +1884,17 @@ fn op_utime_sync(state: &mut OpState, args: UtimeArgs) -> Result<(), AnyError> {
#[op]
async fn op_utime_async(
state: Rc<RefCell<OpState>>,
args: UtimeArgs,
path: String,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> 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);
let path = PathBuf::from(&path);
let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
state
.borrow_mut()

View file

@ -302,6 +302,9 @@ pub struct FfiDescriptor(pub PathBuf);
impl UnaryPermission<ReadDescriptor> {
pub fn query(&self, path: Option<&Path>) -> PermissionState {
if self.global_state == PermissionState::Granted {
return PermissionState::Granted;
}
let path = path.map(|p| resolve_from_cwd(p).unwrap());
if self.global_state == PermissionState::Denied
&& match path.as_ref() {
@ -454,6 +457,9 @@ impl Default for UnaryPermission<ReadDescriptor> {
impl UnaryPermission<WriteDescriptor> {
pub fn query(&self, path: Option<&Path>) -> PermissionState {
if self.global_state == PermissionState::Granted {
return PermissionState::Granted;
}
let path = path.map(|p| resolve_from_cwd(p).unwrap());
if self.global_state == PermissionState::Denied
&& match path.as_ref() {