summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/BUILD.gn1
-rw-r--r--cli/msg.fbs6
-rw-r--r--cli/ops.rs23
-rw-r--r--js/deno.ts1
-rw-r--r--js/link.ts31
-rw-r--r--js/link_test.ts104
-rw-r--r--js/unit_tests.ts1
7 files changed, 167 insertions, 0 deletions
diff --git a/cli/BUILD.gn b/cli/BUILD.gn
index ee032a757..f385a0391 100644
--- a/cli/BUILD.gn
+++ b/cli/BUILD.gn
@@ -72,6 +72,7 @@ ts_sources = [
"../js/headers.ts",
"../js/io.ts",
"../js/lib.web_assembly.d.ts",
+ "../js/link.ts",
"../js/location.ts",
"../js/main.ts",
"../js/make_temp_dir.ts",
diff --git a/cli/msg.fbs b/cli/msg.fbs
index 695515f55..c515b9add 100644
--- a/cli/msg.fbs
+++ b/cli/msg.fbs
@@ -21,6 +21,7 @@ union Any {
GlobalTimerStop,
IsTTY,
IsTTYRes,
+ Link,
Listen,
ListenRes,
MakeTempDir,
@@ -391,6 +392,11 @@ table Symlink {
newname: string;
}
+table Link {
+ oldname: string;
+ newname: string;
+}
+
table Stat {
filename: string;
lstat: bool;
diff --git a/cli/ops.rs b/cli/ops.rs
index 130a22431..889061651 100644
--- a/cli/ops.rs
+++ b/cli/ops.rs
@@ -176,6 +176,7 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option<OpCreator> {
msg::Any::GlobalTimer => Some(op_global_timer),
msg::Any::GlobalTimerStop => Some(op_global_timer_stop),
msg::Any::IsTTY => Some(op_is_tty),
+ msg::Any::Link => Some(op_link),
msg::Any::Listen => Some(op_listen),
msg::Any::MakeTempDir => Some(op_make_temp_dir),
msg::Any::Metrics => Some(op_metrics),
@@ -1259,6 +1260,28 @@ fn op_rename(
})
}
+fn op_link(
+ sc: &IsolateStateContainer,
+ base: &msg::Base<'_>,
+ data: deno_buf,
+) -> Box<OpWithError> {
+ assert_eq!(data.len(), 0);
+ let inner = base.inner_as_link().unwrap();
+ let oldname = PathBuf::from(inner.oldname().unwrap());
+ let newname_ = inner.newname().unwrap();
+ let newname = PathBuf::from(newname_);
+
+ if let Err(e) = sc.state().check_write(&newname_) {
+ return odd_future(e);
+ }
+
+ blocking(base.sync(), move || -> OpResult {
+ debug!("op_link {} {}", oldname.display(), newname.display());
+ std::fs::hard_link(&oldname, &newname)?;
+ Ok(empty_buf())
+ })
+}
+
fn op_symlink(
sc: &IsolateStateContainer,
base: &msg::Base<'_>,
diff --git a/js/deno.ts b/js/deno.ts
index f7505fea4..51cc0791a 100644
--- a/js/deno.ts
+++ b/js/deno.ts
@@ -53,6 +53,7 @@ export { readDirSync, readDir } from "./read_dir";
export { copyFileSync, copyFile } from "./copy_file";
export { readlinkSync, readlink } from "./read_link";
export { statSync, lstatSync, stat, lstat } from "./stat";
+export { linkSync, link } from "./link";
export { symlinkSync, symlink } from "./symlink";
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
export { ErrorKind, DenoError } from "./errors";
diff --git a/js/link.ts b/js/link.ts
new file mode 100644
index 000000000..e6ffde66b
--- /dev/null
+++ b/js/link.ts
@@ -0,0 +1,31 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import * as msg from "gen/cli/msg_generated";
+import * as flatbuffers from "./flatbuffers";
+import * as dispatch from "./dispatch";
+
+function req(
+ oldname: string,
+ newname: string
+): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] {
+ const builder = flatbuffers.createBuilder();
+ const oldname_ = builder.createString(oldname);
+ const newname_ = builder.createString(newname);
+ const inner = msg.Link.createLink(builder, oldname_, newname_);
+ return [builder, msg.Any.Link, inner];
+}
+
+/** Synchronously creates `newname` as a hard link to `oldname`.
+ *
+ * Deno.linkSync("old/name", "new/name");
+ */
+export function linkSync(oldname: string, newname: string): void {
+ dispatch.sendSync(...req(oldname, newname));
+}
+
+/** Creates `newname` as a hard link to `oldname`.
+ *
+ * await Deno.link("old/name", "new/name");
+ */
+export async function link(oldname: string, newname: string): Promise<void> {
+ await dispatch.sendAsync(...req(oldname, newname));
+}
diff --git a/js/link_test.ts b/js/link_test.ts
new file mode 100644
index 000000000..c2ca7b1f8
--- /dev/null
+++ b/js/link_test.ts
@@ -0,0 +1,104 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import { test, testPerm, assert, assertEquals } from "./test_util.ts";
+
+testPerm({ read: true, write: true }, function linkSyncSuccess() {
+ const testDir = Deno.makeTempDirSync();
+ const oldData = "Hardlink";
+ const oldName = testDir + "/oldname";
+ const newName = testDir + "/newname";
+ Deno.writeFileSync(oldName, new TextEncoder().encode(oldData));
+ // Create the hard link.
+ Deno.linkSync(oldName, newName);
+ // We should expect reading the same content.
+ const newData = new TextDecoder().decode(Deno.readFileSync(newName));
+ assertEquals(oldData, newData);
+ // Writing to newname also affects oldname.
+ const newData2 = "Modified";
+ Deno.writeFileSync(newName, new TextEncoder().encode(newData2));
+ assertEquals(newData2, new TextDecoder().decode(Deno.readFileSync(oldName)));
+ // Writing to oldname also affects newname.
+ const newData3 = "ModifiedAgain";
+ Deno.writeFileSync(oldName, new TextEncoder().encode(newData3));
+ assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
+ // Remove oldname. File still accessible through newname.
+ Deno.removeSync(oldName);
+ const newNameStat = Deno.statSync(newName);
+ assert(newNameStat.isFile());
+ assert(!newNameStat.isSymlink()); // Not a symlink.
+ assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
+});
+
+testPerm({ read: true, write: true }, function linkSyncExists() {
+ const testDir = Deno.makeTempDirSync();
+ const oldName = testDir + "/oldname";
+ const newName = testDir + "/newname";
+ Deno.writeFileSync(oldName, new TextEncoder().encode("oldName"));
+ // newname is already created.
+ Deno.writeFileSync(newName, new TextEncoder().encode("newName"));
+
+ let err;
+ try {
+ Deno.linkSync(oldName, newName);
+ } catch (e) {
+ err = e;
+ }
+ assert(!!err);
+ console.log(err);
+ assertEquals(err.kind, Deno.ErrorKind.AlreadyExists);
+ assertEquals(err.name, "AlreadyExists");
+});
+
+testPerm({ read: true, write: true }, function linkSyncNotFound() {
+ const testDir = Deno.makeTempDirSync();
+ const oldName = testDir + "/oldname";
+ const newName = testDir + "/newname";
+
+ let err;
+ try {
+ Deno.linkSync(oldName, newName);
+ } catch (e) {
+ err = e;
+ }
+ assert(!!err);
+ console.log(err);
+ assertEquals(err.kind, Deno.ErrorKind.NotFound);
+ assertEquals(err.name, "NotFound");
+});
+
+test(function linkSyncPerm() {
+ let err;
+ try {
+ Deno.linkSync("oldbaddir", "newbaddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEquals(err.kind, Deno.ErrorKind.PermissionDenied);
+ assertEquals(err.name, "PermissionDenied");
+});
+
+testPerm({ read: true, write: true }, async function linkSuccess() {
+ const testDir = Deno.makeTempDirSync();
+ const oldData = "Hardlink";
+ const oldName = testDir + "/oldname";
+ const newName = testDir + "/newname";
+ Deno.writeFileSync(oldName, new TextEncoder().encode(oldData));
+ // Create the hard link.
+ await Deno.link(oldName, newName);
+ // We should expect reading the same content.
+ const newData = new TextDecoder().decode(Deno.readFileSync(newName));
+ assertEquals(oldData, newData);
+ // Writing to newname also affects oldname.
+ const newData2 = "Modified";
+ Deno.writeFileSync(newName, new TextEncoder().encode(newData2));
+ assertEquals(newData2, new TextDecoder().decode(Deno.readFileSync(oldName)));
+ // Writing to oldname also affects newname.
+ const newData3 = "ModifiedAgain";
+ Deno.writeFileSync(oldName, new TextEncoder().encode(newData3));
+ assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
+ // Remove oldname. File still accessible through newname.
+ Deno.removeSync(oldName);
+ const newNameStat = Deno.statSync(newName);
+ assert(newNameStat.isFile());
+ assert(!newNameStat.isSymlink()); // Not a symlink.
+ assertEquals(newData3, new TextDecoder().decode(Deno.readFileSync(newName)));
+});
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index e013de04e..6d626d4d4 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -23,6 +23,7 @@ import "./files_test.ts";
import "./form_data_test.ts";
import "./globals_test.ts";
import "./headers_test.ts";
+import "./link_test.ts";
import "./location_test.ts";
import "./make_temp_dir_test.ts";
import "./metrics_test.ts";