diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 6cae7d1a21ff8d..0a7803fc265bba 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -121,12 +121,19 @@ async function writeFileHandle(filehandle, data, options) { data : Buffer.from('' + data, options.encoding || 'utf8'); let remaining = buffer.length; if (remaining === 0) return; + // If the file is opened in APPEND mode set position as null, so that the + // data will be always written at the end of the file, otherwise start at 0. + let position = (options.flag || '').includes('a') ? null : 0; + do { const { bytesWritten } = await write(filehandle, buffer, 0, - Math.min(16384, buffer.length)); + Math.min(16384, buffer.length), position); remaining -= bytesWritten; buffer = buffer.slice(bytesWritten); + if (typeof position === 'number') { + position += bytesWritten; + } } while (remaining > 0); } diff --git a/test/parallel/test-fs-promises-file-handle-writeFile-after-truncate.js b/test/parallel/test-fs-promises-file-handle-writeFile-after-truncate.js new file mode 100644 index 00000000000000..0352d1901fe426 --- /dev/null +++ b/test/parallel/test-fs-promises-file-handle-writeFile-after-truncate.js @@ -0,0 +1,48 @@ +'use strict'; + +// This tests if the writeFile automatically adjusts the file offset even if +// the file is truncated. + +const common = require('../common'); +const fsPromises = require('fs').promises; +const path = require('path'); +const assert = require('assert'); +const tmpdir = require('../common/tmpdir'); +const tmpDir = tmpdir.path; +const Data = 'FS Promises'; + +tmpdir.refresh(); + +(async function() { + assert(!/A/.test(Data)); + const fileName = path.resolve(tmpDir, 'temp.txt'); + const fileHandle = await fsPromises.open(fileName, 'w'); + await fileHandle.writeFile('A'.repeat(Data.length * 2)); + await fileHandle.truncate(); + await fileHandle.writeFile(Data); + await fileHandle.close(); + assert.deepStrictEqual(await fsPromises.readFile(fileName, 'UTF-8'), Data); +})().then(common.mustCall()); + +(async function() { + // In this case there is no truncate call, but still the contents of the file + // should be truncated before the new string is written. + assert(!/A/.test(Data)); + const fileName = path.resolve(tmpDir, 'temp.txt'); + const fileHandle = await fsPromises.open(fileName, 'w'); + await fileHandle.writeFile('A'.repeat(Data.length * 2)); + await fileHandle.writeFile(Data); + await fileHandle.close(); + assert.deepStrictEqual(await fsPromises.readFile(fileName, 'UTF-8'), Data); +})().then(common.mustCall()); + +(async function() { + // This tests appendFile as well, as appendFile internally uses writeFile + const fileName = path.resolve(tmpDir, 'temp-1.txt'); + const fileHandle = await fsPromises.open(fileName, 'w'); + await fileHandle.writeFile('A'.repeat(Data.length * 2)); + await fileHandle.truncate(); + await fileHandle.appendFile(Data); + await fileHandle.close(); + assert.deepStrictEqual(await fsPromises.readFile(fileName, 'UTF-8'), Data); +})().then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-file-handle-writeFile.js b/test/parallel/test-fs-promises-file-handle-writeFile.js index e39c59f5cae80c..761453b5207269 100644 --- a/test/parallel/test-fs-promises-file-handle-writeFile.js +++ b/test/parallel/test-fs-promises-file-handle-writeFile.js @@ -24,5 +24,23 @@ async function validateWriteFile() { assert.deepStrictEqual(buffer, readFileData); } +async function validateLargeWriteFile() { + const fileName = path.resolve(tmpDir, 'large.txt'); + const handle = await open(fileName, 'w'); + // 16385 is written as (16384 + 1) because, 16384 is the max chunk size used + // in the writeFile, the max chunk size is searchable, and the boundary + // testing is also done. + const buffer = Buffer.from( + Array.apply(null, { length: (16384 + 1) * 3 }) + .map(Math.random) + .map((number) => (number * (1 << 8))) + ); + + await handle.writeFile(buffer); + await handle.close(); + assert.deepStrictEqual(fs.readFileSync(fileName), buffer); +} + validateWriteFile() + .then(validateLargeWriteFile) .then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-writefile.js b/test/parallel/test-fs-promises-writefile.js index 858e90bc6291de..1fdcc19aae90d0 100644 --- a/test/parallel/test-fs-promises-writefile.js +++ b/test/parallel/test-fs-promises-writefile.js @@ -20,6 +20,22 @@ async function doWrite() { assert.deepStrictEqual(data, buffer); } +async function doLargeWrite() { + // 16385 is written as (16384 + 1) because, 16384 is the max chunk size used + // in the writeFile, the max chunk size is searchable, and the boundary + // testing is also done. + const buffer = Buffer.from( + Array.apply(null, { length: (16384 + 1) * 3 }) + .map(Math.random) + .map((number) => (number * (1 << 8))) + ); + const dest = path.resolve(tmpDir, 'large.txt'); + + await fsPromises.writeFile(dest, buffer); + const data = fs.readFileSync(dest); + assert.deepStrictEqual(data, buffer); +} + async function doAppend() { await fsPromises.appendFile(dest, buffer2); const data = fs.readFileSync(dest); @@ -41,6 +57,7 @@ async function doReadWithEncoding() { } doWrite() + .then(doLargeWrite) .then(doAppend) .then(doRead) .then(doReadWithEncoding)