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

fix(ext/node): add writev method to FileHandle #27563

Merged
merged 6 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading