diff --git a/cli/js/deno_unstable.ts b/cli/js/deno_unstable.ts index 192137e1f6..991df99553 100644 --- a/cli/js/deno_unstable.ts +++ b/cli/js/deno_unstable.ts @@ -12,6 +12,7 @@ export { applySourceMap, formatDiagnostics } from "./ops/errors.ts"; export { signal, signals, Signal, SignalStream } from "./signals.ts"; export { setRaw } from "./ops/tty.ts"; export { utimeSync, utime } from "./ops/fs/utime.ts"; +export { ftruncateSync, ftruncate } from "./ops/fs/truncate.ts"; export { ShutdownMode, shutdown } from "./net.ts"; export { listen, listenDatagram, connect } from "./net_unstable.ts"; export { startTls } from "./tls.ts"; diff --git a/cli/js/lib.deno.unstable.d.ts b/cli/js/lib.deno.unstable.d.ts index 0a80eb2c64..dd8de2eb62 100644 --- a/cli/js/lib.deno.unstable.d.ts +++ b/cli/js/lib.deno.unstable.d.ts @@ -1248,4 +1248,43 @@ declare namespace Deno { /** **UNSTABLE**: The URL of the file that was originally executed from the command-line. */ export const mainModule: string; + + /** Synchronously truncates or extends the specified file stream, to reach the + * specified `len`. If `len` is not specified then the entire file contents + * are truncated. + * + * ```ts + * // truncate the entire file + * const file = Deno.open("my_file.txt", { read: true, write: true, truncate: true, create: true }); + * Deno.ftruncateSync(file.rid); + * + * // truncate part of the file + * const file = Deno.open("my_file.txt", { read: true, write: true, create: true }); + * Deno.write(file.rid, new TextEncoder().encode("Hello World")); + * Deno.ftruncateSync(file.rid, 7); + * const data = new Uint8Array(32); + * Deno.readSync(file.rid, data); + * console.log(new TextDecoder().decode(data)); // Hello W + * ``` + */ + export function ftruncateSync(rid: number, len?: number): void; + + /** Truncates or extends the specified file stream, to reach the specified `len`. If + * `len` is not specified then the entire file contents are truncated. + * + * ```ts + * // truncate the entire file + * const file = Deno.open("my_file.txt", { read: true, write: true, create: true }); + * await Deno.ftruncate(file.rid); + * + * // truncate part of the file + * const file = Deno.open("my_file.txt", { read: true, write: true, create: true }); + * await Deno.write(file.rid, new TextEncoder().encode("Hello World")); + * await Deno.ftruncate(file.rid, 7); + * const data = new Uint8Array(32); + * await Deno.read(file.rid, data); + * console.log(new TextDecoder().decode(data)); // Hello W + * ``` + */ + export function ftruncate(rid: number, len?: number): Promise; } diff --git a/cli/js/ops/fs/truncate.ts b/cli/js/ops/fs/truncate.ts index 861e843f86..2b805e5ac7 100644 --- a/cli/js/ops/fs/truncate.ts +++ b/cli/js/ops/fs/truncate.ts @@ -13,6 +13,14 @@ function coerceLen(len?: number): number { return len; } +export function ftruncateSync(rid: number, len?: number): void { + sendSync("op_ftruncate", { rid, len: coerceLen(len) }); +} + +export async function ftruncate(rid: number, len?: number): Promise { + await sendAsync("op_ftruncate", { rid, len: coerceLen(len) }); +} + export function truncateSync(path: string, len?: number): void { sendSync("op_truncate", { path, len: coerceLen(len) }); } diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 63b3ad7f56..9ca4b31aeb 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -36,6 +36,7 @@ pub fn init(i: &mut CoreIsolate, s: &State) { i.register_op("op_link", s.stateful_json_op(op_link)); i.register_op("op_symlink", s.stateful_json_op(op_symlink)); i.register_op("op_read_link", s.stateful_json_op(op_read_link)); + i.register_op("op_ftruncate", s.stateful_json_op2(op_ftruncate)); i.register_op("op_truncate", s.stateful_json_op(op_truncate)); i.register_op("op_make_temp_dir", s.stateful_json_op(op_make_temp_dir)); i.register_op("op_make_temp_file", s.stateful_json_op(op_make_temp_file)); @@ -783,6 +784,52 @@ fn op_read_link( }) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FtruncateArgs { + promise_id: Option, + rid: i32, + len: i32, +} + +fn op_ftruncate( + isolate_state: &mut CoreIsolateState, + state: &State, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + state.check_unstable("Deno.ftruncate"); + let args: FtruncateArgs = serde_json::from_value(args)?; + let rid = args.rid as u32; + let len = args.len as u64; + + let resource_table = isolate_state.resource_table.clone(); + let is_sync = args.promise_id.is_none(); + + if is_sync { + let mut resource_table = resource_table.borrow_mut(); + std_file_resource(&mut resource_table, rid, |r| match r { + Ok(std_file) => std_file.set_len(len).map_err(OpError::from), + Err(_) => Err(OpError::type_error( + "cannot truncate this type of resource".to_string(), + )), + })?; + Ok(JsonOp::Sync(json!({}))) + } else { + let fut = async move { + let mut resource_table = resource_table.borrow_mut(); + std_file_resource(&mut resource_table, rid, |r| match r { + Ok(std_file) => std_file.set_len(len).map_err(OpError::from), + Err(_) => Err(OpError::type_error( + "cannot truncate this type of resource".to_string(), + )), + })?; + Ok(json!({})) + }; + Ok(JsonOp::Async(fut.boxed_local())) + } +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct TruncateArgs { diff --git a/cli/tests/unit/truncate_test.ts b/cli/tests/unit/truncate_test.ts index 0148157026..82f8f03eb3 100644 --- a/cli/tests/unit/truncate_test.ts +++ b/cli/tests/unit/truncate_test.ts @@ -1,6 +1,50 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { unitTest, assertEquals, assert } from "./test_util.ts"; +unitTest( + { perms: { read: true, write: true } }, + function ftruncateSyncSuccess(): void { + const filename = Deno.makeTempDirSync() + "/test_ftruncateSync.txt"; + const file = Deno.openSync(filename, { + create: true, + read: true, + write: true, + }); + + Deno.ftruncateSync(file.rid, 20); + assertEquals(Deno.readFileSync(filename).byteLength, 20); + Deno.ftruncateSync(file.rid, 5); + assertEquals(Deno.readFileSync(filename).byteLength, 5); + Deno.ftruncateSync(file.rid, -5); + assertEquals(Deno.readFileSync(filename).byteLength, 0); + + Deno.close(file.rid); + Deno.removeSync(filename); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function ftruncateSuccess(): Promise { + const filename = Deno.makeTempDirSync() + "/test_ftruncate.txt"; + const file = await Deno.open(filename, { + create: true, + read: true, + write: true, + }); + + await Deno.ftruncate(file.rid, 20); + assertEquals((await Deno.readFile(filename)).byteLength, 20); + await Deno.ftruncate(file.rid, 5); + assertEquals((await Deno.readFile(filename)).byteLength, 5); + await Deno.ftruncate(file.rid, -5); + assertEquals((await Deno.readFile(filename)).byteLength, 0); + + Deno.close(file.rid); + await Deno.remove(filename); + } +); + unitTest( { perms: { read: true, write: true } }, function truncateSyncSuccess(): void {