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