From f88dc4e197f36842b13843dd88da3d74d67578e5 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Tue, 26 Nov 2019 00:40:57 -0800 Subject: [PATCH] Add Deno.realpath (#3404) --- cli/js/deno.ts | 1 + cli/js/dispatch.ts | 2 + cli/js/lib.deno_runtime.d.ts | 15 ++++++ cli/js/realpath.ts | 19 ++++++++ cli/js/realpath_test.ts | 91 ++++++++++++++++++++++++++++++++++++ cli/js/unit_tests.ts | 1 + cli/ops/fs.rs | 28 +++++++++++ 7 files changed, 157 insertions(+) create mode 100644 cli/js/realpath.ts create mode 100644 cli/js/realpath_test.ts diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 2a72747271..6f07bef675 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -56,6 +56,7 @@ export { chownSync, chown } from "./chown.ts"; export { utimeSync, utime } from "./utime.ts"; export { removeSync, remove, RemoveOption } from "./remove.ts"; export { renameSync, rename } from "./rename.ts"; +export { realpathSync, realpath } from "./realpath.ts"; export { readFileSync, readFile } from "./read_file.ts"; export { readDirSync, readDir } from "./read_dir.ts"; export { copyFileSync, copyFile } from "./copy_file.ts"; diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index c2690ad325..35806c3add 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -55,6 +55,7 @@ export let OP_CHOWN: number; export let OP_REMOVE: number; export let OP_COPY_FILE: number; export let OP_STAT: number; +export let OP_REALPATH: number; export let OP_READ_DIR: number; export let OP_RENAME: number; export let OP_LINK: number; @@ -97,6 +98,7 @@ export function asyncMsgFromRust(opId: number, ui8: Uint8Array): void { case OP_REMOVE: case OP_COPY_FILE: case OP_STAT: + case OP_REALPATH: case OP_READ_DIR: case OP_RENAME: case OP_LINK: diff --git a/cli/js/lib.deno_runtime.d.ts b/cli/js/lib.deno_runtime.d.ts index 3fdc28a549..fb7767aa63 100644 --- a/cli/js/lib.deno_runtime.d.ts +++ b/cli/js/lib.deno_runtime.d.ts @@ -605,6 +605,21 @@ declare namespace Deno { isSymlink(): boolean; } + // @url js/realpath.d.ts + + /** Returns absolute normalized path with symbolic links resolved + * synchronously. + * + * const realPath = Deno.realpathSync("./some/path"); + */ + export function realpathSync(path: string): string; + + /** Returns absolute normalized path with symbolic links resolved. + * + * const realPath = await Deno.realpath("./some/path"); + */ + export function realpath(path: string): Promise; + // @url js/read_dir.d.ts /** Reads the directory given by path and returns a list of file info diff --git a/cli/js/realpath.ts b/cli/js/realpath.ts new file mode 100644 index 0000000000..c17a0f564b --- /dev/null +++ b/cli/js/realpath.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { sendSync, sendAsync } from "./dispatch_json.ts"; +import * as dispatch from "./dispatch.ts"; + +/** Returns absolute normalized path with symbolic links resolved synchronously. + * + * const realPath = Deno.realpathSync("./some/path"); + */ +export function realpathSync(path: string): string { + return sendSync(dispatch.OP_REALPATH, { path }); +} + +/** Returns absolute normalized path with symbolic links resolved. + * + * const realPath = await Deno.realpath("./some/path"); + */ +export async function realpath(path: string): Promise { + return await sendAsync(dispatch.OP_REALPATH, { path }); +} diff --git a/cli/js/realpath_test.ts b/cli/js/realpath_test.ts new file mode 100644 index 0000000000..1dc9765784 --- /dev/null +++ b/cli/js/realpath_test.ts @@ -0,0 +1,91 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { testPerm, assert, assertEquals } from "./test_util.ts"; + +testPerm({ read: true }, function realpathSyncSuccess(): void { + const incompletePath = "cli/tests/fixture.json"; + const realPath = Deno.realpathSync(incompletePath); + assert(realPath.startsWith("/")); + assert(realPath.endsWith(incompletePath)); +}); + +if (Deno.build.os !== "win") { + testPerm({ read: true, write: true }, function realpathSyncSymlink(): void { + const testDir = Deno.makeTempDirSync(); + const target = testDir + "/target"; + const symlink = testDir + "/symln"; + Deno.mkdirSync(target); + Deno.symlinkSync(target, symlink); + const targetPath = Deno.realpathSync(symlink); + assert(targetPath.startsWith("/")); + assert(targetPath.endsWith("/target")); + }); +} + +testPerm({ read: false }, function realpathSyncPerm(): void { + let caughtError = false; + try { + Deno.realpathSync("some_file"); + } catch (e) { + caughtError = true; + assertEquals(e.kind, Deno.ErrorKind.PermissionDenied); + assertEquals(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, function realpathSyncNotFound(): void { + let caughtError = false; + try { + Deno.realpathSync("bad_filename"); + } catch (e) { + caughtError = true; + assertEquals(e.kind, Deno.ErrorKind.NotFound); + } + assert(caughtError); +}); + +testPerm({ read: true }, async function realpathSuccess(): Promise { + const incompletePath = "cli/tests/fixture.json"; + const realPath = await Deno.realpath(incompletePath); + assert(realPath.startsWith("/")); + assert(realPath.endsWith(incompletePath)); +}); + +if (Deno.build.os !== "win") { + testPerm( + { read: true, write: true }, + async function realpathSymlink(): Promise { + const testDir = Deno.makeTempDirSync(); + const target = testDir + "/target"; + const symlink = testDir + "/symln"; + Deno.mkdirSync(target); + Deno.symlinkSync(target, symlink); + const targetPath = await Deno.realpath(symlink); + assert(targetPath.startsWith("/")); + assert(targetPath.endsWith("/target")); + } + ); +} + +testPerm({ read: false }, async function realpathPerm(): Promise { + let caughtError = false; + try { + await Deno.realpath("some_file"); + } catch (e) { + caughtError = true; + assertEquals(e.kind, Deno.ErrorKind.PermissionDenied); + assertEquals(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ read: true }, async function realpathNotFound(): Promise { + let caughtError = false; + try { + await Deno.realpath("bad_filename"); + } catch (e) { + caughtError = true; + assertEquals(e.kind, Deno.ErrorKind.NotFound); + } + assert(caughtError); +}); diff --git a/cli/js/unit_tests.ts b/cli/js/unit_tests.ts index b5d85fcc83..c63fc5f262 100644 --- a/cli/js/unit_tests.ts +++ b/cli/js/unit_tests.ts @@ -33,6 +33,7 @@ import "./mkdir_test.ts"; import "./net_test.ts"; import "./os_test.ts"; import "./process_test.ts"; +import "./realpath_test.ts"; import "./read_dir_test.ts"; import "./read_file_test.ts"; import "./read_link_test.ts"; diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 4d54aaad6b..54ac1971bb 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -24,6 +24,7 @@ pub fn init(i: &mut Isolate, s: &ThreadSafeState) { i.register_op("remove", s.core_op(json_op(s.stateful_op(op_remove)))); i.register_op("copy_file", s.core_op(json_op(s.stateful_op(op_copy_file)))); i.register_op("stat", s.core_op(json_op(s.stateful_op(op_stat)))); + i.register_op("realpath", s.core_op(json_op(s.stateful_op(op_realpath)))); i.register_op("read_dir", s.core_op(json_op(s.stateful_op(op_read_dir)))); i.register_op("rename", s.core_op(json_op(s.stateful_op(op_rename)))); i.register_op("link", s.core_op(json_op(s.stateful_op(op_link)))); @@ -277,6 +278,33 @@ fn op_stat( }) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RealpathArgs { + promise_id: Option, + path: String, +} + +fn op_realpath( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option, +) -> Result { + let args: RealpathArgs = serde_json::from_value(args)?; + let (_, path_) = deno_fs::resolve_from_cwd(args.path.as_ref())?; + state.check_read(&path_)?; + let path = args.path.clone(); + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + debug!("op_realpath {}", &path); + // corresponds to the realpath on Unix and + // CreateFile and GetFinalPathNameByHandle on Windows + let realpath = fs::canonicalize(&path)?; + let realpath_str = realpath.to_str().unwrap().to_owned().replace("\\", "/"); + Ok(json!(realpath_str)) + }) +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct ReadDirArgs {