diff options
author | Bert Belder <bertbelder@gmail.com> | 2018-08-24 00:36:45 +0200 |
---|---|---|
committer | Bert Belder <bertbelder@gmail.com> | 2018-08-29 22:40:05 +0200 |
commit | ceaf82268282d16b97101c00c75612745de416bb (patch) | |
tree | 6294eeeb063ca2196ef285ad7f125437cbc3bd45 | |
parent | a836c493f30323e7b40e988140ed2603f0e3d10f (diff) |
Implement makeTempDirSync()
-rw-r--r-- | BUILD.gn | 1 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | js/deno.ts | 2 | ||||
-rw-r--r-- | js/os.ts | 51 | ||||
-rw-r--r-- | js/unit_tests.ts | 69 | ||||
-rw-r--r-- | src/fs.rs | 32 | ||||
-rw-r--r-- | src/handlers.rs | 44 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/msg.fbs | 12 |
9 files changed, 188 insertions, 25 deletions
@@ -43,6 +43,7 @@ main_extern = [ "$rust_build:log", "$rust_build:sha1", "$rust_build:tempfile", + "$rust_build:rand", "$rust_build:tokio", "$rust_build:tokio_current_thread", "$rust_build:url", diff --git a/Cargo.toml b/Cargo.toml index 7d73ede4c..c6ebbbf15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ version = "0.0.0" url = "1.7.1" libc = "0.2.42" log = "0.4.3" +rand = "0.5.4" sha1 = "0.6.0" tempfile = "3" tokio = {git = "https://github.com/tokio-rs/tokio.git", rev = "5d0d2a2e1214f856993df6965825c89bfcaa879e"} diff --git a/js/deno.ts b/js/deno.ts index 63bee0b7c..7a5c4217e 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -1,5 +1,5 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. // Public deno module. -export { exit, readFileSync, writeFileSync } from "./os"; +export { exit, makeTempDirSync, readFileSync, writeFileSync } from "./os"; export { libdeno } from "./libdeno"; export const argv: string[] = []; @@ -85,6 +85,57 @@ export function codeCache( } } +/** + * makeTempDirSync creates a new temporary directory in the directory `dir`, its + * name beginning with `prefix` and ending with `suffix`. + * It returns the full path to the newly created directory. + * If `dir` is unspecified, tempDir uses the default directory for temporary + * files. Multiple programs calling tempDir simultaneously will not choose the + * same directory. It is the caller's responsibility to remove the directory + * when no longer needed. + */ +export interface MakeTempDirOptions { + dir?: string; + prefix?: string; + suffix?: string; +} +export function makeTempDirSync({ + dir, + prefix, + suffix +}: MakeTempDirOptions = {}): string { + const builder = new flatbuffers.Builder(); + const fbDir = dir == null ? -1 : builder.createString(dir); + const fbPrefix = prefix == null ? -1 : builder.createString(prefix); + const fbSuffix = suffix == null ? -1 : builder.createString(suffix); + fbs.MakeTempDir.startMakeTempDir(builder); + if (dir != null) { + fbs.MakeTempDir.addDir(builder, fbDir); + } + if (prefix != null) { + fbs.MakeTempDir.addPrefix(builder, fbPrefix); + } + if (suffix != null) { + fbs.MakeTempDir.addSuffix(builder, fbSuffix); + } + const msg = fbs.MakeTempDir.endMakeTempDir(builder); + fbs.Base.startBase(builder); + fbs.Base.addMsg(builder, msg); + fbs.Base.addMsgType(builder, fbs.Any.MakeTempDir); + builder.finish(fbs.Base.endBase(builder)); + const resBuf = libdeno.send(builder.asUint8Array()); + assert(resBuf != null); + const bb = new flatbuffers.ByteBuffer(new Uint8Array(resBuf!)); + const baseRes = fbs.Base.getRootAsBase(bb); + maybeThrowError(baseRes); + assert(fbs.Any.MakeTempDirRes === baseRes.msgType()); + const res = new fbs.MakeTempDirRes(); + assert(baseRes.msg(res) != null); + const path = res.path(); + assert(path != null); + return path!; +} + export function readFileSync(filename: string): Uint8Array { /* Ideally we could write const res = send({ diff --git a/js/unit_tests.ts b/js/unit_tests.ts index 17ddd5256..92957c493 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -41,16 +41,16 @@ test(function tests_readFileSync_NotFound() { }); */ -/* TODO(ry) Add this once we can create a tmpDir to write the file into. -test(function writeFileSyncSuccess() { +testPerm({ write: true }, function writeFileSync() { const enc = new TextEncoder(); - const dataWritten = enc.encode("Hello"); - const filename = "TEMPDIR/test.txt"; - deno.writeFileSync(filename, dataWritten, 0o666); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + deno.writeFileSync(filename, data, 0o666); const dataRead = deno.readFileSync(filename); - assertEqual(dataRead, dataWritten); + 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. @@ -70,23 +70,48 @@ testPerm({ write: true }, function writeFileSyncFail() { assert(caughtError); }); +testPerm({ write: true }, function makeTempDirSync() { + const dir1 = deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); + const dir2 = deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); + // Check that both dirs are different. + assert(dir1 != dir2); + for (const dir of [dir1, dir2]) { + // Check that the prefix and suffix are applied. + const lastPart = dir.replace(/^.*[\\\/]/, ""); + assert(lastPart.startsWith("hello")); + assert(lastPart.endsWith("world")); + } + // Check that the `dir` option works. + const dir3 = deno.makeTempDirSync({ dir: dir1 }); + assert(dir3.startsWith(dir1)); + assert(/^[\\\/]/.test(dir3.slice(dir1.length))); + // Check that creating a temp dir inside a nonexisting directory fails. + let err; + try { + deno.makeTempDirSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + // TODO assert(err instanceof deno.NotFound). + assert(err); + assertEqual(err.name, "deno.NotFound"); +}); + +test(function makeTempDirSyncPerm() { + // makeTempDirSync should require write permissions (for now). + let err; + try { + deno.makeTempDirSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + // TODO assert(err instanceof deno.PermissionDenied). + assert(err); + assertEqual(err.name, "deno.PermissionDenied"); +}); + testPerm({ net: true }, async function tests_fetch() { const response = await fetch("http://localhost:4545/package.json"); const json = await response.json(); assertEqual(json.name, "deno"); }); - -/* -test(async function tests_writeFileSync() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - // TODO need ability to get tmp dir. - const fn = "/tmp/test.txt"; - writeFileSync("/tmp/test.txt", data, 0o666); - const dataRead = deno.readFileSync("/tmp/test.txt"); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEqual("Hello", actual); -}); - -*/ @@ -1,13 +1,41 @@ use std; -use std::fs::File; +use std::fs::{create_dir, File}; +use std::io::ErrorKind; use std::io::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; + +use rand; +use rand::RngCore; pub fn write_file_sync(path: &Path, content: &[u8]) -> std::io::Result<()> { let mut f = File::create(path)?; f.write_all(content) } +pub fn make_temp_dir( + dir: Option<&Path>, + prefix: Option<&str>, + suffix: Option<&str>, +) -> std::io::Result<PathBuf> { + let prefix_ = prefix.unwrap_or(""); + let suffix_ = suffix.unwrap_or(""); + let mut buf: PathBuf = match dir { + Some(ref p) => p.to_path_buf(), + None => std::env::temp_dir(), + }.join("_"); + loop { + let unique = rand::thread_rng().next_u32(); + buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_)); + // TODO: on posix, set mode flags to 0o700. + let r = create_dir(buf.as_path()); + match r { + Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue, + Ok(_) => return Ok(buf), + Err(e) => return Err(e), + } + } +} + pub fn mkdir(path: &Path) -> std::io::Result<()> { debug!("mkdir -p {}", path.display()); assert!(path.has_root(), "non-has_root not yet implemented"); diff --git a/src/handlers.rs b/src/handlers.rs index 4638288ca..866ca8b3a 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -65,6 +65,13 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) { let msg = msg::Exit::init_from_table(base.msg().unwrap()); std::process::exit(msg.code()) } + msg::Any::MakeTempDir => { + let msg = msg::MakeTempDir::init_from_table(base.msg().unwrap()); + let dir = msg.dir(); + let prefix = msg.prefix(); + let suffix = msg.suffix(); + handle_make_temp_dir(d, &mut builder, dir, prefix, suffix) + } msg::Any::ReadFileSync => { // TODO base.msg_as_ReadFileSync(); let msg = msg::ReadFileSync::init_from_table(base.msg().unwrap()); @@ -395,6 +402,43 @@ fn send_timer_ready(d: *const DenoC, timer_id: u32, done: bool) { ); } +fn handle_make_temp_dir( + d: *const DenoC, + builder: &mut FlatBufferBuilder, + dir: Option<&str>, + prefix: Option<&str>, + suffix: Option<&str>, +) -> HandlerResult { + let deno = from_c(d); + if !deno.flags.allow_write { + let err = std::io::Error::new( + std::io::ErrorKind::PermissionDenied, + "allow_write is off.", + ); + return Err(err.into()); + } + // TODO(piscisaureus): use byte vector for paths, not a string. + // See https://github.com/denoland/deno/issues/627. + // We can't assume that paths are always valid utf8 strings. + let path = deno_fs::make_temp_dir(dir.map(Path::new), prefix, suffix)?; + let path_off = builder.create_string(path.to_str().unwrap()); + let msg = msg::MakeTempDirRes::create( + builder, + &msg::MakeTempDirResArgs { + path: Some(path_off), + ..Default::default() + }, + ); + Ok(create_msg( + builder, + &msg::BaseArgs { + msg: Some(flatbuffers::Offset::new(msg.value())), + msg_type: msg::Any::MakeTempDirRes, + ..Default::default() + }, + )) +} + // Prototype https://github.com/denoland/deno/blob/golang/os.go#L171-L184 fn handle_read_file_sync( _d: *const DenoC, diff --git a/src/main.rs b/src/main.rs index 28e852fdc..6a013bea0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate futures; extern crate hyper; extern crate libc; extern crate msg_rs as msg_generated; +extern crate rand; extern crate sha1; extern crate tempfile; extern crate tokio; diff --git a/src/msg.fbs b/src/msg.fbs index a20af6892..162a8ddee 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -12,6 +12,8 @@ union Any { TimerClear, FetchReq, FetchRes, + MakeTempDir, + MakeTempDirRes, ReadFileSync, ReadFileSyncRes, WriteFileSync, @@ -133,6 +135,16 @@ table FetchRes { body: [ubyte]; } +table MakeTempDir { + dir: string; + prefix: string; + suffix: string; +} + +table MakeTempDirRes { + path: string; +} + table ReadFileSync { filename: string; } |