Initial commit
[yaffs-website] / node_modules / globule / node_modules / graceful-fs / graceful-fs.js
1 // this keeps a queue of opened file descriptors, and will make
2 // fs operations wait until some have closed before trying to open more.
3
4 var fs = exports = module.exports = {}
5 fs._originalFs = require("fs")
6
7 Object.getOwnPropertyNames(fs._originalFs).forEach(function(prop) {
8   var desc = Object.getOwnPropertyDescriptor(fs._originalFs, prop)
9   Object.defineProperty(fs, prop, desc)
10 })
11
12 var queue = []
13   , constants = require("constants")
14
15 fs._curOpen = 0
16
17 fs.MIN_MAX_OPEN = 64
18 fs.MAX_OPEN = 1024
19
20 // prevent EMFILE errors
21 function OpenReq (path, flags, mode, cb) {
22   this.path = path
23   this.flags = flags
24   this.mode = mode
25   this.cb = cb
26 }
27
28 function noop () {}
29
30 fs.open = gracefulOpen
31
32 function gracefulOpen (path, flags, mode, cb) {
33   if (typeof mode === "function") cb = mode, mode = null
34   if (typeof cb !== "function") cb = noop
35
36   if (fs._curOpen >= fs.MAX_OPEN) {
37     queue.push(new OpenReq(path, flags, mode, cb))
38     setTimeout(flush)
39     return
40   }
41   open(path, flags, mode, function (er, fd) {
42     if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
43       // that was too many.  reduce max, get back in queue.
44       // this should only happen once in a great while, and only
45       // if the ulimit -n is set lower than 1024.
46       fs.MAX_OPEN = fs._curOpen - 1
47       return fs.open(path, flags, mode, cb)
48     }
49     cb(er, fd)
50   })
51 }
52
53 function open (path, flags, mode, cb) {
54   cb = cb || noop
55   fs._curOpen ++
56   fs._originalFs.open.call(fs, path, flags, mode, function (er, fd) {
57     if (er) onclose()
58     cb(er, fd)
59   })
60 }
61
62 fs.openSync = function (path, flags, mode) {
63   var ret
64   ret = fs._originalFs.openSync.call(fs, path, flags, mode)
65   fs._curOpen ++
66   return ret
67 }
68
69 function onclose () {
70   fs._curOpen --
71   flush()
72 }
73
74 function flush () {
75   while (fs._curOpen < fs.MAX_OPEN) {
76     var req = queue.shift()
77     if (!req) return
78     switch (req.constructor.name) {
79       case 'OpenReq':
80         open(req.path, req.flags || "r", req.mode || 0777, req.cb)
81         break
82       case 'ReaddirReq':
83         readdir(req.path, req.cb)
84         break
85       case 'ReadFileReq':
86         readFile(req.path, req.options, req.cb)
87         break
88       case 'WriteFileReq':
89         writeFile(req.path, req.data, req.options, req.cb)
90         break
91       default:
92         throw new Error('Unknown req type: ' + req.constructor.name)
93     }
94   }
95 }
96
97 fs.close = function (fd, cb) {
98   cb = cb || noop
99   fs._originalFs.close.call(fs, fd, function (er) {
100     onclose()
101     cb(er)
102   })
103 }
104
105 fs.closeSync = function (fd) {
106   try {
107     return fs._originalFs.closeSync.call(fs, fd)
108   } finally {
109     onclose()
110   }
111 }
112
113
114 // readdir takes a fd as well.
115 // however, the sync version closes it right away, so
116 // there's no need to wrap.
117 // It would be nice to catch when it throws an EMFILE,
118 // but that's relatively rare anyway.
119
120 fs.readdir = gracefulReaddir
121
122 function gracefulReaddir (path, cb) {
123   if (fs._curOpen >= fs.MAX_OPEN) {
124     queue.push(new ReaddirReq(path, cb))
125     setTimeout(flush)
126     return
127   }
128
129   readdir(path, function (er, files) {
130     if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
131       fs.MAX_OPEN = fs._curOpen - 1
132       return fs.readdir(path, cb)
133     }
134     cb(er, files)
135   })
136 }
137
138 function readdir (path, cb) {
139   cb = cb || noop
140   fs._curOpen ++
141   fs._originalFs.readdir.call(fs, path, function (er, files) {
142     onclose()
143     cb(er, files)
144   })
145 }
146
147 function ReaddirReq (path, cb) {
148   this.path = path
149   this.cb = cb
150 }
151
152
153 fs.readFile = gracefulReadFile
154
155 function gracefulReadFile(path, options, cb) {
156   if (typeof options === "function") cb = options, options = null
157   if (typeof cb !== "function") cb = noop
158
159   if (fs._curOpen >= fs.MAX_OPEN) {
160     queue.push(new ReadFileReq(path, options, cb))
161     setTimeout(flush)
162     return
163   }
164
165   readFile(path, options, function (er, data) {
166     if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
167       fs.MAX_OPEN = fs._curOpen - 1
168       return fs.readFile(path, options, cb)
169     }
170     cb(er, data)
171   })
172 }
173
174 function readFile (path, options, cb) {
175   cb = cb || noop
176   fs._curOpen ++
177   fs._originalFs.readFile.call(fs, path, options, function (er, data) {
178     onclose()
179     cb(er, data)
180   })
181 }
182
183 function ReadFileReq (path, options, cb) {
184   this.path = path
185   this.options = options
186   this.cb = cb
187 }
188
189
190
191
192 fs.writeFile = gracefulWriteFile
193
194 function gracefulWriteFile(path, data, options, cb) {
195   if (typeof options === "function") cb = options, options = null
196   if (typeof cb !== "function") cb = noop
197
198   if (fs._curOpen >= fs.MAX_OPEN) {
199     queue.push(new WriteFileReq(path, data, options, cb))
200     setTimeout(flush)
201     return
202   }
203
204   writeFile(path, data, options, function (er) {
205     if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
206       fs.MAX_OPEN = fs._curOpen - 1
207       return fs.writeFile(path, data, options, cb)
208     }
209     cb(er)
210   })
211 }
212
213 function writeFile (path, data, options, cb) {
214   cb = cb || noop
215   fs._curOpen ++
216   fs._originalFs.writeFile.call(fs, path, data, options, function (er) {
217     onclose()
218     cb(er)
219   })
220 }
221
222 function WriteFileReq (path, data, options, cb) {
223   this.path = path
224   this.data = data
225   this.options = options
226   this.cb = cb
227 }
228
229
230 // (re-)implement some things that are known busted or missing.
231
232 var constants = require("constants")
233
234 // lchmod, broken prior to 0.6.2
235 // back-port the fix here.
236 if (constants.hasOwnProperty('O_SYMLINK') &&
237     process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
238   fs.lchmod = function (path, mode, callback) {
239     callback = callback || noop
240     fs.open( path
241            , constants.O_WRONLY | constants.O_SYMLINK
242            , mode
243            , function (err, fd) {
244       if (err) {
245         callback(err)
246         return
247       }
248       // prefer to return the chmod error, if one occurs,
249       // but still try to close, and report closing errors if they occur.
250       fs.fchmod(fd, mode, function (err) {
251         fs.close(fd, function(err2) {
252           callback(err || err2)
253         })
254       })
255     })
256   }
257
258   fs.lchmodSync = function (path, mode) {
259     var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode)
260
261     // prefer to return the chmod error, if one occurs,
262     // but still try to close, and report closing errors if they occur.
263     var err, err2
264     try {
265       var ret = fs.fchmodSync(fd, mode)
266     } catch (er) {
267       err = er
268     }
269     try {
270       fs.closeSync(fd)
271     } catch (er) {
272       err2 = er
273     }
274     if (err || err2) throw (err || err2)
275     return ret
276   }
277 }
278
279
280 // lutimes implementation, or no-op
281 if (!fs.lutimes) {
282   if (constants.hasOwnProperty("O_SYMLINK")) {
283     fs.lutimes = function (path, at, mt, cb) {
284       fs.open(path, constants.O_SYMLINK, function (er, fd) {
285         cb = cb || noop
286         if (er) return cb(er)
287         fs.futimes(fd, at, mt, function (er) {
288           fs.close(fd, function (er2) {
289             return cb(er || er2)
290           })
291         })
292       })
293     }
294
295     fs.lutimesSync = function (path, at, mt) {
296       var fd = fs.openSync(path, constants.O_SYMLINK)
297         , err
298         , err2
299         , ret
300
301       try {
302         var ret = fs.futimesSync(fd, at, mt)
303       } catch (er) {
304         err = er
305       }
306       try {
307         fs.closeSync(fd)
308       } catch (er) {
309         err2 = er
310       }
311       if (err || err2) throw (err || err2)
312       return ret
313     }
314
315   } else if (fs.utimensat && constants.hasOwnProperty("AT_SYMLINK_NOFOLLOW")) {
316     // maybe utimensat will be bound soonish?
317     fs.lutimes = function (path, at, mt, cb) {
318       fs.utimensat(path, at, mt, constants.AT_SYMLINK_NOFOLLOW, cb)
319     }
320
321     fs.lutimesSync = function (path, at, mt) {
322       return fs.utimensatSync(path, at, mt, constants.AT_SYMLINK_NOFOLLOW)
323     }
324
325   } else {
326     fs.lutimes = function (_a, _b, _c, cb) { process.nextTick(cb) }
327     fs.lutimesSync = function () {}
328   }
329 }
330
331
332 // https://github.com/isaacs/node-graceful-fs/issues/4
333 // Chown should not fail on einval or eperm if non-root.
334
335 fs.chown = chownFix(fs.chown)
336 fs.fchown = chownFix(fs.fchown)
337 fs.lchown = chownFix(fs.lchown)
338
339 fs.chownSync = chownFixSync(fs.chownSync)
340 fs.fchownSync = chownFixSync(fs.fchownSync)
341 fs.lchownSync = chownFixSync(fs.lchownSync)
342
343 function chownFix (orig) {
344   if (!orig) return orig
345   return function (target, uid, gid, cb) {
346     return orig.call(fs, target, uid, gid, function (er, res) {
347       if (chownErOk(er)) er = null
348       cb(er, res)
349     })
350   }
351 }
352
353 function chownFixSync (orig) {
354   if (!orig) return orig
355   return function (target, uid, gid) {
356     try {
357       return orig.call(fs, target, uid, gid)
358     } catch (er) {
359       if (!chownErOk(er)) throw er
360     }
361   }
362 }
363
364 function chownErOk (er) {
365   // if there's no getuid, or if getuid() is something other than 0,
366   // and the error is EINVAL or EPERM, then just ignore it.
367   // This specific case is a silent failure in cp, install, tar,
368   // and most other unix tools that manage permissions.
369   // When running as root, or if other types of errors are encountered,
370   // then it's strict.
371   if (!er || (!process.getuid || process.getuid() !== 0)
372       && (er.code === "EINVAL" || er.code === "EPERM")) return true
373 }
374
375
376 // if lchmod/lchown do not exist, then make them no-ops
377 if (!fs.lchmod) {
378   fs.lchmod = function (path, mode, cb) {
379     process.nextTick(cb)
380   }
381   fs.lchmodSync = function () {}
382 }
383 if (!fs.lchown) {
384   fs.lchown = function (path, uid, gid, cb) {
385     process.nextTick(cb)
386   }
387   fs.lchownSync = function () {}
388 }
389
390
391
392 // on Windows, A/V software can lock the directory, causing this
393 // to fail with an EACCES or EPERM if the directory contains newly
394 // created files.  Try again on failure, for up to 1 second.
395 if (process.platform === "win32") {
396   var rename_ = fs.rename
397   fs.rename = function rename (from, to, cb) {
398     var start = Date.now()
399     rename_(from, to, function CB (er) {
400       if (er
401           && (er.code === "EACCES" || er.code === "EPERM")
402           && Date.now() - start < 1000) {
403         return rename_(from, to, CB)
404       }
405       cb(er)
406     })
407   }
408 }
409
410
411 // if read() returns EAGAIN, then just try it again.
412 var read = fs.read
413 fs.read = function (fd, buffer, offset, length, position, callback_) {
414   var callback
415   if (callback_ && typeof callback_ === 'function') {
416     var eagCounter = 0
417     callback = function (er, _, __) {
418       if (er && er.code === 'EAGAIN' && eagCounter < 10) {
419         eagCounter ++
420         return read.call(fs, fd, buffer, offset, length, position, callback)
421       }
422       callback_.apply(this, arguments)
423     }
424   }
425   return read.call(fs, fd, buffer, offset, length, position, callback)
426 }
427
428 var readSync = fs.readSync
429 fs.readSync = function (fd, buffer, offset, length, position) {
430   var eagCounter = 0
431   while (true) {
432     try {
433       return readSync.call(fs, fd, buffer, offset, length, position)
434     } catch (er) {
435       if (er.code === 'EAGAIN' && eagCounter < 10) {
436         eagCounter ++
437         continue
438       }
439       throw er
440     }
441   }
442 }