From 017ef096dfb0592d828804175e582895a3f39954 Mon Sep 17 00:00:00 2001 From: Mani Maghsoudlou Date: Tue, 18 Sep 2018 21:38:24 -0700 Subject: [PATCH] Implement deno.symlink() (#742) --- BUILD.gn | 1 + js/deno.ts | 1 + js/symlink.ts | 56 ++++++++++++++++++++++++++++++++++++ js/symlink_test.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++ js/unit_tests.ts | 1 + src/handlers.rs | 31 +++++++++++++++++++- src/msg.fbs | 6 ++++ 7 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 js/symlink.ts create mode 100644 js/symlink_test.ts diff --git a/BUILD.gn b/BUILD.gn index 220406700b..9ea56582a3 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -76,6 +76,7 @@ ts_sources = [ "js/remove.ts", "js/rename.ts", "js/stat.ts", + "js/symlink.ts", "js/text_encoding.ts", "js/timers.ts", "js/types.ts", diff --git a/js/deno.ts b/js/deno.ts index 7ff763e8d7..7d52441ec8 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -8,6 +8,7 @@ export { removeSync, remove, removeAllSync, removeAll } from "./remove"; export { readFileSync, readFile } from "./read_file"; export { renameSync, rename } from "./rename"; export { FileInfo, statSync, lstatSync, stat, lstat } from "./stat"; +export { symlinkSync, symlink } from "./symlink"; export { writeFileSync, writeFile } from "./write_file"; export { ErrorKind, DenoError } from "./errors"; export { libdeno } from "./libdeno"; diff --git a/js/symlink.ts b/js/symlink.ts new file mode 100644 index 0000000000..225dae5f0b --- /dev/null +++ b/js/symlink.ts @@ -0,0 +1,56 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import * as fbs from "gen/msg_generated"; +import { flatbuffers } from "flatbuffers"; +import * as dispatch from "./dispatch"; +import * as util from "./util"; + +/** + * Synchronously creates newname as a symbolic link to oldname. + * The type argument can be set to 'dir' or 'file' and is only + * available on Windows (ignored on other platforms). + * + * import { symlinkSync } from "deno"; + * symlinkSync("old/name", "new/name"); + */ +export function symlinkSync( + oldname: string, + newname: string, + type?: string +): void { + dispatch.sendSync(...req(oldname, newname, type)); +} + +/** + * Creates newname as a symbolic link to oldname. + * The type argument can be set to 'dir' or 'file' and is only + * available on Windows (ignored on other platforms). + * + * import { symlink } from "deno"; + * await symlink("old/name", "new/name"); + */ +export async function symlink( + oldname: string, + newname: string, + type?: string +): Promise { + await dispatch.sendAsync(...req(oldname, newname, type)); +} + +function req( + oldname: string, + newname: string, + type?: string +): [flatbuffers.Builder, fbs.Any, flatbuffers.Offset] { + // TODO Use type for Windows. + if (type) { + return util.notImplemented(); + } + const builder = new flatbuffers.Builder(); + const oldname_ = builder.createString(oldname); + const newname_ = builder.createString(newname); + fbs.Symlink.startSymlink(builder); + fbs.Symlink.addOldname(builder, oldname_); + fbs.Symlink.addNewname(builder, newname_); + const msg = fbs.Symlink.endSymlink(builder); + return [builder, fbs.Any.Symlink, msg]; +} diff --git a/js/symlink_test.ts b/js/symlink_test.ts new file mode 100644 index 0000000000..a3203ac6ec --- /dev/null +++ b/js/symlink_test.ts @@ -0,0 +1,71 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import { test, testPerm, assert, assertEqual } from "./test_util.ts"; +import * as deno from "deno"; + +testPerm({ write: true }, function symlinkSyncSuccess() { + const testDir = deno.makeTempDirSync() + "/test-symlink-sync"; + const oldname = testDir + "/oldname"; + const newname = testDir + "/newname"; + deno.mkdirSync(oldname); + let errOnWindows; + // Just for now, until we implement symlink for Windows. + try { + deno.symlinkSync(oldname, newname); + } catch (e) { + errOnWindows = e; + } + if (errOnWindows) { + assertEqual(errOnWindows.kind, deno.ErrorKind.Other); + assertEqual(errOnWindows.message, "Not implemented"); + } else { + const newNameInfoLStat = deno.lstatSync(newname); + const newNameInfoStat = deno.statSync(newname); + assert(newNameInfoLStat.isSymlink()); + assert(newNameInfoStat.isDirectory()); + } +}); + +testPerm({ write: false }, function symlinkSyncPerm() { + let err; + try { + deno.symlinkSync("oldbaddir", "newbaddir"); + } catch (e) { + err = e; + } + assertEqual(err.kind, deno.ErrorKind.PermissionDenied); + assertEqual(err.name, "PermissionDenied"); +}); + +// Just for now, until we implement symlink for Windows. +testPerm({ write: true }, function symlinkSyncNotImplemented() { + let err; + try { + deno.symlinkSync("oldname", "newname", "dir"); + } catch (e) { + err = e; + } + assertEqual(err.message, "Not implemented"); +}); + +testPerm({ write: true }, async function symlinkSuccess() { + const testDir = deno.makeTempDirSync() + "/test-symlink"; + const oldname = testDir + "/oldname"; + const newname = testDir + "/newname"; + deno.mkdirSync(oldname); + let errOnWindows; + // Just for now, until we implement symlink for Windows. + try { + await deno.symlink(oldname, newname); + } catch (e) { + errOnWindows = e; + } + if (errOnWindows) { + assertEqual(errOnWindows.kind, deno.ErrorKind.Other); + assertEqual(errOnWindows.message, "Not implemented"); + } else { + const newNameInfoLStat = deno.lstatSync(newname); + const newNameInfoStat = deno.statSync(newname); + assert(newNameInfoLStat.isSymlink()); + assert(newNameInfoStat.isDirectory()); + } +}); diff --git a/js/unit_tests.ts b/js/unit_tests.ts index 4f7b9681d8..f1eed319e8 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -13,3 +13,4 @@ import "./stat_test.ts"; import "./rename_test.ts"; import "./blob_test.ts"; import "./timers_test.ts"; +import "./symlink_test.ts"; diff --git a/src/handlers.rs b/src/handlers.rs index cf0d1a3159..ad6552dc1a 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -56,6 +56,7 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) { msg::Any::Remove => handle_remove, msg::Any::ReadFile => handle_read_file, msg::Any::Rename => handle_rename, + msg::Any::Symlink => handle_symlink, msg::Any::SetEnv => handle_set_env, msg::Any::Stat => handle_stat, msg::Any::WriteFile => handle_write_file, @@ -142,6 +143,13 @@ fn permission_denied() -> DenoError { )) } +fn not_implemented() -> DenoError { + DenoError::from(std::io::Error::new( + std::io::ErrorKind::Other, + "Not implemented" + )) +} + fn handle_exit(_d: *const DenoC, base: &msg::Base) -> Box { let msg = base.msg_as_exit().unwrap(); std::process::exit(msg.code()) @@ -663,7 +671,7 @@ fn handle_rename(d: *const DenoC, base: &msg::Base) -> Box { let isolate = from_c(d); if !isolate.flags.allow_write { return odd_future(permission_denied()); - }; + } let msg = base.msg_as_rename().unwrap(); let oldpath = String::from(msg.oldpath().unwrap()); let newpath = String::from(msg.newpath().unwrap()); @@ -673,3 +681,24 @@ fn handle_rename(d: *const DenoC, base: &msg::Base) -> Box { Ok(None) }())) } + +fn handle_symlink(d: *const DenoC, base: &msg::Base) -> Box { + let deno = from_c(d); + if !deno.flags.allow_write { + return odd_future(permission_denied()); + } + // TODO Use type for Windows. + if cfg!(windows) { + return odd_future(not_implemented()); + } else { + let msg = base.msg_as_symlink().unwrap(); + let oldname = String::from(msg.oldname().unwrap()); + let newname = String::from(msg.newname().unwrap()); + Box::new(futures::future::result(|| -> OpResult { + debug!("handle_symlink {} {}", oldname, newname); + #[cfg(any(unix))] + std::os::unix::fs::symlink(Path::new(&oldname), Path::new(&newname))?; + Ok(None) + }())) + } +} diff --git a/src/msg.fbs b/src/msg.fbs index 37a48e2dfd..5a8d52f090 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -20,6 +20,7 @@ union Any { ReadFileRes, WriteFile, Rename, + Symlink, Stat, StatRes, SetEnv, @@ -200,6 +201,11 @@ table Rename { newpath: string; } +table Symlink { + oldname: string; + newname: string; +} + table Stat { filename: string; lstat: bool;