1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

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, ObjectPrototypeIsPrototypeOf,
SymbolAsyncIterator, SymbolAsyncIterator,
SymbolIterator, SymbolIterator,
Function,
ObjectEntries,
Uint32Array,
} = window.__bootstrap.primordials; } = window.__bootstrap.primordials;
const { pathFromURL } = window.__bootstrap.util; const { pathFromURL } = window.__bootstrap.util;
const build = window.__bootstrap.build.build; const build = window.__bootstrap.build.build;
function chmodSync(path, mode) { function chmodSync(path, mode) {
ops.op_chmod_sync({ path: pathFromURL(path), mode }); ops.op_chmod_sync(pathFromURL(path), mode);
} }
async function chmod(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( function chownSync(
@ -28,7 +31,7 @@
uid, uid,
gid, gid,
) { ) {
ops.op_chown_sync({ path: pathFromURL(path), uid, gid }); ops.op_chown_sync(pathFromURL(path), uid, gid);
} }
async function chown( async function chown(
@ -38,7 +41,9 @@
) { ) {
await core.opAsync( await core.opAsync(
"op_chown_async", "op_chown_async",
{ path: pathFromURL(path), uid, gid }, pathFromURL(path),
uid,
gid,
); );
} }
@ -46,20 +51,21 @@
fromPath, fromPath,
toPath, toPath,
) { ) {
ops.op_copy_file_sync({ ops.op_copy_file_sync(
from: pathFromURL(fromPath), pathFromURL(fromPath),
to: pathFromURL(toPath), pathFromURL(toPath),
}); );
} }
async function copyFile( async function copyFile(
fromPath, fromPath,
toPath, toPath,
) { ) {
await core.opAsync("op_copy_file_async", { await core.opAsync(
from: pathFromURL(fromPath), "op_copy_file_async",
to: pathFromURL(toPath), pathFromURL(fromPath),
}); pathFromURL(toPath),
);
} }
function cwd() { function cwd() {
@ -148,36 +154,111 @@
path, path,
options = {}, options = {},
) { ) {
ops.op_remove_sync({ ops.op_remove_sync(
path: pathFromURL(path), pathFromURL(path),
recursive: !!options.recursive, !!options.recursive,
}); );
} }
async function remove( async function remove(
path, path,
options = {}, options = {},
) { ) {
await core.opAsync("op_remove_async", { await core.opAsync(
path: pathFromURL(path), "op_remove_async",
recursive: !!options.recursive, pathFromURL(path),
}); !!options.recursive,
);
} }
function renameSync(oldpath, newpath) { function renameSync(oldpath, newpath) {
ops.op_rename_sync({ ops.op_rename_sync(
oldpath: pathFromURL(oldpath), pathFromURL(oldpath),
newpath: pathFromURL(newpath), pathFromURL(newpath),
}); );
} }
async function rename(oldpath, newpath) { async function rename(oldpath, newpath) {
await core.opAsync("op_rename_async", { await core.opAsync(
oldpath: pathFromURL(oldpath), "op_rename_async",
newpath: pathFromURL(newpath), 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) { function parseFileInfo(response) {
const unix = build.os === "darwin" || build.os === "linux"; const unix = build.os === "darwin" || build.os === "linux";
return { return {
@ -185,9 +266,9 @@
isDirectory: response.isDirectory, isDirectory: response.isDirectory,
isSymlink: response.isSymlink, isSymlink: response.isSymlink,
size: response.size, size: response.size,
mtime: response.mtime != null ? new Date(response.mtime) : null, mtime: response.mtimeSet !== null ? new Date(response.mtime) : null,
atime: response.atime != null ? new Date(response.atime) : null, atime: response.atimeSet !== null ? new Date(response.atime) : null,
birthtime: response.birthtime != null birthtime: response.birthtimeSet !== null
? new Date(response.birthtime) ? new Date(response.birthtime)
: null, : null,
// Only non-null if on Unix // Only non-null if on Unix
@ -204,7 +285,8 @@
} }
function fstatSync(rid) { function fstatSync(rid) {
return parseFileInfo(ops.op_fstat_sync(rid)); ops.op_fstat_sync(rid, statBuf);
return statStruct(statBuf);
} }
async function fstat(rid) { async function fstat(rid) {
@ -220,11 +302,12 @@
} }
function lstatSync(path) { function lstatSync(path) {
const res = ops.op_stat_sync({ ops.op_stat_sync(
path: pathFromURL(path), pathFromURL(path),
lstat: true, true,
}); statBuf,
return parseFileInfo(res); );
return statStruct(statBuf);
} }
async function stat(path) { async function stat(path) {
@ -236,11 +319,12 @@
} }
function statSync(path) { function statSync(path) {
const res = ops.op_stat_sync({ ops.op_stat_sync(
path: pathFromURL(path), pathFromURL(path),
lstat: false, false,
}); statBuf,
return parseFileInfo(res); );
return statStruct(statBuf);
} }
function coerceLen(len) { function coerceLen(len) {
@ -252,19 +336,19 @@
} }
function ftruncateSync(rid, len) { function ftruncateSync(rid, len) {
ops.op_ftruncate_sync({ rid, len: coerceLen(len) }); ops.op_ftruncate_sync(rid, coerceLen(len));
} }
async function ftruncate(rid, 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) { function truncateSync(path, len) {
ops.op_truncate_sync({ path, len: coerceLen(len) }); ops.op_truncate_sync(path, coerceLen(len));
} }
async function truncate(path, 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) { function umask(mask) {
@ -272,11 +356,11 @@
} }
function linkSync(oldpath, newpath) { function linkSync(oldpath, newpath) {
ops.op_link_sync({ oldpath, newpath }); ops.op_link_sync(oldpath, newpath);
} }
async function link(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) { function toUnixTimeFromEpoch(value) {
@ -305,11 +389,9 @@
atime, atime,
mtime, mtime,
) { ) {
ops.op_futime_sync({ const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
rid, const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
atime: toUnixTimeFromEpoch(atime), ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec);
mtime: toUnixTimeFromEpoch(mtime),
});
} }
async function futime( async function futime(
@ -317,11 +399,16 @@
atime, atime,
mtime, mtime,
) { ) {
await core.opAsync("op_futime_async", { const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
await core.opAsync(
"op_futime_async",
rid, rid,
atime: toUnixTimeFromEpoch(atime), atimeSec,
mtime: toUnixTimeFromEpoch(mtime), atimeNsec,
}); mtimeSec,
mtimeNsec,
);
} }
function utimeSync( function utimeSync(
@ -329,11 +416,15 @@
atime, atime,
mtime, mtime,
) { ) {
ops.op_utime_sync({ const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
path: pathFromURL(path), const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
atime: toUnixTimeFromEpoch(atime), ops.op_utime_sync(
mtime: toUnixTimeFromEpoch(mtime), pathFromURL(path),
}); atimeSec,
atimeNsec,
mtimeSec,
mtimeNsec,
);
} }
async function utime( async function utime(
@ -341,11 +432,16 @@
atime, atime,
mtime, mtime,
) { ) {
await core.opAsync("op_utime_async", { const [atimeSec, atimeNsec] = toUnixTimeFromEpoch(atime);
path: pathFromURL(path), const [mtimeSec, mtimeNsec] = toUnixTimeFromEpoch(mtime);
atime: toUnixTimeFromEpoch(atime), await core.opAsync(
mtime: toUnixTimeFromEpoch(mtime), "op_utime_async",
}); pathFromURL(path),
atimeSec,
atimeNsec,
mtimeSec,
mtimeNsec,
);
} }
function symlinkSync( function symlinkSync(
@ -353,11 +449,11 @@
newpath, newpath,
options, options,
) { ) {
ops.op_symlink_sync({ ops.op_symlink_sync(
oldpath: pathFromURL(oldpath), pathFromURL(oldpath),
newpath: pathFromURL(newpath), pathFromURL(newpath),
options, options?.type,
}); );
} }
async function symlink( async function symlink(
@ -365,11 +461,12 @@
newpath, newpath,
options, options,
) { ) {
await core.opAsync("op_symlink_async", { await core.opAsync(
oldpath: pathFromURL(oldpath), "op_symlink_async",
newpath: pathFromURL(newpath), pathFromURL(oldpath),
options, pathFromURL(newpath),
}); options?.type,
);
} }
function fdatasyncSync(rid) { function fdatasyncSync(rid) {

View file

@ -394,11 +394,14 @@ async fn op_fsync_async(
fn op_fstat_sync( fn op_fstat_sync(
state: &mut OpState, state: &mut OpState,
rid: ResourceId, rid: ResourceId,
) -> Result<FsStat, AnyError> { out_buf: &mut [u32],
) -> Result<(), AnyError> {
let metadata = StdFileResource::with_file(state, rid, |std_file| { let metadata = StdFileResource::with_file(state, rid, |std_file| {
std_file.metadata().map_err(AnyError::from) std_file.metadata().map_err(AnyError::from)
})?; })?;
Ok(get_stat(metadata)) let stat = get_stat(metadata);
stat.write(out_buf);
Ok(())
} }
#[op] #[op]
@ -579,40 +582,34 @@ async fn op_mkdir_async(
.unwrap() .unwrap()
} }
#[derive(Deserialize)] #[op]
#[serde(rename_all = "camelCase")] fn op_chmod_sync(
pub struct ChmodArgs { state: &mut OpState,
path: String, path: String,
mode: u32, mode: u32,
} ) -> Result<(), AnyError> {
let path = Path::new(&path);
#[op] let mode = mode & 0o777;
fn op_chmod_sync(state: &mut OpState, args: ChmodArgs) -> Result<(), AnyError> {
let path = Path::new(&args.path);
let mode = args.mode & 0o777;
state.borrow_mut::<Permissions>().write.check(path)?; state.borrow_mut::<Permissions>().write.check(path)?;
debug!("op_chmod_sync {} {:o}", path.display(), mode);
raw_chmod(path, mode) raw_chmod(path, mode)
} }
#[op] #[op]
async fn op_chmod_async( async fn op_chmod_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: ChmodArgs, path: String,
mode: u32,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let path = Path::new(&args.path).to_path_buf(); let path = Path::new(&path).to_path_buf();
let mode = args.mode & 0o777; let mode = mode & 0o777;
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
state.borrow_mut::<Permissions>().write.check(&path)?; state.borrow_mut::<Permissions>().write.check(&path)?;
} }
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || raw_chmod(&path, mode))
debug!("op_chmod_async {} {:o}", path.display(), mode);
raw_chmod(&path, mode)
})
.await .await
.unwrap() .unwrap()
} }
@ -637,30 +634,21 @@ fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> {
} }
} }
#[derive(Deserialize)] #[op]
#[serde(rename_all = "camelCase")] fn op_chown_sync(
pub struct ChownArgs { state: &mut OpState,
path: String, path: String,
uid: Option<u32>, uid: Option<u32>,
gid: Option<u32>, gid: Option<u32>,
} ) -> Result<(), AnyError> {
let path = Path::new(&path).to_path_buf();
#[op]
fn op_chown_sync(state: &mut OpState, args: ChownArgs) -> Result<(), AnyError> {
let path = Path::new(&args.path).to_path_buf();
state.borrow_mut::<Permissions>().write.check(&path)?; state.borrow_mut::<Permissions>().write.check(&path)?;
debug!(
"op_chown_sync {} {:?} {:?}",
path.display(),
args.uid,
args.gid,
);
#[cfg(unix)] #[cfg(unix)]
{ {
use crate::errors::get_nix_error_class; use crate::errors::get_nix_error_class;
use nix::unistd::{chown, Gid, Uid}; use nix::unistd::{chown, Gid, Uid};
let nix_uid = args.uid.map(Uid::from_raw); let nix_uid = uid.map(Uid::from_raw);
let nix_gid = args.gid.map(Gid::from_raw); let nix_gid = gid.map(Gid::from_raw);
chown(&path, nix_uid, nix_gid).map_err(|err| { chown(&path, nix_uid, nix_gid).map_err(|err| {
custom_error( custom_error(
get_nix_error_class(&err), get_nix_error_class(&err),
@ -679,9 +667,11 @@ fn op_chown_sync(state: &mut OpState, args: ChownArgs) -> Result<(), AnyError> {
#[op] #[op]
async fn op_chown_async( async fn op_chown_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: ChownArgs, path: String,
uid: Option<u32>,
gid: Option<u32>,
) -> Result<(), AnyError> { ) -> 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(); let mut state = state.borrow_mut();
@ -689,18 +679,12 @@ async fn op_chown_async(
} }
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
debug!(
"op_chown_async {} {:?} {:?}",
path.display(),
args.uid,
args.gid,
);
#[cfg(unix)] #[cfg(unix)]
{ {
use crate::errors::get_nix_error_class; use crate::errors::get_nix_error_class;
use nix::unistd::{chown, Gid, Uid}; use nix::unistd::{chown, Gid, Uid};
let nix_uid = args.uid.map(Uid::from_raw); let nix_uid = uid.map(Uid::from_raw);
let nix_gid = args.gid.map(Gid::from_raw); let nix_gid = gid.map(Gid::from_raw);
chown(&path, nix_uid, nix_gid).map_err(|err| { chown(&path, nix_uid, nix_gid).map_err(|err| {
custom_error( custom_error(
get_nix_error_class(&err), get_nix_error_class(&err),
@ -717,20 +701,13 @@ async fn op_chown_async(
.unwrap() .unwrap()
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoveArgs {
path: String,
recursive: bool,
}
#[op] #[op]
fn op_remove_sync( fn op_remove_sync(
state: &mut OpState, state: &mut OpState,
args: RemoveArgs, path: String,
recursive: bool,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path); let path = PathBuf::from(&path);
let recursive = args.recursive;
state.borrow_mut::<Permissions>().write.check(&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)?; let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?;
debug!("op_remove_sync {} {}", path.display(), recursive);
let file_type = metadata.file_type(); let file_type = metadata.file_type();
if file_type.is_file() { if file_type.is_file() {
std::fs::remove_file(&path).map_err(err_mapper)?; std::fs::remove_file(&path).map_err(err_mapper)?;
@ -772,10 +748,10 @@ fn op_remove_sync(
#[op] #[op]
async fn op_remove_async( async fn op_remove_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: RemoveArgs, path: String,
recursive: bool,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path); let path = PathBuf::from(&path);
let recursive = args.recursive;
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
@ -820,36 +796,29 @@ async fn op_remove_async(
.unwrap() .unwrap()
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CopyFileArgs {
from: String,
to: String,
}
#[op] #[op]
fn op_copy_file_sync( fn op_copy_file_sync(
state: &mut OpState, state: &mut OpState,
args: CopyFileArgs, from: String,
to: String,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let from = PathBuf::from(&args.from); let from_path = PathBuf::from(&from);
let to = PathBuf::from(&args.to); let to_path = PathBuf::from(&to);
let permissions = state.borrow_mut::<Permissions>(); let permissions = state.borrow_mut::<Permissions>();
permissions.read.check(&from)?; permissions.read.check(&from_path)?;
permissions.write.check(&to)?; permissions.write.check(&to_path)?;
debug!("op_copy_file_sync {} {}", from.display(), to.display());
// On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput // On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
// See https://github.com/rust-lang/rust/issues/54800 // See https://github.com/rust-lang/rust/issues/54800
// Once the issue is resolved, we should remove this workaround. // 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( return Err(custom_error(
"NotFound", "NotFound",
format!( format!(
"File not found, copy '{}' -> '{}'", "File not found, copy '{}' -> '{}'",
from.display(), from_path.display(),
to.display() to_path.display()
), ),
)); ));
} }
@ -857,21 +826,80 @@ fn op_copy_file_sync(
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new( Error::new(
err.kind(), 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) // 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(()) Ok(())
} }
#[op] #[op]
async fn op_copy_file_async( async fn op_copy_file_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: CopyFileArgs, from: String,
to: String,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let from = PathBuf::from(&args.from); let from = PathBuf::from(&from);
let to = PathBuf::from(&args.to); let to = PathBuf::from(&to);
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
@ -880,7 +908,6 @@ async fn op_copy_file_async(
permissions.write.check(&to)?; permissions.write.check(&to)?;
} }
debug!("op_copy_file_async {} {}", from.display(), to.display());
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
// On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput // On *nix, Rust reports non-existent `from` as ErrorKind::InvalidInput
// See https://github.com/rust-lang/rust/issues/54800 // See https://github.com/rust-lang/rust/issues/54800
@ -910,30 +937,57 @@ async fn op_copy_file_async(
.unwrap() .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 { match maybe_time {
Ok(time) => { Ok(time) => (
let msec = time time
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.map(|t| t.as_millis() as u64) .map(|t| t.as_millis() as u64)
.unwrap_or_else(|err| err.duration().as_millis() as u64); .unwrap_or_else(|err| err.duration().as_millis() as u64),
Some(msec) true,
),
Err(_) => (0, false),
} }
Err(_) => None, }
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)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct $name {
$($field: $type),*
}
};
}
create_struct_writer! {
pub struct FsStat { pub struct FsStat {
is_file: bool, is_file: bool,
is_directory: bool, is_directory: bool,
is_symlink: bool, is_symlink: bool,
size: u64, size: u64,
// In milliseconds, like JavaScript. Available on both Unix or Windows. // In milliseconds, like JavaScript. Available on both Unix or Windows.
mtime: Option<u64>, mtime_set: bool,
atime: Option<u64>, mtime: u64,
birthtime: Option<u64>, atime_set: bool,
atime: u64,
birthtime_set: bool,
birthtime: u64,
// Following are only valid under Unix. // Following are only valid under Unix.
dev: u64, dev: u64,
ino: u64, ino: u64,
@ -945,6 +999,7 @@ pub struct FsStat {
blksize: u64, blksize: u64,
blocks: u64, blocks: u64,
} }
}
#[inline(always)] #[inline(always)]
fn get_stat(metadata: std::fs::Metadata) -> FsStat { fn get_stat(metadata: std::fs::Metadata) -> FsStat {
@ -964,15 +1019,22 @@ fn get_stat(metadata: std::fs::Metadata) -> FsStat {
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::MetadataExt; 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 { FsStat {
is_file: metadata.is_file(), is_file: metadata.is_file(),
is_directory: metadata.is_dir(), is_directory: metadata.is_dir(),
is_symlink: metadata.file_type().is_symlink(), is_symlink: metadata.file_type().is_symlink(),
size: metadata.len(), size: metadata.len(),
// In milliseconds, like JavaScript. Available on both Unix or Windows. // In milliseconds, like JavaScript. Available on both Unix or Windows.
mtime: to_msec(metadata.modified()), mtime_set,
atime: to_msec(metadata.accessed()), mtime,
birthtime: to_msec(metadata.created()), atime_set,
atime,
birthtime_set,
birthtime,
// Following are only valid under Unix. // Following are only valid under Unix.
dev: usm!(dev), dev: usm!(dev),
ino: usm!(ino), ino: usm!(ino),
@ -996,12 +1058,12 @@ pub struct StatArgs {
#[op] #[op]
fn op_stat_sync( fn op_stat_sync(
state: &mut OpState, state: &mut OpState,
args: StatArgs, path: String,
) -> Result<FsStat, AnyError> { lstat: bool,
let path = PathBuf::from(&args.path); out_buf: &mut [u32],
let lstat = args.lstat; ) -> Result<(), AnyError> {
let path = PathBuf::from(&path);
state.borrow_mut::<Permissions>().read.check(&path)?; state.borrow_mut::<Permissions>().read.check(&path)?;
debug!("op_stat_sync {} {}", path.display(), lstat);
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new(err.kind(), format!("{}, stat '{}'", err, path.display())) Error::new(err.kind(), format!("{}, stat '{}'", err, path.display()))
}; };
@ -1010,7 +1072,11 @@ fn op_stat_sync(
} else { } else {
std::fs::metadata(&path).map_err(err_mapper)? std::fs::metadata(&path).map_err(err_mapper)?
}; };
Ok(get_stat(metadata))
let stat = get_stat(metadata);
stat.write(out_buf);
Ok(())
} }
#[op] #[op]
@ -1185,26 +1251,20 @@ async fn op_read_dir_async(
.unwrap() .unwrap()
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RenameArgs {
oldpath: String,
newpath: String,
}
#[op] #[op]
fn op_rename_sync( fn op_rename_sync(
state: &mut OpState, state: &mut OpState,
args: RenameArgs, oldpath: String,
newpath: String,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath); let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&args.newpath); let newpath = PathBuf::from(&newpath);
let permissions = state.borrow_mut::<Permissions>(); let permissions = state.borrow_mut::<Permissions>();
permissions.read.check(&oldpath)?; permissions.read.check(&oldpath)?;
permissions.write.check(&oldpath)?; permissions.write.check(&oldpath)?;
permissions.write.check(&newpath)?; permissions.write.check(&newpath)?;
debug!("op_rename_sync {} {}", oldpath.display(), newpath.display());
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new( Error::new(
err.kind(), err.kind(),
@ -1223,10 +1283,11 @@ fn op_rename_sync(
#[op] #[op]
async fn op_rename_async( async fn op_rename_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: RenameArgs, oldpath: String,
newpath: String,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath); let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&args.newpath); let newpath = PathBuf::from(&newpath);
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<Permissions>(); let permissions = state.borrow_mut::<Permissions>();
@ -1235,11 +1296,6 @@ async fn op_rename_async(
permissions.write.check(&newpath)?; permissions.write.check(&newpath)?;
} }
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
debug!(
"op_rename_async {} {}",
oldpath.display(),
newpath.display()
);
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new( Error::new(
err.kind(), err.kind(),
@ -1258,17 +1314,14 @@ async fn op_rename_async(
.unwrap() .unwrap()
} }
#[derive(Deserialize)] #[op]
#[serde(rename_all = "camelCase")] fn op_link_sync(
pub struct LinkArgs { state: &mut OpState,
oldpath: String, oldpath: String,
newpath: String, newpath: String,
} ) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&oldpath);
#[op] let newpath = PathBuf::from(&newpath);
fn op_link_sync(state: &mut OpState, args: LinkArgs) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath);
let newpath = PathBuf::from(&args.newpath);
let permissions = state.borrow_mut::<Permissions>(); let permissions = state.borrow_mut::<Permissions>();
permissions.read.check(&oldpath)?; permissions.read.check(&oldpath)?;
@ -1276,7 +1329,6 @@ fn op_link_sync(state: &mut OpState, args: LinkArgs) -> Result<(), AnyError> {
permissions.read.check(&newpath)?; permissions.read.check(&newpath)?;
permissions.write.check(&newpath)?; permissions.write.check(&newpath)?;
debug!("op_link_sync {} {}", oldpath.display(), newpath.display());
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new( Error::new(
err.kind(), err.kind(),
@ -1295,10 +1347,11 @@ fn op_link_sync(state: &mut OpState, args: LinkArgs) -> Result<(), AnyError> {
#[op] #[op]
async fn op_link_async( async fn op_link_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: LinkArgs, oldpath: String,
newpath: String,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath); let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&args.newpath); let newpath = PathBuf::from(&newpath);
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
@ -1310,7 +1363,6 @@ async fn op_link_async(
} }
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
debug!("op_link_async {} {}", oldpath.display(), newpath.display());
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new( Error::new(
err.kind(), err.kind(),
@ -1329,38 +1381,19 @@ async fn op_link_async(
.unwrap() .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] #[op]
fn op_symlink_sync( fn op_symlink_sync(
state: &mut OpState, state: &mut OpState,
args: SymlinkArgs, oldpath: String,
newpath: String,
_type: Option<String>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath); let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&args.newpath); let newpath = PathBuf::from(&newpath);
state.borrow_mut::<Permissions>().write.check_all()?; state.borrow_mut::<Permissions>().write.check_all()?;
state.borrow_mut::<Permissions>().read.check_all()?; state.borrow_mut::<Permissions>().read.check_all()?;
debug!(
"op_symlink_sync {} {}",
oldpath.display(),
newpath.display()
);
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new( Error::new(
err.kind(), err.kind(),
@ -1382,8 +1415,8 @@ fn op_symlink_sync(
{ {
use std::os::windows::fs::{symlink_dir, symlink_file}; use std::os::windows::fs::{symlink_dir, symlink_file};
match args.options { match _type {
Some(options) => match options._type.as_ref() { Some(ty) => match ty.as_ref() {
"file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?, "file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
"dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?, "dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
_ => return Err(type_error("unsupported type")), _ => return Err(type_error("unsupported type")),
@ -1409,10 +1442,12 @@ fn op_symlink_sync(
#[op] #[op]
async fn op_symlink_async( async fn op_symlink_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: SymlinkArgs, oldpath: String,
newpath: String,
_type: Option<String>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let oldpath = PathBuf::from(&args.oldpath); let oldpath = PathBuf::from(&oldpath);
let newpath = PathBuf::from(&args.newpath); let newpath = PathBuf::from(&newpath);
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
@ -1421,7 +1456,6 @@ async fn op_symlink_async(
} }
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
debug!("op_symlink_async {} {}", oldpath.display(), newpath.display());
let err_mapper = |err: Error| { let err_mapper = |err: Error| {
Error::new( Error::new(
err.kind(), err.kind(),
@ -1443,8 +1477,8 @@ async fn op_symlink_async(
{ {
use std::os::windows::fs::{symlink_dir, symlink_file}; use std::os::windows::fs::{symlink_dir, symlink_file};
match args.options { match _type {
Some(options) => match options._type.as_ref() { Some(ty) => match ty.as_ref() {
"file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?, "file" => symlink_file(&oldpath, &newpath).map_err(err_mapper)?,
"dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?, "dir" => symlink_dir(&oldpath, &newpath).map_err(err_mapper)?,
_ => return Err(type_error("unsupported type")), _ => return Err(type_error("unsupported type")),
@ -1521,20 +1555,13 @@ async fn op_read_link_async(
.unwrap() .unwrap()
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FtruncateArgs {
rid: ResourceId,
len: i32,
}
#[op] #[op]
fn op_ftruncate_sync( fn op_ftruncate_sync(
state: &mut OpState, state: &mut OpState,
args: FtruncateArgs, rid: u32,
len: i32,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let rid = args.rid; let len = len as u64;
let len = args.len as u64;
StdFileResource::with_file(state, rid, |std_file| { StdFileResource::with_file(state, rid, |std_file| {
std_file.set_len(len).map_err(AnyError::from) std_file.set_len(len).map_err(AnyError::from)
})?; })?;
@ -1544,10 +1571,10 @@ fn op_ftruncate_sync(
#[op] #[op]
async fn op_ftruncate_async( async fn op_ftruncate_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: FtruncateArgs, rid: ResourceId,
len: i32,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let rid = args.rid; let len = len as u64;
let len = args.len as u64;
StdFileResource::with_file_blocking_task(state, rid, move |std_file| { StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
std_file.set_len(len)?; std_file.set_len(len)?;
@ -1556,20 +1583,13 @@ async fn op_ftruncate_async(
.await .await
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TruncateArgs {
path: String,
len: u64,
}
#[op] #[op]
fn op_truncate_sync( fn op_truncate_sync(
state: &mut OpState, state: &mut OpState,
args: TruncateArgs, path: String,
len: u64,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path); let path = PathBuf::from(&path);
let len = args.len;
state.borrow_mut::<Permissions>().write.check(&path)?; state.borrow_mut::<Permissions>().write.check(&path)?;
@ -1591,10 +1611,11 @@ fn op_truncate_sync(
#[op] #[op]
async fn op_truncate_async( async fn op_truncate_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: TruncateArgs, path: String,
len: u64,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let path = PathBuf::from(&args.path); let path = PathBuf::from(&path);
let len = args.len;
{ {
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
state.borrow_mut::<Permissions>().write.check(&path)?; state.borrow_mut::<Permissions>().write.check(&path)?;
@ -1797,23 +1818,18 @@ async fn op_make_temp_file_async(
.unwrap() .unwrap()
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FutimeArgs {
rid: ResourceId,
atime: (i64, u32),
mtime: (i64, u32),
}
#[op] #[op]
fn op_futime_sync( fn op_futime_sync(
state: &mut OpState, state: &mut OpState,
args: FutimeArgs, rid: ResourceId,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
super::check_unstable(state, "Deno.futimeSync"); super::check_unstable(state, "Deno.futimeSync");
let rid = args.rid; let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1);
StdFileResource::with_file(state, rid, |std_file| { StdFileResource::with_file(state, rid, |std_file| {
filetime::set_file_handle_times(std_file, Some(atime), Some(mtime)) filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))
@ -1826,12 +1842,15 @@ fn op_futime_sync(
#[op] #[op]
async fn op_futime_async( async fn op_futime_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: FutimeArgs, rid: ResourceId,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
super::check_unstable2(&state, "Deno.futime"); super::check_unstable2(&state, "Deno.futime");
let rid = args.rid; let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1);
StdFileResource::with_file_blocking_task(state, rid, move |std_file| { StdFileResource::with_file_blocking_task(state, rid, move |std_file| {
filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))?; filetime::set_file_handle_times(std_file, Some(atime), Some(mtime))?;
@ -1840,21 +1859,20 @@ async fn op_futime_async(
.await .await
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UtimeArgs {
path: String,
atime: (i64, u32),
mtime: (i64, u32),
}
#[op] #[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"); super::check_unstable(state, "Deno.utime");
let path = PathBuf::from(&args.path); let path = PathBuf::from(&path);
let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
state.borrow_mut::<Permissions>().write.check(&path)?; state.borrow_mut::<Permissions>().write.check(&path)?;
filetime::set_file_times(&path, atime, mtime).map_err(|err| { 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] #[op]
async fn op_utime_async( async fn op_utime_async(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
args: UtimeArgs, path: String,
atime_secs: i64,
atime_nanos: u32,
mtime_secs: i64,
mtime_nanos: u32,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
super::check_unstable(&state.borrow(), "Deno.utime"); super::check_unstable(&state.borrow(), "Deno.utime");
let path = PathBuf::from(&args.path); let path = PathBuf::from(&path);
let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
state state
.borrow_mut() .borrow_mut()

View file

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