Skip to content

Commit

Permalink
fix(ext/node): add writev method to FileHandle (#27563)
Browse files Browse the repository at this point in the history
Part of #25554
  • Loading branch information
aaron-ang authored and bartlomieju committed Jan 16, 2025
1 parent 72124bb commit 30c99a7
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 80 deletions.
2 changes: 1 addition & 1 deletion ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ deno_core::extension!(deno_node,
"_fs/_fs_watch.ts",
"_fs/_fs_write.mjs",
"_fs/_fs_writeFile.ts",
"_fs/_fs_writev.mjs",
"_fs/_fs_writev.ts",
"_next_tick.ts",
"_process/exiting.ts",
"_process/process.ts",
Expand Down
65 changes: 0 additions & 65 deletions ext/node/polyfills/_fs/_fs_writev.d.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,53 @@
// deno-lint-ignore-file prefer-primordials

import { Buffer } from "node:buffer";
import process from "node:process";
import { ErrnoException } from "ext:deno_node/_global.d.ts";
import { validateBufferArray } from "ext:deno_node/internal/fs/utils.mjs";
import { getValidatedFd } from "ext:deno_node/internal/fs/utils.mjs";
import { WriteVResult } from "ext:deno_node/internal/fs/handle.ts";
import { maybeCallback } from "ext:deno_node/_fs/_fs_common.ts";
import * as io from "ext:deno_io/12_io.js";
import { op_fs_seek_async, op_fs_seek_sync } from "ext:core/ops";

export function writev(fd, buffers, position, callback) {
export interface WriteVResult {
bytesWritten: number;
buffers: ReadonlyArray<ArrayBufferView>;
}

type writeVCallback = (
err: ErrnoException | null,
bytesWritten: number,
buffers: ReadonlyArray<ArrayBufferView>,
) => void;

/**
* Write an array of `ArrayBufferView`s to the file specified by `fd` using`writev()`.
*
* `position` is the offset from the beginning of the file where this data
* should be written. If `typeof position !== 'number'`, the data will be written
* at the current position.
*
* The callback will be given three arguments: `err`, `bytesWritten`, and`buffers`. `bytesWritten` is how many bytes were written from `buffers`.
*
* If this method is `util.promisify()` ed, it returns a promise for an`Object` with `bytesWritten` and `buffers` properties.
*
* It is unsafe to use `fs.writev()` multiple times on the same file without
* waiting for the callback. For this scenario, use {@link createWriteStream}.
*
* On Linux, positional writes don't work when the file is opened in append mode.
* The kernel ignores the position argument and always appends the data to
* the end of the file.
* @since v12.9.0
*/
export function writev(
fd: number,
buffers: ReadonlyArray<ArrayBufferView>,
position?: number | null,
callback?: writeVCallback,
): void {
const innerWritev = async (fd, buffers, position) => {
const chunks = [];
const chunks: Buffer[] = [];
const offset = 0;
for (let i = 0; i < buffers.length; i++) {
if (Buffer.isBuffer(buffers[i])) {
Expand Down Expand Up @@ -45,16 +83,24 @@ export function writev(fd, buffers, position, callback) {
if (typeof position !== "number") position = null;

innerWritev(fd, buffers, position).then(
(nwritten) => {
callback(null, nwritten, buffers);
},
(nwritten) => callback(null, nwritten, buffers),
(err) => callback(err),
);
}

export function writevSync(fd, buffers, position) {
/**
* For detailed information, see the documentation of the asynchronous version of
* this API: {@link writev}.
* @since v12.9.0
* @return The number of bytes written.
*/
export function writevSync(
fd: number,
buffers: ArrayBufferView[],
position?: number | null,
): number {
const innerWritev = (fd, buffers, position) => {
const chunks = [];
const chunks: Buffer[] = [];
const offset = 0;
for (let i = 0; i < buffers.length; i++) {
if (Buffer.isBuffer(buffers[i])) {
Expand Down Expand Up @@ -85,3 +131,16 @@ export function writevSync(fd, buffers, position) {

return innerWritev(fd, buffers, position);
}

export function writevPromise(
fd: number,
buffers: ArrayBufferView[],
position?: number,
): Promise<WriteVResult> {
return new Promise((resolve, reject) => {
writev(fd, buffers, position, (err, bytesWritten, buffers) => {
if (err) reject(err);
else resolve({ bytesWritten, buffers });
});
});
}
2 changes: 1 addition & 1 deletion ext/node/polyfills/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ import {
// @deno-types="./_fs/_fs_write.d.ts"
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 { writev, writevSync } from "ext:deno_node/_fs/_fs_writev.ts";
import { readv, readvSync } from "ext:deno_node/_fs/_fs_readv.ts";
import {
writeFile,
Expand Down
13 changes: 9 additions & 4 deletions ext/node/polyfills/internal/fs/handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
import { EventEmitter } from "node:events";
import { Buffer } from "node:buffer";
import { Mode, promises, read, write } from "node:fs";
export type { BigIntStats, Stats } from "ext:deno_node/_fs/_fs_stat.ts";
import { core } from "ext:core/mod.js";
import {
BinaryOptionsArgument,
FileOptionsArgument,
ReadOptions,
TextOptionsArgument,
} from "ext:deno_node/_fs/_fs_common.ts";
import { ftruncatePromise } from "ext:deno_node/_fs/_fs_ftruncate.ts";
import { core } from "ext:core/mod.js";
export type { BigIntStats, Stats } from "ext:deno_node/_fs/_fs_stat.ts";
import { writevPromise, WriteVResult } from "ext:deno_node/_fs/_fs_writev.ts";

interface WriteResult {
bytesWritten: number;
Expand Down Expand Up @@ -64,15 +65,15 @@ export class FileHandle extends EventEmitter {
position,
(err, bytesRead, buffer) => {
if (err) reject(err);
else resolve({ buffer: buffer, bytesRead: bytesRead });
else resolve({ buffer, bytesRead });
},
);
});
} else {
return new Promise((resolve, reject) => {
read(this.fd, bufferOrOpt, (err, bytesRead, buffer) => {
if (err) reject(err);
else resolve({ buffer: buffer, bytesRead: bytesRead });
else resolve({ buffer, bytesRead });
});
});
}
Expand Down Expand Up @@ -137,6 +138,10 @@ export class FileHandle extends EventEmitter {
return fsCall(promises.writeFile, this, data, options);
}

writev(buffers: ArrayBufferView[], position?: number): Promise<WriteVResult> {
return fsCall(writevPromise, this, buffers, position);
}

close(): Promise<void> {
// Note that Deno.close is not async
return Promise.resolve(core.close(this.fd));
Expand Down
2 changes: 1 addition & 1 deletion ext/node/polyfills/internal/fs/streams.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { errorOrDestroy } from "ext:deno_node/internal/streams/destroy.mjs";
import { open as fsOpen } from "ext:deno_node/_fs/_fs_open.ts";
import { read as fsRead } from "ext:deno_node/_fs/_fs_read.ts";
import { write as fsWrite } from "ext:deno_node/_fs/_fs_write.mjs";
import { writev as fsWritev } from "ext:deno_node/_fs/_fs_writev.mjs";
import { writev as fsWritev } from "ext:deno_node/_fs/_fs_writev.ts";
import { close as fsClose } from "ext:deno_node/_fs/_fs_close.ts";
import { Buffer } from "node:buffer";
import {
Expand Down
39 changes: 39 additions & 0 deletions tests/unit_node/_fs/_fs_handle_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,45 @@ Deno.test("[node/fs filehandle.writeFile] Write to file", async function () {
assertEquals(decoder.decode(data), "hello world");
});

Deno.test(
"[node/fs filehandle.writev] Write array of buffers to file",
async function () {
const tempFile: string = await Deno.makeTempFile();
const fileHandle = await fs.open(tempFile, "w");

const buffer1 = Buffer.from("hello ");
const buffer2 = Buffer.from("world");
const res = await fileHandle.writev([buffer1, buffer2]);

const data = Deno.readFileSync(tempFile);
await Deno.remove(tempFile);
await fileHandle.close();

assertEquals(res.bytesWritten, 11);
assertEquals(decoder.decode(data), "hello world");
},
);

Deno.test(
"[node/fs filehandle.writev] Write array of buffers to file with position",
async function () {
const tempFile: string = await Deno.makeTempFile();
const fileHandle = await fs.open(tempFile, "w");

const buffer1 = Buffer.from("hello ");
const buffer2 = Buffer.from("world");
await fileHandle.writev([buffer1, buffer2], 0);
const buffer3 = Buffer.from("lorem ipsum");
await fileHandle.writev([buffer3], 6);

const data = Deno.readFileSync(tempFile);
await Deno.remove(tempFile);
await fileHandle.close();

assertEquals(decoder.decode(data), "hello lorem ipsum");
},
);

Deno.test(
"[node/fs filehandle.truncate] Truncate file with length",
async function () {
Expand Down
2 changes: 1 addition & 1 deletion tools/core_import_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"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",
"ext:deno_node/_fs/_fs_writev.mjs": "../ext/node/polyfills/_fs/_fs_writev.mjs",
"ext:deno_node/_fs/_fs_writev.ts": "../ext/node/polyfills/_fs/_fs_writev.ts",
"ext:deno_node/_global.d.ts": "../ext/node/polyfills/_global.d.ts",
"node:_http_agent": "../ext/node/polyfills/_http_agent.mjs",
"node:_http_common": "../ext/node/polyfills/_http_common.ts",
Expand Down

0 comments on commit 30c99a7

Please sign in to comment.