• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1var fs = require('fs')
2var polyfills = require('./polyfills.js')
3var legacy = require('./legacy-streams.js')
4var clone = require('./clone.js')
5
6var util = require('util')
7
8/* istanbul ignore next - node 0.x polyfill */
9var gracefulQueue
10var previousSymbol
11
12/* istanbul ignore else - node 0.x polyfill */
13if (typeof Symbol === 'function' && typeof Symbol.for === 'function') {
14  gracefulQueue = Symbol.for('graceful-fs.queue')
15  // This is used in testing by future versions
16  previousSymbol = Symbol.for('graceful-fs.previous')
17} else {
18  gracefulQueue = '___graceful-fs.queue'
19  previousSymbol = '___graceful-fs.previous'
20}
21
22function noop () {}
23
24function publishQueue(context, queue) {
25  Object.defineProperty(context, gracefulQueue, {
26    get: function() {
27      return queue
28    }
29  })
30}
31
32var debug = noop
33if (util.debuglog)
34  debug = util.debuglog('gfs4')
35else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || ''))
36  debug = function() {
37    var m = util.format.apply(util, arguments)
38    m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ')
39    console.error(m)
40  }
41
42// Once time initialization
43if (!fs[gracefulQueue]) {
44  // This queue can be shared by multiple loaded instances
45  var queue = global[gracefulQueue] || []
46  publishQueue(fs, queue)
47
48  // Patch fs.close/closeSync to shared queue version, because we need
49  // to retry() whenever a close happens *anywhere* in the program.
50  // This is essential when multiple graceful-fs instances are
51  // in play at the same time.
52  fs.close = (function (fs$close) {
53    function close (fd, cb) {
54      return fs$close.call(fs, fd, function (err) {
55        // This function uses the graceful-fs shared queue
56        if (!err) {
57          retry()
58        }
59
60        if (typeof cb === 'function')
61          cb.apply(this, arguments)
62      })
63    }
64
65    Object.defineProperty(close, previousSymbol, {
66      value: fs$close
67    })
68    return close
69  })(fs.close)
70
71  fs.closeSync = (function (fs$closeSync) {
72    function closeSync (fd) {
73      // This function uses the graceful-fs shared queue
74      fs$closeSync.apply(fs, arguments)
75      retry()
76    }
77
78    Object.defineProperty(closeSync, previousSymbol, {
79      value: fs$closeSync
80    })
81    return closeSync
82  })(fs.closeSync)
83
84  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {
85    process.on('exit', function() {
86      debug(fs[gracefulQueue])
87      require('assert').equal(fs[gracefulQueue].length, 0)
88    })
89  }
90}
91
92if (!global[gracefulQueue]) {
93  publishQueue(global, fs[gracefulQueue]);
94}
95
96module.exports = patch(clone(fs))
97if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) {
98    module.exports = patch(fs)
99    fs.__patched = true;
100}
101
102function patch (fs) {
103  // Everything that references the open() function needs to be in here
104  polyfills(fs)
105  fs.gracefulify = patch
106
107  fs.createReadStream = createReadStream
108  fs.createWriteStream = createWriteStream
109  var fs$readFile = fs.readFile
110  fs.readFile = readFile
111  function readFile (path, options, cb) {
112    if (typeof options === 'function')
113      cb = options, options = null
114
115    return go$readFile(path, options, cb)
116
117    function go$readFile (path, options, cb) {
118      return fs$readFile(path, options, function (err) {
119        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
120          enqueue([go$readFile, [path, options, cb]])
121        else {
122          if (typeof cb === 'function')
123            cb.apply(this, arguments)
124          retry()
125        }
126      })
127    }
128  }
129
130  var fs$writeFile = fs.writeFile
131  fs.writeFile = writeFile
132  function writeFile (path, data, options, cb) {
133    if (typeof options === 'function')
134      cb = options, options = null
135
136    return go$writeFile(path, data, options, cb)
137
138    function go$writeFile (path, data, options, cb) {
139      return fs$writeFile(path, data, options, function (err) {
140        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
141          enqueue([go$writeFile, [path, data, options, cb]])
142        else {
143          if (typeof cb === 'function')
144            cb.apply(this, arguments)
145          retry()
146        }
147      })
148    }
149  }
150
151  var fs$appendFile = fs.appendFile
152  if (fs$appendFile)
153    fs.appendFile = appendFile
154  function appendFile (path, data, options, cb) {
155    if (typeof options === 'function')
156      cb = options, options = null
157
158    return go$appendFile(path, data, options, cb)
159
160    function go$appendFile (path, data, options, cb) {
161      return fs$appendFile(path, data, options, function (err) {
162        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
163          enqueue([go$appendFile, [path, data, options, cb]])
164        else {
165          if (typeof cb === 'function')
166            cb.apply(this, arguments)
167          retry()
168        }
169      })
170    }
171  }
172
173  var fs$readdir = fs.readdir
174  fs.readdir = readdir
175  function readdir (path, options, cb) {
176    var args = [path]
177    if (typeof options !== 'function') {
178      args.push(options)
179    } else {
180      cb = options
181    }
182    args.push(go$readdir$cb)
183
184    return go$readdir(args)
185
186    function go$readdir$cb (err, files) {
187      if (files && files.sort)
188        files.sort()
189
190      if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
191        enqueue([go$readdir, [args]])
192
193      else {
194        if (typeof cb === 'function')
195          cb.apply(this, arguments)
196        retry()
197      }
198    }
199  }
200
201  function go$readdir (args) {
202    return fs$readdir.apply(fs, args)
203  }
204
205  if (process.version.substr(0, 4) === 'v0.8') {
206    var legStreams = legacy(fs)
207    ReadStream = legStreams.ReadStream
208    WriteStream = legStreams.WriteStream
209  }
210
211  var fs$ReadStream = fs.ReadStream
212  if (fs$ReadStream) {
213    ReadStream.prototype = Object.create(fs$ReadStream.prototype)
214    ReadStream.prototype.open = ReadStream$open
215  }
216
217  var fs$WriteStream = fs.WriteStream
218  if (fs$WriteStream) {
219    WriteStream.prototype = Object.create(fs$WriteStream.prototype)
220    WriteStream.prototype.open = WriteStream$open
221  }
222
223  Object.defineProperty(fs, 'ReadStream', {
224    get: function () {
225      return ReadStream
226    },
227    set: function (val) {
228      ReadStream = val
229    },
230    enumerable: true,
231    configurable: true
232  })
233  Object.defineProperty(fs, 'WriteStream', {
234    get: function () {
235      return WriteStream
236    },
237    set: function (val) {
238      WriteStream = val
239    },
240    enumerable: true,
241    configurable: true
242  })
243
244  // legacy names
245  var FileReadStream = ReadStream
246  Object.defineProperty(fs, 'FileReadStream', {
247    get: function () {
248      return FileReadStream
249    },
250    set: function (val) {
251      FileReadStream = val
252    },
253    enumerable: true,
254    configurable: true
255  })
256  var FileWriteStream = WriteStream
257  Object.defineProperty(fs, 'FileWriteStream', {
258    get: function () {
259      return FileWriteStream
260    },
261    set: function (val) {
262      FileWriteStream = val
263    },
264    enumerable: true,
265    configurable: true
266  })
267
268  function ReadStream (path, options) {
269    if (this instanceof ReadStream)
270      return fs$ReadStream.apply(this, arguments), this
271    else
272      return ReadStream.apply(Object.create(ReadStream.prototype), arguments)
273  }
274
275  function ReadStream$open () {
276    var that = this
277    open(that.path, that.flags, that.mode, function (err, fd) {
278      if (err) {
279        if (that.autoClose)
280          that.destroy()
281
282        that.emit('error', err)
283      } else {
284        that.fd = fd
285        that.emit('open', fd)
286        that.read()
287      }
288    })
289  }
290
291  function WriteStream (path, options) {
292    if (this instanceof WriteStream)
293      return fs$WriteStream.apply(this, arguments), this
294    else
295      return WriteStream.apply(Object.create(WriteStream.prototype), arguments)
296  }
297
298  function WriteStream$open () {
299    var that = this
300    open(that.path, that.flags, that.mode, function (err, fd) {
301      if (err) {
302        that.destroy()
303        that.emit('error', err)
304      } else {
305        that.fd = fd
306        that.emit('open', fd)
307      }
308    })
309  }
310
311  function createReadStream (path, options) {
312    return new fs.ReadStream(path, options)
313  }
314
315  function createWriteStream (path, options) {
316    return new fs.WriteStream(path, options)
317  }
318
319  var fs$open = fs.open
320  fs.open = open
321  function open (path, flags, mode, cb) {
322    if (typeof mode === 'function')
323      cb = mode, mode = null
324
325    return go$open(path, flags, mode, cb)
326
327    function go$open (path, flags, mode, cb) {
328      return fs$open(path, flags, mode, function (err, fd) {
329        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
330          enqueue([go$open, [path, flags, mode, cb]])
331        else {
332          if (typeof cb === 'function')
333            cb.apply(this, arguments)
334          retry()
335        }
336      })
337    }
338  }
339
340  return fs
341}
342
343function enqueue (elem) {
344  debug('ENQUEUE', elem[0].name, elem[1])
345  fs[gracefulQueue].push(elem)
346}
347
348function retry () {
349  var elem = fs[gracefulQueue].shift()
350  if (elem) {
351    debug('RETRY', elem[0].name, elem[1])
352    elem[0].apply(null, elem[1])
353  }
354}
355