diff --git a/js/chmod_test.ts b/js/chmod_test.ts index 75f4524db3..ceee5b0656 100644 --- a/js/chmod_test.ts +++ b/js/chmod_test.ts @@ -9,7 +9,7 @@ testPerm({ write: true }, function chmodSyncSuccess() { const data = enc.encode("Hello"); const tempDir = deno.makeTempDirSync(); const filename = tempDir + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); // On windows no effect, but should not crash deno.chmodSync(filename, 0o777); @@ -29,7 +29,7 @@ if (isNotWindows) { const tempDir = deno.makeTempDirSync(); const filename = tempDir + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); const symlinkName = tempDir + "/test_symlink.txt"; deno.symlinkSync(filename, symlinkName); @@ -74,7 +74,7 @@ testPerm({ write: true }, async function chmodSuccess() { const data = enc.encode("Hello"); const tempDir = deno.makeTempDirSync(); const filename = tempDir + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); // On windows no effect, but should not crash await deno.chmod(filename, 0o777); @@ -94,7 +94,7 @@ if (isNotWindows) { const tempDir = deno.makeTempDirSync(); const filename = tempDir + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); const symlinkName = tempDir + "/test_symlink.txt"; deno.symlinkSync(filename, symlinkName); diff --git a/js/copy_file_test.ts b/js/copy_file_test.ts index 02f13c6f35..bdc455f45e 100644 --- a/js/copy_file_test.ts +++ b/js/copy_file_test.ts @@ -11,7 +11,7 @@ function readFileString(filename: string): string { function writeFileString(filename: string, s: string) { const enc = new TextEncoder(); const data = enc.encode(s); - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); } function assertSameContent(filename1: string, filename2: string) { diff --git a/js/metrics_test.ts b/js/metrics_test.ts index d81f54c3e4..77473a1d5e 100644 --- a/js/metrics_test.ts +++ b/js/metrics_test.ts @@ -27,7 +27,7 @@ testPerm({ write: true }, function metricsUpdatedIfNoResponseSync() { const filename = deno.makeTempDirSync() + "/test.txt"; const data = new Uint8Array([41, 42, 43]); - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); const metrics = deno.metrics(); assert(metrics.opsDispatched === metrics.opsCompleted); @@ -37,7 +37,7 @@ testPerm({ write: true }, async function metricsUpdatedIfNoResponseAsync() { const filename = deno.makeTempDirSync() + "/test.txt"; const data = new Uint8Array([41, 42, 43]); - await deno.writeFile(filename, data, 0o666); + await deno.writeFile(filename, data, { perm: 0o666 }); const metrics = deno.metrics(); assert(metrics.opsDispatched === metrics.opsCompleted); diff --git a/js/remove_test.ts b/js/remove_test.ts index df4fe72d6f..b0382c865e 100644 --- a/js/remove_test.ts +++ b/js/remove_test.ts @@ -28,7 +28,7 @@ testPerm({ write: true }, function removeSyncFileSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); const fileInfo = deno.statSync(filename); assert(fileInfo.isFile()); // check exist first deno.removeSync(filename); // remove @@ -129,7 +129,7 @@ testPerm({ write: true }, function removeAllSyncFileSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); const fileInfo = deno.statSync(filename); assert(fileInfo.isFile()); // check exist first deno.removeSync(filename, { recursive: true }); // remove @@ -195,7 +195,7 @@ testPerm({ write: true }, async function removeFileSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); const fileInfo = deno.statSync(filename); assert(fileInfo.isFile()); // check exist first await deno.remove(filename); // remove @@ -295,7 +295,7 @@ testPerm({ write: true }, async function removeAllFileSuccess() { const enc = new TextEncoder(); const data = enc.encode("Hello"); const filename = deno.makeTempDirSync() + "/test.txt"; - deno.writeFileSync(filename, data, 0o666); + deno.writeFileSync(filename, data, { perm: 0o666 }); const fileInfo = deno.statSync(filename); assert(fileInfo.isFile()); // check exist first await deno.remove(filename, { recursive: true }); // remove diff --git a/js/write_file.ts b/js/write_file.ts index dad22f8c29..12ba67130a 100644 --- a/js/write_file.ts +++ b/js/write_file.ts @@ -3,6 +3,17 @@ import * as msg from "gen/msg_generated"; import * as flatbuffers from "./flatbuffers"; import * as dispatch from "./dispatch"; +/** Options for writing to a file. + * `perm` would change the file's permission if set. + * `create` decides if the file should be created if not exists (default: true) + * `append` decides if the file should be appended (default: false) + */ +export interface WriteFileOptions { + perm?: number; + create?: boolean; + append?: boolean; +} + /** Write a new file, with given filename and data synchronously. * * import { writeFileSync } from "deno"; @@ -14,9 +25,9 @@ import * as dispatch from "./dispatch"; export function writeFileSync( filename: string, data: Uint8Array, - perm = 0o666 + options: WriteFileOptions = {} ): void { - dispatch.sendSync(...req(filename, data, perm)); + dispatch.sendSync(...req(filename, data, options)); } /** Write a new file, with given filename and data. @@ -30,21 +41,35 @@ export function writeFileSync( export async function writeFile( filename: string, data: Uint8Array, - perm = 0o666 + options: WriteFileOptions = {} ): Promise { - await dispatch.sendAsync(...req(filename, data, perm)); + await dispatch.sendAsync(...req(filename, data, options)); } function req( filename: string, data: Uint8Array, - perm: number + options: WriteFileOptions ): [flatbuffers.Builder, msg.Any, flatbuffers.Offset, Uint8Array] { const builder = flatbuffers.createBuilder(); const filename_ = builder.createString(filename); msg.WriteFile.startWriteFile(builder); msg.WriteFile.addFilename(builder, filename_); - msg.WriteFile.addPerm(builder, perm); + // Perm is not updated by default + if (options.perm !== undefined && options.perm !== null) { + msg.WriteFile.addUpdatePerm(builder, true); + msg.WriteFile.addPerm(builder, options.perm!); + } else { + msg.WriteFile.addUpdatePerm(builder, false); + msg.WriteFile.addPerm(builder, 0o666); + } + // Create is turned on by default + if (options.create !== undefined) { + msg.WriteFile.addIsCreate(builder, !!options.create); + } else { + msg.WriteFile.addIsCreate(builder, true); + } + msg.WriteFile.addIsAppend(builder, !!options.append); const inner = msg.WriteFile.endWriteFile(builder); return [builder, msg.Any.WriteFile, inner, data]; } diff --git a/js/write_file_test.ts b/js/write_file_test.ts index e9e6203637..7a43dec627 100644 --- a/js/write_file_test.ts +++ b/js/write_file_test.ts @@ -6,7 +6,7 @@ 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); + deno.writeFileSync(filename, data); const dataRead = deno.readFileSync(filename); const dec = new TextDecoder("utf-8"); const actual = dec.decode(dataRead); @@ -45,11 +45,69 @@ testPerm({ write: false }, function writeFileSyncPerm() { assert(caughtError); }); +testPerm({ write: true }, function writeFileSyncUpdatePerm() { + if (deno.platform.os !== "win") { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + deno.writeFileSync(filename, data, { perm: 0o755 }); + assertEqual(deno.statSync(filename).mode & 0o777, 0o755); + deno.writeFileSync(filename, data, { perm: 0o666 }); + assertEqual(deno.statSync(filename).mode & 0o777, 0o666); + } +}); + +testPerm({ write: true }, function writeFileSyncCreate() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + let caughtError = false; + // if create turned off, the file won't be created + try { + deno.writeFileSync(filename, data, { create: false }); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.NotFound); + assertEqual(e.name, "NotFound"); + } + assert(caughtError); + + // Turn on create, should have no error + deno.writeFileSync(filename, data, { create: true }); + deno.writeFileSync(filename, data, { create: false }); + const dataRead = deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEqual("Hello", actual); +}); + +testPerm({ write: true }, function writeFileSyncAppend() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + deno.writeFileSync(filename, data); + deno.writeFileSync(filename, data, { append: true }); + let dataRead = deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + let actual = dec.decode(dataRead); + assertEqual("HelloHello", actual); + // Now attempt overwrite + deno.writeFileSync(filename, data, { append: false }); + dataRead = deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEqual("Hello", actual); + // append not set should also overwrite + deno.writeFileSync(filename, data); + dataRead = deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEqual("Hello", actual); +}); + 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); + await deno.writeFile(filename, data); const dataRead = deno.readFileSync(filename); const dec = new TextDecoder("utf-8"); const actual = dec.decode(dataRead); @@ -87,3 +145,61 @@ testPerm({ write: false }, async function writeFilePerm() { } assert(caughtError); }); + +testPerm({ write: true }, async function writeFileUpdatePerm() { + if (deno.platform.os !== "win") { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + await deno.writeFile(filename, data, { perm: 0o755 }); + assertEqual(deno.statSync(filename).mode & 0o777, 0o755); + await deno.writeFile(filename, data, { perm: 0o666 }); + assertEqual(deno.statSync(filename).mode & 0o777, 0o666); + } +}); + +testPerm({ write: true }, async function writeFileCreate() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + let caughtError = false; + // if create turned off, the file won't be created + try { + await deno.writeFile(filename, data, { create: false }); + } catch (e) { + caughtError = true; + assertEqual(e.kind, deno.ErrorKind.NotFound); + assertEqual(e.name, "NotFound"); + } + assert(caughtError); + + // Turn on create, should have no error + await deno.writeFile(filename, data, { create: true }); + await deno.writeFile(filename, data, { create: false }); + const dataRead = deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEqual("Hello", actual); +}); + +testPerm({ write: true }, async function writeFileAppend() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = deno.makeTempDirSync() + "/test.txt"; + await deno.writeFile(filename, data); + await deno.writeFile(filename, data, { append: true }); + let dataRead = deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + let actual = dec.decode(dataRead); + assertEqual("HelloHello", actual); + // Now attempt overwrite + await deno.writeFile(filename, data, { append: false }); + dataRead = deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEqual("Hello", actual); + // append not set should also overwrite + await deno.writeFile(filename, data); + dataRead = deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEqual("Hello", actual); +}); diff --git a/src/fs.rs b/src/fs.rs index 9748cffab3..ff0da95e58 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -18,16 +18,29 @@ pub fn write_file>( data: T, perm: u32, ) -> std::io::Result<()> { - let is_append = perm & (1 << 31) != 0; + write_file_2(filename, data, true, perm, true, false) +} + +pub fn write_file_2>( + filename: &Path, + data: T, + update_perm: bool, + perm: u32, + is_create: bool, + is_append: bool, +) -> std::io::Result<()> { let mut file = OpenOptions::new() .read(false) .write(true) .append(is_append) .truncate(!is_append) - .create(true) + .create(is_create) .open(filename)?; - set_permissions(&mut file, perm)?; + if update_perm { + set_permissions(&mut file, perm)?; + } + file.write_all(data.as_ref()) } diff --git a/src/msg.fbs b/src/msg.fbs index 06a2d86608..1035d7149e 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -295,8 +295,11 @@ table ReadDirRes { table WriteFile { filename: string; data: [ubyte]; + update_perm: bool; perm: uint; // perm specified by https://godoc.org/os#FileMode + is_create: bool; + is_append: bool; } table CopyFile { diff --git a/src/ops.rs b/src/ops.rs index 3826b73ffa..4c3d31218d 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1058,7 +1058,10 @@ fn op_write_file( ) -> Box { let inner = base.inner_as_write_file().unwrap(); let filename = String::from(inner.filename().unwrap()); + let update_perm = inner.update_perm(); let perm = inner.perm(); + let is_create = inner.is_create(); + let is_append = inner.is_append(); if let Err(e) = state.check_write(&filename) { return odd_future(e); @@ -1066,7 +1069,14 @@ fn op_write_file( blocking(base.sync(), move || -> OpResult { debug!("op_write_file {} {}", filename, data.len()); - deno_fs::write_file(Path::new(&filename), data, perm)?; + deno_fs::write_file_2( + Path::new(&filename), + data, + update_perm, + perm, + is_create, + is_append, + )?; Ok(empty_buf()) }) }