summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/internal/fs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/polyfills/internal/fs')
-rw-r--r--ext/node/polyfills/internal/fs/streams.d.ts326
-rw-r--r--ext/node/polyfills/internal/fs/streams.mjs494
-rw-r--r--ext/node/polyfills/internal/fs/utils.mjs1045
3 files changed, 1865 insertions, 0 deletions
diff --git a/ext/node/polyfills/internal/fs/streams.d.ts b/ext/node/polyfills/internal/fs/streams.d.ts
new file mode 100644
index 000000000..9e70c2431
--- /dev/null
+++ b/ext/node/polyfills/internal/fs/streams.d.ts
@@ -0,0 +1,326 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright DefinitelyTyped contributors. All rights reserved. MIT license.
+// deno-lint-ignore-file no-explicit-any
+
+import * as stream from "internal:deno_node/polyfills/_stream.d.ts";
+import * as promises from "internal:deno_node/polyfills/fs/promises.ts";
+
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import {
+ BufferEncoding,
+ ErrnoException,
+} from "internal:deno_node/polyfills/_global.d.ts";
+
+type PathLike = string | Buffer | URL;
+
+/**
+ * Instances of `fs.ReadStream` are created and returned using the {@link createReadStream} function.
+ * @since v0.1.93
+ */
+export class ReadStream extends stream.Readable {
+ close(callback?: (err?: ErrnoException | null) => void): void;
+ /**
+ * The number of bytes that have been read so far.
+ * @since v6.4.0
+ */
+ bytesRead: number;
+ /**
+ * The path to the file the stream is reading from as specified in the first
+ * argument to `fs.createReadStream()`. If `path` is passed as a string, then`readStream.path` will be a string. If `path` is passed as a `Buffer`, then`readStream.path` will be a
+ * `Buffer`. If `fd` is specified, then`readStream.path` will be `undefined`.
+ * @since v0.1.93
+ */
+ path: string | Buffer;
+ /**
+ * This property is `true` if the underlying file has not been opened yet,
+ * i.e. before the `'ready'` event is emitted.
+ * @since v11.2.0, v10.16.0
+ */
+ pending: boolean;
+ /**
+ * events.EventEmitter
+ * 1. open
+ * 2. close
+ * 3. ready
+ */
+ addListener(event: "close", listener: () => void): this;
+ addListener(event: "data", listener: (chunk: Buffer | string) => void): this;
+ addListener(event: "end", listener: () => void): this;
+ addListener(event: "error", listener: (err: Error) => void): this;
+ addListener(event: "open", listener: (fd: number) => void): this;
+ addListener(event: "pause", listener: () => void): this;
+ addListener(event: "readable", listener: () => void): this;
+ addListener(event: "ready", listener: () => void): this;
+ addListener(event: "resume", listener: () => void): this;
+ addListener(event: string | symbol, listener: (...args: any[]) => void): this;
+ on(event: "close", listener: () => void): this;
+ on(event: "data", listener: (chunk: Buffer | string) => void): this;
+ on(event: "end", listener: () => void): this;
+ on(event: "error", listener: (err: Error) => void): this;
+ on(event: "open", listener: (fd: number) => void): this;
+ on(event: "pause", listener: () => void): this;
+ on(event: "readable", listener: () => void): this;
+ on(event: "ready", listener: () => void): this;
+ on(event: "resume", listener: () => void): this;
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
+ once(event: "close", listener: () => void): this;
+ once(event: "data", listener: (chunk: Buffer | string) => void): this;
+ once(event: "end", listener: () => void): this;
+ once(event: "error", listener: (err: Error) => void): this;
+ once(event: "open", listener: (fd: number) => void): this;
+ once(event: "pause", listener: () => void): this;
+ once(event: "readable", listener: () => void): this;
+ once(event: "ready", listener: () => void): this;
+ once(event: "resume", listener: () => void): this;
+ once(event: string | symbol, listener: (...args: any[]) => void): this;
+ prependListener(event: "close", listener: () => void): this;
+ prependListener(
+ event: "data",
+ listener: (chunk: Buffer | string) => void,
+ ): this;
+ prependListener(event: "end", listener: () => void): this;
+ prependListener(event: "error", listener: (err: Error) => void): this;
+ prependListener(event: "open", listener: (fd: number) => void): this;
+ prependListener(event: "pause", listener: () => void): this;
+ prependListener(event: "readable", listener: () => void): this;
+ prependListener(event: "ready", listener: () => void): this;
+ prependListener(event: "resume", listener: () => void): this;
+ prependListener(
+ event: string | symbol,
+ listener: (...args: any[]) => void,
+ ): this;
+ prependOnceListener(event: "close", listener: () => void): this;
+ prependOnceListener(
+ event: "data",
+ listener: (chunk: Buffer | string) => void,
+ ): this;
+ prependOnceListener(event: "end", listener: () => void): this;
+ prependOnceListener(event: "error", listener: (err: Error) => void): this;
+ prependOnceListener(event: "open", listener: (fd: number) => void): this;
+ prependOnceListener(event: "pause", listener: () => void): this;
+ prependOnceListener(event: "readable", listener: () => void): this;
+ prependOnceListener(event: "ready", listener: () => void): this;
+ prependOnceListener(event: "resume", listener: () => void): this;
+ prependOnceListener(
+ event: string | symbol,
+ listener: (...args: any[]) => void,
+ ): this;
+}
+/**
+ * * Extends `stream.Writable`
+ *
+ * Instances of `fs.WriteStream` are created and returned using the {@link createWriteStream} function.
+ * @since v0.1.93
+ */
+export class WriteStream extends stream.Writable {
+ /**
+ * Closes `writeStream`. Optionally accepts a
+ * callback that will be executed once the `writeStream`is closed.
+ * @since v0.9.4
+ */
+ close(callback?: (err?: ErrnoException | null) => void): void;
+ /**
+ * The number of bytes written so far. Does not include data that is still queued
+ * for writing.
+ * @since v0.4.7
+ */
+ bytesWritten: number;
+ /**
+ * The path to the file the stream is writing to as specified in the first
+ * argument to {@link createWriteStream}. If `path` is passed as a string, then`writeStream.path` will be a string. If `path` is passed as a `Buffer`, then`writeStream.path` will be a
+ * `Buffer`.
+ * @since v0.1.93
+ */
+ path: string | Buffer;
+ /**
+ * This property is `true` if the underlying file has not been opened yet,
+ * i.e. before the `'ready'` event is emitted.
+ * @since v11.2.0
+ */
+ pending: boolean;
+ /**
+ * events.EventEmitter
+ * 1. open
+ * 2. close
+ * 3. ready
+ */
+ addListener(event: "close", listener: () => void): this;
+ addListener(event: "drain", listener: () => void): this;
+ addListener(event: "error", listener: (err: Error) => void): this;
+ addListener(event: "finish", listener: () => void): this;
+ addListener(event: "open", listener: (fd: number) => void): this;
+ addListener(event: "pipe", listener: (src: stream.Readable) => void): this;
+ addListener(event: "ready", listener: () => void): this;
+ addListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
+ addListener(event: string | symbol, listener: (...args: any[]) => void): this;
+ on(event: "close", listener: () => void): this;
+ on(event: "drain", listener: () => void): this;
+ on(event: "error", listener: (err: Error) => void): this;
+ on(event: "finish", listener: () => void): this;
+ on(event: "open", listener: (fd: number) => void): this;
+ on(event: "pipe", listener: (src: stream.Readable) => void): this;
+ on(event: "ready", listener: () => void): this;
+ on(event: "unpipe", listener: (src: stream.Readable) => void): this;
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
+ once(event: "close", listener: () => void): this;
+ once(event: "drain", listener: () => void): this;
+ once(event: "error", listener: (err: Error) => void): this;
+ once(event: "finish", listener: () => void): this;
+ once(event: "open", listener: (fd: number) => void): this;
+ once(event: "pipe", listener: (src: stream.Readable) => void): this;
+ once(event: "ready", listener: () => void): this;
+ once(event: "unpipe", listener: (src: stream.Readable) => void): this;
+ once(event: string | symbol, listener: (...args: any[]) => void): this;
+ prependListener(event: "close", listener: () => void): this;
+ prependListener(event: "drain", listener: () => void): this;
+ prependListener(event: "error", listener: (err: Error) => void): this;
+ prependListener(event: "finish", listener: () => void): this;
+ prependListener(event: "open", listener: (fd: number) => void): this;
+ prependListener(
+ event: "pipe",
+ listener: (src: stream.Readable) => void,
+ ): this;
+ prependListener(event: "ready", listener: () => void): this;
+ prependListener(
+ event: "unpipe",
+ listener: (src: stream.Readable) => void,
+ ): this;
+ prependListener(
+ event: string | symbol,
+ listener: (...args: any[]) => void,
+ ): this;
+ prependOnceListener(event: "close", listener: () => void): this;
+ prependOnceListener(event: "drain", listener: () => void): this;
+ prependOnceListener(event: "error", listener: (err: Error) => void): this;
+ prependOnceListener(event: "finish", listener: () => void): this;
+ prependOnceListener(event: "open", listener: (fd: number) => void): this;
+ prependOnceListener(
+ event: "pipe",
+ listener: (src: stream.Readable) => void,
+ ): this;
+ prependOnceListener(event: "ready", listener: () => void): this;
+ prependOnceListener(
+ event: "unpipe",
+ listener: (src: stream.Readable) => void,
+ ): this;
+ prependOnceListener(
+ event: string | symbol,
+ listener: (...args: any[]) => void,
+ ): this;
+}
+interface StreamOptions {
+ flags?: string | undefined;
+ encoding?: BufferEncoding | undefined;
+ // @ts-ignore promises.FileHandle is not implemented
+ fd?: number | promises.FileHandle | undefined;
+ mode?: number | undefined;
+ autoClose?: boolean | undefined;
+ /**
+ * @default false
+ */
+ emitClose?: boolean | undefined;
+ start?: number | undefined;
+ highWaterMark?: number | undefined;
+}
+interface ReadStreamOptions extends StreamOptions {
+ end?: number | undefined;
+}
+/**
+ * Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream
+ * returned by this method has a default `highWaterMark` of 64 kb.
+ *
+ * `options` can include `start` and `end` values to read a range of bytes from
+ * the file instead of the entire file. Both `start` and `end` are inclusive and
+ * start counting at 0, allowed values are in the
+ * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. If `fd` is specified and `start` is
+ * omitted or `undefined`, `fs.createReadStream()` reads sequentially from the
+ * current file position. The `encoding` can be any one of those accepted by `Buffer`.
+ *
+ * If `fd` is specified, `ReadStream` will ignore the `path` argument and will use
+ * the specified file descriptor. This means that no `'open'` event will be
+ * emitted. `fd` should be blocking; non-blocking `fd`s should be passed to `net.Socket`.
+ *
+ * If `fd` points to a character device that only supports blocking reads
+ * (such as keyboard or sound card), read operations do not finish until data is
+ * available. This can prevent the process from exiting and the stream from
+ * closing naturally.
+ *
+ * By default, the stream will emit a `'close'` event after it has been
+ * destroyed. Set the `emitClose` option to `false` to change this behavior.
+ *
+ * By providing the `fs` option, it is possible to override the corresponding `fs`implementations for `open`, `read`, and `close`. When providing the `fs` option,
+ * an override for `read` is required. If no `fd` is provided, an override for`open` is also required. If `autoClose` is `true`, an override for `close` is
+ * also required.
+ *
+ * ```js
+ * import { createReadStream } from "internal:deno_node/polyfills/internal/fs/fs";
+ *
+ * // Create a stream from some character device.
+ * const stream = createReadStream('/dev/input/event0');
+ * setTimeout(() => {
+ * stream.close(); // This may not close the stream.
+ * // Artificially marking end-of-stream, as if the underlying resource had
+ * // indicated end-of-file by itself, allows the stream to close.
+ * // This does not cancel pending read operations, and if there is such an
+ * // operation, the process may still not be able to exit successfully
+ * // until it finishes.
+ * stream.push(null);
+ * stream.read(0);
+ * }, 100);
+ * ```
+ *
+ * If `autoClose` is false, then the file descriptor won't be closed, even if
+ * there's an error. It is the application's responsibility to close it and make
+ * sure there's no file descriptor leak. If `autoClose` is set to true (default
+ * behavior), on `'error'` or `'end'` the file descriptor will be closed
+ * automatically.
+ *
+ * `mode` sets the file mode (permission and sticky bits), but only if the
+ * file was created.
+ *
+ * An example to read the last 10 bytes of a file which is 100 bytes long:
+ *
+ * ```js
+ * import { createReadStream } from "internal:deno_node/polyfills/internal/fs/fs";
+ *
+ * createReadStream('sample.txt', { start: 90, end: 99 });
+ * ```
+ *
+ * If `options` is a string, then it specifies the encoding.
+ * @since v0.1.31
+ */
+export function createReadStream(
+ path: PathLike,
+ options?: BufferEncoding | ReadStreamOptions,
+): ReadStream;
+/**
+ * `options` may also include a `start` option to allow writing data at some
+ * position past the beginning of the file, allowed values are in the
+ * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. Modifying a file rather than
+ * replacing it may require the `flags` option to be set to `r+` rather than the
+ * default `w`. The `encoding` can be any one of those accepted by `Buffer`.
+ *
+ * If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false,
+ * then the file descriptor won't be closed, even if there's an error.
+ * It is the application's responsibility to close it and make sure there's no
+ * file descriptor leak.
+ *
+ * By default, the stream will emit a `'close'` event after it has been
+ * destroyed. Set the `emitClose` option to `false` to change this behavior.
+ *
+ * By providing the `fs` option it is possible to override the corresponding `fs`implementations for `open`, `write`, `writev` and `close`. Overriding `write()`without `writev()` can reduce
+ * performance as some optimizations (`_writev()`)
+ * will be disabled. When providing the `fs` option, overrides for at least one of`write` and `writev` are required. If no `fd` option is supplied, an override
+ * for `open` is also required. If `autoClose` is `true`, an override for `close`is also required.
+ *
+ * Like `fs.ReadStream`, if `fd` is specified, `fs.WriteStream` will ignore the`path` argument and will use the specified file descriptor. This means that no`'open'` event will be
+ * emitted. `fd` should be blocking; non-blocking `fd`s
+ * should be passed to `net.Socket`.
+ *
+ * If `options` is a string, then it specifies the encoding.
+ * @since v0.1.31
+ */
+export function createWriteStream(
+ path: PathLike,
+ options?: BufferEncoding | StreamOptions,
+): WriteStream;
diff --git a/ext/node/polyfills/internal/fs/streams.mjs b/ext/node/polyfills/internal/fs/streams.mjs
new file mode 100644
index 000000000..4d751df76
--- /dev/null
+++ b/ext/node/polyfills/internal/fs/streams.mjs
@@ -0,0 +1,494 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
+
+import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "internal:deno_node/polyfills/internal/errors.ts";
+import { kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs";
+import { deprecate } from "internal:deno_node/polyfills/util.ts";
+import { validateFunction, validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs";
+import { errorOrDestroy } from "internal:deno_node/polyfills/internal/streams/destroy.mjs";
+import { open as fsOpen } from "internal:deno_node/polyfills/_fs/_fs_open.ts";
+import { read as fsRead } from "internal:deno_node/polyfills/_fs/_fs_read.ts";
+import { write as fsWrite } from "internal:deno_node/polyfills/_fs/_fs_write.mjs";
+import { writev as fsWritev } from "internal:deno_node/polyfills/_fs/_fs_writev.mjs";
+import { close as fsClose } from "internal:deno_node/polyfills/_fs/_fs_close.ts";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import {
+ copyObject,
+ getOptions,
+ getValidatedFd,
+ validatePath,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { finished, Readable, Writable } from "internal:deno_node/polyfills/stream.ts";
+import { toPathIfFileURL } from "internal:deno_node/polyfills/internal/url.ts";
+import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts";
+const kIoDone = Symbol("kIoDone");
+const kIsPerformingIO = Symbol("kIsPerformingIO");
+
+const kFs = Symbol("kFs");
+
+function _construct(callback) {
+ // deno-lint-ignore no-this-alias
+ const stream = this;
+ if (typeof stream.fd === "number") {
+ callback();
+ return;
+ }
+
+ if (stream.open !== openWriteFs && stream.open !== openReadFs) {
+ // Backwards compat for monkey patching open().
+ const orgEmit = stream.emit;
+ stream.emit = function (...args) {
+ if (args[0] === "open") {
+ this.emit = orgEmit;
+ callback();
+ Reflect.apply(orgEmit, this, args);
+ } else if (args[0] === "error") {
+ this.emit = orgEmit;
+ callback(args[1]);
+ } else {
+ Reflect.apply(orgEmit, this, args);
+ }
+ };
+ stream.open();
+ } else {
+ stream[kFs].open(
+ stream.path.toString(),
+ stream.flags,
+ stream.mode,
+ (er, fd) => {
+ if (er) {
+ callback(er);
+ } else {
+ stream.fd = fd;
+ callback();
+ stream.emit("open", stream.fd);
+ stream.emit("ready");
+ }
+ },
+ );
+ }
+}
+
+function close(stream, err, cb) {
+ if (!stream.fd) {
+ cb(err);
+ } else {
+ stream[kFs].close(stream.fd, (er) => {
+ cb(er || err);
+ });
+ stream.fd = null;
+ }
+}
+
+function importFd(stream, options) {
+ if (typeof options.fd === "number") {
+ // When fd is a raw descriptor, we must keep our fingers crossed
+ // that the descriptor won't get closed, or worse, replaced with
+ // another one
+ // https://github.com/nodejs/node/issues/35862
+ if (stream instanceof ReadStream) {
+ stream[kFs] = options.fs || { read: fsRead, close: fsClose };
+ }
+ if (stream instanceof WriteStream) {
+ stream[kFs] = options.fs ||
+ { write: fsWrite, writev: fsWritev, close: fsClose };
+ }
+ return options.fd;
+ }
+
+ throw new ERR_INVALID_ARG_TYPE("options.fd", ["number"], options.fd);
+}
+
+export function ReadStream(path, options) {
+ if (!(this instanceof ReadStream)) {
+ return new ReadStream(path, options);
+ }
+
+ // A little bit bigger buffer and water marks by default
+ options = copyObject(getOptions(options, kEmptyObject));
+ if (options.highWaterMark === undefined) {
+ options.highWaterMark = 64 * 1024;
+ }
+
+ if (options.autoDestroy === undefined) {
+ options.autoDestroy = false;
+ }
+
+ if (options.fd == null) {
+ this.fd = null;
+ this[kFs] = options.fs || { open: fsOpen, read: fsRead, close: fsClose };
+ validateFunction(this[kFs].open, "options.fs.open");
+
+ // Path will be ignored when fd is specified, so it can be falsy
+ this.path = toPathIfFileURL(path);
+ this.flags = options.flags === undefined ? "r" : options.flags;
+ this.mode = options.mode === undefined ? 0o666 : options.mode;
+
+ validatePath(this.path);
+ } else {
+ this.fd = getValidatedFd(importFd(this, options));
+ }
+
+ options.autoDestroy = options.autoClose === undefined
+ ? true
+ : options.autoClose;
+
+ validateFunction(this[kFs].read, "options.fs.read");
+
+ if (options.autoDestroy) {
+ validateFunction(this[kFs].close, "options.fs.close");
+ }
+
+ this.start = options.start;
+ this.end = options.end ?? Infinity;
+ this.pos = undefined;
+ this.bytesRead = 0;
+ this[kIsPerformingIO] = false;
+
+ if (this.start !== undefined) {
+ validateInteger(this.start, "start", 0);
+
+ this.pos = this.start;
+ }
+
+ if (this.end !== Infinity) {
+ validateInteger(this.end, "end", 0);
+
+ if (this.start !== undefined && this.start > this.end) {
+ throw new ERR_OUT_OF_RANGE(
+ "start",
+ `<= "end" (here: ${this.end})`,
+ this.start,
+ );
+ }
+ }
+
+ Reflect.apply(Readable, this, [options]);
+}
+
+Object.setPrototypeOf(ReadStream.prototype, Readable.prototype);
+Object.setPrototypeOf(ReadStream, Readable);
+
+Object.defineProperty(ReadStream.prototype, "autoClose", {
+ get() {
+ return this._readableState.autoDestroy;
+ },
+ set(val) {
+ this._readableState.autoDestroy = val;
+ },
+});
+
+const openReadFs = deprecate(
+ function () {
+ // Noop.
+ },
+ "ReadStream.prototype.open() is deprecated",
+ "DEP0135",
+);
+ReadStream.prototype.open = openReadFs;
+
+ReadStream.prototype._construct = _construct;
+
+ReadStream.prototype._read = async function (n) {
+ n = this.pos !== undefined
+ ? Math.min(this.end - this.pos + 1, n)
+ : Math.min(this.end - this.bytesRead + 1, n);
+
+ if (n <= 0) {
+ this.push(null);
+ return;
+ }
+
+ const buf = Buffer.allocUnsafeSlow(n);
+
+ let error = null;
+ let bytesRead = null;
+ let buffer = undefined;
+
+ this[kIsPerformingIO] = true;
+
+ await new Promise((resolve) => {
+ this[kFs]
+ .read(
+ this.fd,
+ buf,
+ 0,
+ n,
+ this.pos ?? null,
+ (_er, _bytesRead, _buf) => {
+ error = _er;
+ bytesRead = _bytesRead;
+ buffer = _buf;
+ return resolve(true);
+ },
+ );
+ });
+
+ this[kIsPerformingIO] = false;
+
+ // Tell ._destroy() that it's safe to close the fd now.
+ if (this.destroyed) {
+ this.emit(kIoDone, error);
+ return;
+ }
+
+ if (error) {
+ errorOrDestroy(this, error);
+ } else if (
+ typeof bytesRead === "number" &&
+ bytesRead > 0
+ ) {
+ if (this.pos !== undefined) {
+ this.pos += bytesRead;
+ }
+
+ this.bytesRead += bytesRead;
+
+ if (bytesRead !== buffer.length) {
+ // Slow path. Shrink to fit.
+ // Copy instead of slice so that we don't retain
+ // large backing buffer for small reads.
+ const dst = Buffer.allocUnsafeSlow(bytesRead);
+ buffer.copy(dst, 0, 0, bytesRead);
+ buffer = dst;
+ }
+
+ this.push(buffer);
+ } else {
+ this.push(null);
+ }
+};
+
+ReadStream.prototype._destroy = function (err, cb) {
+ // Usually for async IO it is safe to close a file descriptor
+ // even when there are pending operations. However, due to platform
+ // differences file IO is implemented using synchronous operations
+ // running in a thread pool. Therefore, file descriptors are not safe
+ // to close while used in a pending read or write operation. Wait for
+ // any pending IO (kIsPerformingIO) to complete (kIoDone).
+ if (this[kIsPerformingIO]) {
+ this.once(kIoDone, (er) => close(this, err || er, cb));
+ } else {
+ close(this, err, cb);
+ }
+};
+
+ReadStream.prototype.close = function (cb) {
+ if (typeof cb === "function") finished(this, cb);
+ this.destroy();
+};
+
+Object.defineProperty(ReadStream.prototype, "pending", {
+ get() {
+ return this.fd === null;
+ },
+ configurable: true,
+});
+
+export function WriteStream(path, options) {
+ if (!(this instanceof WriteStream)) {
+ return new WriteStream(path, options);
+ }
+
+ options = copyObject(getOptions(options, kEmptyObject));
+
+ // Only buffers are supported.
+ options.decodeStrings = true;
+
+ if (options.fd == null) {
+ this.fd = null;
+ this[kFs] = options.fs ||
+ { open: fsOpen, write: fsWrite, writev: fsWritev, close: fsClose };
+ validateFunction(this[kFs].open, "options.fs.open");
+
+ // Path will be ignored when fd is specified, so it can be falsy
+ this.path = toPathIfFileURL(path);
+ this.flags = options.flags === undefined ? "w" : options.flags;
+ this.mode = options.mode === undefined ? 0o666 : options.mode;
+
+ validatePath(this.path);
+ } else {
+ this.fd = getValidatedFd(importFd(this, options));
+ }
+
+ options.autoDestroy = options.autoClose === undefined
+ ? true
+ : options.autoClose;
+
+ if (!this[kFs].write && !this[kFs].writev) {
+ throw new ERR_INVALID_ARG_TYPE(
+ "options.fs.write",
+ "function",
+ this[kFs].write,
+ );
+ }
+
+ if (this[kFs].write) {
+ validateFunction(this[kFs].write, "options.fs.write");
+ }
+
+ if (this[kFs].writev) {
+ validateFunction(this[kFs].writev, "options.fs.writev");
+ }
+
+ if (options.autoDestroy) {
+ validateFunction(this[kFs].close, "options.fs.close");
+ }
+
+ // It's enough to override either, in which case only one will be used.
+ if (!this[kFs].write) {
+ this._write = null;
+ }
+ if (!this[kFs].writev) {
+ this._writev = null;
+ }
+
+ this.start = options.start;
+ this.pos = undefined;
+ this.bytesWritten = 0;
+ this[kIsPerformingIO] = false;
+
+ if (this.start !== undefined) {
+ validateInteger(this.start, "start", 0);
+
+ this.pos = this.start;
+ }
+
+ Reflect.apply(Writable, this, [options]);
+
+ if (options.encoding) {
+ this.setDefaultEncoding(options.encoding);
+ }
+}
+
+Object.setPrototypeOf(WriteStream.prototype, Writable.prototype);
+Object.setPrototypeOf(WriteStream, Writable);
+
+Object.defineProperty(WriteStream.prototype, "autoClose", {
+ get() {
+ return this._writableState.autoDestroy;
+ },
+ set(val) {
+ this._writableState.autoDestroy = val;
+ },
+});
+
+const openWriteFs = deprecate(
+ function () {
+ // Noop.
+ },
+ "WriteStream.prototype.open() is deprecated",
+ "DEP0135",
+);
+WriteStream.prototype.open = openWriteFs;
+
+WriteStream.prototype._construct = _construct;
+
+WriteStream.prototype._write = function (data, _encoding, cb) {
+ this[kIsPerformingIO] = true;
+ this[kFs].write(this.fd, data, 0, data.length, this.pos, (er, bytes) => {
+ this[kIsPerformingIO] = false;
+ if (this.destroyed) {
+ // Tell ._destroy() that it's safe to close the fd now.
+ cb(er);
+ return this.emit(kIoDone, er);
+ }
+
+ if (er) {
+ return cb(er);
+ }
+
+ this.bytesWritten += bytes;
+ cb();
+ });
+
+ if (this.pos !== undefined) {
+ this.pos += data.length;
+ }
+};
+
+WriteStream.prototype._writev = function (data, cb) {
+ const len = data.length;
+ const chunks = new Array(len);
+ let size = 0;
+
+ for (let i = 0; i < len; i++) {
+ const chunk = data[i].chunk;
+
+ chunks[i] = chunk;
+ size += chunk.length;
+ }
+
+ this[kIsPerformingIO] = true;
+ this[kFs].writev(this.fd, chunks, this.pos ?? null, (er, bytes) => {
+ this[kIsPerformingIO] = false;
+ if (this.destroyed) {
+ // Tell ._destroy() that it's safe to close the fd now.
+ cb(er);
+ return this.emit(kIoDone, er);
+ }
+
+ if (er) {
+ return cb(er);
+ }
+
+ this.bytesWritten += bytes;
+ cb();
+ });
+
+ if (this.pos !== undefined) {
+ this.pos += size;
+ }
+};
+
+WriteStream.prototype._destroy = function (err, cb) {
+ // Usually for async IO it is safe to close a file descriptor
+ // even when there are pending operations. However, due to platform
+ // differences file IO is implemented using synchronous operations
+ // running in a thread pool. Therefore, file descriptors are not safe
+ // to close while used in a pending read or write operation. Wait for
+ // any pending IO (kIsPerformingIO) to complete (kIoDone).
+ if (this[kIsPerformingIO]) {
+ this.once(kIoDone, (er) => close(this, err || er, cb));
+ } else {
+ close(this, err, cb);
+ }
+};
+
+WriteStream.prototype.close = function (cb) {
+ if (cb) {
+ if (this.closed) {
+ nextTick(cb);
+ return;
+ }
+ this.on("close", cb);
+ }
+
+ // If we are not autoClosing, we should call
+ // destroy on 'finish'.
+ if (!this.autoClose) {
+ this.on("finish", this.destroy);
+ }
+
+ // We use end() instead of destroy() because of
+ // https://github.com/nodejs/node/issues/2006
+ this.end();
+};
+
+// There is no shutdown() for files.
+WriteStream.prototype.destroySoon = WriteStream.prototype.end;
+
+Object.defineProperty(WriteStream.prototype, "pending", {
+ get() {
+ return this.fd === null;
+ },
+ configurable: true,
+});
+
+export function createReadStream(path, options) {
+ return new ReadStream(path, options);
+}
+
+export function createWriteStream(path, options) {
+ return new WriteStream(path, options);
+}
diff --git a/ext/node/polyfills/internal/fs/utils.mjs b/ext/node/polyfills/internal/fs/utils.mjs
new file mode 100644
index 000000000..9d74c5eee
--- /dev/null
+++ b/ext/node/polyfills/internal/fs/utils.mjs
@@ -0,0 +1,1045 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+"use strict";
+
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import {
+ ERR_FS_EISDIR,
+ ERR_FS_INVALID_SYMLINK_TYPE,
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_ARG_VALUE,
+ ERR_OUT_OF_RANGE,
+ hideStackFrames,
+ uvException,
+} from "internal:deno_node/polyfills/internal/errors.ts";
+
+import {
+ isArrayBufferView,
+ isBigUint64Array,
+ isDate,
+ isUint8Array,
+} from "internal:deno_node/polyfills/internal/util/types.ts";
+import { once } from "internal:deno_node/polyfills/internal/util.mjs";
+import { deprecate } from "internal:deno_node/polyfills/util.ts";
+import { toPathIfFileURL } from "internal:deno_node/polyfills/internal/url.ts";
+import {
+ validateAbortSignal,
+ validateBoolean,
+ validateFunction,
+ validateInt32,
+ validateInteger,
+ validateObject,
+ validateUint32,
+} from "internal:deno_node/polyfills/internal/validators.mjs";
+import pathModule from "internal:deno_node/polyfills/path.ts";
+const kType = Symbol("type");
+const kStats = Symbol("stats");
+import assert from "internal:deno_node/polyfills/internal/assert.mjs";
+import { lstat, lstatSync } from "internal:deno_node/polyfills/_fs/_fs_lstat.ts";
+import { stat, statSync } from "internal:deno_node/polyfills/_fs/_fs_stat.ts";
+import { isWindows } from "internal:deno_node/polyfills/_util/os.ts";
+import process from "internal:deno_node/polyfills/process.ts";
+
+import {
+ fs as fsConstants,
+ os as osConstants,
+} from "internal:deno_node/polyfills/internal_binding/constants.ts";
+const {
+ F_OK = 0,
+ W_OK = 0,
+ R_OK = 0,
+ X_OK = 0,
+ COPYFILE_EXCL,
+ COPYFILE_FICLONE,
+ COPYFILE_FICLONE_FORCE,
+ O_APPEND,
+ O_CREAT,
+ O_EXCL,
+ O_RDONLY,
+ O_RDWR,
+ O_SYNC,
+ O_TRUNC,
+ O_WRONLY,
+ S_IFBLK,
+ S_IFCHR,
+ S_IFDIR,
+ S_IFIFO,
+ S_IFLNK,
+ S_IFMT,
+ S_IFREG,
+ S_IFSOCK,
+ UV_FS_SYMLINK_DIR,
+ UV_FS_SYMLINK_JUNCTION,
+ UV_DIRENT_UNKNOWN,
+ UV_DIRENT_FILE,
+ UV_DIRENT_DIR,
+ UV_DIRENT_LINK,
+ UV_DIRENT_FIFO,
+ UV_DIRENT_SOCKET,
+ UV_DIRENT_CHAR,
+ UV_DIRENT_BLOCK,
+} = fsConstants;
+
+// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
+// available on specific systems. They can be used in combination as well
+// (F_OK | R_OK | W_OK | X_OK).
+const kMinimumAccessMode = Math.min(F_OK, W_OK, R_OK, X_OK);
+const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;
+
+const kDefaultCopyMode = 0;
+// The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or
+// COPYFILE_FICLONE_FORCE. They can be used in combination as well
+// (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE).
+const kMinimumCopyMode = Math.min(
+ kDefaultCopyMode,
+ COPYFILE_EXCL,
+ COPYFILE_FICLONE,
+ COPYFILE_FICLONE_FORCE,
+);
+const kMaximumCopyMode = COPYFILE_EXCL |
+ COPYFILE_FICLONE |
+ COPYFILE_FICLONE_FORCE;
+
+// Most platforms don't allow reads or writes >= 2 GB.
+// See https://github.com/libuv/libuv/pull/1501.
+const kIoMaxLength = 2 ** 31 - 1;
+
+// Use 64kb in case the file type is not a regular file and thus do not know the
+// actual file size. Increasing the value further results in more frequent over
+// allocation for small files and consumes CPU time and memory that should be
+// used else wise.
+// Use up to 512kb per read otherwise to partition reading big files to prevent
+// blocking other threads in case the available threads are all in use.
+const kReadFileUnknownBufferLength = 64 * 1024;
+const kReadFileBufferLength = 512 * 1024;
+
+const kWriteFileMaxChunkSize = 512 * 1024;
+
+export const kMaxUserId = 2 ** 32 - 1;
+
+export function assertEncoding(encoding) {
+ if (encoding && !Buffer.isEncoding(encoding)) {
+ const reason = "is invalid encoding";
+ throw new ERR_INVALID_ARG_VALUE(encoding, "encoding", reason);
+ }
+}
+
+export class Dirent {
+ constructor(name, type) {
+ this.name = name;
+ this[kType] = type;
+ }
+
+ isDirectory() {
+ return this[kType] === UV_DIRENT_DIR;
+ }
+
+ isFile() {
+ return this[kType] === UV_DIRENT_FILE;
+ }
+
+ isBlockDevice() {
+ return this[kType] === UV_DIRENT_BLOCK;
+ }
+
+ isCharacterDevice() {
+ return this[kType] === UV_DIRENT_CHAR;
+ }
+
+ isSymbolicLink() {
+ return this[kType] === UV_DIRENT_LINK;
+ }
+
+ isFIFO() {
+ return this[kType] === UV_DIRENT_FIFO;
+ }
+
+ isSocket() {
+ return this[kType] === UV_DIRENT_SOCKET;
+ }
+}
+
+class DirentFromStats extends Dirent {
+ constructor(name, stats) {
+ super(name, null);
+ this[kStats] = stats;
+ }
+}
+
+for (const name of Reflect.ownKeys(Dirent.prototype)) {
+ if (name === "constructor") {
+ continue;
+ }
+ DirentFromStats.prototype[name] = function () {
+ return this[kStats][name]();
+ };
+}
+
+export function copyObject(source) {
+ const target = {};
+ for (const key in source) {
+ target[key] = source[key];
+ }
+ return target;
+}
+
+const bufferSep = Buffer.from(pathModule.sep);
+
+function join(path, name) {
+ if (
+ (typeof path === "string" || isUint8Array(path)) &&
+ name === undefined
+ ) {
+ return path;
+ }
+
+ if (typeof path === "string" && isUint8Array(name)) {
+ const pathBuffer = Buffer.from(pathModule.join(path, pathModule.sep));
+ return Buffer.concat([pathBuffer, name]);
+ }
+
+ if (typeof path === "string" && typeof name === "string") {
+ return pathModule.join(path, name);
+ }
+
+ if (isUint8Array(path) && isUint8Array(name)) {
+ return Buffer.concat([path, bufferSep, name]);
+ }
+
+ throw new ERR_INVALID_ARG_TYPE(
+ "path",
+ ["string", "Buffer"],
+ path,
+ );
+}
+
+export function getDirents(path, { 0: names, 1: types }, callback) {
+ let i;
+ if (typeof callback === "function") {
+ const len = names.length;
+ let toFinish = 0;
+ callback = once(callback);
+ for (i = 0; i < len; i++) {
+ const type = types[i];
+ if (type === UV_DIRENT_UNKNOWN) {
+ const name = names[i];
+ const idx = i;
+ toFinish++;
+ let filepath;
+ try {
+ filepath = join(path, name);
+ } catch (err) {
+ callback(err);
+ return;
+ }
+ lstat(filepath, (err, stats) => {
+ if (err) {
+ callback(err);
+ return;
+ }
+ names[idx] = new DirentFromStats(name, stats);
+ if (--toFinish === 0) {
+ callback(null, names);
+ }
+ });
+ } else {
+ names[i] = new Dirent(names[i], types[i]);
+ }
+ }
+ if (toFinish === 0) {
+ callback(null, names);
+ }
+ } else {
+ const len = names.length;
+ for (i = 0; i < len; i++) {
+ names[i] = getDirent(path, names[i], types[i]);
+ }
+ return names;
+ }
+}
+
+export function getDirent(path, name, type, callback) {
+ if (typeof callback === "function") {
+ if (type === UV_DIRENT_UNKNOWN) {
+ let filepath;
+ try {
+ filepath = join(path, name);
+ } catch (err) {
+ callback(err);
+ return;
+ }
+ lstat(filepath, (err, stats) => {
+ if (err) {
+ callback(err);
+ return;
+ }
+ callback(null, new DirentFromStats(name, stats));
+ });
+ } else {
+ callback(null, new Dirent(name, type));
+ }
+ } else if (type === UV_DIRENT_UNKNOWN) {
+ const stats = lstatSync(join(path, name));
+ return new DirentFromStats(name, stats);
+ } else {
+ return new Dirent(name, type);
+ }
+}
+
+export function getOptions(options, defaultOptions) {
+ if (
+ options === null || options === undefined ||
+ typeof options === "function"
+ ) {
+ return defaultOptions;
+ }
+
+ if (typeof options === "string") {
+ defaultOptions = { ...defaultOptions };
+ defaultOptions.encoding = options;
+ options = defaultOptions;
+ } else if (typeof options !== "object") {
+ throw new ERR_INVALID_ARG_TYPE("options", ["string", "Object"], options);
+ }
+
+ if (options.encoding !== "buffer") {
+ assertEncoding(options.encoding);
+ }
+
+ if (options.signal !== undefined) {
+ validateAbortSignal(options.signal, "options.signal");
+ }
+ return options;
+}
+
+/**
+ * @param {InternalFSBinding.FSSyncContext} ctx
+ */
+export function handleErrorFromBinding(ctx) {
+ if (ctx.errno !== undefined) { // libuv error numbers
+ const err = uvException(ctx);
+ Error.captureStackTrace(err, handleErrorFromBinding);
+ throw err;
+ }
+ if (ctx.error !== undefined) { // Errors created in C++ land.
+ // TODO(joyeecheung): currently, ctx.error are encoding errors
+ // usually caused by memory problems. We need to figure out proper error
+ // code(s) for this.
+ Error.captureStackTrace(ctx.error, handleErrorFromBinding);
+ throw ctx.error;
+ }
+}
+
+// Check if the path contains null types if it is a string nor Uint8Array,
+// otherwise return silently.
+export const nullCheck = hideStackFrames(
+ (path, propName, throwError = true) => {
+ const pathIsString = typeof path === "string";
+ const pathIsUint8Array = isUint8Array(path);
+
+ // We can only perform meaningful checks on strings and Uint8Arrays.
+ if (
+ (!pathIsString && !pathIsUint8Array) ||
+ (pathIsString && !path.includes("\u0000")) ||
+ (pathIsUint8Array && !path.includes(0))
+ ) {
+ return;
+ }
+
+ const err = new ERR_INVALID_ARG_VALUE(
+ propName,
+ path,
+ "must be a string or Uint8Array without null bytes",
+ );
+ if (throwError) {
+ throw err;
+ }
+ return err;
+ },
+);
+
+export function preprocessSymlinkDestination(path, type, linkPath) {
+ if (!isWindows) {
+ // No preprocessing is needed on Unix.
+ return path;
+ }
+ path = "" + path;
+ if (type === "junction") {
+ // Junctions paths need to be absolute and \\?\-prefixed.
+ // A relative target is relative to the link's parent directory.
+ path = pathModule.resolve(linkPath, "..", path);
+ return pathModule.toNamespacedPath(path);
+ }
+ if (pathModule.isAbsolute(path)) {
+ // If the path is absolute, use the \\?\-prefix to enable long filenames
+ return pathModule.toNamespacedPath(path);
+ }
+ // Windows symlinks don't tolerate forward slashes.
+ return path.replace(/\//g, "\\");
+}
+
+// Constructor for file stats.
+function StatsBase(
+ dev,
+ mode,
+ nlink,
+ uid,
+ gid,
+ rdev,
+ blksize,
+ ino,
+ size,
+ blocks,
+) {
+ this.dev = dev;
+ this.mode = mode;
+ this.nlink = nlink;
+ this.uid = uid;
+ this.gid = gid;
+ this.rdev = rdev;
+ this.blksize = blksize;
+ this.ino = ino;
+ this.size = size;
+ this.blocks = blocks;
+}
+
+StatsBase.prototype.isDirectory = function () {
+ return this._checkModeProperty(S_IFDIR);
+};
+
+StatsBase.prototype.isFile = function () {
+ return this._checkModeProperty(S_IFREG);
+};
+
+StatsBase.prototype.isBlockDevice = function () {
+ return this._checkModeProperty(S_IFBLK);
+};
+
+StatsBase.prototype.isCharacterDevice = function () {
+ return this._checkModeProperty(S_IFCHR);
+};
+
+StatsBase.prototype.isSymbolicLink = function () {
+ return this._checkModeProperty(S_IFLNK);
+};
+
+StatsBase.prototype.isFIFO = function () {
+ return this._checkModeProperty(S_IFIFO);
+};
+
+StatsBase.prototype.isSocket = function () {
+ return this._checkModeProperty(S_IFSOCK);
+};
+
+const kNsPerMsBigInt = 10n ** 6n;
+const kNsPerSecBigInt = 10n ** 9n;
+const kMsPerSec = 10 ** 3;
+const kNsPerMs = 10 ** 6;
+function msFromTimeSpec(sec, nsec) {
+ return sec * kMsPerSec + nsec / kNsPerMs;
+}
+
+function nsFromTimeSpecBigInt(sec, nsec) {
+ return sec * kNsPerSecBigInt + nsec;
+}
+
+// The Date constructor performs Math.floor() to the timestamp.
+// https://www.ecma-international.org/ecma-262/#sec-timeclip
+// Since there may be a precision loss when the timestamp is
+// converted to a floating point number, we manually round
+// the timestamp here before passing it to Date().
+// Refs: https://github.com/nodejs/node/pull/12607
+function dateFromMs(ms) {
+ return new Date(Number(ms) + 0.5);
+}
+
+export function BigIntStats(
+ dev,
+ mode,
+ nlink,
+ uid,
+ gid,
+ rdev,
+ blksize,
+ ino,
+ size,
+ blocks,
+ atimeNs,
+ mtimeNs,
+ ctimeNs,
+ birthtimeNs,
+) {
+ Reflect.apply(StatsBase, this, [
+ dev,
+ mode,
+ nlink,
+ uid,
+ gid,
+ rdev,
+ blksize,
+ ino,
+ size,
+ blocks,
+ ]);
+
+ this.atimeMs = atimeNs / kNsPerMsBigInt;
+ this.mtimeMs = mtimeNs / kNsPerMsBigInt;
+ this.ctimeMs = ctimeNs / kNsPerMsBigInt;
+ this.birthtimeMs = birthtimeNs / kNsPerMsBigInt;
+ this.atimeNs = atimeNs;
+ this.mtimeNs = mtimeNs;
+ this.ctimeNs = ctimeNs;
+ this.birthtimeNs = birthtimeNs;
+ this.atime = dateFromMs(this.atimeMs);
+ this.mtime = dateFromMs(this.mtimeMs);
+ this.ctime = dateFromMs(this.ctimeMs);
+ this.birthtime = dateFromMs(this.birthtimeMs);
+}
+
+Object.setPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
+Object.setPrototypeOf(BigIntStats, StatsBase);
+
+BigIntStats.prototype._checkModeProperty = function (property) {
+ if (
+ isWindows && (property === S_IFIFO || property === S_IFBLK ||
+ property === S_IFSOCK)
+ ) {
+ return false; // Some types are not available on Windows
+ }
+ return (this.mode & BigInt(S_IFMT)) === BigInt(property);
+};
+
+export function Stats(
+ dev,
+ mode,
+ nlink,
+ uid,
+ gid,
+ rdev,
+ blksize,
+ ino,
+ size,
+ blocks,
+ atimeMs,
+ mtimeMs,
+ ctimeMs,
+ birthtimeMs,
+) {
+ StatsBase.call(
+ this,
+ dev,
+ mode,
+ nlink,
+ uid,
+ gid,
+ rdev,
+ blksize,
+ ino,
+ size,
+ blocks,
+ );
+ this.atimeMs = atimeMs;
+ this.mtimeMs = mtimeMs;
+ this.ctimeMs = ctimeMs;
+ this.birthtimeMs = birthtimeMs;
+ this.atime = dateFromMs(atimeMs);
+ this.mtime = dateFromMs(mtimeMs);
+ this.ctime = dateFromMs(ctimeMs);
+ this.birthtime = dateFromMs(birthtimeMs);
+}
+
+Object.setPrototypeOf(Stats.prototype, StatsBase.prototype);
+Object.setPrototypeOf(Stats, StatsBase);
+
+// HACK: Workaround for https://github.com/standard-things/esm/issues/821.
+// TODO(ronag): Remove this as soon as `esm` publishes a fixed version.
+Stats.prototype.isFile = StatsBase.prototype.isFile;
+
+Stats.prototype._checkModeProperty = function (property) {
+ if (
+ isWindows && (property === S_IFIFO || property === S_IFBLK ||
+ property === S_IFSOCK)
+ ) {
+ return false; // Some types are not available on Windows
+ }
+ return (this.mode & S_IFMT) === property;
+};
+
+/**
+ * @param {Float64Array | BigUint64Array} stats
+ * @param {number} offset
+ * @returns
+ */
+export function getStatsFromBinding(stats, offset = 0) {
+ if (isBigUint64Array(stats)) {
+ return new BigIntStats(
+ stats[0 + offset],
+ stats[1 + offset],
+ stats[2 + offset],
+ stats[3 + offset],
+ stats[4 + offset],
+ stats[5 + offset],
+ stats[6 + offset],
+ stats[7 + offset],
+ stats[8 + offset],
+ stats[9 + offset],
+ nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]),
+ nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]),
+ nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]),
+ nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset]),
+ );
+ }
+ return new Stats(
+ stats[0 + offset],
+ stats[1 + offset],
+ stats[2 + offset],
+ stats[3 + offset],
+ stats[4 + offset],
+ stats[5 + offset],
+ stats[6 + offset],
+ stats[7 + offset],
+ stats[8 + offset],
+ stats[9 + offset],
+ msFromTimeSpec(stats[10 + offset], stats[11 + offset]),
+ msFromTimeSpec(stats[12 + offset], stats[13 + offset]),
+ msFromTimeSpec(stats[14 + offset], stats[15 + offset]),
+ msFromTimeSpec(stats[16 + offset], stats[17 + offset]),
+ );
+}
+
+export function stringToFlags(flags, name = "flags") {
+ if (typeof flags === "number") {
+ validateInt32(flags, name);
+ return flags;
+ }
+
+ if (flags == null) {
+ return O_RDONLY;
+ }
+
+ switch (flags) {
+ case "r":
+ return O_RDONLY;
+ case "rs": // Fall through.
+ case "sr":
+ return O_RDONLY | O_SYNC;
+ case "r+":
+ return O_RDWR;
+ case "rs+": // Fall through.
+ case "sr+":
+ return O_RDWR | O_SYNC;
+
+ case "w":
+ return O_TRUNC | O_CREAT | O_WRONLY;
+ case "wx": // Fall through.
+ case "xw":
+ return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
+
+ case "w+":
+ return O_TRUNC | O_CREAT | O_RDWR;
+ case "wx+": // Fall through.
+ case "xw+":
+ return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
+
+ case "a":
+ return O_APPEND | O_CREAT | O_WRONLY;
+ case "ax": // Fall through.
+ case "xa":
+ return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
+ case "as": // Fall through.
+ case "sa":
+ return O_APPEND | O_CREAT | O_WRONLY | O_SYNC;
+
+ case "a+":
+ return O_APPEND | O_CREAT | O_RDWR;
+ case "ax+": // Fall through.
+ case "xa+":
+ return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
+ case "as+": // Fall through.
+ case "sa+":
+ return O_APPEND | O_CREAT | O_RDWR | O_SYNC;
+ }
+
+ throw new ERR_INVALID_ARG_VALUE("flags", flags);
+}
+
+export const stringToSymlinkType = hideStackFrames((type) => {
+ let flags = 0;
+ if (typeof type === "string") {
+ switch (type) {
+ case "dir":
+ flags |= UV_FS_SYMLINK_DIR;
+ break;
+ case "junction":
+ flags |= UV_FS_SYMLINK_JUNCTION;
+ break;
+ case "file":
+ break;
+ default:
+ throw new ERR_FS_INVALID_SYMLINK_TYPE(type);
+ }
+ }
+ return flags;
+});
+
+// converts Date or number to a fractional UNIX timestamp
+export function toUnixTimestamp(time, name = "time") {
+ // eslint-disable-next-line eqeqeq
+ if (typeof time === "string" && +time == time) {
+ return +time;
+ }
+ if (Number.isFinite(time)) {
+ if (time < 0) {
+ return Date.now() / 1000;
+ }
+ return time;
+ }
+ if (isDate(time)) {
+ // Convert to 123.456 UNIX timestamp
+ return Date.getTime(time) / 1000;
+ }
+ throw new ERR_INVALID_ARG_TYPE(name, ["Date", "Time in seconds"], time);
+}
+
+export const validateOffsetLengthRead = hideStackFrames(
+ (offset, length, bufferLength) => {
+ if (offset < 0) {
+ throw new ERR_OUT_OF_RANGE("offset", ">= 0", offset);
+ }
+ if (length < 0) {
+ throw new ERR_OUT_OF_RANGE("length", ">= 0", length);
+ }
+ if (offset + length > bufferLength) {
+ throw new ERR_OUT_OF_RANGE(
+ "length",
+ `<= ${bufferLength - offset}`,
+ length,
+ );
+ }
+ },
+);
+
+export const validateOffsetLengthWrite = hideStackFrames(
+ (offset, length, byteLength) => {
+ if (offset > byteLength) {
+ throw new ERR_OUT_OF_RANGE("offset", `<= ${byteLength}`, offset);
+ }
+
+ if (length > byteLength - offset) {
+ throw new ERR_OUT_OF_RANGE("length", `<= ${byteLength - offset}`, length);
+ }
+
+ if (length < 0) {
+ throw new ERR_OUT_OF_RANGE("length", ">= 0", length);
+ }
+
+ validateInt32(length, "length", 0);
+ },
+);
+
+export const validatePath = hideStackFrames((path, propName = "path") => {
+ if (typeof path !== "string" && !isUint8Array(path)) {
+ throw new ERR_INVALID_ARG_TYPE(propName, ["string", "Buffer", "URL"], path);
+ }
+
+ const err = nullCheck(path, propName, false);
+
+ if (err !== undefined) {
+ throw err;
+ }
+});
+
+export const getValidatedPath = hideStackFrames(
+ (fileURLOrPath, propName = "path") => {
+ const path = toPathIfFileURL(fileURLOrPath);
+ validatePath(path, propName);
+ return path;
+ },
+);
+
+export const getValidatedFd = hideStackFrames((fd, propName = "fd") => {
+ if (Object.is(fd, -0)) {
+ return 0;
+ }
+
+ validateInt32(fd, propName, 0);
+
+ return fd;
+});
+
+export const validateBufferArray = hideStackFrames(
+ (buffers, propName = "buffers") => {
+ if (!Array.isArray(buffers)) {
+ throw new ERR_INVALID_ARG_TYPE(propName, "ArrayBufferView[]", buffers);
+ }
+
+ for (let i = 0; i < buffers.length; i++) {
+ if (!isArrayBufferView(buffers[i])) {
+ throw new ERR_INVALID_ARG_TYPE(propName, "ArrayBufferView[]", buffers);
+ }
+ }
+
+ return buffers;
+ },
+);
+
+let nonPortableTemplateWarn = true;
+
+export function warnOnNonPortableTemplate(template) {
+ // Template strings passed to the mkdtemp() family of functions should not
+ // end with 'X' because they are handled inconsistently across platforms.
+ if (nonPortableTemplateWarn && template.endsWith("X")) {
+ process.emitWarning(
+ "mkdtemp() templates ending with X are not portable. " +
+ "For details see: https://nodejs.org/api/fs.html",
+ );
+ nonPortableTemplateWarn = false;
+ }
+}
+
+const defaultCpOptions = {
+ dereference: false,
+ errorOnExist: false,
+ filter: undefined,
+ force: true,
+ preserveTimestamps: false,
+ recursive: false,
+};
+
+const defaultRmOptions = {
+ recursive: false,
+ force: false,
+ retryDelay: 100,
+ maxRetries: 0,
+};
+
+const defaultRmdirOptions = {
+ retryDelay: 100,
+ maxRetries: 0,
+ recursive: false,
+};
+
+export const validateCpOptions = hideStackFrames((options) => {
+ if (options === undefined) {
+ return { ...defaultCpOptions };
+ }
+ validateObject(options, "options");
+ options = { ...defaultCpOptions, ...options };
+ validateBoolean(options.dereference, "options.dereference");
+ validateBoolean(options.errorOnExist, "options.errorOnExist");
+ validateBoolean(options.force, "options.force");
+ validateBoolean(options.preserveTimestamps, "options.preserveTimestamps");
+ validateBoolean(options.recursive, "options.recursive");
+ if (options.filter !== undefined) {
+ validateFunction(options.filter, "options.filter");
+ }
+ return options;
+});
+
+export const validateRmOptions = hideStackFrames(
+ (path, options, expectDir, cb) => {
+ options = validateRmdirOptions(options, defaultRmOptions);
+ validateBoolean(options.force, "options.force");
+
+ stat(path, (err, stats) => {
+ if (err) {
+ if (options.force && err.code === "ENOENT") {
+ return cb(null, options);
+ }
+ return cb(err, options);
+ }
+
+ if (expectDir && !stats.isDirectory()) {
+ return cb(false);
+ }
+
+ if (stats.isDirectory() && !options.recursive) {
+ return cb(
+ new ERR_FS_EISDIR({
+ code: "EISDIR",
+ message: "is a directory",
+ path,
+ syscall: "rm",
+ errno: osConstants.errno.EISDIR,
+ }),
+ );
+ }
+ return cb(null, options);
+ });
+ },
+);
+
+export const validateRmOptionsSync = hideStackFrames(
+ (path, options, expectDir) => {
+ options = validateRmdirOptions(options, defaultRmOptions);
+ validateBoolean(options.force, "options.force");
+
+ if (!options.force || expectDir || !options.recursive) {
+ const isDirectory = statSync(path, { throwIfNoEntry: !options.force })
+ ?.isDirectory();
+
+ if (expectDir && !isDirectory) {
+ return false;
+ }
+
+ if (isDirectory && !options.recursive) {
+ throw new ERR_FS_EISDIR({
+ code: "EISDIR",
+ message: "is a directory",
+ path,
+ syscall: "rm",
+ errno: EISDIR,
+ });
+ }
+ }
+
+ return options;
+ },
+);
+
+let recursiveRmdirWarned = process.noDeprecation;
+export function emitRecursiveRmdirWarning() {
+ if (!recursiveRmdirWarned) {
+ process.emitWarning(
+ "In future versions of Node.js, fs.rmdir(path, { recursive: true }) " +
+ "will be removed. Use fs.rm(path, { recursive: true }) instead",
+ "DeprecationWarning",
+ "DEP0147",
+ );
+ recursiveRmdirWarned = true;
+ }
+}
+
+export const validateRmdirOptions = hideStackFrames(
+ (options, defaults = defaultRmdirOptions) => {
+ if (options === undefined) {
+ return defaults;
+ }
+ validateObject(options, "options");
+
+ options = { ...defaults, ...options };
+
+ validateBoolean(options.recursive, "options.recursive");
+ validateInt32(options.retryDelay, "options.retryDelay", 0);
+ validateUint32(options.maxRetries, "options.maxRetries");
+
+ return options;
+ },
+);
+
+export const getValidMode = hideStackFrames((mode, type) => {
+ let min = kMinimumAccessMode;
+ let max = kMaximumAccessMode;
+ let def = F_OK;
+ if (type === "copyFile") {
+ min = kMinimumCopyMode;
+ max = kMaximumCopyMode;
+ def = mode || kDefaultCopyMode;
+ } else {
+ assert(type === "access");
+ }
+ if (mode == null) {
+ return def;
+ }
+ if (Number.isInteger(mode) && mode >= min && mode <= max) {
+ return mode;
+ }
+ if (typeof mode !== "number") {
+ throw new ERR_INVALID_ARG_TYPE("mode", "integer", mode);
+ }
+ throw new ERR_OUT_OF_RANGE(
+ "mode",
+ `an integer >= ${min} && <= ${max}`,
+ mode,
+ );
+});
+
+export const validateStringAfterArrayBufferView = hideStackFrames(
+ (buffer, name) => {
+ if (typeof buffer === "string") {
+ return;
+ }
+
+ if (
+ typeof buffer === "object" &&
+ buffer !== null &&
+ typeof buffer.toString === "function" &&
+ Object.prototype.hasOwnProperty.call(buffer, "toString")
+ ) {
+ return;
+ }
+
+ throw new ERR_INVALID_ARG_TYPE(
+ name,
+ ["string", "Buffer", "TypedArray", "DataView"],
+ buffer,
+ );
+ },
+);
+
+export const validatePosition = hideStackFrames((position) => {
+ if (typeof position === "number") {
+ validateInteger(position, "position");
+ } else if (typeof position === "bigint") {
+ if (!(position >= -(2n ** 63n) && position <= 2n ** 63n - 1n)) {
+ throw new ERR_OUT_OF_RANGE(
+ "position",
+ `>= ${-(2n ** 63n)} && <= ${2n ** 63n - 1n}`,
+ position,
+ );
+ }
+ } else {
+ throw new ERR_INVALID_ARG_TYPE("position", ["integer", "bigint"], position);
+ }
+});
+
+export const realpathCacheKey = Symbol("realpathCacheKey");
+export const constants = {
+ kIoMaxLength,
+ kMaxUserId,
+ kReadFileBufferLength,
+ kReadFileUnknownBufferLength,
+ kWriteFileMaxChunkSize,
+};
+
+export const showStringCoercionDeprecation = deprecate(
+ () => {},
+ "Implicit coercion of objects with own toString property is deprecated.",
+ "DEP0162",
+);
+
+export default {
+ constants,
+ assertEncoding,
+ BigIntStats, // for testing
+ copyObject,
+ Dirent,
+ emitRecursiveRmdirWarning,
+ getDirent,
+ getDirents,
+ getOptions,
+ getValidatedFd,
+ getValidatedPath,
+ getValidMode,
+ handleErrorFromBinding,
+ kMaxUserId,
+ nullCheck,
+ preprocessSymlinkDestination,
+ realpathCacheKey,
+ getStatsFromBinding,
+ showStringCoercionDeprecation,
+ stringToFlags,
+ stringToSymlinkType,
+ Stats,
+ toUnixTimestamp,
+ validateBufferArray,
+ validateCpOptions,
+ validateOffsetLengthRead,
+ validateOffsetLengthWrite,
+ validatePath,
+ validatePosition,
+ validateRmOptions,
+ validateRmOptionsSync,
+ validateRmdirOptions,
+ validateStringAfterArrayBufferView,
+ warnOnNonPortableTemplate,
+};