summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUILD.gn1
-rw-r--r--js/copy_file.ts46
-rw-r--r--js/copy_file_test.ts130
-rw-r--r--js/deno.ts1
-rw-r--r--js/unit_tests.ts1
-rw-r--r--src/handlers.rs22
-rw-r--r--src/msg.fbs6
7 files changed, 207 insertions, 0 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 285d6ee13..5d53ae41f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -101,6 +101,7 @@ ts_sources = [
"js/util.ts",
"js/v8_source_maps.ts",
"js/write_file.ts",
+ "js/copy_file.ts",
"tsconfig.json",
# Listing package.json and yarn.lock as sources ensures the bundle is rebuilt
diff --git a/js/copy_file.ts b/js/copy_file.ts
new file mode 100644
index 000000000..ff08cc00b
--- /dev/null
+++ b/js/copy_file.ts
@@ -0,0 +1,46 @@
+// 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";
+
+/**
+ * Copies the contents of a file to another by name synchronously.
+ * Creates a new file if target does not exists, and if target exists,
+ * overwrites original content of the target file.
+ * It would also copy the permission of the original file
+ * to the destination.
+ *
+ * import { copyFileSync } from "deno";
+ * copyFileSync("from.txt", "to.txt");
+ */
+export function copyFileSync(from: string, to: string): void {
+ dispatch.sendSync(...req(from, to));
+}
+
+/**
+ * Copies the contents of a file to another by name.
+ * Creates a new file if target does not exists, and if target exists,
+ * overwrites original content of the target file.
+ * It would also copy the permission of the original file
+ * to the destination.
+ *
+ * import { copyFile } from "deno";
+ * await copyFile("from.txt", "to.txt");
+ */
+export async function copyFile(from: string, to: string): Promise<void> {
+ await dispatch.sendAsync(...req(from, to));
+}
+
+function req(
+ from: string,
+ to: string
+): [flatbuffers.Builder, fbs.Any, flatbuffers.Offset] {
+ const builder = new flatbuffers.Builder();
+ const from_ = builder.createString(from);
+ const to_ = builder.createString(to);
+ fbs.CopyFile.startCopyFile(builder);
+ fbs.CopyFile.addFrom(builder, from_);
+ fbs.CopyFile.addTo(builder, to_);
+ const msg = fbs.CopyFile.endCopyFile(builder);
+ return [builder, fbs.Any.CopyFile, msg];
+}
diff --git a/js/copy_file_test.ts b/js/copy_file_test.ts
new file mode 100644
index 000000000..846cfcbc4
--- /dev/null
+++ b/js/copy_file_test.ts
@@ -0,0 +1,130 @@
+import { testPerm, assert, assertEqual } from "./test_util.ts";
+import * as deno from "deno";
+
+function readFileString(filename: string): string {
+ const dataRead = deno.readFileSync(filename);
+ const dec = new TextDecoder("utf-8");
+ return dec.decode(dataRead);
+}
+
+function writeFileString(filename: string, s: string) {
+ const enc = new TextEncoder();
+ const data = enc.encode(s);
+ deno.writeFileSync(filename, data, 0o666);
+}
+
+function assertSameContent(filename1: string, filename2: string) {
+ const data1 = deno.readFileSync(filename1);
+ const data2 = deno.readFileSync(filename2);
+ assertEqual(data1, data2);
+}
+
+testPerm({ write: true }, function copyFileSyncSuccess() {
+ const tempDir = deno.makeTempDirSync();
+ const fromFilename = tempDir + "/from.txt";
+ const toFilename = tempDir + "/to.txt";
+ writeFileString(fromFilename, "Hello world!");
+ deno.copyFileSync(fromFilename, toFilename);
+ // No change to original file
+ assertEqual(readFileString(fromFilename), "Hello world!");
+ // Original == Dest
+ assertSameContent(fromFilename, toFilename);
+});
+
+testPerm({ write: true }, function copyFileSyncFailure() {
+ const tempDir = deno.makeTempDirSync();
+ const fromFilename = tempDir + "/from.txt";
+ const toFilename = tempDir + "/to.txt";
+ // We skip initial writing here, from.txt does not exist
+ let err;
+ try {
+ deno.copyFileSync(fromFilename, toFilename);
+ } catch (e) {
+ err = e;
+ }
+ assert(!!err);
+ // Rust deem non-existent path as invalid input
+ assertEqual(err.kind, deno.ErrorKind.InvalidInput);
+ assertEqual(err.name, "InvalidInput");
+});
+
+testPerm({ write: true }, function copyFileSyncOverwrite() {
+ const tempDir = deno.makeTempDirSync();
+ const fromFilename = tempDir + "/from.txt";
+ const toFilename = tempDir + "/to.txt";
+ writeFileString(fromFilename, "Hello world!");
+ // Make Dest exist and have different content
+ writeFileString(toFilename, "Goodbye!");
+ deno.copyFileSync(fromFilename, toFilename);
+ // No change to original file
+ assertEqual(readFileString(fromFilename), "Hello world!");
+ // Original == Dest
+ assertSameContent(fromFilename, toFilename);
+});
+
+testPerm({ write: false }, function copyFileSyncPerm() {
+ let err;
+ try {
+ deno.copyFileSync("/from.txt", "/to.txt");
+ } catch (e) {
+ err = e;
+ }
+ assert(!!err);
+ assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
+ assertEqual(err.name, "PermissionDenied");
+});
+
+testPerm({ write: true }, async function copyFileSuccess() {
+ const tempDir = deno.makeTempDirSync();
+ const fromFilename = tempDir + "/from.txt";
+ const toFilename = tempDir + "/to.txt";
+ writeFileString(fromFilename, "Hello world!");
+ await deno.copyFile(fromFilename, toFilename);
+ // No change to original file
+ assertEqual(readFileString(fromFilename), "Hello world!");
+ // Original == Dest
+ assertSameContent(fromFilename, toFilename);
+});
+
+testPerm({ write: true }, async function copyFileFailure() {
+ const tempDir = deno.makeTempDirSync();
+ const fromFilename = tempDir + "/from.txt";
+ const toFilename = tempDir + "/to.txt";
+ // We skip initial writing here, from.txt does not exist
+ let err;
+ try {
+ await deno.copyFile(fromFilename, toFilename);
+ } catch (e) {
+ err = e;
+ }
+ assert(!!err);
+ // Rust deem non-existent path as invalid input
+ assertEqual(err.kind, deno.ErrorKind.InvalidInput);
+ assertEqual(err.name, "InvalidInput");
+});
+
+testPerm({ write: true }, async function copyFileOverwrite() {
+ const tempDir = deno.makeTempDirSync();
+ const fromFilename = tempDir + "/from.txt";
+ const toFilename = tempDir + "/to.txt";
+ writeFileString(fromFilename, "Hello world!");
+ // Make Dest exist and have different content
+ writeFileString(toFilename, "Goodbye!");
+ await deno.copyFile(fromFilename, toFilename);
+ // No change to original file
+ assertEqual(readFileString(fromFilename), "Hello world!");
+ // Original == Dest
+ assertSameContent(fromFilename, toFilename);
+});
+
+testPerm({ write: false }, async function copyFilePerm() {
+ let err;
+ try {
+ await deno.copyFile("/from.txt", "/to.txt");
+ } catch (e) {
+ err = e;
+ }
+ assert(!!err);
+ assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
+ assertEqual(err.name, "PermissionDenied");
+});
diff --git a/js/deno.ts b/js/deno.ts
index cd290bfa3..03f3d1a89 100644
--- a/js/deno.ts
+++ b/js/deno.ts
@@ -9,6 +9,7 @@ export { makeTempDirSync, makeTempDir } from "./make_temp_dir";
export { removeSync, remove, removeAllSync, removeAll } from "./remove";
export { renameSync, rename } from "./rename";
export { readFileSync, readFile } from "./read_file";
+export { copyFileSync, copyFile } from "./copy_file";
export { readlinkSync, readlink } from "./read_link";
export { FileInfo, statSync, lstatSync, stat, lstat } from "./stat";
export { symlinkSync, symlink } from "./symlink";
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index 2eea6c17b..e33fcf245 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -8,6 +8,7 @@ import "./os_test.ts";
import "./files_test.ts";
import "./read_file_test.ts";
import "./write_file_test.ts";
+import "./copy_file_test.ts";
import "./mkdir_test.ts";
import "./make_temp_dir_test.ts";
import "./stat_test.ts";
diff --git a/src/handlers.rs b/src/handlers.rs
index c615f5669..1f861f000 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -80,6 +80,7 @@ pub fn msg_from_js(
msg::Any::Truncate => handle_truncate,
msg::Any::WriteFile => handle_write_file,
msg::Any::Exit => handle_exit,
+ msg::Any::CopyFile => handle_copy_file,
_ => panic!(format!(
"Unhandled message {}",
msg::enum_name_any(msg_type)
@@ -747,6 +748,27 @@ fn handle_read_file(
})
}
+fn handle_copy_file(
+ state: Arc<IsolateState>,
+ base: &msg::Base,
+ data: &'static mut [u8],
+) -> Box<Op> {
+ assert_eq!(data.len(), 0);
+ let msg = base.msg_as_copy_file().unwrap();
+ let from = PathBuf::from(msg.from().unwrap());
+ let to = PathBuf::from(msg.to().unwrap());
+
+ if !state.flags.allow_write {
+ return odd_future(permission_denied());
+ }
+
+ debug!("handle_copy_file {} {}", from.display(), to.display());
+ blocking!(base.sync(), || {
+ fs::copy(&from, &to)?;
+ Ok(empty_buf())
+ })
+}
+
macro_rules! to_seconds {
($time:expr) => {{
// Unwrap is safe here as if the file is before the unix epoch
diff --git a/src/msg.fbs b/src/msg.fbs
index 8b95ec741..ef2f4946b 100644
--- a/src/msg.fbs
+++ b/src/msg.fbs
@@ -20,6 +20,7 @@ union Any {
ReadFile,
ReadFileRes,
WriteFile,
+ CopyFile,
Rename,
Readlink,
ReadlinkRes,
@@ -214,6 +215,11 @@ table WriteFile {
// perm specified by https://godoc.org/os#FileMode
}
+table CopyFile {
+ from: string;
+ to: string;
+}
+
table Rename {
oldpath: string;
newpath: string;