Skip to content

Commit 8a92018

Browse files
committed
fs: make open and close stream override optional when unused
When using `createReadStream` or `createWriteStream` with a specific file descriptor or `FileHandle` instead of a path, `open` method is not used, there is no point in forcing users to provide it. When using `createReadStream` or `createWriteStream` with `autoClose` set to false, `close` method is not used, there is no point in forcing users to provide it. PR-URL: #40013 Reviewed-By: James M Snell <[email protected]>
1 parent 3decfa7 commit 8a92018

File tree

2 files changed

+77
-59
lines changed

2 files changed

+77
-59
lines changed

doc/api/fs.md

+19-3
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,12 @@ behavior is similar to `cp dir1/ dir2/`.
19351935
<!-- YAML
19361936
added: v0.1.31
19371937
changes:
1938+
- version: REPLACEME
1939+
pr-url: https://github.com/nodejs/node/pull/40013
1940+
description: The `fs` option does not need `open` method if an `fd` was provided.
1941+
- version: REPLACEME
1942+
pr-url: https://github.com/nodejs/node/pull/40013
1943+
description: The `fs` option does not need `close` method if `autoClose` is `false`.
19381944
- version:
19391945
- v15.4.0
19401946
pr-url: https://github.com/nodejs/node/pull/35922
@@ -2010,7 +2016,9 @@ destroyed, like most `Readable` streams. Set the `emitClose` option to
20102016
20112017
By providing the `fs` option, it is possible to override the corresponding `fs`
20122018
implementations for `open`, `read`, and `close`. When providing the `fs` option,
2013-
overrides for `open`, `read`, and `close` are required.
2019+
an override for `read` is required. If no `fd` is provided, an override for
2020+
`open` is also required. If `autoClose` is `true`, an override for `close` is
2021+
also required.
20142022
20152023
```mjs
20162024
import { createReadStream } from 'fs';
@@ -2052,6 +2060,12 @@ If `options` is a string, then it specifies the encoding.
20522060
<!-- YAML
20532061
added: v0.1.31
20542062
changes:
2063+
- version: REPLACEME
2064+
pr-url: https://github.com/nodejs/node/pull/40013
2065+
description: The `fs` option does not need `open` method if an `fd` was provided.
2066+
- version: REPLACEME
2067+
pr-url: https://github.com/nodejs/node/pull/40013
2068+
description: The `fs` option does not need `close` method if `autoClose` is `false`.
20552069
- version:
20562070
- v15.4.0
20572071
pr-url: https://github.com/nodejs/node/pull/35922
@@ -2115,8 +2129,10 @@ destroyed, like most `Writable` streams. Set the `emitClose` option to
21152129
By providing the `fs` option it is possible to override the corresponding `fs`
21162130
implementations for `open`, `write`, `writev` and `close`. Overriding `write()`
21172131
without `writev()` can reduce performance as some optimizations (`_writev()`)
2118-
will be disabled. When providing the `fs` option, overrides for `open`,
2119-
`close`, and at least one of `write` and `writev` are required.
2132+
will be disabled. When providing the `fs` option, overrides for at least one of
2133+
`write` and `writev` are required. If no `fd` option is supplied, an override
2134+
for `open` is also required. If `autoClose` is `true`, an override for `close`
2135+
is also required.
21202136
21212137
Like {fs.ReadStream}, if `fd` is specified, {fs.WriteStream} will ignore the
21222138
`path` argument and will use the specified file descriptor. This means that no

lib/internal/fs/streams.js

+58-56
Original file line numberDiff line numberDiff line change
@@ -120,29 +120,29 @@ function close(stream, err, cb) {
120120
}
121121

122122
function importFd(stream, options) {
123-
stream.fd = null;
124-
if (options.fd != null) {
125-
if (typeof options.fd === 'number') {
126-
// When fd is a raw descriptor, we must keep our fingers crossed
127-
// that the descriptor won't get closed, or worse, replaced with
128-
// another one
129-
// https://github.com/nodejs/node/issues/35862
130-
stream.fd = options.fd;
131-
} else if (typeof options.fd === 'object' &&
132-
options.fd instanceof FileHandle) {
133-
// When fd is a FileHandle we can listen for 'close' events
134-
if (options.fs)
135-
// FileHandle is not supported with custom fs operations
136-
throw new ERR_METHOD_NOT_IMPLEMENTED('FileHandle with fs');
137-
stream[kHandle] = options.fd;
138-
stream.fd = options.fd.fd;
139-
stream[kFs] = FileHandleOperations(stream[kHandle]);
140-
stream[kHandle][kRef]();
141-
options.fd.on('close', FunctionPrototypeBind(stream.close, stream));
142-
} else
143-
throw ERR_INVALID_ARG_TYPE('options.fd',
144-
['number', 'FileHandle'], options.fd);
123+
if (typeof options.fd === 'number') {
124+
// When fd is a raw descriptor, we must keep our fingers crossed
125+
// that the descriptor won't get closed, or worse, replaced with
126+
// another one
127+
// https://github.com/nodejs/node/issues/35862
128+
stream[kFs] = options.fs || fs;
129+
return options.fd;
130+
} else if (typeof options.fd === 'object' &&
131+
options.fd instanceof FileHandle) {
132+
// When fd is a FileHandle we can listen for 'close' events
133+
if (options.fs) {
134+
// FileHandle is not supported with custom fs operations
135+
throw new ERR_METHOD_NOT_IMPLEMENTED('FileHandle with fs');
136+
}
137+
stream[kHandle] = options.fd;
138+
stream[kFs] = FileHandleOperations(stream[kHandle]);
139+
stream[kHandle][kRef]();
140+
options.fd.on('close', FunctionPrototypeBind(stream.close, stream));
141+
return options.fd.fd;
145142
}
143+
144+
throw ERR_INVALID_ARG_TYPE('options.fd',
145+
['number', 'FileHandle'], options.fd);
146146
}
147147

148148
function ReadStream(path, options) {
@@ -158,21 +158,29 @@ function ReadStream(path, options) {
158158
options.autoDestroy = false;
159159
}
160160

161-
this[kFs] = options.fs || fs;
161+
if (options.fd == null) {
162+
this.fd = null;
163+
this[kFs] = options.fs || fs;
164+
validateFunction(this[kFs].open, 'options.fs.open');
162165

163-
validateFunction(this[kFs].open, 'options.fs.open');
164-
validateFunction(this[kFs].read, 'options.fs.read');
165-
validateFunction(this[kFs].close, 'options.fs.close');
166+
// Path will be ignored when fd is specified, so it can be falsy
167+
this.path = toPathIfFileURL(path);
168+
this.flags = options.flags === undefined ? 'r' : options.flags;
169+
this.mode = options.mode === undefined ? 0o666 : options.mode;
170+
171+
validatePath(this.path);
172+
} else {
173+
this.fd = getValidatedFd(importFd(this, options));
174+
}
166175

167176
options.autoDestroy = options.autoClose === undefined ?
168177
true : options.autoClose;
169178

170-
// Path will be ignored when fd is specified, so it can be falsy
171-
this.path = toPathIfFileURL(path);
172-
this.flags = options.flags === undefined ? 'r' : options.flags;
173-
this.mode = options.mode === undefined ? 0o666 : options.mode;
179+
validateFunction(this[kFs].read, 'options.fs.read');
174180

175-
importFd(this, options);
181+
if (options.autoDestroy) {
182+
validateFunction(this[kFs].close, 'options.fs.close');
183+
}
176184

177185
this.start = options.start;
178186
this.end = options.end;
@@ -187,12 +195,6 @@ function ReadStream(path, options) {
187195
this.pos = this.start;
188196
}
189197

190-
// If fd has been set, validate, otherwise validate path.
191-
if (this.fd != null) {
192-
this.fd = getValidatedFd(this.fd);
193-
} else {
194-
validatePath(this.path);
195-
}
196198

197199
if (this.end === undefined) {
198200
this.end = Infinity;
@@ -310,9 +312,23 @@ function WriteStream(path, options) {
310312
// Only buffers are supported.
311313
options.decodeStrings = true;
312314

313-
this[kFs] = options.fs || fs;
315+
if (options.fd == null) {
316+
this.fd = null;
317+
this[kFs] = options.fs || fs;
318+
validateFunction(this[kFs].open, 'options.fs.open');
319+
320+
// Path will be ignored when fd is specified, so it can be falsy
321+
this.path = toPathIfFileURL(path);
322+
this.flags = options.flags === undefined ? 'w' : options.flags;
323+
this.mode = options.mode === undefined ? 0o666 : options.mode;
324+
325+
validatePath(this.path);
326+
} else {
327+
this.fd = getValidatedFd(importFd(this, options));
328+
}
314329

315-
validateFunction(this[kFs].open, 'options.fs.open');
330+
options.autoDestroy = options.autoClose === undefined ?
331+
true : options.autoClose;
316332

317333
if (!this[kFs].write && !this[kFs].writev) {
318334
throw new ERR_INVALID_ARG_TYPE('options.fs.write', 'function',
@@ -327,7 +343,9 @@ function WriteStream(path, options) {
327343
validateFunction(this[kFs].writev, 'options.fs.writev');
328344
}
329345

330-
validateFunction(this[kFs].close, 'options.fs.close');
346+
if (options.autoDestroy) {
347+
validateFunction(this[kFs].close, 'options.fs.close');
348+
}
331349

332350
// It's enough to override either, in which case only one will be used.
333351
if (!this[kFs].write) {
@@ -337,28 +355,12 @@ function WriteStream(path, options) {
337355
this._writev = null;
338356
}
339357

340-
options.autoDestroy = options.autoClose === undefined ?
341-
true : options.autoClose;
342-
343-
// Path will be ignored when fd is specified, so it can be falsy
344-
this.path = toPathIfFileURL(path);
345-
this.flags = options.flags === undefined ? 'w' : options.flags;
346-
this.mode = options.mode === undefined ? 0o666 : options.mode;
347-
348-
importFd(this, options);
349-
350358
this.start = options.start;
351359
this.pos = undefined;
352360
this.bytesWritten = 0;
353361
this.closed = false;
354362
this[kIsPerformingIO] = false;
355363

356-
// If fd has been set, validate, otherwise validate path.
357-
if (this.fd != null) {
358-
this.fd = getValidatedFd(this.fd);
359-
} else {
360-
validatePath(this.path);
361-
}
362364

363365
if (this.start !== undefined) {
364366
validateInteger(this.start, 'start', 0);

0 commit comments

Comments
 (0)