diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2023-03-07 05:13:44 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-07 18:13:44 +0900 |
commit | fe368b72c1dd2f2d17b7b7e5965b9e3d9ca61c35 (patch) | |
tree | ebc76fb86e3bee1baf1e336feb36a507affa7316 /runtime | |
parent | 64354f41125642d420d80cbf617dfb8ddca398e5 (diff) |
refactor: Add "deno_fs" extension crate (#18040)
This commit factors out APIs related to file system from "runtime/"
to a separate "deno_fs" extension crate.
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/Cargo.toml | 2 | ||||
-rw-r--r-- | runtime/build.rs | 38 | ||||
-rw-r--r-- | runtime/js/30_fs.js | 899 | ||||
-rw-r--r-- | runtime/js/40_process.js | 2 | ||||
-rw-r--r-- | runtime/js/90_deno_ns.js | 2 | ||||
-rw-r--r-- | runtime/lib.rs | 1 | ||||
-rw-r--r-- | runtime/ops/fs.rs | 2214 | ||||
-rw-r--r-- | runtime/ops/mod.rs | 1 | ||||
-rw-r--r-- | runtime/permissions/mod.rs | 2 | ||||
-rw-r--r-- | runtime/web_worker.rs | 2 | ||||
-rw-r--r-- | runtime/worker.rs | 2 |
11 files changed, 45 insertions, 3120 deletions
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 5a4028349..112156203 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -42,6 +42,7 @@ deno_crypto.workspace = true deno_fetch.workspace = true deno_ffi.workspace = true deno_flash.workspace = true +deno_fs.workspace = true deno_http.workspace = true deno_io.workspace = true deno_net.workspace = true @@ -70,6 +71,7 @@ deno_crypto.workspace = true deno_fetch.workspace = true deno_ffi.workspace = true deno_flash.workspace = true +deno_fs.workspace = true deno_http.workspace = true deno_io.workspace = true deno_napi.workspace = true diff --git a/runtime/build.rs b/runtime/build.rs index 76c0534bf..166421586 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -165,6 +165,41 @@ mod startup_snapshot { } } + impl deno_fs::FsPermissions for Permissions { + fn check_read( + &mut self, + _path: &Path, + _api_name: &str, + ) -> Result<(), AnyError> { + unreachable!("snapshotting!") + } + + fn check_read_blind( + &mut self, + _path: &Path, + _display: &str, + _api_name: &str, + ) -> Result<(), AnyError> { + unreachable!("snapshotting!") + } + + fn check_write( + &mut self, + _path: &Path, + _api_name: &str, + ) -> Result<(), AnyError> { + unreachable!("snapshotting!") + } + + fn check_read_all(&mut self, _api_name: &str) -> Result<(), AnyError> { + unreachable!("snapshotting!") + } + + fn check_write_all(&mut self, _api_name: &str) -> Result<(), AnyError> { + unreachable!("snapshotting!") + } + } + fn create_runtime_snapshot( snapshot_path: PathBuf, maybe_additional_extension: Option<Extension>, @@ -191,6 +226,7 @@ mod startup_snapshot { "deno_http", "deno_flash", "deno_io", + "deno_fs", ]) .esm(include_js_files!( dir "js", @@ -200,7 +236,6 @@ mod startup_snapshot { "10_permissions.js", "11_workers.js", "13_buffer.js", - "30_fs.js", "30_os.js", "40_fs_events.js", "40_http.js", @@ -240,6 +275,7 @@ mod startup_snapshot { deno_napi::init::<Permissions>(), deno_http::init(), deno_io::init(Default::default()), + deno_fs::init::<Permissions>(false), deno_flash::init::<Permissions>(false), // No --unstable runtime_extension, // FIXME(bartlomieju): these extensions are specified last, because they diff --git a/runtime/js/30_fs.js b/runtime/js/30_fs.js deleted file mode 100644 index 19e7f372b..000000000 --- a/runtime/js/30_fs.js +++ /dev/null @@ -1,899 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -const core = globalThis.Deno.core; -const ops = core.ops; -const primordials = globalThis.__bootstrap.primordials; -const { - ArrayPrototypeFilter, - Date, - DatePrototype, - Error, - Function, - MathTrunc, - ObjectEntries, - ObjectPrototypeIsPrototypeOf, - ObjectValues, - SymbolAsyncIterator, - SymbolIterator, - Uint32Array, -} = primordials; -import { read, readSync, write, writeSync } from "internal:deno_io/12_io.js"; -import * as abortSignal from "internal:deno_web/03_abort_signal.js"; -import { - readableStreamForRid, - ReadableStreamPrototype, - writableStreamForRid, -} from "internal:deno_web/06_streams.js"; -import { pathFromURL } from "internal:deno_web/00_infra.js"; - -function chmodSync(path, mode) { - ops.op_chmod_sync(pathFromURL(path), mode); -} - -async function chmod(path, mode) { - await core.opAsync("op_chmod_async", pathFromURL(path), mode); -} - -function chownSync( - path, - uid, - gid, -) { - ops.op_chown_sync(pathFromURL(path), uid, gid); -} - -async function chown( - path, - uid, - gid, -) { - await core.opAsync( - "op_chown_async", - pathFromURL(path), - uid, - gid, - ); -} - -function copyFileSync( - fromPath, - toPath, -) { - ops.op_copy_file_sync( - pathFromURL(fromPath), - pathFromURL(toPath), - ); -} - -async function copyFile( - fromPath, - toPath, -) { - await core.opAsync( - "op_copy_file_async", - pathFromURL(fromPath), - pathFromURL(toPath), - ); -} - -function cwd() { - return ops.op_cwd(); -} - -function chdir(directory) { - ops.op_chdir(pathFromURL(directory)); -} - -function makeTempDirSync(options = {}) { - return ops.op_make_temp_dir_sync(options); -} - -function makeTempDir(options = {}) { - return core.opAsync("op_make_temp_dir_async", options); -} - -function makeTempFileSync(options = {}) { - return ops.op_make_temp_file_sync(options); -} - -function makeTempFile(options = {}) { - return core.opAsync("op_make_temp_file_async", options); -} - -function mkdirArgs(path, options) { - const args = { path: pathFromURL(path), recursive: false }; - if (options != null) { - if (typeof options.recursive == "boolean") { - args.recursive = options.recursive; - } - if (options.mode) { - args.mode = options.mode; - } - } - return args; -} - -function mkdirSync(path, options) { - ops.op_mkdir_sync(mkdirArgs(path, options)); -} - -async function mkdir( - path, - options, -) { - await core.opAsync("op_mkdir_async", mkdirArgs(path, options)); -} - -function readDirSync(path) { - return ops.op_read_dir_sync(pathFromURL(path))[ - SymbolIterator - ](); -} - -function readDir(path) { - const array = core.opAsync( - "op_read_dir_async", - pathFromURL(path), - ); - return { - async *[SymbolAsyncIterator]() { - yield* await array; - }, - }; -} - -function readLinkSync(path) { - return ops.op_read_link_sync(pathFromURL(path)); -} - -function readLink(path) { - return core.opAsync("op_read_link_async", pathFromURL(path)); -} - -function realPathSync(path) { - return ops.op_realpath_sync(pathFromURL(path)); -} - -function realPath(path) { - return core.opAsync("op_realpath_async", pathFromURL(path)); -} - -function removeSync( - path, - options = {}, -) { - ops.op_remove_sync( - pathFromURL(path), - !!options.recursive, - ); -} - -async function remove( - path, - options = {}, -) { - await core.opAsync( - "op_remove_async", - pathFromURL(path), - !!options.recursive, - ); -} - -function renameSync(oldpath, newpath) { - ops.op_rename_sync( - pathFromURL(oldpath), - pathFromURL(newpath), - ); -} - -async function rename(oldpath, 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 {'; - const typeEntries = ObjectEntries(types); - for (let i = 0; i < typeEntries.length; ++i) { - let { 0: name, 1: type } = typeEntries[i]; - - 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 { 0: statStruct, 1: 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 = core.build.os === "darwin" || core.build.os === "linux"; - return { - isFile: response.isFile, - isDirectory: response.isDirectory, - isSymlink: response.isSymlink, - size: response.size, - 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 - dev: unix ? response.dev : null, - ino: unix ? response.ino : null, - mode: unix ? response.mode : null, - nlink: unix ? response.nlink : null, - uid: unix ? response.uid : null, - gid: unix ? response.gid : null, - rdev: unix ? response.rdev : null, - blksize: unix ? response.blksize : null, - blocks: unix ? response.blocks : null, - }; -} - -function fstatSync(rid) { - ops.op_fstat_sync(rid, statBuf); - return statStruct(statBuf); -} - -async function fstat(rid) { - return parseFileInfo(await core.opAsync("op_fstat_async", rid)); -} - -async function lstat(path) { - const res = await core.opAsync("op_stat_async", { - path: pathFromURL(path), - lstat: true, - }); - return parseFileInfo(res); -} - -function lstatSync(path) { - ops.op_stat_sync( - pathFromURL(path), - true, - statBuf, - ); - return statStruct(statBuf); -} - -async function stat(path) { - const res = await core.opAsync("op_stat_async", { - path: pathFromURL(path), - lstat: false, - }); - return parseFileInfo(res); -} - -function statSync(path) { - ops.op_stat_sync( - pathFromURL(path), - false, - statBuf, - ); - return statStruct(statBuf); -} - -function coerceLen(len) { - if (len == null || len < 0) { - return 0; - } - - return len; -} - -function ftruncateSync(rid, len) { - ops.op_ftruncate_sync(rid, coerceLen(len)); -} - -async function ftruncate(rid, len) { - await core.opAsync("op_ftruncate_async", rid, coerceLen(len)); -} - -function truncateSync(path, len) { - ops.op_truncate_sync(path, coerceLen(len)); -} - -async function truncate(path, len) { - await core.opAsync("op_truncate_async", path, coerceLen(len)); -} - -function umask(mask) { - return ops.op_umask(mask); -} - -function linkSync(oldpath, newpath) { - ops.op_link_sync(oldpath, newpath); -} - -async function link(oldpath, newpath) { - await core.opAsync("op_link_async", oldpath, newpath); -} - -function toUnixTimeFromEpoch(value) { - if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { - const time = value.valueOf(); - const seconds = MathTrunc(time / 1e3); - const nanoseconds = MathTrunc(time - (seconds * 1e3)) * 1e6; - - return [ - seconds, - nanoseconds, - ]; - } - - const seconds = value; - const nanoseconds = 0; - - return [ - seconds, - nanoseconds, - ]; -} - -function futimeSync( - rid, - atime, - mtime, -) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec); -} - -async function futime( - rid, - atime, - mtime, -) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - await core.opAsync( - "op_futime_async", - rid, - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); -} - -function utimeSync( - path, - atime, - mtime, -) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - ops.op_utime_sync( - pathFromURL(path), - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); -} - -async function utime( - path, - atime, - mtime, -) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - await core.opAsync( - "op_utime_async", - pathFromURL(path), - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); -} - -function symlinkSync( - oldpath, - newpath, - options, -) { - ops.op_symlink_sync( - pathFromURL(oldpath), - pathFromURL(newpath), - options?.type, - ); -} - -async function symlink( - oldpath, - newpath, - options, -) { - await core.opAsync( - "op_symlink_async", - pathFromURL(oldpath), - pathFromURL(newpath), - options?.type, - ); -} - -function fdatasyncSync(rid) { - ops.op_fdatasync_sync(rid); -} - -async function fdatasync(rid) { - await core.opAsync("op_fdatasync_async", rid); -} - -function fsyncSync(rid) { - ops.op_fsync_sync(rid); -} - -async function fsync(rid) { - await core.opAsync("op_fsync_async", rid); -} - -function flockSync(rid, exclusive) { - ops.op_flock_sync(rid, exclusive === true); -} - -async function flock(rid, exclusive) { - await core.opAsync("op_flock_async", rid, exclusive === true); -} - -function funlockSync(rid) { - ops.op_funlock_sync(rid); -} - -async function funlock(rid) { - await core.opAsync("op_funlock_async", rid); -} - -function seekSync( - rid, - offset, - whence, -) { - return ops.op_seek_sync({ rid, offset, whence }); -} - -function seek( - rid, - offset, - whence, -) { - return core.opAsync("op_seek_async", { rid, offset, whence }); -} - -function openSync( - path, - options, -) { - if (options) checkOpenOptions(options); - const mode = options?.mode; - const rid = ops.op_open_sync( - pathFromURL(path), - options, - mode, - ); - - return new FsFile(rid); -} - -async function open( - path, - options, -) { - if (options) checkOpenOptions(options); - const mode = options?.mode; - const rid = await core.opAsync( - "op_open_async", - pathFromURL(path), - options, - mode, - ); - - return new FsFile(rid); -} - -function createSync(path) { - return openSync(path, { - read: true, - write: true, - truncate: true, - create: true, - }); -} - -function create(path) { - return open(path, { - read: true, - write: true, - truncate: true, - create: true, - }); -} - -class FsFile { - #rid = 0; - - #readable; - #writable; - - constructor(rid) { - this.#rid = rid; - } - - get rid() { - return this.#rid; - } - - write(p) { - return write(this.rid, p); - } - - writeSync(p) { - return writeSync(this.rid, p); - } - - truncate(len) { - return ftruncate(this.rid, len); - } - - truncateSync(len) { - return ftruncateSync(this.rid, len); - } - - read(p) { - return read(this.rid, p); - } - - readSync(p) { - return readSync(this.rid, p); - } - - seek(offset, whence) { - return seek(this.rid, offset, whence); - } - - seekSync(offset, whence) { - return seekSync(this.rid, offset, whence); - } - - stat() { - return fstat(this.rid); - } - - statSync() { - return fstatSync(this.rid); - } - - close() { - core.close(this.rid); - } - - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRid(this.rid); - } - return this.#readable; - } - - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } -} - -function checkOpenOptions(options) { - if ( - ArrayPrototypeFilter( - ObjectValues(options), - (val) => val === true, - ).length === 0 - ) { - throw new Error("OpenOptions requires at least one option to be true"); - } - - if (options.truncate && !options.write) { - throw new Error("'truncate' option requires 'write' option"); - } - - const createOrCreateNewWithoutWriteOrAppend = - (options.create || options.createNew) && - !(options.write || options.append); - - if (createOrCreateNewWithoutWriteOrAppend) { - throw new Error( - "'create' or 'createNew' options require 'write' or 'append' option", - ); - } -} - -const File = FsFile; - -function readFileSync(path) { - return ops.op_readfile_sync(pathFromURL(path)); -} - -async function readFile(path, options) { - let cancelRid; - let abortHandler; - if (options?.signal) { - options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } - - try { - const read = await core.opAsync( - "op_readfile_async", - pathFromURL(path), - cancelRid, - ); - return read; - } finally { - if (options?.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } - } -} - -function readTextFileSync(path) { - return ops.op_readfile_text_sync(pathFromURL(path)); -} - -async function readTextFile(path, options) { - let cancelRid; - let abortHandler; - if (options?.signal) { - options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } - - try { - const read = await core.opAsync( - "op_readfile_text_async", - pathFromURL(path), - cancelRid, - ); - return read; - } finally { - if (options?.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } - } -} - -function writeFileSync( - path, - data, - options = {}, -) { - options.signal?.throwIfAborted(); - ops.op_write_file_sync( - pathFromURL(path), - options.mode, - options.append ?? false, - options.create ?? true, - options.createNew ?? false, - data, - ); -} - -async function writeFile( - path, - data, - options = {}, -) { - let cancelRid; - let abortHandler; - if (options.signal) { - options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } - try { - if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { - const file = await open(path, { - mode: options.mode, - append: options.append ?? false, - create: options.create ?? true, - createNew: options.createNew ?? false, - write: true, - }); - await data.pipeTo(file.writable, { - signal: options.signal, - }); - } else { - await core.opAsync( - "op_write_file_async", - pathFromURL(path), - options.mode, - options.append ?? false, - options.create ?? true, - options.createNew ?? false, - data, - cancelRid, - ); - } - } finally { - if (options.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } - } -} - -function writeTextFileSync( - path, - data, - options = {}, -) { - const encoder = new TextEncoder(); - return writeFileSync(path, encoder.encode(data), options); -} - -function writeTextFile( - path, - data, - options = {}, -) { - if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { - return writeFile( - path, - data.pipeThrough(new TextEncoderStream()), - options, - ); - } else { - const encoder = new TextEncoder(); - return writeFile(path, encoder.encode(data), options); - } -} - -export { - chdir, - chmod, - chmodSync, - chown, - chownSync, - copyFile, - copyFileSync, - create, - createSync, - cwd, - fdatasync, - fdatasyncSync, - File, - flock, - flockSync, - FsFile, - fstat, - fstatSync, - fsync, - fsyncSync, - ftruncate, - ftruncateSync, - funlock, - funlockSync, - futime, - futimeSync, - link, - linkSync, - lstat, - lstatSync, - makeTempDir, - makeTempDirSync, - makeTempFile, - makeTempFileSync, - mkdir, - mkdirSync, - open, - openSync, - readDir, - readDirSync, - readFile, - readFileSync, - readLink, - readLinkSync, - readTextFile, - readTextFileSync, - realPath, - realPathSync, - remove, - removeSync, - rename, - renameSync, - seek, - seekSync, - stat, - statSync, - symlink, - symlinkSync, - truncate, - truncateSync, - umask, - utime, - utimeSync, - writeFile, - writeFileSync, - writeTextFile, - writeTextFileSync, -}; diff --git a/runtime/js/40_process.js b/runtime/js/40_process.js index 28bb2870c..661f972df 100644 --- a/runtime/js/40_process.js +++ b/runtime/js/40_process.js @@ -16,7 +16,7 @@ const { SymbolFor, Symbol, } = primordials; -import { FsFile } from "internal:runtime/30_fs.js"; +import { FsFile } from "internal:deno_fs/30_fs.js"; import { readAll } from "internal:deno_io/12_io.js"; import { assert, pathFromURL } from "internal:deno_web/00_infra.js"; import * as abortSignal from "internal:deno_web/03_abort_signal.js"; diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index 45db05292..93d327787 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -15,7 +15,7 @@ import * as version from "internal:runtime/01_version.ts"; import * as permissions from "internal:runtime/10_permissions.js"; import * as io from "internal:deno_io/12_io.js"; import * as buffer from "internal:runtime/13_buffer.js"; -import * as fs from "internal:runtime/30_fs.js"; +import * as fs from "internal:deno_fs/30_fs.js"; import * as os from "internal:runtime/30_os.js"; import * as fsEvents from "internal:runtime/40_fs_events.js"; import * as process from "internal:runtime/40_process.js"; diff --git a/runtime/lib.rs b/runtime/lib.rs index 6bb84698d..97034bca9 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -8,6 +8,7 @@ pub use deno_crypto; pub use deno_fetch; pub use deno_ffi; pub use deno_flash; +pub use deno_fs; pub use deno_http; pub use deno_io; pub use deno_napi; diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs deleted file mode 100644 index 546d1d68a..000000000 --- a/runtime/ops/fs.rs +++ /dev/null @@ -1,2214 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Some deserializer fields are only used on Unix and Windows build fails without it -use super::utils::into_string; -use crate::fs_util::canonicalize_path; -use deno_core::error::custom_error; -use deno_core::error::type_error; -use deno_core::error::AnyError; -use deno_core::op; -use deno_core::CancelFuture; -use deno_core::CancelHandle; -use deno_core::Extension; -use deno_core::OpState; -use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; -use deno_crypto::rand::thread_rng; -use deno_crypto::rand::Rng; -use deno_io::StdFileResource; -use log::debug; -use serde::Deserialize; -use serde::Serialize; -use std::borrow::Cow; -use std::cell::RefCell; -use std::convert::From; -use std::env::current_dir; -use std::env::set_current_dir; -use std::env::temp_dir; -use std::io; -use std::io::Error; -use std::io::Seek; -use std::io::SeekFrom; -use std::io::Write; -use std::path::Path; -use std::path::PathBuf; -use std::rc::Rc; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; - -pub trait FsPermissions { - fn check_read(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>; - fn check_read_all(&mut self, api_name: &str) -> Result<(), AnyError>; - fn check_read_blind( - &mut self, - p: &Path, - display: &str, - api_name: &str, - ) -> Result<(), AnyError>; - fn check_write(&mut self, p: &Path, api_name: &str) -> Result<(), AnyError>; - fn check_write_all(&mut self, api_name: &str) -> Result<(), AnyError>; -} - -#[cfg(not(unix))] -use deno_core::error::generic_error; -#[cfg(not(unix))] -use deno_core::error::not_supported; - -pub fn init<P: FsPermissions + 'static>() -> Extension { - Extension::builder("deno_fs") - .ops(vec![ - op_open_sync::decl::<P>(), - op_open_async::decl::<P>(), - op_write_file_sync::decl::<P>(), - op_write_file_async::decl::<P>(), - op_seek_sync::decl(), - op_seek_async::decl(), - op_fdatasync_sync::decl(), - op_fdatasync_async::decl(), - op_fsync_sync::decl(), - op_fsync_async::decl(), - op_fstat_sync::decl(), - op_fstat_async::decl(), - op_flock_sync::decl(), - op_flock_async::decl(), - op_funlock_sync::decl(), - op_funlock_async::decl(), - op_umask::decl(), - op_chdir::decl::<P>(), - op_mkdir_sync::decl::<P>(), - op_mkdir_async::decl::<P>(), - op_chmod_sync::decl::<P>(), - op_chmod_async::decl::<P>(), - op_chown_sync::decl::<P>(), - op_chown_async::decl::<P>(), - op_remove_sync::decl::<P>(), - op_remove_async::decl::<P>(), - op_copy_file_sync::decl::<P>(), - op_copy_file_async::decl::<P>(), - op_stat_sync::decl::<P>(), - op_stat_async::decl::<P>(), - op_realpath_sync::decl::<P>(), - op_realpath_async::decl::<P>(), - op_read_dir_sync::decl::<P>(), - op_read_dir_async::decl::<P>(), - op_rename_sync::decl::<P>(), - op_rename_async::decl::<P>(), - op_link_sync::decl::<P>(), - op_link_async::decl::<P>(), - op_symlink_sync::decl::<P>(), - op_symlink_async::decl::<P>(), - op_read_link_sync::decl::<P>(), - op_read_link_async::decl::<P>(), - op_ftruncate_sync::decl(), - op_ftruncate_async::decl(), - op_truncate_sync::decl::<P>(), - op_truncate_async::decl::<P>(), - op_make_temp_dir_sync::decl::<P>(), - op_make_temp_dir_async::decl::<P>(), - op_make_temp_file_sync::decl::<P>(), - op_make_temp_file_async::decl::<P>(), - op_cwd::decl::<P>(), - op_futime_sync::decl(), - op_futime_async::decl(), - op_utime_sync::decl::<P>(), - op_utime_async::decl::<P>(), - op_readfile_sync::decl::<P>(), - op_readfile_text_sync::decl::<P>(), - op_readfile_async::decl::<P>(), - op_readfile_text_async::decl::<P>(), - ]) - .build() -} - -fn default_err_mapper(err: Error, desc: String) -> Error { - Error::new(err.kind(), format!("{err}, {desc}")) -} - -#[derive(Deserialize, Default, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(default)] -pub struct OpenOptions { - read: bool, - write: bool, - create: bool, - truncate: bool, - append: bool, - create_new: bool, -} - -#[inline] -fn open_helper<P>( - state: &mut OpState, - path: &str, - mode: Option<u32>, - options: Option<&OpenOptions>, - api_name: &str, -) -> Result<(PathBuf, std::fs::OpenOptions), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(path).to_path_buf(); - - let mut open_options = std::fs::OpenOptions::new(); - - if let Some(mode) = mode { - // mode only used if creating the file on Unix - // if not specified, defaults to 0o666 - #[cfg(unix)] - { - use std::os::unix::fs::OpenOptionsExt; - open_options.mode(mode & 0o777); - } - #[cfg(not(unix))] - let _ = mode; // avoid unused warning - } - - let permissions = state.borrow_mut::<P>(); - - match options { - None => { - permissions.check_read(&path, api_name)?; - open_options - .read(true) - .create(false) - .write(false) - .truncate(false) - .append(false) - .create_new(false); - } - Some(options) => { - if options.read { - permissions.check_read(&path, api_name)?; - } - - if options.write || options.append { - permissions.check_write(&path, api_name)?; - } - - open_options - .read(options.read) - .create(options.create) - .write(options.write) - .truncate(options.truncate) - .append(options.append) - .create_new(options.create_new); - } - } - - Ok((path, open_options)) -} - -#[op] -fn op_open_sync<P>( - state: &mut OpState, - path: String, - options: Option<OpenOptions>, - mode: Option<u32>, -) -> Result<ResourceId, AnyError> -where - P: FsPermissions + 'static, -{ - let (path, open_options) = - open_helper::<P>(state, &path, mode, options.as_ref(), "Deno.openSync()")?; - let std_file = open_options.open(&path).map_err(|err| { - default_err_mapper(err, format!("open '{}'", path.display())) - })?; - let resource = StdFileResource::fs_file(std_file); - let rid = state.resource_table.add(resource); - Ok(rid) -} - -#[op] -async fn op_open_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - options: Option<OpenOptions>, - mode: Option<u32>, -) -> Result<ResourceId, AnyError> -where - P: FsPermissions + 'static, -{ - let (path, open_options) = open_helper::<P>( - &mut state.borrow_mut(), - &path, - mode, - options.as_ref(), - "Deno.open()", - )?; - let std_file = tokio::task::spawn_blocking(move || { - open_options.open(&path).map_err(|err| { - default_err_mapper(err, format!("open '{}'", path.display())) - }) - }) - .await?; - let resource = StdFileResource::fs_file(std_file?); - let rid = state.borrow_mut().resource_table.add(resource); - Ok(rid) -} - -#[inline] -fn write_open_options( - create: bool, - append: bool, - create_new: bool, -) -> OpenOptions { - OpenOptions { - read: false, - write: true, - create, - truncate: !append, - append, - create_new, - } -} - -#[op] -fn op_write_file_sync<P>( - state: &mut OpState, - path: String, - mode: Option<u32>, - append: bool, - create: bool, - create_new: bool, - data: ZeroCopyBuf, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let (path, open_options) = open_helper::<P>( - state, - &path, - mode, - Some(&write_open_options(create, append, create_new)), - "Deno.writeFileSync()", - )?; - write_file(&path, open_options, mode, data) -} - -#[op] -async fn op_write_file_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - mode: Option<u32>, - append: bool, - create: bool, - create_new: bool, - data: ZeroCopyBuf, - cancel_rid: Option<ResourceId>, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let (path, open_options) = open_helper::<P>( - &mut state.borrow_mut(), - &path, - mode, - Some(&write_open_options(create, append, create_new)), - "Deno.writeFile()", - )?; - - let write_future = tokio::task::spawn_blocking(move || { - write_file(&path, open_options, mode, data) - }); - - let cancel_handle = cancel_rid.and_then(|rid| { - state - .borrow_mut() - .resource_table - .get::<CancelHandle>(rid) - .ok() - }); - - if let Some(cancel_handle) = cancel_handle { - let write_future_rv = write_future.or_cancel(cancel_handle).await; - - if let Some(cancel_rid) = cancel_rid { - state.borrow_mut().resource_table.close(cancel_rid).ok(); - }; - - return write_future_rv??; - } - - write_future.await? -} - -fn write_file( - path: &Path, - open_options: std::fs::OpenOptions, - _mode: Option<u32>, - data: ZeroCopyBuf, -) -> Result<(), AnyError> { - let mut std_file = open_options.open(path).map_err(|err| { - default_err_mapper(err, format!("open '{}'", path.display())) - })?; - - // need to chmod the file if it already exists and a mode is specified - #[cfg(unix)] - if let Some(mode) = _mode { - use std::os::unix::fs::PermissionsExt; - let permissions = PermissionsExt::from_mode(mode & 0o777); - std_file.set_permissions(permissions).map_err(|err| { - default_err_mapper(err, format!("chmod '{}'", path.display())) - })?; - } - - std_file.write_all(&data)?; - Ok(()) -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SeekArgs { - rid: ResourceId, - offset: i64, - whence: i32, -} - -fn seek_helper(args: SeekArgs) -> Result<(u32, SeekFrom), AnyError> { - let rid = args.rid; - let offset = args.offset; - let whence = args.whence as u32; - // Translate seek mode to Rust repr. - let seek_from = match whence { - 0 => SeekFrom::Start(offset as u64), - 1 => SeekFrom::Current(offset), - 2 => SeekFrom::End(offset), - _ => { - return Err(type_error(format!("Invalid seek mode: {whence}"))); - } - }; - - Ok((rid, seek_from)) -} - -#[op] -fn op_seek_sync(state: &mut OpState, args: SeekArgs) -> Result<u64, AnyError> { - let (rid, seek_from) = seek_helper(args)?; - StdFileResource::with_file(state, rid, |std_file| { - std_file.seek(seek_from).map_err(AnyError::from) - }) -} - -#[op] -async fn op_seek_async( - state: Rc<RefCell<OpState>>, - args: SeekArgs, -) -> Result<u64, AnyError> { - let (rid, seek_from) = seek_helper(args)?; - - StdFileResource::with_file_blocking_task(state, rid, move |std_file| { - std_file.seek(seek_from).map_err(AnyError::from) - }) - .await -} - -#[op] -fn op_fdatasync_sync( - state: &mut OpState, - rid: ResourceId, -) -> Result<(), AnyError> { - StdFileResource::with_file(state, rid, |std_file| { - std_file.sync_data().map_err(AnyError::from) - }) -} - -#[op] -async fn op_fdatasync_async( - state: Rc<RefCell<OpState>>, - rid: ResourceId, -) -> Result<(), AnyError> { - StdFileResource::with_file_blocking_task(state, rid, move |std_file| { - std_file.sync_data().map_err(AnyError::from) - }) - .await -} - -#[op] -fn op_fsync_sync(state: &mut OpState, rid: ResourceId) -> Result<(), AnyError> { - StdFileResource::with_file(state, rid, |std_file| { - std_file.sync_all().map_err(AnyError::from) - }) -} - -#[op] -async fn op_fsync_async( - state: Rc<RefCell<OpState>>, - rid: ResourceId, -) -> Result<(), AnyError> { - StdFileResource::with_file_blocking_task(state, rid, move |std_file| { - std_file.sync_all().map_err(AnyError::from) - }) - .await -} - -#[op] -fn op_fstat_sync( - state: &mut OpState, - rid: ResourceId, - out_buf: &mut [u32], -) -> Result<(), AnyError> { - let metadata = StdFileResource::with_file(state, rid, |std_file| { - std_file.metadata().map_err(AnyError::from) - })?; - let stat = get_stat(metadata); - stat.write(out_buf); - Ok(()) -} - -#[op] -async fn op_fstat_async( - state: Rc<RefCell<OpState>>, - rid: ResourceId, -) -> Result<FsStat, AnyError> { - let metadata = - StdFileResource::with_file_blocking_task(state, rid, move |std_file| { - std_file.metadata().map_err(AnyError::from) - }) - .await?; - Ok(get_stat(metadata)) -} - -#[op] -fn op_flock_sync( - state: &mut OpState, - rid: ResourceId, - exclusive: bool, -) -> Result<(), AnyError> { - use fs3::FileExt; - super::check_unstable(state, "Deno.flockSync"); - - StdFileResource::with_file(state, rid, |std_file| { - if exclusive { - std_file.lock_exclusive()?; - } else { - std_file.lock_shared()?; - } - Ok(()) - }) -} - -#[op] -async fn op_flock_async( - state: Rc<RefCell<OpState>>, - rid: ResourceId, - exclusive: bool, -) -> Result<(), AnyError> { - use fs3::FileExt; - super::check_unstable2(&state, "Deno.flock"); - - StdFileResource::with_file_blocking_task(state, rid, move |std_file| { - if exclusive { - std_file.lock_exclusive()?; - } else { - std_file.lock_shared()?; - } - Ok(()) - }) - .await -} - -#[op] -fn op_funlock_sync( - state: &mut OpState, - rid: ResourceId, -) -> Result<(), AnyError> { - use fs3::FileExt; - super::check_unstable(state, "Deno.funlockSync"); - - StdFileResource::with_file(state, rid, |std_file| { - std_file.unlock()?; - Ok(()) - }) -} - -#[op] -async fn op_funlock_async( - state: Rc<RefCell<OpState>>, - rid: ResourceId, -) -> Result<(), AnyError> { - use fs3::FileExt; - super::check_unstable2(&state, "Deno.funlock"); - - StdFileResource::with_file_blocking_task(state, rid, move |std_file| { - std_file.unlock()?; - Ok(()) - }) - .await -} - -#[op] -fn op_umask(state: &mut OpState, mask: Option<u32>) -> Result<u32, AnyError> { - super::check_unstable(state, "Deno.umask"); - // TODO implement umask for Windows - // see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc - // and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019 - #[cfg(not(unix))] - { - let _ = mask; // avoid unused warning. - Err(not_supported()) - } - #[cfg(unix)] - { - use nix::sys::stat::mode_t; - use nix::sys::stat::umask; - use nix::sys::stat::Mode; - let r = if let Some(mask) = mask { - // If mask provided, return previous. - umask(Mode::from_bits_truncate(mask as mode_t)) - } else { - // If no mask provided, we query the current. Requires two syscalls. - let prev = umask(Mode::from_bits_truncate(0o777)); - let _ = umask(prev); - prev - }; - #[cfg(target_os = "linux")] - { - Ok(r.bits()) - } - #[cfg(target_os = "macos")] - { - Ok(r.bits() as u32) - } - } -} - -#[op] -fn op_chdir<P>(state: &mut OpState, directory: &str) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let d = PathBuf::from(&directory); - state.borrow_mut::<P>().check_read(&d, "Deno.chdir()")?; - set_current_dir(&d) - .map_err(|err| default_err_mapper(err, format!("chdir '{directory}'")))?; - Ok(()) -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MkdirArgs { - path: String, - recursive: bool, - mode: Option<u32>, -} - -#[op] -fn op_mkdir_sync<P>( - state: &mut OpState, - args: MkdirArgs, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(&args.path).to_path_buf(); - let mode = args.mode.unwrap_or(0o777) & 0o777; - state - .borrow_mut::<P>() - .check_write(&path, "Deno.mkdirSync()")?; - debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive); - let mut builder = std::fs::DirBuilder::new(); - builder.recursive(args.recursive); - #[cfg(unix)] - { - use std::os::unix::fs::DirBuilderExt; - builder.mode(mode); - } - builder.create(&path).map_err(|err| { - default_err_mapper(err, format!("mkdir '{}'", path.display())) - })?; - Ok(()) -} - -#[op] -async fn op_mkdir_async<P>( - state: Rc<RefCell<OpState>>, - args: MkdirArgs, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(&args.path).to_path_buf(); - let mode = args.mode.unwrap_or(0o777) & 0o777; - - { - let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write(&path, "Deno.mkdir()")?; - } - - tokio::task::spawn_blocking(move || { - debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive); - let mut builder = std::fs::DirBuilder::new(); - builder.recursive(args.recursive); - #[cfg(unix)] - { - use std::os::unix::fs::DirBuilderExt; - builder.mode(mode); - } - builder.create(&path).map_err(|err| { - default_err_mapper(err, format!("mkdir '{}'", path.display())) - })?; - Ok(()) - }) - .await - .unwrap() -} - -#[op] -fn op_chmod_sync<P>( - state: &mut OpState, - path: &str, - mode: u32, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(path); - let mode = mode & 0o777; - - state - .borrow_mut::<P>() - .check_write(path, "Deno.chmodSync()")?; - raw_chmod(path, mode) -} - -#[op] -async fn op_chmod_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - mode: u32, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(&path).to_path_buf(); - let mode = mode & 0o777; - - { - let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write(&path, "Deno.chmod()")?; - } - - tokio::task::spawn_blocking(move || raw_chmod(&path, mode)) - .await - .unwrap() -} - -fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> { - let err_mapper = - |err| default_err_mapper(err, format!("chmod '{}'", path.display())); - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let permissions = PermissionsExt::from_mode(_raw_mode); - std::fs::set_permissions(path, permissions).map_err(err_mapper)?; - Ok(()) - } - // TODO Implement chmod for Windows (#4357) - #[cfg(not(unix))] - { - // Still check file/dir exists on Windows - let _metadata = std::fs::metadata(path).map_err(err_mapper)?; - Err(not_supported()) - } -} - -#[op] -fn op_chown_sync<P>( - state: &mut OpState, - path: &str, - #[cfg_attr(windows, allow(unused_variables))] uid: Option<u32>, - #[cfg_attr(windows, allow(unused_variables))] gid: Option<u32>, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(path).to_path_buf(); - state - .borrow_mut::<P>() - .check_write(&path, "Deno.chownSync()")?; - #[cfg(unix)] - { - use crate::errors::get_nix_error_class; - use nix::unistd::chown; - use nix::unistd::Gid; - use nix::unistd::Uid; - 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), - format!("{}, chown '{}'", err.desc(), path.display()), - ) - })?; - Ok(()) - } - // TODO Implement chown for Windows - #[cfg(not(unix))] - { - Err(generic_error("Not implemented")) - } -} - -#[op] -async fn op_chown_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - #[cfg_attr(windows, allow(unused_variables))] uid: Option<u32>, - #[cfg_attr(windows, allow(unused_variables))] gid: Option<u32>, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(&path).to_path_buf(); - - { - let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write(&path, "Deno.chown()")?; - } - - tokio::task::spawn_blocking(move || { - #[cfg(unix)] - { - use crate::errors::get_nix_error_class; - use nix::unistd::chown; - use nix::unistd::Gid; - use nix::unistd::Uid; - 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), - format!("{}, chown '{}'", err.desc(), path.display()), - ) - })?; - Ok(()) - } - // TODO Implement chown for Windows - #[cfg(not(unix))] - Err(not_supported()) - }) - .await - .unwrap() -} - -#[op] -fn op_remove_sync<P>( - state: &mut OpState, - path: &str, - recursive: bool, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(path); - - state - .borrow_mut::<P>() - .check_write(&path, "Deno.removeSync()")?; - - #[cfg(not(unix))] - use std::os::windows::prelude::MetadataExt; - - let err_mapper = - |err| default_err_mapper(err, format!("remove '{}'", path.display())); - let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?; - - let file_type = metadata.file_type(); - if file_type.is_file() { - std::fs::remove_file(&path).map_err(err_mapper)?; - } else if recursive { - std::fs::remove_dir_all(&path).map_err(err_mapper)?; - } else if file_type.is_symlink() { - #[cfg(unix)] - std::fs::remove_file(&path).map_err(err_mapper)?; - #[cfg(not(unix))] - { - use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; - if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { - std::fs::remove_dir(&path).map_err(err_mapper)?; - } else { - std::fs::remove_file(&path).map_err(err_mapper)?; - } - } - } else if file_type.is_dir() { - std::fs::remove_dir(&path).map_err(err_mapper)?; - } else { - // pipes, sockets, etc... - std::fs::remove_file(&path).map_err(err_mapper)?; - } - Ok(()) -} - -#[op] -async fn op_remove_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - recursive: bool, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - { - let mut state = state.borrow_mut(); - state - .borrow_mut::<P>() - .check_write(&path, "Deno.remove()")?; - } - - tokio::task::spawn_blocking(move || { - #[cfg(not(unix))] - use std::os::windows::prelude::MetadataExt; - let err_mapper = - |err| default_err_mapper(err, format!("remove '{}'", path.display())); - let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?; - - debug!("op_remove_async {} {}", path.display(), recursive); - let file_type = metadata.file_type(); - if file_type.is_file() { - std::fs::remove_file(&path).map_err(err_mapper)?; - } else if recursive { - std::fs::remove_dir_all(&path).map_err(err_mapper)?; - } else if file_type.is_symlink() { - #[cfg(unix)] - std::fs::remove_file(&path).map_err(err_mapper)?; - #[cfg(not(unix))] - { - use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; - if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { - std::fs::remove_dir(&path).map_err(err_mapper)?; - } else { - std::fs::remove_file(&path).map_err(err_mapper)?; - } - } - } else if file_type.is_dir() { - std::fs::remove_dir(&path).map_err(err_mapper)?; - } else { - // pipes, sockets, etc... - std::fs::remove_file(&path).map_err(err_mapper)?; - } - Ok(()) - }) - .await - .unwrap() -} - -#[op] -fn op_copy_file_sync<P>( - state: &mut OpState, - from: &str, - to: &str, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let from_path = PathBuf::from(from); - let to_path = PathBuf::from(to); - - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&from_path, "Deno.copyFileSync()")?; - permissions.check_write(&to_path, "Deno.copyFileSync()")?; - - // 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_path.is_file() { - return Err(custom_error( - "NotFound", - format!( - "File not found, copy '{}' -> '{}'", - from_path.display(), - to_path.display() - ), - )); - } - - let err_mapper = |err| { - default_err_mapper( - err, - format!("copy '{}' -> '{}'", from_path.display(), to_path.display()), - ) - }; - - #[cfg(target_os = "macos")] - { - use libc::clonefile; - use libc::stat; - use libc::unlink; - use std::ffi::CString; - use std::io::Read; - use std::os::unix::fs::OpenOptionsExt; - use std::os::unix::fs::PermissionsExt; - - 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 perm = from_file.metadata().map_err(err_mapper)?.permissions(); - - let mut to_file = std::fs::OpenOptions::new() - // create the file with the correct mode right away - .mode(perm.mode()) - .write(true) - .create(true) - .truncate(true) - .open(&to_path) - .map_err(err_mapper)?; - let writer_metadata = to_file.metadata()?; - if writer_metadata.is_file() { - // Set the correct file permissions, in case the file already existed. - // Don't set the permissions on already existing non-files like - // pipes/FIFOs or device nodes. - to_file.set_permissions(perm)?; - } - 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_path, &to_path).map_err(err_mapper)?; - Ok(()) -} - -#[op] -async fn op_copy_file_async<P>( - state: Rc<RefCell<OpState>>, - from: String, - to: String, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let from = PathBuf::from(&from); - let to = PathBuf::from(&to); - - { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&from, "Deno.copyFile()")?; - permissions.check_write(&to, "Deno.copyFile()")?; - } - - tokio::task::spawn_blocking(move || { - // 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() { - return Err(custom_error( - "NotFound", - format!( - "File not found, copy '{}' -> '{}'", - from.display(), - to.display() - ), - )); - } - // returns size of from as u64 (we ignore) - std::fs::copy(&from, &to).map_err(|err| { - default_err_mapper( - err, - format!("copy '{}' -> '{}'", from.display(), to.display()), - ) - })?; - Ok(()) - }) - .await - .unwrap() -} - -fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> (u64, bool) { - match maybe_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), - true, - ), - Err(_) => (0, false), - } -} - -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)] -fn get_stat(metadata: std::fs::Metadata) -> FsStat { - // Unix stat member (number types only). 0 if not on unix. - macro_rules! usm { - ($member:ident) => {{ - #[cfg(unix)] - { - metadata.$member() - } - #[cfg(not(unix))] - { - 0 - } - }}; - } - - #[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_set, - mtime, - atime_set, - atime, - birthtime_set, - birthtime, - // Following are only valid under Unix. - dev: usm!(dev), - ino: usm!(ino), - mode: usm!(mode), - nlink: usm!(nlink), - uid: usm!(uid), - gid: usm!(gid), - rdev: usm!(rdev), - blksize: usm!(blksize), - blocks: usm!(blocks), - } -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct StatArgs { - path: String, - lstat: bool, -} - -#[op] -fn op_stat_sync<P>( - state: &mut OpState, - path: &str, - lstat: bool, - out_buf: &mut [u32], -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(path); - state - .borrow_mut::<P>() - .check_read(&path, "Deno.statSync()")?; - let err_mapper = - |err| default_err_mapper(err, format!("stat '{}'", path.display())); - let metadata = if lstat { - std::fs::symlink_metadata(&path).map_err(err_mapper)? - } else { - std::fs::metadata(&path).map_err(err_mapper)? - }; - - let stat = get_stat(metadata); - stat.write(out_buf); - - Ok(()) -} - -#[op] -async fn op_stat_async<P>( - state: Rc<RefCell<OpState>>, - args: StatArgs, -) -> Result<FsStat, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&args.path); - let lstat = args.lstat; - - { - let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_read(&path, "Deno.stat()")?; - } - - tokio::task::spawn_blocking(move || { - debug!("op_stat_async {} {}", path.display(), lstat); - let err_mapper = - |err| default_err_mapper(err, format!("stat '{}'", path.display())); - let metadata = if lstat { - std::fs::symlink_metadata(&path).map_err(err_mapper)? - } else { - std::fs::metadata(&path).map_err(err_mapper)? - }; - Ok(get_stat(metadata)) - }) - .await - .unwrap() -} - -#[op] -fn op_realpath_sync<P>( - state: &mut OpState, - path: String, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.realPathSync()")?; - if path.is_relative() { - permissions.check_read_blind( - ¤t_dir()?, - "CWD", - "Deno.realPathSync()", - )?; - } - - debug!("op_realpath_sync {}", path.display()); - // corresponds to the realpath on Unix and - // CreateFile and GetFinalPathNameByHandle on Windows - let realpath = canonicalize_path(&path)?; - let realpath_str = into_string(realpath.into_os_string())?; - Ok(realpath_str) -} - -#[op] -async fn op_realpath_async<P>( - state: Rc<RefCell<OpState>>, - path: String, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&path, "Deno.realPath()")?; - if path.is_relative() { - permissions.check_read_blind( - ¤t_dir()?, - "CWD", - "Deno.realPath()", - )?; - } - } - - tokio::task::spawn_blocking(move || { - debug!("op_realpath_async {}", path.display()); - // corresponds to the realpath on Unix and - // CreateFile and GetFinalPathNameByHandle on Windows - let realpath = canonicalize_path(&path)?; - let realpath_str = into_string(realpath.into_os_string())?; - Ok(realpath_str) - }) - .await - .unwrap() -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct DirEntry { - name: String, - is_file: bool, - is_directory: bool, - is_symlink: bool, -} - -#[op] -fn op_read_dir_sync<P>( - state: &mut OpState, - path: String, -) -> Result<Vec<DirEntry>, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - state - .borrow_mut::<P>() - .check_read(&path, "Deno.readDirSync()")?; - - debug!("op_read_dir_sync {}", path.display()); - - let entries: Vec<_> = std::fs::read_dir(&path) - .map_err(|err| { - default_err_mapper(err, format!("readdir '{}'", path.display())) - })? - .filter_map(|entry| { - let entry = entry.unwrap(); - // Not all filenames can be encoded as UTF-8. Skip those for now. - if let Ok(name) = into_string(entry.file_name()) { - Some(DirEntry { - name, - is_file: entry - .file_type() - .map_or(false, |file_type| file_type.is_file()), - is_directory: entry - .file_type() - .map_or(false, |file_type| file_type.is_dir()), - is_symlink: entry - .file_type() - .map_or(false, |file_type| file_type.is_symlink()), - }) - } else { - None - } - }) - .collect(); - - Ok(entries) -} - -#[op] -async fn op_read_dir_async<P>( - state: Rc<RefCell<OpState>>, - path: String, -) -> Result<Vec<DirEntry>, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - { - let mut state = state.borrow_mut(); - state - .borrow_mut::<P>() - .check_read(&path, "Deno.readDir()")?; - } - tokio::task::spawn_blocking(move || { - debug!("op_read_dir_async {}", path.display()); - - let entries: Vec<_> = std::fs::read_dir(&path) - .map_err(|err| { - default_err_mapper(err, format!("readdir '{}'", path.display())) - })? - .filter_map(|entry| { - let entry = entry.unwrap(); - // Not all filenames can be encoded as UTF-8. Skip those for now. - if let Ok(name) = into_string(entry.file_name()) { - Some(DirEntry { - name, - is_file: entry - .file_type() - .map_or(false, |file_type| file_type.is_file()), - is_directory: entry - .file_type() - .map_or(false, |file_type| file_type.is_dir()), - is_symlink: entry - .file_type() - .map_or(false, |file_type| file_type.is_symlink()), - }) - } else { - None - } - }) - .collect(); - - Ok(entries) - }) - .await - .unwrap() -} - -#[op] -fn op_rename_sync<P>( - state: &mut OpState, - oldpath: &str, - newpath: &str, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let oldpath = PathBuf::from(oldpath); - let newpath = PathBuf::from(newpath); - - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.renameSync()")?; - permissions.check_write(&oldpath, "Deno.renameSync()")?; - permissions.check_write(&newpath, "Deno.renameSync()")?; - - std::fs::rename(&oldpath, &newpath).map_err(|err| { - default_err_mapper( - err, - format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()), - ) - })?; - Ok(()) -} - -#[op] -async fn op_rename_async<P>( - state: Rc<RefCell<OpState>>, - oldpath: String, - newpath: String, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let oldpath = PathBuf::from(&oldpath); - let newpath = PathBuf::from(&newpath); - { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.rename()")?; - permissions.check_write(&oldpath, "Deno.rename()")?; - permissions.check_write(&newpath, "Deno.rename()")?; - } - - tokio::task::spawn_blocking(move || { - std::fs::rename(&oldpath, &newpath).map_err(|err| { - default_err_mapper( - err, - format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()), - ) - })?; - Ok(()) - }) - .await - .unwrap() -} - -#[op] -fn op_link_sync<P>( - state: &mut OpState, - oldpath: &str, - newpath: &str, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let oldpath = PathBuf::from(oldpath); - let newpath = PathBuf::from(newpath); - - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.linkSync()")?; - permissions.check_write(&oldpath, "Deno.linkSync()")?; - permissions.check_read(&newpath, "Deno.linkSync()")?; - permissions.check_write(&newpath, "Deno.linkSync()")?; - - std::fs::hard_link(&oldpath, &newpath).map_err(|err| { - default_err_mapper( - err, - format!("link '{}' -> '{}'", oldpath.display(), newpath.display()), - ) - })?; - Ok(()) -} - -#[op] -async fn op_link_async<P>( - state: Rc<RefCell<OpState>>, - oldpath: String, - newpath: String, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let oldpath = PathBuf::from(&oldpath); - let newpath = PathBuf::from(&newpath); - - { - let mut state = state.borrow_mut(); - let permissions = state.borrow_mut::<P>(); - permissions.check_read(&oldpath, "Deno.link()")?; - permissions.check_write(&oldpath, "Deno.link()")?; - permissions.check_read(&newpath, "Deno.link()")?; - permissions.check_write(&newpath, "Deno.link()")?; - } - - tokio::task::spawn_blocking(move || { - let err_mapper = |err| { - default_err_mapper( - err, - format!("link '{}' -> '{}'", oldpath.display(), newpath.display()), - ) - }; - std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?; - Ok(()) - }) - .await - .unwrap() -} - -#[op] -fn op_symlink_sync<P>( - state: &mut OpState, - oldpath: &str, - newpath: &str, - _type: Option<String>, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let oldpath = PathBuf::from(oldpath); - let newpath = PathBuf::from(newpath); - - state - .borrow_mut::<P>() - .check_write_all("Deno.symlinkSync()")?; - state - .borrow_mut::<P>() - .check_read_all("Deno.symlinkSync()")?; - - let err_mapper = |err| { - default_err_mapper( - err, - format!("symlink '{}' -> '{}'", oldpath.display(), newpath.display()), - ) - }; - - #[cfg(unix)] - { - use std::os::unix::fs::symlink; - symlink(&oldpath, &newpath).map_err(err_mapper)?; - Ok(()) - } - #[cfg(not(unix))] - { - use std::os::windows::fs::symlink_dir; - use std::os::windows::fs::symlink_file; - - 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")), - }, - None => { - let old_meta = std::fs::metadata(&oldpath); - match old_meta { - Ok(metadata) => { - if metadata.is_file() { - symlink_file(&oldpath, &newpath).map_err(err_mapper)? - } else if metadata.is_dir() { - symlink_dir(&oldpath, &newpath).map_err(err_mapper)? - } - } - Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())), - } - } - }; - Ok(()) - } -} - -#[op] -async fn op_symlink_async<P>( - state: Rc<RefCell<OpState>>, - oldpath: String, - newpath: String, - _type: Option<String>, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let oldpath = PathBuf::from(&oldpath); - let newpath = PathBuf::from(&newpath); - - { - let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write_all("Deno.symlink()")?; - state.borrow_mut::<P>().check_read_all("Deno.symlink()")?; - } - - tokio::task::spawn_blocking(move || { - let err_mapper = |err| default_err_mapper(err, format!( - "symlink '{}' -> '{}'", - oldpath.display(), - newpath.display() - )); - - #[cfg(unix)] - { - use std::os::unix::fs::symlink; - symlink(&oldpath, &newpath).map_err(err_mapper)?; - Ok(()) - } - #[cfg(not(unix))] - { - use std::os::windows::fs::{symlink_dir, symlink_file}; - - 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")), - }, - None => { - let old_meta = std::fs::metadata(&oldpath); - match old_meta { - Ok(metadata) => { - if metadata.is_file() { - symlink_file(&oldpath, &newpath).map_err(err_mapper)? - } else if metadata.is_dir() { - symlink_dir(&oldpath, &newpath).map_err(err_mapper)? - } - } - Err(_) => return Err(type_error("you must pass a `options` argument for non-existent target path in windows".to_string())), - } - } - }; - Ok(()) - } - }) - .await - .unwrap() -} - -#[op] -fn op_read_link_sync<P>( - state: &mut OpState, - path: String, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - state - .borrow_mut::<P>() - .check_read(&path, "Deno.readLink()")?; - - debug!("op_read_link_value {}", path.display()); - let target = std::fs::read_link(&path) - .map_err(|err| { - default_err_mapper(err, format!("readlink '{}'", path.display())) - })? - .into_os_string(); - let targetstr = into_string(target)?; - Ok(targetstr) -} - -#[op] -async fn op_read_link_async<P>( - state: Rc<RefCell<OpState>>, - path: String, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - { - let mut state = state.borrow_mut(); - state - .borrow_mut::<P>() - .check_read(&path, "Deno.readLink()")?; - } - tokio::task::spawn_blocking(move || { - debug!("op_read_link_async {}", path.display()); - let target = std::fs::read_link(&path) - .map_err(|err| { - default_err_mapper(err, format!("readlink '{}'", path.display())) - })? - .into_os_string(); - let targetstr = into_string(target)?; - Ok(targetstr) - }) - .await - .unwrap() -} - -#[op] -fn op_ftruncate_sync( - state: &mut OpState, - rid: u32, - len: i32, -) -> Result<(), AnyError> { - let len = len as u64; - StdFileResource::with_file(state, rid, |std_file| { - std_file.set_len(len).map_err(AnyError::from) - })?; - Ok(()) -} - -#[op] -async fn op_ftruncate_async( - state: Rc<RefCell<OpState>>, - rid: ResourceId, - len: i32, -) -> Result<(), AnyError> { - let len = len as u64; - - StdFileResource::with_file_blocking_task(state, rid, move |std_file| { - std_file.set_len(len)?; - Ok(()) - }) - .await -} - -#[op] -fn op_truncate_sync<P>( - state: &mut OpState, - path: &str, - len: u64, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(path); - - state - .borrow_mut::<P>() - .check_write(&path, "Deno.truncateSync()")?; - - debug!("op_truncate_sync {} {}", path.display(), len); - let err_mapper = - |err| default_err_mapper(err, format!("truncate '{}'", path.display())); - let f = std::fs::OpenOptions::new() - .write(true) - .open(&path) - .map_err(err_mapper)?; - f.set_len(len).map_err(err_mapper)?; - Ok(()) -} - -#[op] -async fn op_truncate_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - len: u64, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - let path = PathBuf::from(&path); - - { - let mut state = state.borrow_mut(); - state - .borrow_mut::<P>() - .check_write(&path, "Deno.truncate()")?; - } - tokio::task::spawn_blocking(move || { - debug!("op_truncate_async {} {}", path.display(), len); - let err_mapper = - |err| default_err_mapper(err, format!("truncate '{}'", path.display())); - let f = std::fs::OpenOptions::new() - .write(true) - .open(&path) - .map_err(err_mapper)?; - f.set_len(len).map_err(err_mapper)?; - Ok(()) - }) - .await - .unwrap() -} - -fn make_temp( - dir: Option<&Path>, - prefix: Option<&str>, - suffix: Option<&str>, - is_dir: bool, -) -> std::io::Result<PathBuf> { - let prefix_ = prefix.unwrap_or(""); - let suffix_ = suffix.unwrap_or(""); - let mut buf: PathBuf = match dir { - Some(p) => p.to_path_buf(), - None => temp_dir(), - } - .join("_"); - let mut rng = thread_rng(); - loop { - let unique = rng.gen::<u32>(); - buf.set_file_name(format!("{prefix_}{unique:08x}{suffix_}")); - let r = if is_dir { - #[allow(unused_mut)] - let mut builder = std::fs::DirBuilder::new(); - #[cfg(unix)] - { - use std::os::unix::fs::DirBuilderExt; - builder.mode(0o700); - } - builder.create(buf.as_path()) - } else { - let mut open_options = std::fs::OpenOptions::new(); - open_options.write(true).create_new(true); - #[cfg(unix)] - { - use std::os::unix::fs::OpenOptionsExt; - open_options.mode(0o600); - } - open_options.open(buf.as_path())?; - Ok(()) - }; - match r { - Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue, - Ok(_) => return Ok(buf), - Err(e) => return Err(e), - } - } -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MakeTempArgs { - dir: Option<String>, - prefix: Option<String>, - suffix: Option<String>, -} - -#[op] -fn op_make_temp_dir_sync<P>( - state: &mut OpState, - args: MakeTempArgs, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let dir = args.dir.map(|s| PathBuf::from(&s)); - let prefix = args.prefix.map(String::from); - let suffix = args.suffix.map(String::from); - - state.borrow_mut::<P>().check_write( - dir.clone().unwrap_or_else(temp_dir).as_path(), - "Deno.makeTempDirSync()", - )?; - - // TODO(piscisaureus): use byte vector for paths, not a string. - // See https://github.com/denoland/deno/issues/627. - // We can't assume that paths are always valid utf8 strings. - let path = make_temp( - // Converting Option<String> to Option<&str> - dir.as_deref(), - prefix.as_deref(), - suffix.as_deref(), - true, - )?; - let path_str = into_string(path.into_os_string())?; - - Ok(path_str) -} - -#[op] -async fn op_make_temp_dir_async<P>( - state: Rc<RefCell<OpState>>, - args: MakeTempArgs, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let dir = args.dir.map(|s| PathBuf::from(&s)); - let prefix = args.prefix.map(String::from); - let suffix = args.suffix.map(String::from); - { - let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write( - dir.clone().unwrap_or_else(temp_dir).as_path(), - "Deno.makeTempDir()", - )?; - } - tokio::task::spawn_blocking(move || { - // TODO(piscisaureus): use byte vector for paths, not a string. - // See https://github.com/denoland/deno/issues/627. - // We can't assume that paths are always valid utf8 strings. - let path = make_temp( - // Converting Option<String> to Option<&str> - dir.as_deref(), - prefix.as_deref(), - suffix.as_deref(), - true, - )?; - let path_str = into_string(path.into_os_string())?; - - Ok(path_str) - }) - .await - .unwrap() -} - -#[op] -fn op_make_temp_file_sync<P>( - state: &mut OpState, - args: MakeTempArgs, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let dir = args.dir.map(|s| PathBuf::from(&s)); - let prefix = args.prefix.map(String::from); - let suffix = args.suffix.map(String::from); - - state.borrow_mut::<P>().check_write( - dir.clone().unwrap_or_else(temp_dir).as_path(), - "Deno.makeTempFileSync()", - )?; - - // TODO(piscisaureus): use byte vector for paths, not a string. - // See https://github.com/denoland/deno/issues/627. - // We can't assume that paths are always valid utf8 strings. - let path = make_temp( - // Converting Option<String> to Option<&str> - dir.as_deref(), - prefix.as_deref(), - suffix.as_deref(), - false, - )?; - let path_str = into_string(path.into_os_string())?; - - Ok(path_str) -} - -#[op] -async fn op_make_temp_file_async<P>( - state: Rc<RefCell<OpState>>, - args: MakeTempArgs, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let dir = args.dir.map(|s| PathBuf::from(&s)); - let prefix = args.prefix.map(String::from); - let suffix = args.suffix.map(String::from); - { - let mut state = state.borrow_mut(); - state.borrow_mut::<P>().check_write( - dir.clone().unwrap_or_else(temp_dir).as_path(), - "Deno.makeTempFile()", - )?; - } - tokio::task::spawn_blocking(move || { - // TODO(piscisaureus): use byte vector for paths, not a string. - // See https://github.com/denoland/deno/issues/627. - // We can't assume that paths are always valid utf8 strings. - let path = make_temp( - // Converting Option<String> to Option<&str> - dir.as_deref(), - prefix.as_deref(), - suffix.as_deref(), - false, - )?; - let path_str = into_string(path.into_os_string())?; - - Ok(path_str) - }) - .await - .unwrap() -} - -#[op] -fn op_futime_sync( - state: &mut OpState, - rid: ResourceId, - atime_secs: i64, - atime_nanos: u32, - mtime_secs: i64, - mtime_nanos: u32, -) -> Result<(), AnyError> { - 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)) - .map_err(AnyError::from) - })?; - - Ok(()) -} - -#[op] -async fn op_futime_async( - state: Rc<RefCell<OpState>>, - rid: ResourceId, - atime_secs: i64, - atime_nanos: u32, - mtime_secs: i64, - mtime_nanos: u32, -) -> Result<(), AnyError> { - 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))?; - Ok(()) - }) - .await -} - -#[op] -fn op_utime_sync<P>( - state: &mut OpState, - path: &str, - atime_secs: i64, - atime_nanos: u32, - mtime_secs: i64, - mtime_nanos: u32, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - 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::<P>().check_write(&path, "Deno.utime()")?; - filetime::set_file_times(&path, atime, mtime).map_err(|err| { - default_err_mapper(err, format!("utime '{}'", path.display())) - })?; - Ok(()) -} - -#[op] -async fn op_utime_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - atime_secs: i64, - atime_nanos: u32, - mtime_secs: i64, - mtime_nanos: u32, -) -> Result<(), AnyError> -where - P: FsPermissions + 'static, -{ - 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() - .borrow_mut::<P>() - .check_write(&path, "Deno.utime()")?; - - tokio::task::spawn_blocking(move || { - filetime::set_file_times(&path, atime, mtime).map_err(|err| { - default_err_mapper(err, format!("utime '{}'", path.display())) - })?; - Ok(()) - }) - .await - .unwrap() -} - -#[op] -fn op_cwd<P>(state: &mut OpState) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let path = current_dir()?; - state - .borrow_mut::<P>() - .check_read_blind(&path, "CWD", "Deno.cwd()")?; - let path_str = into_string(path.into_os_string())?; - Ok(path_str) -} - -#[op] -fn op_readfile_sync<P>( - state: &mut OpState, - path: String, -) -> Result<ZeroCopyBuf, AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(&path); - state - .borrow_mut::<P>() - .check_read(path, "Deno.readFileSync()")?; - Ok( - std::fs::read(path) - .map_err(|err| { - default_err_mapper(err, format!("readfile '{}'", path.display())) - })? - .into(), - ) -} - -#[op] -fn op_readfile_text_sync<P>( - state: &mut OpState, - path: String, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - let path = Path::new(&path); - state - .borrow_mut::<P>() - .check_read(path, "Deno.readTextFileSync()")?; - Ok(string_from_utf8_lossy(std::fs::read(path).map_err( - |err| default_err_mapper(err, format!("readfile '{}'", path.display())), - )?)) -} - -#[op] -async fn op_readfile_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - cancel_rid: Option<ResourceId>, -) -> Result<ZeroCopyBuf, AnyError> -where - P: FsPermissions + 'static, -{ - { - let path = Path::new(&path); - let mut state = state.borrow_mut(); - state - .borrow_mut::<P>() - .check_read(path, "Deno.readFile()")?; - } - - let read_future = tokio::task::spawn_blocking(move || { - let path = Path::new(&path); - Ok(std::fs::read(path).map(ZeroCopyBuf::from).map_err(|err| { - default_err_mapper(err, format!("readfile '{}'", path.display())) - })?) - }); - - let cancel_handle = cancel_rid.and_then(|rid| { - state - .borrow_mut() - .resource_table - .get::<CancelHandle>(rid) - .ok() - }); - - if let Some(cancel_handle) = cancel_handle { - let read_future_rv = read_future.or_cancel(cancel_handle).await; - - if let Some(cancel_rid) = cancel_rid { - state.borrow_mut().resource_table.close(cancel_rid).ok(); - }; - - return read_future_rv??; - } - - read_future.await? -} - -#[op] -async fn op_readfile_text_async<P>( - state: Rc<RefCell<OpState>>, - path: String, - cancel_rid: Option<ResourceId>, -) -> Result<String, AnyError> -where - P: FsPermissions + 'static, -{ - { - let path = Path::new(&path); - let mut state = state.borrow_mut(); - state - .borrow_mut::<P>() - .check_read(path, "Deno.readTextFile()")?; - } - - let read_future = tokio::task::spawn_blocking(move || { - let path = Path::new(&path); - Ok(string_from_utf8_lossy(std::fs::read(path).map_err( - |err| default_err_mapper(err, format!("readfile '{}'", path.display())), - )?)) - }); - - let cancel_handle = cancel_rid.and_then(|rid| { - state - .borrow_mut() - .resource_table - .get::<CancelHandle>(rid) - .ok() - }); - - if let Some(cancel_handle) = cancel_handle { - let read_future_rv = read_future.or_cancel(cancel_handle).await; - - if let Some(cancel_rid) = cancel_rid { - state.borrow_mut().resource_table.close(cancel_rid).ok(); - }; - - return read_future_rv??; - } - - read_future.await? -} - -// Like String::from_utf8_lossy but operates on owned values -fn string_from_utf8_lossy(buf: Vec<u8>) -> String { - match String::from_utf8_lossy(&buf) { - // buf contained non-utf8 chars than have been patched - Cow::Owned(s) => s, - // SAFETY: if Borrowed then the buf only contains utf8 chars, - // we do this instead of .into_owned() to avoid copying the input buf - Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(buf) }, - } -} diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs index 48c22ca92..5cb7dcbff 100644 --- a/runtime/ops/mod.rs +++ b/runtime/ops/mod.rs @@ -1,6 +1,5 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -pub mod fs; pub mod fs_events; pub mod http; pub mod os; diff --git a/runtime/permissions/mod.rs b/runtime/permissions/mod.rs index 5edfbe8f7..a954387e5 100644 --- a/runtime/permissions/mod.rs +++ b/runtime/permissions/mod.rs @@ -1915,7 +1915,7 @@ impl deno_websocket::WebSocketPermissions for PermissionsContainer { } } -impl crate::ops::fs::FsPermissions for PermissionsContainer { +impl deno_fs::FsPermissions for PermissionsContainer { fn check_read( &mut self, path: &Path, diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 06b594dd1..cce69fabb 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -425,7 +425,7 @@ impl WebWorker { ), // Extensions providing Deno.* features ops::fs_events::init(), - ops::fs::init::<PermissionsContainer>(), + deno_fs::init::<PermissionsContainer>(unstable), deno_io::init(options.stdio), deno_tls::init(), deno_net::init::<PermissionsContainer>( diff --git a/runtime/worker.rs b/runtime/worker.rs index 66497489d..1a6b6f2fc 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -255,7 +255,7 @@ impl MainWorker { options.format_js_error_fn.clone(), ), ops::fs_events::init(), - ops::fs::init::<PermissionsContainer>(), + deno_fs::init::<PermissionsContainer>(unstable), deno_io::init(options.stdio), deno_tls::init(), deno_net::init::<PermissionsContainer>( |