diff options
author | Sk Sajidul Kadir <sheikh.sajid522@gmail.com> | 2020-03-16 16:50:27 +0300 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2020-03-30 11:18:58 +0300 |
commit | 97ef4d76c96c070aad76bd3b6d5b89f0f03e458c (patch) | |
tree | 72370490b9cb49293258f93194c9cdc1307e07ee | |
parent | 89ae1f1b73451ed40fa78d379e66d5c061f04548 (diff) |
fs: add fs.readv()
Fixes: https://github.com/nodejs/node/issues/2298
PR-URL: https://github.com/nodejs/node/pull/32356
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
-rw-r--r-- | doc/api/fs.md | 56 | ||||
-rw-r--r-- | lib/fs.js | 35 | ||||
-rw-r--r-- | lib/internal/fs/promises.js | 16 | ||||
-rw-r--r-- | src/node_file.cc | 47 | ||||
-rw-r--r-- | test/parallel/test-fs-readv-promises.js | 67 | ||||
-rw-r--r-- | test/parallel/test-fs-readv-sync.js | 92 | ||||
-rw-r--r-- | test/parallel/test-fs-readv.js | 97 |
7 files changed, 410 insertions, 0 deletions
diff --git a/doc/api/fs.md b/doc/api/fs.md index 7c3f7d2204f..5e6cfb31786 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -3069,6 +3069,42 @@ Returns the number of `bytesRead`. For detailed information, see the documentation of the asynchronous version of this API: [`fs.read()`][]. +## `fs.readv(fd, buffers[, position], callback)` +<!-- YAML +added: REPLACEME +--> + +* `fd` {integer} +* `buffers` {ArrayBufferView[]} +* `position` {integer} +* `callback` {Function} + * `err` {Error} + * `bytesRead` {integer} + * `buffers` {ArrayBufferView[]} + +Read from a file specified by `fd` and write to an array of `ArrayBufferView`s +using `readv()`. + +`position` is the offset from the beginning of the file from where data +should be read. If `typeof position !== 'number'`, the data will be read +from the current position. + +The callback will be given three arguments: `err`, `bytesRead`, and +`buffers`. `bytesRead` is how many bytes were read from the file. + +## `fs.readvSync(fd, buffers[, position])` +<!-- YAML +added: REPLACEME +--> + +* `fd` {integer} +* `buffers` {ArrayBufferView[]} +* `position` {integer} +* Returns: {number} The number of bytes read. + +For detailed information, see the documentation of the asynchronous version of +this API: [`fs.readv()`][]. + ## `fs.realpath(path[, options], callback)` <!-- YAML added: v0.1.31 @@ -4445,6 +4481,25 @@ If one or more `filehandle.read()` calls are made on a file handle and then a position till the end of the file. It doesn't always read from the beginning of the file. +#### `filehandle.readv(buffers[, position])` +<!-- YAML +added: REPLACEME +--> + +* `buffers` {ArrayBufferView[]} +* `position` {integer} +* Returns: {Promise} + +Read from a file and write to an array of `ArrayBufferView`s + +The `Promise` is resolved with an object containing a `bytesRead` property +identifying the number of bytes read, and a `buffers` property containing +a reference to the `buffers` input. + +`position` is the offset from the beginning of the file where this data +should be read from. If `typeof position !== 'number'`, the data will be read +from the current position. + #### `filehandle.stat([options])` <!-- YAML added: v10.0.0 @@ -5655,6 +5710,7 @@ the file contents. [`fs.readFileSync()`]: #fs_fs_readfilesync_path_options [`fs.readdir()`]: #fs_fs_readdir_path_options_callback [`fs.readdirSync()`]: #fs_fs_readdirsync_path_options +[`fs.readv()`]: #fs_fs_readv_fd_buffers_position_callback [`fs.realpath()`]: #fs_fs_realpath_path_options_callback [`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback [`fs.stat()`]: #fs_fs_stat_path_options_callback diff --git a/lib/fs.js b/lib/fs.js index 81bfaee1fd5..29d0e1344b5 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -564,6 +564,39 @@ function readSync(fd, buffer, offset, length, position) { return result; } +function readv(fd, buffers, position, callback) { + function wrapper(err, read) { + callback(err, read || 0, buffers); + } + + validateInt32(fd, 'fd', /* min */ 0); + validateBufferArray(buffers); + + const req = new FSReqCallback(); + req.oncomplete = wrapper; + + callback = maybeCallback(callback || position); + + if (typeof position !== 'number') + position = null; + + return binding.readBuffers(fd, buffers, position, req); +} + +function readvSync(fd, buffers, position) { + validateInt32(fd, 'fd', 0); + validateBufferArray(buffers); + + const ctx = {}; + + if (typeof position !== 'number') + position = null; + + const result = binding.readBuffers(fd, buffers, position, undefined, ctx); + handleErrorFromBinding(ctx); + return result; +} + // usage: // fs.write(fd, buffer[, offset[, length[, position]]], callback); // OR @@ -1928,6 +1961,8 @@ module.exports = fs = { readdirSync, read, readSync, + readv, + readvSync, readFile, readFileSync, readlink, diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index e75a7bc1911..d04e84aa9aa 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -101,6 +101,10 @@ class FileHandle { return read(this, buffer, offset, length, position); } + readv(buffers, position) { + return readv(this, buffers, position); + } + readFile(options) { return readFile(this, options); } @@ -253,6 +257,18 @@ async function read(handle, buffer, offset, length, position) { return { bytesRead, buffer }; } +async function readv(handle, buffers, position) { + validateFileHandle(handle); + validateBufferArray(buffers); + + if (typeof position !== 'number') + position = null; + + const bytesRead = (await binding.readBuffers(handle.fd, buffers, position, + kUsePromises)) || 0; + return { bytesRead, buffers }; +} + async function write(handle, buffer, offset, length, position) { validateFileHandle(handle); diff --git a/src/node_file.cc b/src/node_file.cc index 5fc8dba7d8d..d00595d1ca9 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -1976,6 +1976,52 @@ static void Read(const FunctionCallbackInfo<Value>& args) { } +// Wrapper for readv(2). +// +// bytesRead = fs.readv(fd, buffers[, position], callback) +// 0 fd integer. file descriptor +// 1 buffers array of buffers to read +// 2 position if integer, position to read at in the file. +// if null, read from the current position +static void ReadBuffers(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + const int argc = args.Length(); + CHECK_GE(argc, 3); + + CHECK(args[0]->IsInt32()); + const int fd = args[0].As<Int32>()->Value(); + + CHECK(args[1]->IsArray()); + Local<Array> buffers = args[1].As<Array>(); + + int64_t pos = GetOffset(args[2]); // -1 if not a valid JS int + + MaybeStackBuffer<uv_buf_t> iovs(buffers->Length()); + + // Init uv buffers from ArrayBufferViews + for (uint32_t i = 0; i < iovs.length(); i++) { + Local<Value> buffer = buffers->Get(env->context(), i).ToLocalChecked(); + CHECK(Buffer::HasInstance(buffer)); + iovs[i] = uv_buf_init(Buffer::Data(buffer), Buffer::Length(buffer)); + } + + FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); + if (req_wrap_async != nullptr) { // readBuffers(fd, buffers, pos, req) + AsyncCall(env, req_wrap_async, args, "read", UTF8, AfterInteger, + uv_fs_read, fd, *iovs, iovs.length(), pos); + } else { // readBuffers(fd, buffers, undefined, ctx) + CHECK_EQ(argc, 5); + FSReqWrapSync req_wrap_sync; + FS_SYNC_TRACE_BEGIN(read); + int bytesRead = SyncCall(env, /* ctx */ args[4], &req_wrap_sync, "read", + uv_fs_read, fd, *iovs, iovs.length(), pos); + FS_SYNC_TRACE_END(read, "bytesRead", bytesRead); + args.GetReturnValue().Set(bytesRead); + } +} + + /* fs.chmod(path, mode); * Wrapper for chmod(1) / EIO_CHMOD */ @@ -2239,6 +2285,7 @@ void Initialize(Local<Object> target, env->SetMethod(target, "open", Open); env->SetMethod(target, "openFileHandle", OpenFileHandle); env->SetMethod(target, "read", Read); + env->SetMethod(target, "readBuffers", ReadBuffers); env->SetMethod(target, "fdatasync", Fdatasync); env->SetMethod(target, "fsync", Fsync); env->SetMethod(target, "rename", Rename); diff --git a/test/parallel/test-fs-readv-promises.js b/test/parallel/test-fs-readv-promises.js new file mode 100644 index 00000000000..e0536505c91 --- /dev/null +++ b/test/parallel/test-fs-readv-promises.js @@ -0,0 +1,67 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs').promises; +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف'; +const exptectedBuff = Buffer.from(expected); + +let cnt = 0; +function getFileName() { + return path.join(tmpdir.path, `readv_promises_${++cnt}.txt`); +} + +const allocateEmptyBuffers = (combinedLength) => { + const bufferArr = []; + // Allocate two buffers, each half the size of exptectedBuff + bufferArr[0] = Buffer.alloc(Math.floor(combinedLength / 2)), + bufferArr[1] = Buffer.alloc(combinedLength - bufferArr[0].length); + + return bufferArr; +}; + +(async () => { + { + const filename = getFileName(); + await fs.writeFile(filename, exptectedBuff); + const handle = await fs.open(filename, 'r'); + // const buffer = Buffer.from(expected); + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + const expectedLength = exptectedBuff.length; + + let { bytesRead, buffers } = await handle.readv([Buffer.from('')], + null); + assert.deepStrictEqual(bytesRead, 0); + assert.deepStrictEqual(buffers, [Buffer.from('')]); + + ({ bytesRead, buffers } = await handle.readv(bufferArr, null)); + assert.deepStrictEqual(bytesRead, expectedLength); + assert.deepStrictEqual(buffers, bufferArr); + assert(Buffer.concat(bufferArr).equals(await fs.readFile(filename))); + handle.close(); + } + + { + const filename = getFileName(); + await fs.writeFile(filename, exptectedBuff); + const handle = await fs.open(filename, 'r'); + // const buffer = Buffer.from(expected); + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + const expectedLength = exptectedBuff.length; + + let { bytesRead, buffers } = await handle.readv([Buffer.from('')]); + assert.deepStrictEqual(bytesRead, 0); + assert.deepStrictEqual(buffers, [Buffer.from('')]); + + ({ bytesRead, buffers } = await handle.readv(bufferArr)); + assert.deepStrictEqual(bytesRead, expectedLength); + assert.deepStrictEqual(buffers, bufferArr); + assert(Buffer.concat(bufferArr).equals(await fs.readFile(filename))); + handle.close(); + } +})(); diff --git a/test/parallel/test-fs-readv-sync.js b/test/parallel/test-fs-readv-sync.js new file mode 100644 index 00000000000..f5bb448f920 --- /dev/null +++ b/test/parallel/test-fs-readv-sync.js @@ -0,0 +1,92 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف'; + +const exptectedBuff = Buffer.from(expected); +const expectedLength = exptectedBuff.length; + +const filename = 'readv_sync.txt'; +fs.writeFileSync(filename, exptectedBuff); + +const allocateEmptyBuffers = (combinedLength) => { + const bufferArr = []; + // Allocate two buffers, each half the size of exptectedBuff + bufferArr[0] = Buffer.alloc(Math.floor(combinedLength / 2)), + bufferArr[1] = Buffer.alloc(combinedLength - bufferArr[0].length); + + return bufferArr; +}; + +// fs.readvSync with array of buffers with all parameters +{ + const fd = fs.openSync(filename, 'r'); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + + let read = fs.readvSync(fd, [Buffer.from('')], 0); + assert.deepStrictEqual(read, 0); + + read = fs.readvSync(fd, bufferArr, 0); + assert.deepStrictEqual(read, expectedLength); + + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); +} + +// fs.readvSync with array of buffers without position +{ + const fd = fs.openSync(filename, 'r'); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + + let read = fs.readvSync(fd, [Buffer.from('')]); + assert.deepStrictEqual(read, 0); + + read = fs.readvSync(fd, bufferArr); + assert.deepStrictEqual(read, expectedLength); + + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); +} + +/** + * Testing with incorrect arguments + */ +const wrongInputs = [false, 'test', {}, [{}], ['sdf'], null, undefined]; + +{ + const fd = fs.openSync(filename, 'r'); + + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readvSync(fd, wrongInput, null), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); + + fs.closeSync(fd); +} + +{ + // fs.readv with wrong fd argument + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readvSync(wrongInput), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); +} diff --git a/test/parallel/test-fs-readv.js b/test/parallel/test-fs-readv.js new file mode 100644 index 00000000000..1fa9d80f9f2 --- /dev/null +++ b/test/parallel/test-fs-readv.js @@ -0,0 +1,97 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف'; + +let cnt = 0; +const getFileName = () => path.join(tmpdir.path, `readv_${++cnt}.txt`); +const exptectedBuff = Buffer.from(expected); + +const allocateEmptyBuffers = (combinedLength) => { + const bufferArr = []; + // Allocate two buffers, each half the size of exptectedBuff + bufferArr[0] = Buffer.alloc(Math.floor(combinedLength / 2)), + bufferArr[1] = Buffer.alloc(combinedLength - bufferArr[0].length); + + return bufferArr; +}; + +const getCallback = (fd, bufferArr) => { + return common.mustCall((err, bytesRead, buffers) => { + assert.ifError(err); + + assert.deepStrictEqual(bufferArr, buffers); + const expectedLength = exptectedBuff.length; + assert.deepStrictEqual(bytesRead, expectedLength); + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(exptectedBuff)); + }); +}; + +// fs.readv with array of buffers with all parameters +{ + const filename = getFileName(); + const fd = fs.openSync(filename, 'w+'); + fs.writeSync(fd, exptectedBuff); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + const callback = getCallback(fd, bufferArr); + + fs.readv(fd, bufferArr, 0, callback); +} + +// fs.readv with array of buffers without position +{ + const filename = getFileName(); + fs.writeFileSync(filename, exptectedBuff); + const fd = fs.openSync(filename, 'r'); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + const callback = getCallback(fd, bufferArr); + + fs.readv(fd, bufferArr, callback); +} + +/** + * Testing with incorrect arguments + */ +const wrongInputs = [false, 'test', {}, [{}], ['sdf'], null, undefined]; + +{ + const filename = getFileName(2); + fs.writeFileSync(filename, exptectedBuff); + const fd = fs.openSync(filename, 'r'); + + + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readv(fd, wrongInput, null, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); + + fs.closeSync(fd); +} + +{ + // fs.readv with wrong fd argument + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readv(wrongInput, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); +} |