Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: add fs.readv() #32356

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 56 additions & 0 deletions doc/api/fs.md
Expand Up @@ -3062,6 +3062,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
Expand Down Expand Up @@ -4438,6 +4474,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
Expand Down Expand Up @@ -5648,6 +5703,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
Expand Down
35 changes: 35 additions & 0 deletions lib/fs.js
Expand Up @@ -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
Expand Down Expand Up @@ -1928,6 +1961,8 @@ module.exports = fs = {
readdirSync,
read,
readSync,
readv,
readvSync,
readFile,
readFileSync,
readlink,
Expand Down
16 changes: 16 additions & 0 deletions lib/internal/fs/promises.js
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);

Expand Down
47 changes: 47 additions & 0 deletions src/node_file.cc
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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);
Expand Down
67 changes: 67 additions & 0 deletions 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();
}
})();
92 changes: 92 additions & 0 deletions 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'
}
);
});
}