Skip to content

Commit

Permalink
fs: add fs.readv()
Browse files Browse the repository at this point in the history
Fixes: #2298
PR-URL: #32356
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
SheikhSajid authored and addaleax committed Mar 30, 2020
1 parent 89ae1f1 commit 97ef4d7
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 0 deletions.
56 changes: 56 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions lib/fs.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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'
}
);
});
}
Loading

0 comments on commit 97ef4d7

Please sign in to comment.