Skip to content

Commit 30d55a3

Browse files
SheikhSajidaddaleax
authored andcommitted
fs: add fs.readv()
Fixes: #2298 PR-URL: #32356 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 6d86651 commit 30d55a3

File tree

7 files changed

+410
-0
lines changed

7 files changed

+410
-0
lines changed

doc/api/fs.md

+56
Original file line numberDiff line numberDiff line change
@@ -3080,6 +3080,42 @@ Returns the number of `bytesRead`.
30803080
For detailed information, see the documentation of the asynchronous version of
30813081
this API: [`fs.read()`][].
30823082

3083+
## `fs.readv(fd, buffers[, position], callback)`
3084+
<!-- YAML
3085+
added: REPLACEME
3086+
-->
3087+
3088+
* `fd` {integer}
3089+
* `buffers` {ArrayBufferView[]}
3090+
* `position` {integer}
3091+
* `callback` {Function}
3092+
* `err` {Error}
3093+
* `bytesRead` {integer}
3094+
* `buffers` {ArrayBufferView[]}
3095+
3096+
Read from a file specified by `fd` and write to an array of `ArrayBufferView`s
3097+
using `readv()`.
3098+
3099+
`position` is the offset from the beginning of the file from where data
3100+
should be read. If `typeof position !== 'number'`, the data will be read
3101+
from the current position.
3102+
3103+
The callback will be given three arguments: `err`, `bytesRead`, and
3104+
`buffers`. `bytesRead` is how many bytes were read from the file.
3105+
3106+
## `fs.readvSync(fd, buffers[, position])`
3107+
<!-- YAML
3108+
added: REPLACEME
3109+
-->
3110+
3111+
* `fd` {integer}
3112+
* `buffers` {ArrayBufferView[]}
3113+
* `position` {integer}
3114+
* Returns: {number} The number of bytes read.
3115+
3116+
For detailed information, see the documentation of the asynchronous version of
3117+
this API: [`fs.readv()`][].
3118+
30833119
## `fs.realpath(path[, options], callback)`
30843120
<!-- YAML
30853121
added: v0.1.31
@@ -4430,6 +4466,25 @@ If one or more `filehandle.read()` calls are made on a file handle and then a
44304466
position till the end of the file. It doesn't always read from the beginning
44314467
of the file.
44324468

4469+
#### `filehandle.readv(buffers[, position])`
4470+
<!-- YAML
4471+
added: REPLACEME
4472+
-->
4473+
4474+
* `buffers` {ArrayBufferView[]}
4475+
* `position` {integer}
4476+
* Returns: {Promise}
4477+
4478+
Read from a file and write to an array of `ArrayBufferView`s
4479+
4480+
The `Promise` is resolved with an object containing a `bytesRead` property
4481+
identifying the number of bytes read, and a `buffers` property containing
4482+
a reference to the `buffers` input.
4483+
4484+
`position` is the offset from the beginning of the file where this data
4485+
should be read from. If `typeof position !== 'number'`, the data will be read
4486+
from the current position.
4487+
44334488
#### `filehandle.stat([options])`
44344489
<!-- YAML
44354490
added: v10.0.0
@@ -5623,6 +5678,7 @@ the file contents.
56235678
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
56245679
[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
56255680
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
5681+
[`fs.readv()`]: #fs_fs_readv_fd_buffers_position_callback
56265682
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
56275683
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
56285684
[`fs.stat()`]: #fs_fs_stat_path_options_callback

lib/fs.js

+35
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,39 @@ function readSync(fd, buffer, offset, length, position) {
565565
return result;
566566
}
567567

568+
function readv(fd, buffers, position, callback) {
569+
function wrapper(err, read) {
570+
callback(err, read || 0, buffers);
571+
}
572+
573+
validateInt32(fd, 'fd', /* min */ 0);
574+
validateBufferArray(buffers);
575+
576+
const req = new FSReqCallback();
577+
req.oncomplete = wrapper;
578+
579+
callback = maybeCallback(callback || position);
580+
581+
if (typeof position !== 'number')
582+
position = null;
583+
584+
return binding.readBuffers(fd, buffers, position, req);
585+
}
586+
587+
function readvSync(fd, buffers, position) {
588+
validateInt32(fd, 'fd', 0);
589+
validateBufferArray(buffers);
590+
591+
const ctx = {};
592+
593+
if (typeof position !== 'number')
594+
position = null;
595+
596+
const result = binding.readBuffers(fd, buffers, position, undefined, ctx);
597+
handleErrorFromBinding(ctx);
598+
return result;
599+
}
600+
568601
// usage:
569602
// fs.write(fd, buffer[, offset[, length[, position]]], callback);
570603
// OR
@@ -1917,6 +1950,8 @@ module.exports = fs = {
19171950
readdirSync,
19181951
read,
19191952
readSync,
1953+
readv,
1954+
readvSync,
19201955
readFile,
19211956
readFileSync,
19221957
readlink,

lib/internal/fs/promises.js

+16
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ class FileHandle {
9999
return read(this, buffer, offset, length, position);
100100
}
101101

102+
readv(buffers, position) {
103+
return readv(this, buffers, position);
104+
}
105+
102106
readFile(options) {
103107
return readFile(this, options);
104108
}
@@ -253,6 +257,18 @@ async function read(handle, buffer, offset, length, position) {
253257
return { bytesRead, buffer };
254258
}
255259

260+
async function readv(handle, buffers, position) {
261+
validateFileHandle(handle);
262+
validateBufferArray(buffers);
263+
264+
if (typeof position !== 'number')
265+
position = null;
266+
267+
const bytesRead = (await binding.readBuffers(handle.fd, buffers, position,
268+
kUsePromises)) || 0;
269+
return { bytesRead, buffers };
270+
}
271+
256272
async function write(handle, buffer, offset, length, position) {
257273
validateFileHandle(handle);
258274

src/node_file.cc

+47
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,52 @@ static void Read(const FunctionCallbackInfo<Value>& args) {
19651965
}
19661966

19671967

1968+
// Wrapper for readv(2).
1969+
//
1970+
// bytesRead = fs.readv(fd, buffers[, position], callback)
1971+
// 0 fd integer. file descriptor
1972+
// 1 buffers array of buffers to read
1973+
// 2 position if integer, position to read at in the file.
1974+
// if null, read from the current position
1975+
static void ReadBuffers(const FunctionCallbackInfo<Value>& args) {
1976+
Environment* env = Environment::GetCurrent(args);
1977+
1978+
const int argc = args.Length();
1979+
CHECK_GE(argc, 3);
1980+
1981+
CHECK(args[0]->IsInt32());
1982+
const int fd = args[0].As<Int32>()->Value();
1983+
1984+
CHECK(args[1]->IsArray());
1985+
Local<Array> buffers = args[1].As<Array>();
1986+
1987+
int64_t pos = GetOffset(args[2]); // -1 if not a valid JS int
1988+
1989+
MaybeStackBuffer<uv_buf_t> iovs(buffers->Length());
1990+
1991+
// Init uv buffers from ArrayBufferViews
1992+
for (uint32_t i = 0; i < iovs.length(); i++) {
1993+
Local<Value> buffer = buffers->Get(env->context(), i).ToLocalChecked();
1994+
CHECK(Buffer::HasInstance(buffer));
1995+
iovs[i] = uv_buf_init(Buffer::Data(buffer), Buffer::Length(buffer));
1996+
}
1997+
1998+
FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
1999+
if (req_wrap_async != nullptr) { // readBuffers(fd, buffers, pos, req)
2000+
AsyncCall(env, req_wrap_async, args, "read", UTF8, AfterInteger,
2001+
uv_fs_read, fd, *iovs, iovs.length(), pos);
2002+
} else { // readBuffers(fd, buffers, undefined, ctx)
2003+
CHECK_EQ(argc, 5);
2004+
FSReqWrapSync req_wrap_sync;
2005+
FS_SYNC_TRACE_BEGIN(read);
2006+
int bytesRead = SyncCall(env, /* ctx */ args[4], &req_wrap_sync, "read",
2007+
uv_fs_read, fd, *iovs, iovs.length(), pos);
2008+
FS_SYNC_TRACE_END(read, "bytesRead", bytesRead);
2009+
args.GetReturnValue().Set(bytesRead);
2010+
}
2011+
}
2012+
2013+
19682014
/* fs.chmod(path, mode);
19692015
* Wrapper for chmod(1) / EIO_CHMOD
19702016
*/
@@ -2228,6 +2274,7 @@ void Initialize(Local<Object> target,
22282274
env->SetMethod(target, "open", Open);
22292275
env->SetMethod(target, "openFileHandle", OpenFileHandle);
22302276
env->SetMethod(target, "read", Read);
2277+
env->SetMethod(target, "readBuffers", ReadBuffers);
22312278
env->SetMethod(target, "fdatasync", Fdatasync);
22322279
env->SetMethod(target, "fsync", Fsync);
22332280
env->SetMethod(target, "rename", Rename);
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const path = require('path');
6+
const fs = require('fs').promises;
7+
const tmpdir = require('../common/tmpdir');
8+
9+
tmpdir.refresh();
10+
11+
const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف';
12+
const exptectedBuff = Buffer.from(expected);
13+
14+
let cnt = 0;
15+
function getFileName() {
16+
return path.join(tmpdir.path, `readv_promises_${++cnt}.txt`);
17+
}
18+
19+
const allocateEmptyBuffers = (combinedLength) => {
20+
const bufferArr = [];
21+
// Allocate two buffers, each half the size of exptectedBuff
22+
bufferArr[0] = Buffer.alloc(Math.floor(combinedLength / 2)),
23+
bufferArr[1] = Buffer.alloc(combinedLength - bufferArr[0].length);
24+
25+
return bufferArr;
26+
};
27+
28+
(async () => {
29+
{
30+
const filename = getFileName();
31+
await fs.writeFile(filename, exptectedBuff);
32+
const handle = await fs.open(filename, 'r');
33+
// const buffer = Buffer.from(expected);
34+
const bufferArr = allocateEmptyBuffers(exptectedBuff.length);
35+
const expectedLength = exptectedBuff.length;
36+
37+
let { bytesRead, buffers } = await handle.readv([Buffer.from('')],
38+
null);
39+
assert.deepStrictEqual(bytesRead, 0);
40+
assert.deepStrictEqual(buffers, [Buffer.from('')]);
41+
42+
({ bytesRead, buffers } = await handle.readv(bufferArr, null));
43+
assert.deepStrictEqual(bytesRead, expectedLength);
44+
assert.deepStrictEqual(buffers, bufferArr);
45+
assert(Buffer.concat(bufferArr).equals(await fs.readFile(filename)));
46+
handle.close();
47+
}
48+
49+
{
50+
const filename = getFileName();
51+
await fs.writeFile(filename, exptectedBuff);
52+
const handle = await fs.open(filename, 'r');
53+
// const buffer = Buffer.from(expected);
54+
const bufferArr = allocateEmptyBuffers(exptectedBuff.length);
55+
const expectedLength = exptectedBuff.length;
56+
57+
let { bytesRead, buffers } = await handle.readv([Buffer.from('')]);
58+
assert.deepStrictEqual(bytesRead, 0);
59+
assert.deepStrictEqual(buffers, [Buffer.from('')]);
60+
61+
({ bytesRead, buffers } = await handle.readv(bufferArr));
62+
assert.deepStrictEqual(bytesRead, expectedLength);
63+
assert.deepStrictEqual(buffers, bufferArr);
64+
assert(Buffer.concat(bufferArr).equals(await fs.readFile(filename)));
65+
handle.close();
66+
}
67+
})();

test/parallel/test-fs-readv-sync.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
const tmpdir = require('../common/tmpdir');
7+
8+
tmpdir.refresh();
9+
10+
const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف';
11+
12+
const exptectedBuff = Buffer.from(expected);
13+
const expectedLength = exptectedBuff.length;
14+
15+
const filename = 'readv_sync.txt';
16+
fs.writeFileSync(filename, exptectedBuff);
17+
18+
const allocateEmptyBuffers = (combinedLength) => {
19+
const bufferArr = [];
20+
// Allocate two buffers, each half the size of exptectedBuff
21+
bufferArr[0] = Buffer.alloc(Math.floor(combinedLength / 2)),
22+
bufferArr[1] = Buffer.alloc(combinedLength - bufferArr[0].length);
23+
24+
return bufferArr;
25+
};
26+
27+
// fs.readvSync with array of buffers with all parameters
28+
{
29+
const fd = fs.openSync(filename, 'r');
30+
31+
const bufferArr = allocateEmptyBuffers(exptectedBuff.length);
32+
33+
let read = fs.readvSync(fd, [Buffer.from('')], 0);
34+
assert.deepStrictEqual(read, 0);
35+
36+
read = fs.readvSync(fd, bufferArr, 0);
37+
assert.deepStrictEqual(read, expectedLength);
38+
39+
fs.closeSync(fd);
40+
41+
assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename)));
42+
}
43+
44+
// fs.readvSync with array of buffers without position
45+
{
46+
const fd = fs.openSync(filename, 'r');
47+
48+
const bufferArr = allocateEmptyBuffers(exptectedBuff.length);
49+
50+
let read = fs.readvSync(fd, [Buffer.from('')]);
51+
assert.deepStrictEqual(read, 0);
52+
53+
read = fs.readvSync(fd, bufferArr);
54+
assert.deepStrictEqual(read, expectedLength);
55+
56+
fs.closeSync(fd);
57+
58+
assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename)));
59+
}
60+
61+
/**
62+
* Testing with incorrect arguments
63+
*/
64+
const wrongInputs = [false, 'test', {}, [{}], ['sdf'], null, undefined];
65+
66+
{
67+
const fd = fs.openSync(filename, 'r');
68+
69+
wrongInputs.forEach((wrongInput) => {
70+
assert.throws(
71+
() => fs.readvSync(fd, wrongInput, null), {
72+
code: 'ERR_INVALID_ARG_TYPE',
73+
name: 'TypeError'
74+
}
75+
);
76+
});
77+
78+
fs.closeSync(fd);
79+
}
80+
81+
{
82+
// fs.readv with wrong fd argument
83+
wrongInputs.forEach((wrongInput) => {
84+
assert.throws(
85+
() => fs.readvSync(wrongInput),
86+
{
87+
code: 'ERR_INVALID_ARG_TYPE',
88+
name: 'TypeError'
89+
}
90+
);
91+
});
92+
}

0 commit comments

Comments
 (0)