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