diff --git a/cli/bench/fs/.gitignore b/cli/bench/fs/.gitignore
new file mode 100644
index 0000000000..94a2dd146a
--- /dev/null
+++ b/cli/bench/fs/.gitignore
@@ -0,0 +1 @@
+*.json
\ No newline at end of file
diff --git a/cli/bench/fs/README.md b/cli/bench/fs/README.md
new file mode 100644
index 0000000000..122a70cfd9
--- /dev/null
+++ b/cli/bench/fs/README.md
@@ -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/
+```
diff --git a/cli/bench/fs/run.mjs b/cli/bench/fs/run.mjs
new file mode 100644
index 0000000000..92471d638b
--- /dev/null
+++ b/cli/bench/fs/run.mjs
@@ -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 });
\ No newline at end of file
diff --git a/cli/bench/fs/serve.jsx b/cli/bench/fs/serve.jsx
new file mode 100644
index 0000000000..3ef07c6a09
--- /dev/null
+++ b/cli/bench/fs/serve.jsx
@@ -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]) => (
+
+ {name} |
+
+ {data.reduce((a, b) => a + b, 0) / data.length} ops/sec
+ |
+
+ {nodeResults[name].reduce((a, b) => a + b, 0) /
+ nodeResults[name].length} ops/sec
+ |
+
+ ))
+);
+
+function App() {
+ return (
+
+
+
+ Benchmark |
+ Deno |
+ Node |
+
+
+
+ {body()}
+
+
+ );
+}
+
+const { serve } = Deno;
+serve(router({
+ "/": () => ssr(() => ),
+}));
diff --git a/runtime/js/30_fs.js b/runtime/js/30_fs.js
index 2457713e64..4a78ebcad1 100644
--- a/runtime/js/30_fs.js
+++ b/runtime/js/30_fs.js
@@ -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) {
diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs
index b63ddfd69e..1fc453e2bd 100644
--- a/runtime/ops/fs.rs
+++ b/runtime/ops/fs.rs
@@ -394,11 +394,14 @@ async fn op_fsync_async(
fn op_fstat_sync(
state: &mut OpState,
rid: ResourceId,
-) -> Result {
+ 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::().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,
+ 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::().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,
gid: Option,
-}
-
-#[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::().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>,
- args: ChownArgs,
+ path: String,
+ uid: Option,
+ gid: Option,
) -> 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::().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>,
- 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.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>,
- 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) -> Option {
+fn to_msec(maybe_time: Result) -> (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,
- 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,
+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 {
- 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::().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.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>,
- 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::();
@@ -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.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>,
- 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,
-}
-
-#[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,
) -> 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::().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(),
@@ -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>,
- args: SymlinkArgs,
+ oldpath: String,
+ newpath: String,
+ _type: Option,
) -> 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>,
- 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::().write.check(&path)?;
@@ -1591,10 +1611,11 @@ fn op_truncate_sync(
#[op]
async fn op_truncate_async(
state: Rc>,
- 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::().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>,
- 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::().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>,
- 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()
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
index 2e8e3809ff..44a6582252 100644
--- a/runtime/permissions.rs
+++ b/runtime/permissions.rs
@@ -302,6 +302,9 @@ pub struct FfiDescriptor(pub PathBuf);
impl UnaryPermission {
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 {
impl UnaryPermission {
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() {