@@ -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 ;
@@ -28,6 +29,7 @@ const kIoDone = Symbol('kIoDone');
28
29
const kIsPerformingIO = Symbol ( 'kIsPerformingIO' ) ;
29
30
30
31
const kMinPoolSpace = 128 ;
32
+ const kFs = Symbol ( 'kFs' ) ;
31
33
32
34
let pool ;
33
35
// It can happen that we expect to read a large chunk of data, and reserve
@@ -76,6 +78,23 @@ function ReadStream(path, options) {
76
78
options . emitClose = false ;
77
79
}
78
80
81
+ this [ kFs ] = options . fs || fs ;
82
+
83
+ if ( typeof this [ kFs ] . open !== 'function' ) {
84
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.open' , 'function' ,
85
+ this [ kFs ] . open ) ;
86
+ }
87
+
88
+ if ( typeof this [ kFs ] . read !== 'function' ) {
89
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.read' , 'function' ,
90
+ this [ kFs ] . read ) ;
91
+ }
92
+
93
+ if ( typeof this [ kFs ] . close !== 'function' ) {
94
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.close' , 'function' ,
95
+ this [ kFs ] . close ) ;
96
+ }
97
+
79
98
Readable . call ( this , options ) ;
80
99
81
100
// Path will be ignored when fd is specified, so it can be falsy
@@ -136,7 +155,7 @@ function _openReadFs(stream) {
136
155
return ;
137
156
}
138
157
139
- fs . open ( stream . path , stream . flags , stream . mode , ( er , fd ) => {
158
+ stream [ kFs ] . open ( stream . path , stream . flags , stream . mode , ( er , fd ) => {
140
159
if ( er ) {
141
160
if ( stream . autoClose ) {
142
161
stream . destroy ( ) ;
@@ -186,42 +205,43 @@ ReadStream.prototype._read = function(n) {
186
205
187
206
// the actual read.
188
207
this [ kIsPerformingIO ] = true ;
189
- fs . read ( this . fd , pool , pool . used , toRead , this . pos , ( er , bytesRead ) => {
190
- this [ kIsPerformingIO ] = false ;
191
- // Tell ._destroy() that it's safe to close the fd now.
192
- if ( this . destroyed ) return this . emit ( kIoDone , er ) ;
193
-
194
- if ( er ) {
195
- if ( this . autoClose ) {
196
- this . destroy ( ) ;
197
- }
198
- this . emit ( 'error' , er ) ;
199
- } else {
200
- let b = null ;
201
- // Now that we know how much data we have actually read, re-wind the
202
- // 'used' field if we can, and otherwise allow the remainder of our
203
- // reservation to be used as a new pool later.
204
- if ( start + toRead === thisPool . used && thisPool === pool ) {
205
- const newUsed = thisPool . used + bytesRead - toRead ;
206
- thisPool . used = roundUpToMultipleOf8 ( newUsed ) ;
208
+ this [ kFs ] . read (
209
+ this . fd , pool , pool . used , toRead , this . pos , ( er , bytesRead ) => {
210
+ this [ kIsPerformingIO ] = false ;
211
+ // Tell ._destroy() that it's safe to close the fd now.
212
+ if ( this . destroyed ) return this . emit ( kIoDone , er ) ;
213
+
214
+ if ( er ) {
215
+ if ( this . autoClose ) {
216
+ this . destroy ( ) ;
217
+ }
218
+ this . emit ( 'error' , er ) ;
207
219
} else {
208
- // Round down to the next lowest multiple of 8 to ensure the new pool
209
- // fragment start and end positions are aligned to an 8 byte boundary.
210
- const alignedEnd = ( start + toRead ) & ~ 7 ;
211
- const alignedStart = roundUpToMultipleOf8 ( start + bytesRead ) ;
212
- if ( alignedEnd - alignedStart >= kMinPoolSpace ) {
213
- poolFragments . push ( thisPool . slice ( alignedStart , alignedEnd ) ) ;
220
+ let b = null ;
221
+ // Now that we know how much data we have actually read, re-wind the
222
+ // 'used' field if we can, and otherwise allow the remainder of our
223
+ // reservation to be used as a new pool later.
224
+ if ( start + toRead === thisPool . used && thisPool === pool ) {
225
+ const newUsed = thisPool . used + bytesRead - toRead ;
226
+ thisPool . used = roundUpToMultipleOf8 ( newUsed ) ;
227
+ } else {
228
+ // Round down to the next lowest multiple of 8 to ensure the new pool
229
+ // fragment start and end positions are aligned to an 8 byte boundary.
230
+ const alignedEnd = ( start + toRead ) & ~ 7 ;
231
+ const alignedStart = roundUpToMultipleOf8 ( start + bytesRead ) ;
232
+ if ( alignedEnd - alignedStart >= kMinPoolSpace ) {
233
+ poolFragments . push ( thisPool . slice ( alignedStart , alignedEnd ) ) ;
234
+ }
214
235
}
215
- }
216
236
217
- if ( bytesRead > 0 ) {
218
- this . bytesRead += bytesRead ;
219
- b = thisPool . slice ( start , start + bytesRead ) ;
220
- }
237
+ if ( bytesRead > 0 ) {
238
+ this . bytesRead += bytesRead ;
239
+ b = thisPool . slice ( start , start + bytesRead ) ;
240
+ }
221
241
222
- this . push ( b ) ;
223
- }
224
- } ) ;
242
+ this . push ( b ) ;
243
+ }
244
+ } ) ;
225
245
226
246
// Move the pool positions, and internal position for reading.
227
247
if ( this . pos !== undefined )
@@ -245,7 +265,7 @@ ReadStream.prototype._destroy = function(err, cb) {
245
265
} ;
246
266
247
267
function closeFsStream ( stream , cb , err ) {
248
- fs . close ( stream . fd , ( er ) => {
268
+ stream [ kFs ] . close ( stream . fd , ( er ) => {
249
269
er = er || err ;
250
270
cb ( er ) ;
251
271
stream . closed = true ;
@@ -279,6 +299,40 @@ function WriteStream(path, options) {
279
299
options . emitClose = false ;
280
300
}
281
301
302
+ this [ kFs ] = options . fs || fs ;
303
+ if ( typeof this [ kFs ] . open !== 'function' ) {
304
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.open' , 'function' ,
305
+ this [ kFs ] . open ) ;
306
+ }
307
+
308
+ if ( ! this [ kFs ] . write && ! this [ kFs ] . writev ) {
309
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.write' , 'function' ,
310
+ this [ kFs ] . write ) ;
311
+ }
312
+
313
+ if ( this [ kFs ] . write && typeof this [ kFs ] . write !== 'function' ) {
314
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.write' , 'function' ,
315
+ this [ kFs ] . write ) ;
316
+ }
317
+
318
+ if ( this [ kFs ] . writev && typeof this [ kFs ] . writev !== 'function' ) {
319
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.writev' , 'function' ,
320
+ this [ kFs ] . writev ) ;
321
+ }
322
+
323
+ if ( typeof this [ kFs ] . close !== 'function' ) {
324
+ throw new ERR_INVALID_ARG_TYPE ( 'options.fs.close' , 'function' ,
325
+ this [ kFs ] . close ) ;
326
+ }
327
+
328
+ // It's enough to override either, in which case only one will be used.
329
+ if ( ! this [ kFs ] . write ) {
330
+ this . _write = null ;
331
+ }
332
+ if ( ! this [ kFs ] . writev ) {
333
+ this . _writev = null ;
334
+ }
335
+
282
336
Writable . call ( this , options ) ;
283
337
284
338
// Path will be ignored when fd is specified, so it can be falsy
@@ -335,7 +389,7 @@ function _openWriteFs(stream) {
335
389
return ;
336
390
}
337
391
338
- fs . open ( stream . path , stream . flags , stream . mode , ( er , fd ) => {
392
+ stream [ kFs ] . open ( stream . path , stream . flags , stream . mode , ( er , fd ) => {
339
393
if ( er ) {
340
394
if ( stream . autoClose ) {
341
395
stream . destroy ( ) ;
@@ -361,7 +415,7 @@ WriteStream.prototype._write = function(data, encoding, cb) {
361
415
if ( this . destroyed ) return cb ( new ERR_STREAM_DESTROYED ( 'write' ) ) ;
362
416
363
417
this [ kIsPerformingIO ] = true ;
364
- fs . write ( this . fd , data , 0 , data . length , this . pos , ( er , bytes ) => {
418
+ this [ kFs ] . write ( this . fd , data , 0 , data . length , this . pos , ( er , bytes ) => {
365
419
this [ kIsPerformingIO ] = false ;
366
420
// Tell ._destroy() that it's safe to close the fd now.
367
421
if ( this . destroyed ) {
@@ -405,7 +459,7 @@ WriteStream.prototype._writev = function(data, cb) {
405
459
}
406
460
407
461
this [ kIsPerformingIO ] = true ;
408
- fs . writev ( this . fd , chunks , this . pos , ( er , bytes ) => {
462
+ this [ kFs ] . writev ( this . fd , chunks , this . pos , ( er , bytes ) => {
409
463
this [ kIsPerformingIO ] = false ;
410
464
// Tell ._destroy() that it's safe to close the fd now.
411
465
if ( this . destroyed ) {
0 commit comments