From 08dcf6bff73bbe579769ccd0f135ed4af919ea48 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Tue, 18 Feb 2020 11:45:59 -0800 Subject: [PATCH] feat: Deno.makeTempFile (#4024) --- cli/fs.rs | 13 +++- cli/js/deno.ts | 6 +- cli/js/dispatch.ts | 1 + cli/js/lib.deno.ns.d.ts | 28 ++++++-- cli/js/make_temp.ts | 61 +++++++++++++++++ cli/js/make_temp_dir.ts | 35 ---------- ...ake_temp_dir_test.ts => make_temp_test.ts} | 66 +++++++++++++++++++ cli/js/unit_tests.ts | 2 +- cli/ops/fs.rs | 43 +++++++++++- 9 files changed, 208 insertions(+), 47 deletions(-) create mode 100644 cli/js/make_temp.ts delete mode 100644 cli/js/make_temp_dir.ts rename cli/js/{make_temp_dir_test.ts => make_temp_test.ts} (50%) diff --git a/cli/fs.rs b/cli/fs.rs index 521b996ac32d5..9104d1e7a821d 100644 --- a/cli/fs.rs +++ b/cli/fs.rs @@ -60,10 +60,11 @@ fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> { Ok(()) } -pub fn make_temp_dir( +pub fn make_temp( dir: Option<&Path>, prefix: Option<&str>, suffix: Option<&str>, + is_dir: bool, ) -> std::io::Result { let prefix_ = prefix.unwrap_or(""); let suffix_ = suffix.unwrap_or(""); @@ -77,7 +78,15 @@ pub fn make_temp_dir( let unique = rng.gen::(); buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_)); // TODO: on posix, set mode flags to 0o700. - let r = create_dir(buf.as_path()); + let r = if is_dir { + create_dir(buf.as_path()) + } else { + OpenOptions::new() + .write(true) + .create_new(true) + .open(buf.as_path()) + .map(|_| ()) + }; match r { Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue, Ok(_) => return Ok(buf), diff --git a/cli/js/deno.ts b/cli/js/deno.ts index c52e6dc2d44b8..cdfedcd76b3ed 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -66,8 +66,10 @@ export { linkSync, link } from "./link.ts"; export { makeTempDirSync, makeTempDir, - MakeTempDirOptions -} from "./make_temp_dir.ts"; + makeTempFileSync, + makeTempFile, + MakeTempOptions +} from "./make_temp.ts"; export { metrics, Metrics } from "./metrics.ts"; export { mkdirSync, mkdir } from "./mkdir.ts"; export { diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index 4493d3771017c..b4677b219ccd9 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -65,6 +65,7 @@ export let OP_SYMLINK: number; export let OP_READ_LINK: number; export let OP_TRUNCATE: number; export let OP_MAKE_TEMP_DIR: number; +export let OP_MAKE_TEMP_FILE: number; export let OP_CWD: number; export let OP_CONNECT_TLS: number; export let OP_HOSTNAME: number; diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index b96e108c3b42d..12e8ae4ba9728 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -680,9 +680,9 @@ declare namespace Deno { mode?: number ): Promise; - // @url js/make_temp_dir.d.ts + // @url js/make_temp.d.ts - export interface MakeTempDirOptions { + export interface MakeTempOptions { dir?: string; prefix?: string; suffix?: string; @@ -696,7 +696,7 @@ declare namespace Deno { * Requires allow-write. */ // TODO(ry) Doesn't check permissions. - export function makeTempDirSync(options?: MakeTempDirOptions): string; + export function makeTempDirSync(options?: MakeTempOptions): string; /** makeTempDir creates a new temporary directory in the directory `dir`, its * name beginning with `prefix` and ending with `suffix`. @@ -712,7 +712,27 @@ declare namespace Deno { * Requires allow-write. */ // TODO(ry) Doesn't check permissions. - export function makeTempDir(options?: MakeTempDirOptions): Promise; + export function makeTempDir(options?: MakeTempOptions): Promise; + + /** makeTempFileSync is the synchronous version of `makeTempFile`. + * + * const tempFileName0 = Deno.makeTempFileSync(); + * const tempFileName1 = Deno.makeTempFileSync({ prefix: 'my_temp' }); + */ + export function makeTempFileSync(options?: MakeTempOptions): string; + + /** makeTempFile creates a new temporary file in the directory `dir`, its + * name beginning with `prefix` and ending with `suffix`. + * It returns the full path to the newly created file. + * If `dir` is unspecified, tempFile uses the default directory for temporary + * files. Multiple programs calling tempFile simultaneously will not choose the + * same directory. It is the caller's responsibility to remove the file + * when no longer needed. + * + * const tempFileName0 = await Deno.makeTempFile(); + * const tempFileName1 = await Deno.makeTempFile({ prefix: 'my_temp' }); + */ + export function makeTempFile(options?: MakeTempOptions): Promise; /** Changes the permission of a specific file/directory of specified path * synchronously. diff --git a/cli/js/make_temp.ts b/cli/js/make_temp.ts new file mode 100644 index 0000000000000..8b15ab1848ba2 --- /dev/null +++ b/cli/js/make_temp.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { sendSync, sendAsync } from "./dispatch_json.ts"; +import * as dispatch from "./dispatch.ts"; + +export interface MakeTempOptions { + dir?: string; + prefix?: string; + suffix?: string; +} + +/** makeTempDirSync is the synchronous version of `makeTempDir`. + * + * const tempDirName0 = Deno.makeTempDirSync(); + * const tempDirName1 = Deno.makeTempDirSync({ prefix: 'my_temp' }); + */ +export function makeTempDirSync(options: MakeTempOptions = {}): string { + return sendSync(dispatch.OP_MAKE_TEMP_DIR, options); +} + +/** makeTempDir 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. + * + * const tempDirName0 = await Deno.makeTempDir(); + * const tempDirName1 = await Deno.makeTempDir({ prefix: 'my_temp' }); + */ +export async function makeTempDir( + options: MakeTempOptions = {} +): Promise { + return await sendAsync(dispatch.OP_MAKE_TEMP_DIR, options); +} + +/** makeTempFileSync is the synchronous version of `makeTempFile`. + * + * const tempFileName0 = Deno.makeTempFileSync(); + * const tempFileName1 = Deno.makeTempFileSync({ prefix: 'my_temp' }); + */ +export function makeTempFileSync(options: MakeTempOptions = {}): string { + return sendSync(dispatch.OP_MAKE_TEMP_FILE, options); +} + +/** makeTempFile creates a new temporary file in the directory `dir`, its + * name beginning with `prefix` and ending with `suffix`. + * It returns the full path to the newly created file. + * If `dir` is unspecified, tempFile uses the default directory for temporary + * files. Multiple programs calling tempFile simultaneously will not choose the + * same directory. It is the caller's responsibility to remove the file + * when no longer needed. + * + * const tempFileName0 = await Deno.makeTempFile(); + * const tempFileName1 = await Deno.makeTempFile({ prefix: 'my_temp' }); + */ +export async function makeTempFile( + options: MakeTempOptions = {} +): Promise { + return await sendAsync(dispatch.OP_MAKE_TEMP_FILE, options); +} diff --git a/cli/js/make_temp_dir.ts b/cli/js/make_temp_dir.ts deleted file mode 100644 index 320f9a9645022..0000000000000 --- a/cli/js/make_temp_dir.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { sendSync, sendAsync } from "./dispatch_json.ts"; -import * as dispatch from "./dispatch.ts"; - -export interface MakeTempDirOptions { - dir?: string; - prefix?: string; - suffix?: string; -} - -/** makeTempDirSync is the synchronous version of `makeTempDir`. - * - * const tempDirName0 = Deno.makeTempDirSync(); - * const tempDirName1 = Deno.makeTempDirSync({ prefix: 'my_temp' }); - */ -export function makeTempDirSync(options: MakeTempDirOptions = {}): string { - return sendSync(dispatch.OP_MAKE_TEMP_DIR, options); -} - -/** makeTempDir 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. - * - * const tempDirName0 = await Deno.makeTempDir(); - * const tempDirName1 = await Deno.makeTempDir({ prefix: 'my_temp' }); - */ -export async function makeTempDir( - options: MakeTempDirOptions = {} -): Promise { - return await sendAsync(dispatch.OP_MAKE_TEMP_DIR, options); -} diff --git a/cli/js/make_temp_dir_test.ts b/cli/js/make_temp_test.ts similarity index 50% rename from cli/js/make_temp_dir_test.ts rename to cli/js/make_temp_test.ts index ef6e7a67e794f..f6bf59d32dbbc 100644 --- a/cli/js/make_temp_dir_test.ts +++ b/cli/js/make_temp_test.ts @@ -64,3 +64,69 @@ testPerm({ write: true }, async function makeTempDirSuccess(): Promise { assertEquals(err.kind, Deno.ErrorKind.NotFound); assertEquals(err.name, "NotFound"); }); + +testPerm({ write: true }, function makeTempFileSyncSuccess(): void { + const file1 = Deno.makeTempFileSync({ prefix: "hello", suffix: "world" }); + const file2 = Deno.makeTempFileSync({ prefix: "hello", suffix: "world" }); + // Check that both dirs are different. + assert(file1 !== file2); + for (const dir of [file1, file2]) { + // 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 dir = Deno.makeTempDirSync({ prefix: "tempdir" }); + const file3 = Deno.makeTempFileSync({ dir }); + assert(file3.startsWith(dir)); + assert(/^[\\\/]/.test(file3.slice(dir.length))); + // Check that creating a temp file inside a nonexisting directory fails. + let err; + try { + Deno.makeTempFileSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assertEquals(err.kind, Deno.ErrorKind.NotFound); + assertEquals(err.name, "NotFound"); +}); + +test(function makeTempFileSyncPerm(): void { + // makeTempFileSync should require write permissions (for now). + let err; + try { + Deno.makeTempFileSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assertEquals(err.kind, Deno.ErrorKind.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +testPerm({ write: true }, async function makeTempFileSuccess(): Promise { + const file1 = await Deno.makeTempFile({ prefix: "hello", suffix: "world" }); + const file2 = await Deno.makeTempFile({ prefix: "hello", suffix: "world" }); + // Check that both dirs are different. + assert(file1 !== file2); + for (const dir of [file1, file2]) { + // 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 dir = Deno.makeTempDirSync({ prefix: "tempdir" }); + const file3 = await Deno.makeTempFile({ dir }); + assert(file3.startsWith(dir)); + assert(/^[\\\/]/.test(file3.slice(dir.length))); + // Check that creating a temp file inside a nonexisting directory fails. + let err; + try { + await Deno.makeTempFile({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assertEquals(err.kind, Deno.ErrorKind.NotFound); + assertEquals(err.name, "NotFound"); +}); diff --git a/cli/js/unit_tests.ts b/cli/js/unit_tests.ts index 3c808fe462d0f..c43abaa5c3f7e 100644 --- a/cli/js/unit_tests.ts +++ b/cli/js/unit_tests.ts @@ -29,7 +29,7 @@ import "./headers_test.ts"; import "./internals_test.ts"; import "./link_test.ts"; import "./location_test.ts"; -import "./make_temp_dir_test.ts"; +import "./make_temp_test.ts"; import "./metrics_test.ts"; import "./mixins/dom_iterable_test.ts"; import "./mkdir_test.ts"; diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index ef7a0ba9d4e11..86780ebf7fce8 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -38,6 +38,10 @@ pub fn init(i: &mut Isolate, s: &State) { "make_temp_dir", s.core_op(json_op(s.stateful_op(op_make_temp_dir))), ); + i.register_op( + "make_temp_file", + s.core_op(json_op(s.stateful_op(op_make_temp_file))), + ); i.register_op("cwd", s.core_op(json_op(s.stateful_op(op_cwd)))); i.register_op("utime", s.core_op(json_op(s.stateful_op(op_utime)))); } @@ -529,7 +533,7 @@ fn op_truncate( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] -struct MakeTempDirArgs { +struct MakeTempArgs { promise_id: Option, dir: Option, prefix: Option, @@ -541,7 +545,39 @@ fn op_make_temp_dir( args: Value, _zero_copy: Option, ) -> Result { - let args: MakeTempDirArgs = serde_json::from_value(args)?; + let args: MakeTempArgs = serde_json::from_value(args)?; + + let dir = args.dir.map(PathBuf::from); + let prefix = args.prefix.map(String::from); + let suffix = args.suffix.map(String::from); + + state + .check_write(dir.clone().unwrap_or_else(std::env::temp_dir).as_path())?; + + let is_sync = args.promise_id.is_none(); + blocking_json(is_sync, move || { + // 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( + // Converting Option to Option<&str> + dir.as_ref().map(|x| &**x), + prefix.as_ref().map(|x| &**x), + suffix.as_ref().map(|x| &**x), + true, + )?; + let path_str = path.to_str().unwrap(); + + Ok(json!(path_str)) + }) +} + +fn op_make_temp_file( + state: &State, + args: Value, + _zero_copy: Option, +) -> Result { + let args: MakeTempArgs = serde_json::from_value(args)?; let dir = args.dir.map(PathBuf::from); let prefix = args.prefix.map(String::from); @@ -555,11 +591,12 @@ fn op_make_temp_dir( // 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( + let path = deno_fs::make_temp( // Converting Option to Option<&str> dir.as_ref().map(|x| &**x), prefix.as_ref().map(|x| &**x), suffix.as_ref().map(|x| &**x), + false, )?; let path_str = path.to_str().unwrap();