From c69abaaa5c3a7bde6804b2dba6f7aacd58bf0cf3 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Fri, 15 Apr 2022 22:01:15 +0800 Subject: [PATCH 01/16] fs: add read(buffer, options) signatures This adds the following: `fs.read(fd, buffer[, options], callback)` `filehandle.read(buffer[, options])` --- lib/fs.js | 35 +++++++++++-------- lib/internal/fs/promises.js | 20 ++++++++--- .../test-fs-readSync-optional-params.js | 12 +------ 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 405c206d56685c..a2de246b094433 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -605,21 +605,30 @@ function openSync(path, flags, mode) { * ) => any} callback * @returns {void} */ -function read(fd, buffer, offset, length, position, callback) { +function read(fd, buffer, offsetOrOptions, length, position, callback) { fd = getValidatedFd(fd); - if (arguments.length <= 3) { - // Assume fs.read(fd, options, callback) - let options = ObjectCreate(null); + let offset = offsetOrOptions; + if (arguments.length === 4 && typeof offsetOrOptions === 'object') { + // This is fs.read(fd, buffer, options, callback) + callback = length; + ({ + offset = 0, + length = buffer.byteLength - offset, + position = null + } = offsetOrOptions ?? ObjectCreate(null)); + } else if (arguments.length <= 3) { + // Assume fs.read(fd, params, callback) + let params = ObjectCreate(null); if (arguments.length < 3) { // This is fs.read(fd, callback) // buffer will be the callback callback = buffer; } else { // This is fs.read(fd, {}, callback) - // buffer will be the options object + // buffer will be the params object // offset is the callback - options = buffer; + params = buffer; callback = offset; } @@ -628,7 +637,7 @@ function read(fd, buffer, offset, length, position, callback) { offset = 0, length = buffer.byteLength - offset, position = null - } = options); + } = params); } validateBuffer(buffer); @@ -686,23 +695,21 @@ ObjectDefineProperty(read, internalUtil.customPromisifyArgs, * }} [offset] * @returns {number} */ -function readSync(fd, buffer, offset, length, position) { +function readSync(fd, buffer, offsetOrOptions, length, position) { fd = getValidatedFd(fd); validateBuffer(buffer); - if (arguments.length <= 3) { - // Assume fs.readSync(fd, buffer, options) - const options = offset || ObjectCreate(null); - + let offset = offsetOrOptions; + if (typeof offset === 'object') { ({ offset = 0, length = buffer.byteLength - offset, position = null - } = options); + } = offsetOrOptions ?? ObjectCreate(null)); } - if (offset == null) { + if (offset === false || offset === undefined) { offset = 0; } else { validateInteger(offset, 'offset', 0); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 73c159550740b0..20651c75d2c331 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -508,21 +508,31 @@ async function open(path, flags, mode) { flagsNumber, mode, kUsePromises)); } -async function read(handle, bufferOrOptions, offset, length, position) { - let buffer = bufferOrOptions; +async function read(handle, bufferOrParams, offset, length, position) { + let buffer = bufferOrParams; if (!isArrayBufferView(buffer)) { - bufferOrOptions ??= ObjectCreate(null); + // This is fh.read(params) + bufferOrParams ??= ObjectCreate(null); ({ buffer = Buffer.alloc(16384), offset = 0, length = buffer.byteLength - offset, position = null - } = bufferOrOptions); + } = bufferOrParams); validateBuffer(buffer); } - if (offset == null) { + if (typeof offset === 'object') { + // This is fh.read(buffer, options) + ({ + offset = 0, + length = buffer.byteLength - offset, + position = null + } = offset ?? ObjectCreate(null)); + } + + if (offset === false || offset === undefined) { offset = 0; } else { validateInteger(offset, 'offset', 0); diff --git a/test/parallel/test-fs-readSync-optional-params.js b/test/parallel/test-fs-readSync-optional-params.js index 00f1a5531cf6ea..4399a8c35b54cc 100644 --- a/test/parallel/test-fs-readSync-optional-params.js +++ b/test/parallel/test-fs-readSync-optional-params.js @@ -31,7 +31,7 @@ for (const options of [ { length: expected.length, position: 0 }, { offset: 0, length: expected.length, position: 0 }, - { offset: null }, + { offset: false }, { position: null }, { position: -1 }, { position: 0n }, @@ -39,18 +39,8 @@ for (const options of [ // Test default params {}, null, - undefined, - - // Test if bad params are interpreted as default (not mandatory) - false, - true, - Infinity, - 42n, - Symbol(), // Test even more malicious corner cases - '4'.repeat(expected.length), - new String('4444'), [4, 4, 4, 4], ]) { runTest(Buffer.allocUnsafe(expected.length), options); From aec64cae9d78dd5a13f1fb71dbcc478fb1bdb296 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Sun, 17 Apr 2022 19:12:29 +0800 Subject: [PATCH 02/16] squash: make offsetOrOptions more consistent --- lib/fs.js | 18 ++++----- test/parallel/test-fs-read-offset-null.js | 49 +++++++++++++++++------ 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index a2de246b094433..7eb83e5ef36487 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -595,7 +595,7 @@ function openSync(path, flags, mode) { * Reads file from the specified `fd` (file descriptor). * @param {number} fd * @param {Buffer | TypedArray | DataView} buffer - * @param {number} offset + * @param {number} offsetOrOptions * @param {number} length * @param {number | bigint} position * @param {( @@ -609,7 +609,10 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) { fd = getValidatedFd(fd); let offset = offsetOrOptions; - if (arguments.length === 4 && typeof offsetOrOptions === 'object') { + if ( + arguments.length === 4 && + (offsetOrOptions === undefined || typeof offsetOrOptions === 'object') + ) { // This is fs.read(fd, buffer, options, callback) callback = length; ({ @@ -618,16 +621,13 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) { position = null } = offsetOrOptions ?? ObjectCreate(null)); } else if (arguments.length <= 3) { - // Assume fs.read(fd, params, callback) + // Assume fs.read(fd[, params], callback) let params = ObjectCreate(null); if (arguments.length < 3) { // This is fs.read(fd, callback) - // buffer will be the callback callback = buffer; } else { - // This is fs.read(fd, {}, callback) - // buffer will be the params object - // offset is the callback + // This is fs.read(fd, params, callback) params = buffer; callback = offset; } @@ -643,7 +643,7 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) { validateBuffer(buffer); callback = maybeCallback(callback); - if (offset == null) { + if (offset === false || offset === undefined) { offset = 0; } else { validateInteger(offset, 'offset', 0); @@ -692,7 +692,7 @@ ObjectDefineProperty(read, internalUtil.customPromisifyArgs, * offset?: number; * length?: number; * position?: number | bigint; - * }} [offset] + * }} [offsetOrOptions] * @returns {number} */ function readSync(fd, buffer, offsetOrOptions, length, position) { diff --git a/test/parallel/test-fs-read-offset-null.js b/test/parallel/test-fs-read-offset-null.js index 27e893f781b2ff..22ca5f818f5931 100644 --- a/test/parallel/test-fs-read-offset-null.js +++ b/test/parallel/test-fs-read-offset-null.js @@ -15,28 +15,53 @@ const buf = Buffer.alloc(1); // Reading only one character, hence buffer of one byte is enough. // Test for callback API. -fs.open(filepath, 'r', common.mustSucceed((fd) => { - fs.read(fd, { offset: null, buffer: buf }, - common.mustSucceed((bytesRead, buffer) => { - // Test is done by making sure the first letter in buffer is - // same as first letter in file. - // 120 is the hex for ascii code of letter x. - assert.strictEqual(buffer[0], 120); - fs.close(fd, common.mustSucceed(() => {})); - })); -})); +fs.open(filepath, 'r', (_, fd) => { + assert.throws( + () => fs.read(fd, { offset: null, buffer: buf }, () => {}), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); + assert.throws( + () => fs.read(fd, buf, { offset: null }, () => {}), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); + fs.close(fd, common.mustSucceed(() => {})); +}); let filehandle = null; // Test for promise api (async () => { filehandle = await fsPromises.open(filepath, 'r'); - const readObject = await filehandle.read(buf, null, buf.length); + assert.rejects( + async () => await filehandle.read(buf, { offset: null }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ); +})() +.then(common.mustCall()) +.finally(async () => { + if (filehandle) + await filehandle.close(); +}); + +(async () => { + filehandle = await fsPromises.open(filepath, 'r'); + + // In this test, null is interpreted as default options, not null offset. + // 120 is the ascii code of letter x. + const readObject = await filehandle.read(buf, null); assert.strictEqual(readObject.buffer[0], 120); })() .then(common.mustCall()) .finally(async () => { -// Close the file handle if it is opened if (filehandle) await filehandle.close(); }); From 42c9c40bfe5144202d2a706481ff0646dfe11444 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Sun, 17 Apr 2022 20:08:37 +0800 Subject: [PATCH 03/16] squash: add doc entries --- doc/api/fs.md | 49 +++++++++++++++++++++++ test/parallel/test-fs-read-offset-null.js | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index c32c624f48b4a7..c46a78f78b4ddb 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -417,6 +417,33 @@ Reads data from the file and stores that in the given buffer. If the file is not modified concurrently, the end-of-file is reached when the number of bytes read is zero. +#### `filehandle.read(buffer[, options])` + + + +* `buffer` {Buffer|TypedArray|DataView} A buffer that will be filled with the + file data read. +* `options` {Object} + * `offset` {integer} The location in the buffer at which to start filling. + **Default:** `0` + * `length` {integer} The number of bytes to read. **Default:** + `buffer.byteLength - offset` + * `position` {integer} The location where to begin reading data from the + file. If `null`, data will be read from the current file position, and + the position will be updated. If `position` is an integer, the current + file position will remain unchanged. **Default:**: `null` +* Returns: {Promise} Fulfills upon success with an object with two properties: + * `bytesRead` {integer} The number of bytes read + * `buffer` {Buffer|TypedArray|DataView} A reference to the passed in `buffer` + argument. + +Reads data from the file and stores that in the given buffer. + +If the file is not modified concurrently, the end-of-file is reached when the +number of bytes read is zero. + #### `filehandle.readableWebStream()` + +* `fd` {integer} +* `buffer` {Buffer|TypedArray|DataView} The buffer that the data will be + written to. +* `options` {Object} + * `offset` {integer} **Default:** `0` + * `length` {integer} **Default:** `buffer.byteLength - offset` + * `position` {integer|bigint} **Default:** `null` +* `callback` {Function} + * `err` {Error} + * `bytesRead` {integer} + * `buffer` {Buffer} + +Similar to the [`fs.read()`][] function, this version takes an optional +`options` object. If no `options` object is specified, it will default with the +above values. + ### `fs.readdir(path[, options], callback)`