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 ( + + + + + + + + + + {body()} + +
BenchmarkDenoNode
+ ); +} + +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() {