Skip to content

Commit

Permalink
fix(ext/node): Add fs.readv, fs.readvSync (#23166)
Browse files Browse the repository at this point in the history
Part of #18218.

Implements `fs.readv` and `fs.readvSync` and enables the corresponding
`node_compat` tests.
  • Loading branch information
nathanwhit authored and satyarohith committed Apr 11, 2024
1 parent 9aef08b commit 0a5e4cb
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 2 deletions.
1 change: 1 addition & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ deno_core::extension!(deno_node,
"_fs/_fs_readdir.ts",
"_fs/_fs_readFile.ts",
"_fs/_fs_readlink.ts",
"_fs/_fs_readv.ts",
"_fs/_fs_realpath.ts",
"_fs/_fs_rename.ts",
"_fs/_fs_rm.ts",
Expand Down
130 changes: 130 additions & 0 deletions ext/node/polyfills/_fs/_fs_readv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials

import {
ERR_INVALID_ARG_TYPE,
type ErrnoException,
} from "ext:deno_node/internal/errors.ts";
import {
getValidatedFd,
validateBufferArray,
} from "ext:deno_node/internal/fs/utils.mjs";
import { maybeCallback } from "ext:deno_node/_fs/_fs_common.ts";
import { validateInteger } from "ext:deno_node/internal/validators.mjs";
import * as io from "ext:deno_io/12_io.js";
import * as fs from "ext:deno_fs/30_fs.js";

type Callback = (
err: ErrnoException | null,
bytesRead: number,
buffers: readonly ArrayBufferView[],
) => void;

export function readv(
fd: number,
buffers: readonly ArrayBufferView[],
callback: Callback,
): void;
export function readv(
fd: number,
buffers: readonly ArrayBufferView[],
position: number | Callback,
callback?: Callback,
): void {
if (typeof fd !== "number") {
throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
}
fd = getValidatedFd(fd);
validateBufferArray(buffers);
const cb = maybeCallback(callback || position) as Callback;
let pos: number | null = null;
if (typeof position === "number") {
validateInteger(position, "position", 0);
pos = position;
}

if (buffers.length === 0) {
process.nextTick(cb, null, 0, buffers);
return;
}

const innerReadv = async (
fd: number,
buffers: readonly ArrayBufferView[],
position: number | null,
) => {
if (typeof position === "number") {
await fs.seek(fd, position, io.SeekMode.Start);
}

let readTotal = 0;
let readInBuf = 0;
let bufIdx = 0;
let buf = buffers[bufIdx];
while (bufIdx < buffers.length) {
const nread = await io.read(fd, buf);
if (nread === null) {
break;
}
readInBuf += nread;
if (readInBuf === buf.byteLength) {
readTotal += readInBuf;
readInBuf = 0;
bufIdx += 1;
buf = buffers[bufIdx];
}
}
readTotal += readInBuf;

return readTotal;
};

innerReadv(fd, buffers, pos).then(
(numRead) => {
cb(null, numRead, buffers);
},
(err) => cb(err, -1, buffers),
);
}

export function readvSync(
fd: number,
buffers: readonly ArrayBufferView[],
position: number | null = null,
): number {
if (typeof fd !== "number") {
throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
}
fd = getValidatedFd(fd);
validateBufferArray(buffers);
if (buffers.length === 0) {
return 0;
}
if (typeof position === "number") {
validateInteger(position, "position", 0);
fs.seekSync(fd, position, io.SeekMode.Start);
}

let readTotal = 0;
let readInBuf = 0;
let bufIdx = 0;
let buf = buffers[bufIdx];
while (bufIdx < buffers.length) {
const nread = io.readSync(fd, buf);
if (nread === null) {
break;
}
readInBuf += nread;
if (readInBuf === buf.byteLength) {
readTotal += readInBuf;
readInBuf = 0;
bufIdx += 1;
buf = buffers[bufIdx];
}
}
readTotal += readInBuf;

return readTotal;
}
5 changes: 5 additions & 0 deletions ext/node/polyfills/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import {
import { write, writeSync } from "ext:deno_node/_fs/_fs_write.mjs";
// @deno-types="./_fs/_fs_writev.d.ts"
import { writev, writevSync } from "ext:deno_node/_fs/_fs_writev.mjs";
import { readv, readvSync } from "ext:deno_node/_fs/_fs_readv.ts";
import {
writeFile,
writeFilePromise,
Expand Down Expand Up @@ -250,6 +251,8 @@ export default {
ReadStream,
realpath,
realpathSync,
readv,
readvSync,
rename,
renameSync,
rmdir,
Expand Down Expand Up @@ -353,6 +356,8 @@ export {
readlinkSync,
ReadStream,
readSync,
readv,
readvSync,
realpath,
realpathSync,
rename,
Expand Down
2 changes: 2 additions & 0 deletions tests/node_compat/config.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@
"test-fs-readdir-stack-overflow.js",
"test-fs-readdir.js",
"test-fs-readfile-empty.js",
"test-fs-readv-sync.js",
"test-fs-readv.js",
"test-fs-realpath-native.js",
"test-fs-rmdir-recursive-sync-warns-not-found.js",
"test-fs-rmdir-recursive-sync-warns-on-file.js",
Expand Down
100 changes: 100 additions & 0 deletions tests/node_compat/test/parallel/test-fs-readv-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.12.1
// This file is automatically generated by `tools/node_compat/setup.ts`. Do not modify this file manually.

'use strict';

require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const tmpdir = require('../common/tmpdir');

tmpdir.refresh();

const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف';

const exptectedBuff = Buffer.from(expected);
const expectedLength = exptectedBuff.length;

const filename = path.join(tmpdir.path, '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.strictEqual(read, 0);

read = fs.readvSync(fd, bufferArr, 0);
assert.strictEqual(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.strictEqual(read, 0);

read = fs.readvSync(fd, bufferArr);
assert.strictEqual(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'
}
);
});
}
102 changes: 102 additions & 0 deletions tests/node_compat/test/parallel/test-fs-readv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.12.1
// This file is automatically generated by `tools/node_compat/setup.ts`. Do not modify this file manually.

'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.mustSucceed((bytesRead, buffers) => {
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'
}
);
});
}
1 change: 1 addition & 0 deletions tools/core_import_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"ext:deno_node/_fs/_fs_mkdir.ts": "../ext/node/polyfills/_fs/_fs_mkdir.ts",
"ext:deno_node/_fs/_fs_open.ts": "../ext/node/polyfills/_fs/_fs_open.ts",
"ext:deno_node/_fs/_fs_read.ts": "../ext/node/polyfills/_fs/_fs_read.ts",
"ext:deno_node/_fs/_fs_readv.ts": "../ext/node/polyfills/_fs/_fs_readv.ts",
"ext:deno_node/_fs/_fs_stat.ts": "../ext/node/polyfills/_fs/_fs_stat.ts",
"ext:deno_node/_fs/_fs_watch.ts": "../ext/node/polyfills/_fs/_fs_watch.ts",
"ext:deno_node/_fs/_fs_write.mjs": "../ext/node/polyfills/_fs/_fs_write.mjs",
Expand Down
2 changes: 0 additions & 2 deletions tools/node_compat/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -813,8 +813,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
- [parallel/test-fs-readlink-type-check.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readlink-type-check.js)
- [parallel/test-fs-readv-promises.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv-promises.js)
- [parallel/test-fs-readv-promisify.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv-promisify.js)
- [parallel/test-fs-readv-sync.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv-sync.js)
- [parallel/test-fs-readv.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv.js)
- [parallel/test-fs-ready-event-stream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-ready-event-stream.js)
- [parallel/test-fs-realpath-buffer-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-realpath-buffer-encoding.js)
- [parallel/test-fs-realpath-on-substed-drive.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-realpath-on-substed-drive.js)
Expand Down

0 comments on commit 0a5e4cb

Please sign in to comment.