From 05f87a0cf23a370c0009db8343b3770b518799c8 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Tue, 11 Sep 2018 09:00:57 -0700 Subject: [PATCH] Move writeFileSync to write_file.ts, add writeFile and tests (#728) --- js/deno.ts | 4 +- js/os.ts | 32 ---------------- js/os_test.ts | 29 -------------- js/unit_tests.ts | 1 + js/write_file.ts | 54 ++++++++++++++++++++++++++ js/write_file_test.ts | 89 +++++++++++++++++++++++++++++++++++++++++++ src/deno_dir.rs | 2 +- src/fs.rs | 35 +++++++++++++++-- src/handlers.rs | 13 +++---- src/msg.fbs | 16 ++++---- 10 files changed, 192 insertions(+), 83 deletions(-) create mode 100644 js/write_file.ts create mode 100644 js/write_file_test.ts diff --git a/js/deno.ts b/js/deno.ts index fab8ef4e29..8908d7b673 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -8,11 +8,11 @@ export { makeTempDirSync, renameSync, statSync, - lstatSync, - writeFileSync + lstatSync } from "./os"; export { mkdirSync, mkdir } from "./mkdir"; export { readFileSync, readFile } from "./read_file"; +export { writeFileSync, writeFile } from "./write_file"; export { ErrorKind, DenoError } from "./errors"; export { libdeno } from "./libdeno"; export const argv: string[] = []; diff --git a/js/os.ts b/js/os.ts index 4849ac19fd..8b54b4b8cd 100644 --- a/js/os.ts +++ b/js/os.ts @@ -274,38 +274,6 @@ function statSyncInner(filename: string, lstat: boolean): FileInfo { return new FileInfo(res); } -/** - * Write a new file. - * import { writeFileSync } from "deno"; - * - * const encoder = new TextEncoder("utf-8"); - * const data = encoder.encode("Hello world\n"); - * writeFileSync("hello.txt", data); - */ -export function writeFileSync( - filename: string, - data: Uint8Array, - perm = 0o666 -): void { - /* Ideally we could write: - const res = sendSync({ - command: fbs.Command.WRITE_FILE_SYNC, - writeFileSyncFilename: filename, - writeFileSyncData: data, - writeFileSyncPerm: perm - }); - */ - const builder = new flatbuffers.Builder(); - const filename_ = builder.createString(filename); - const dataOffset = fbs.WriteFileSync.createDataVector(builder, data); - fbs.WriteFileSync.startWriteFileSync(builder); - fbs.WriteFileSync.addFilename(builder, filename_); - fbs.WriteFileSync.addData(builder, dataOffset); - fbs.WriteFileSync.addPerm(builder, perm); - const msg = fbs.WriteFileSync.endWriteFileSync(builder); - sendSync(builder, fbs.Any.WriteFileSync, msg); -} - /** * Renames (moves) oldpath to newpath. * import { renameSync } from "deno"; diff --git a/js/os_test.ts b/js/os_test.ts index df88b50850..4c5da1505f 100644 --- a/js/os_test.ts +++ b/js/os_test.ts @@ -85,35 +85,6 @@ test(async function lstatSyncNotFound() { assertEqual(badInfo, undefined); }); -testPerm({ write: true }, function writeFileSyncSuccess() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = deno.makeTempDirSync() + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); - const dataRead = deno.readFileSync(filename); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEqual("Hello", actual); -}); - -// For this test to pass we need --allow-write permission. -// Otherwise it will fail with deno.PermissionDenied instead of deno.NotFound. -testPerm({ write: true }, function writeFileSyncFail() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - const filename = "/baddir/test.txt"; - // The following should fail because /baddir doesn't exist (hopefully). - let caughtError = false; - try { - deno.writeFileSync(filename, data); - } catch (e) { - caughtError = true; - assertEqual(e.kind, deno.ErrorKind.NotFound); - assertEqual(e.name, "NotFound"); - } - assert(caughtError); -}); - testPerm({ write: true }, function makeTempDirSync() { const dir1 = deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); const dir2 = deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); diff --git a/js/unit_tests.ts b/js/unit_tests.ts index 1c5a9227e3..e00cfc1167 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -6,4 +6,5 @@ import "./console_test.ts"; import "./fetch_test.ts"; import "./os_test.ts"; import "./read_file_test.ts"; +import "./write_file_test.ts"; import "./mkdir_test.ts"; diff --git a/js/write_file.ts b/js/write_file.ts new file mode 100644 index 0000000000..60c6ff6abb --- /dev/null +++ b/js/write_file.ts @@ -0,0 +1,54 @@ +// 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"; + +/** + * Write a new file, with given filename and data synchronously. + * + * import { writeFileSync } from "deno"; + * + * const encoder = new TextEncoder("utf-8"); + * const data = encoder.encode("Hello world\n"); + * writeFileSync("hello.txt", data); + */ +export function writeFileSync( + filename: string, + data: Uint8Array, + perm = 0o666 +): void { + dispatch.sendSync(...req(filename, data, perm)); +} + +/** + * Write a new file, with given filename and data. + * + * import { writeFile } from "deno"; + * + * const encoder = new TextEncoder("utf-8"); + * const data = encoder.encode("Hello world\n"); + * await writeFile("hello.txt", data); + */ +export async function writeFile( + filename: string, + data: Uint8Array, + perm = 0o666 +): Promise { + await dispatch.sendAsync(...req(filename, data, perm)); +} + +function req( + filename: string, + data: Uint8Array, + perm: number +): [flatbuffers.Builder, fbs.Any, flatbuffers.Offset] { + const builder = new flatbuffers.Builder(); + const filename_ = builder.createString(filename); + const dataOffset = fbs.WriteFile.createDataVector(builder, data); + fbs.WriteFile.startWriteFile(builder); + fbs.WriteFile.addFilename(builder, filename_); + fbs.WriteFile.addData(builder, dataOffset); + fbs.WriteFile.addPerm(builder, perm); + const msg = fbs.WriteFile.endWriteFile(builder); + return [builder, fbs.Any.WriteFile, msg]; +} diff --git a/js/write_file_test.ts b/js/write_file_test.ts new file mode 100644 index 0000000000..f10343aeb0 --- /dev/null +++ b/js/write_file_test.ts @@ -0,0 +1,89 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import { testPerm, assert, assertEqual } from "./test_util.ts"; +import * as deno from "deno"; + +testPerm({ write: true }, function writeFileSyncSuccess() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + deno.writeFileSync(filename, data, 0o666); + const dataRead = deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEqual("Hello", actual); +}); + +testPerm({ write: true }, function writeFileSyncFail() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail because /baddir doesn't exist (hopefully). + let caughtError = false; + try { + deno.writeFileSync(filename, data); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.NotFound); + assertEqual(e.name, "NotFound"); + } + assert(caughtError); +}); + +testPerm({ write: false }, function writeFileSyncPerm() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail due to no write permission + let caughtError = false; + try { + deno.writeFileSync(filename, data); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); + +testPerm({ write: true }, async function writeFileSuccess() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + await deno.writeFile(filename, data, 0o666); + const dataRead = deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEqual("Hello", actual); +}); + +testPerm({ write: true }, async function writeFileNotFound() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail because /baddir doesn't exist (hopefully). + let caughtError = false; + try { + await deno.writeFile(filename, data); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.NotFound); + assertEqual(e.name, "NotFound"); + } + assert(caughtError); +}); + +testPerm({ write: false }, async function writeFilePerm() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail due to no write permission + let caughtError = false; + try { + await deno.writeFile(filename, data); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.PermissionDenied); + assertEqual(e.name, "PermissionDenied"); + } + assert(caughtError); +}); diff --git a/src/deno_dir.rs b/src/deno_dir.rs index a56062a34c..a8379b9005 100644 --- a/src/deno_dir.rs +++ b/src/deno_dir.rs @@ -118,7 +118,7 @@ impl DenoDir { Some(ref parent) => fs::create_dir_all(parent), None => Ok(()), }?; - deno_fs::write_file_sync(&p, source.as_bytes())?; + deno_fs::write_file(&p, source.as_bytes(), 0o666)?; source } else { let source = fs::read_to_string(&p)?; diff --git a/src/fs.rs b/src/fs.rs index 9290d94e5b..e04675f66d 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,5 +1,5 @@ use std; -use std::fs::{create_dir, File}; +use std::fs::{create_dir, File, OpenOptions}; use std::io::ErrorKind; use std::io::Write; use std::path::{Path, PathBuf}; @@ -7,9 +7,36 @@ use std::path::{Path, PathBuf}; use rand; use rand::Rng; -pub fn write_file_sync(path: &Path, content: &[u8]) -> std::io::Result<()> { - let mut f = File::create(path)?; - f.write_all(content) +#[cfg(any(unix))] +use std::os::unix::fs::PermissionsExt; + +pub fn write_file( + filename: &Path, + data: &[u8], + perm: u32, +) -> std::io::Result<()> { + let is_append = perm & (1 << 31) != 0; + let mut file = OpenOptions::new() + .read(false) + .write(true) + .append(is_append) + .truncate(!is_append) + .create(true) + .open(filename)?; + + set_permissions(&mut file, perm)?; + file.write_all(data) +} + +#[cfg(any(unix))] +fn set_permissions(file: &mut File, perm: u32) -> std::io::Result<()> { + debug!("set file perm to {}", perm); + file.set_permissions(PermissionsExt::from_mode(perm & 0o777)) +} +#[cfg(not(any(unix)))] +fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> { + // NOOP on windows + Ok(()) } pub fn make_temp_dir( diff --git a/src/handlers.rs b/src/handlers.rs index 7d8fafc10b..1c821b73c1 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -54,7 +54,7 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) { msg::Any::RenameSync => handle_rename_sync, msg::Any::SetEnv => handle_set_env, msg::Any::StatSync => handle_stat_sync, - msg::Any::WriteFileSync => handle_write_file_sync, + msg::Any::WriteFile => handle_write_file, msg::Any::Exit => handle_exit, _ => panic!(format!( "Unhandled message {}", @@ -517,20 +517,19 @@ fn handle_stat_sync(_d: *const DenoC, base: &msg::Base) -> Box { }())) } -fn handle_write_file_sync(d: *const DenoC, base: &msg::Base) -> Box { - let msg = base.msg_as_write_file_sync().unwrap(); +fn handle_write_file(d: *const DenoC, base: &msg::Base) -> Box { + let msg = base.msg_as_write_file().unwrap(); let filename = String::from(msg.filename().unwrap()); let data = msg.data().unwrap(); - // TODO let perm = msg.perm(); + let perm = msg.perm(); let deno = from_c(d); - debug!("handle_write_file_sync {}", filename); + debug!("handle_write_file {}", filename); Box::new(futures::future::result(|| -> OpResult { if !deno.flags.allow_write { Err(permission_denied()) } else { - // TODO(ry) Use perm. - deno_fs::write_file_sync(Path::new(&filename), data)?; + deno_fs::write_file(Path::new(&filename), data, perm)?; Ok(None) } }())) diff --git a/src/msg.fbs b/src/msg.fbs index 33278a6698..fcc8636250 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -17,11 +17,11 @@ union Any { Mkdir, ReadFile, ReadFileRes, + WriteFile, RenameSync, StatSync, StatSyncRes, SetEnv, - WriteFileSync, } enum ErrorKind: byte { @@ -181,6 +181,13 @@ table ReadFileRes { data: [ubyte]; } +table WriteFile { + filename: string; + data: [ubyte]; + perm: uint; + // perm specified by https://godoc.org/os#FileMode +} + table RenameSync { oldpath: string; newpath: string; @@ -200,11 +207,4 @@ table StatSyncRes { created:ulong; } -table WriteFileSync { - filename: string; - data: [ubyte]; - perm: uint; - // perm specified by https://godoc.org/os#FileMode -} - root_type Base;