@@ -11,6 +11,7 @@ const {
11
11
} = primordials ;
12
12
13
13
const {
14
+ ERR_INVALID_ARG_TYPE ,
14
15
ERR_OUT_OF_RANGE ,
15
16
ERR_STREAM_DESTROYED
16
17
} = require ( 'internal/errors' ) . codes ;
@@ -27,6 +28,7 @@ const kIoDone = Symbol('kIoDone');
27
28
const kIsPerformingIO = Symbol ( 'kIsPerformingIO' ) ;
28
29
29
30
const kMinPoolSpace = 128 ;
31
+ const kFs = Symbol ( 'kFs' ) ;
30
32
31
33
let pool ;
32
34
// It can happen that we expect to read a large chunk of data, and reserve
@@ -75,6 +77,23 @@ function ReadStream(path, options) {
75
77
options . emitClose = false ;
76
78
}
77
79
80
+ this [ kFs ] = options . fs || fs ;
81
+
82
+ if ( typeof this [ kFs ] . open !== 'function' ) {
83
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.open' , 'function' ,
84
+ this [ kFs ] . open ) ;
85
+ }
86
+
87
+ if ( typeof this [ kFs ] . read !== 'function' ) {
88
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.read' , 'function' ,
89
+ this [ kFs ] . read ) ;
90
+ }
91
+
92
+ if ( typeof this [ kFs ] . close !== 'function' ) {
93
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.close' , 'function' ,
94
+ this [ kFs ] . close ) ;
95
+ }
96
+
78
97
Readable . call ( this , options ) ;
79
98
80
99
// Path will be ignored when fd is specified, so it can be falsy
@@ -124,7 +143,7 @@ ObjectSetPrototypeOf(ReadStream.prototype, Readable.prototype);
124
143
ObjectSetPrototypeOf ( ReadStream , Readable ) ;
125
144
126
145
ReadStream . prototype . open = function ( ) {
127
- fs . open ( this . path , this . flags , this . mode , ( er , fd ) => {
146
+ this [ kFs ] . open ( this . path , this . flags , this . mode , ( er , fd ) => {
128
147
if ( er ) {
129
148
if ( this . autoClose ) {
130
149
this . destroy ( ) ;
@@ -174,42 +193,43 @@ ReadStream.prototype._read = function(n) {
174
193
175
194
// the actual read.
176
195
this [ kIsPerformingIO ] = true ;
177
- fs . read ( this . fd , pool , pool . used , toRead , this . pos , ( er , bytesRead ) => {
178
- this [ kIsPerformingIO ] = false ;
179
- // Tell ._destroy() that it's safe to close the fd now.
180
- if ( this . destroyed ) return this . emit ( kIoDone , er ) ;
181
-
182
- if ( er ) {
183
- if ( this . autoClose ) {
184
- this . destroy ( ) ;
185
- }
186
- this . emit ( 'error' , er ) ;
187
- } else {
188
- let b = null ;
189
- // Now that we know how much data we have actually read, re-wind the
190
- // 'used' field if we can, and otherwise allow the remainder of our
191
- // reservation to be used as a new pool later.
192
- if ( start + toRead === thisPool . used && thisPool === pool ) {
193
- const newUsed = thisPool . used + bytesRead - toRead ;
194
- thisPool . used = roundUpToMultipleOf8 ( newUsed ) ;
196
+ this [ kFs ] . read (
197
+ this . fd , pool , pool . used , toRead , this . pos , ( er , bytesRead ) => {
198
+ this [ kIsPerformingIO ] = false ;
199
+ // Tell ._destroy() that it's safe to close the fd now.
200
+ if ( this . destroyed ) return this . emit ( kIoDone , er ) ;
201
+
202
+ if ( er ) {
203
+ if ( this . autoClose ) {
204
+ this . destroy ( ) ;
205
+ }
206
+ this . emit ( 'error' , er ) ;
195
207
} else {
196
- // Round down to the next lowest multiple of 8 to ensure the new pool
197
- // fragment start and end positions are aligned to an 8 byte boundary.
198
- const alignedEnd = ( start + toRead ) & ~ 7 ;
199
- const alignedStart = roundUpToMultipleOf8 ( start + bytesRead ) ;
200
- if ( alignedEnd - alignedStart >= kMinPoolSpace ) {
201
- poolFragments . push ( thisPool . slice ( alignedStart , alignedEnd ) ) ;
208
+ let b = null ;
209
+ // Now that we know how much data we have actually read, re-wind the
210
+ // 'used' field if we can, and otherwise allow the remainder of our
211
+ // reservation to be used as a new pool later.
212
+ if ( start + toRead === thisPool . used && thisPool === pool ) {
213
+ const newUsed = thisPool . used + bytesRead - toRead ;
214
+ thisPool . used = roundUpToMultipleOf8 ( newUsed ) ;
215
+ } else {
216
+ // Round down to the next lowest multiple of 8 to ensure the new pool
217
+ // fragment start and end positions are aligned to an 8 byte boundary.
218
+ const alignedEnd = ( start + toRead ) & ~ 7 ;
219
+ const alignedStart = roundUpToMultipleOf8 ( start + bytesRead ) ;
220
+ if ( alignedEnd - alignedStart >= kMinPoolSpace ) {
221
+ poolFragments . push ( thisPool . slice ( alignedStart , alignedEnd ) ) ;
222
+ }
202
223
}
203
- }
204
224
205
- if ( bytesRead > 0 ) {
206
- this . bytesRead += bytesRead ;
207
- b = thisPool . slice ( start , start + bytesRead ) ;
208
- }
225
+ if ( bytesRead > 0 ) {
226
+ this . bytesRead += bytesRead ;
227
+ b = thisPool . slice ( start , start + bytesRead ) ;
228
+ }
209
229
210
- this . push ( b ) ;
211
- }
212
- } ) ;
230
+ this . push ( b ) ;
231
+ }
232
+ } ) ;
213
233
214
234
// Move the pool positions, and internal position for reading.
215
235
if ( this . pos !== undefined )
@@ -233,7 +253,7 @@ ReadStream.prototype._destroy = function(err, cb) {
233
253
} ;
234
254
235
255
function closeFsStream ( stream , cb , err ) {
236
- fs . close ( stream . fd , ( er ) => {
256
+ stream [ kFs ] . close ( stream . fd , ( er ) => {
237
257
er = er || err ;
238
258
cb ( er ) ;
239
259
stream . closed = true ;
@@ -268,6 +288,40 @@ function WriteStream(path, options) {
268
288
options . emitClose = false ;
269
289
}
270
290
291
+ this [ kFs ] = options . fs || fs ;
292
+ if ( typeof this [ kFs ] . open !== 'function' ) {
293
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.open' , 'function' ,
294
+ this [ kFs ] . open ) ;
295
+ }
296
+
297
+ if ( ! this [ kFs ] . write && ! this [ kFs ] . writev ) {
298
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.write' , 'function' ,
299
+ this [ kFs ] . write ) ;
300
+ }
301
+
302
+ if ( this [ kFs ] . write && typeof this [ kFs ] . write !== 'function' ) {
303
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.write' , 'function' ,
304
+ this [ kFs ] . write ) ;
305
+ }
306
+
307
+ if ( this [ kFs ] . writev && typeof this [ kFs ] . writev !== 'function' ) {
308
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.writev' , 'function' ,
309
+ this [ kFs ] . writev ) ;
310
+ }
311
+
312
+ if ( typeof this [ kFs ] . close !== 'function' ) {
313
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.close' , 'function' ,
314
+ this [ kFs ] . close ) ;
315
+ }
316
+
317
+ // It's enough to override either, in which case only one will be used.
318
+ if ( ! this [ kFs ] . write ) {
319
+ this . _write = null ;
320
+ }
321
+ if ( ! this [ kFs ] . writev ) {
322
+ this . _writev = null ;
323
+ }
324
+
271
325
Writable . call ( this , options ) ;
272
326
273
327
// Path will be ignored when fd is specified, so it can be falsy
@@ -313,7 +367,7 @@ WriteStream.prototype._final = function(callback) {
313
367
} ;
314
368
315
369
WriteStream . prototype . open = function ( ) {
316
- fs . open ( this . path , this . flags , this . mode , ( er , fd ) => {
370
+ this [ kFs ] . open ( this . path , this . flags , this . mode , ( er , fd ) => {
317
371
if ( er ) {
318
372
if ( this . autoClose ) {
319
373
this . destroy ( ) ;
@@ -339,7 +393,7 @@ WriteStream.prototype._write = function(data, encoding, cb) {
339
393
if ( this . destroyed ) return cb ( new ERR_STREAM_DESTROYED ( 'write' ) ) ;
340
394
341
395
this [ kIsPerformingIO ] = true ;
342
- fs . write ( this . fd , data , 0 , data . length , this . pos , ( er , bytes ) => {
396
+ this [ kFs ] . write ( this . fd , data , 0 , data . length , this . pos , ( er , bytes ) => {
343
397
this [ kIsPerformingIO ] = false ;
344
398
// Tell ._destroy() that it's safe to close the fd now.
345
399
if ( this . destroyed ) {
@@ -383,7 +437,7 @@ WriteStream.prototype._writev = function(data, cb) {
383
437
}
384
438
385
439
this [ kIsPerformingIO ] = true ;
386
- fs . writev ( this . fd , chunks , this . pos , ( er , bytes ) => {
440
+ this [ kFs ] . writev ( this . fd , chunks , this . pos , ( er , bytes ) => {
387
441
this [ kIsPerformingIO ] = false ;
388
442
// Tell ._destroy() that it's safe to close the fd now.
389
443
if ( this . destroyed ) {
0 commit comments