polyfills.js
  1  var constants = require('constants')
  2  
  3  var origCwd = process.cwd
  4  var cwd = null
  5  
  6  var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform
  7  
  8  process.cwd = function() {
  9    if (!cwd)
 10      cwd = origCwd.call(process)
 11    return cwd
 12  }
 13  try {
 14    process.cwd()
 15  } catch (er) {}
 16  
 17  // This check is needed until node.js 12 is required
 18  if (typeof process.chdir === 'function') {
 19    var chdir = process.chdir
 20    process.chdir = function (d) {
 21      cwd = null
 22      chdir.call(process, d)
 23    }
 24    if (Object.setPrototypeOf) Object.setPrototypeOf(process.chdir, chdir)
 25  }
 26  
 27  module.exports = patch
 28  
 29  function patch (fs) {
 30    // (re-)implement some things that are known busted or missing.
 31  
 32    // lchmod, broken prior to 0.6.2
 33    // back-port the fix here.
 34    if (constants.hasOwnProperty('O_SYMLINK') &&
 35        process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
 36      patchLchmod(fs)
 37    }
 38  
 39    // lutimes implementation, or no-op
 40    if (!fs.lutimes) {
 41      patchLutimes(fs)
 42    }
 43  
 44    // https://github.com/isaacs/node-graceful-fs/issues/4
 45    // Chown should not fail on einval or eperm if non-root.
 46    // It should not fail on enosys ever, as this just indicates
 47    // that a fs doesn't support the intended operation.
 48  
 49    fs.chown = chownFix(fs.chown)
 50    fs.fchown = chownFix(fs.fchown)
 51    fs.lchown = chownFix(fs.lchown)
 52  
 53    fs.chmod = chmodFix(fs.chmod)
 54    fs.fchmod = chmodFix(fs.fchmod)
 55    fs.lchmod = chmodFix(fs.lchmod)
 56  
 57    fs.chownSync = chownFixSync(fs.chownSync)
 58    fs.fchownSync = chownFixSync(fs.fchownSync)
 59    fs.lchownSync = chownFixSync(fs.lchownSync)
 60  
 61    fs.chmodSync = chmodFixSync(fs.chmodSync)
 62    fs.fchmodSync = chmodFixSync(fs.fchmodSync)
 63    fs.lchmodSync = chmodFixSync(fs.lchmodSync)
 64  
 65    fs.stat = statFix(fs.stat)
 66    fs.fstat = statFix(fs.fstat)
 67    fs.lstat = statFix(fs.lstat)
 68  
 69    fs.statSync = statFixSync(fs.statSync)
 70    fs.fstatSync = statFixSync(fs.fstatSync)
 71    fs.lstatSync = statFixSync(fs.lstatSync)
 72  
 73    // if lchmod/lchown do not exist, then make them no-ops
 74    if (!fs.lchmod) {
 75      fs.lchmod = function (path, mode, cb) {
 76        if (cb) process.nextTick(cb)
 77      }
 78      fs.lchmodSync = function () {}
 79    }
 80    if (!fs.lchown) {
 81      fs.lchown = function (path, uid, gid, cb) {
 82        if (cb) process.nextTick(cb)
 83      }
 84      fs.lchownSync = function () {}
 85    }
 86  
 87    // on Windows, A/V software can lock the directory, causing this
 88    // to fail with an EACCES or EPERM if the directory contains newly
 89    // created files.  Try again on failure, for up to 60 seconds.
 90  
 91    // Set the timeout this long because some Windows Anti-Virus, such as Parity
 92    // bit9, may lock files for up to a minute, causing npm package install
 93    // failures. Also, take care to yield the scheduler. Windows scheduling gives
 94    // CPU to a busy looping process, which can cause the program causing the lock
 95    // contention to be starved of CPU by node, so the contention doesn't resolve.
 96    if (platform === "win32") {
 97      fs.rename = (function (fs$rename) { return function (from, to, cb) {
 98        var start = Date.now()
 99        var backoff = 0;
100        fs$rename(from, to, function CB (er) {
101          if (er
102              && (er.code === "EACCES" || er.code === "EPERM")
103              && Date.now() - start < 60000) {
104            setTimeout(function() {
105              fs.stat(to, function (stater, st) {
106                if (stater && stater.code === "ENOENT")
107                  fs$rename(from, to, CB);
108                else
109                  cb(er)
110              })
111            }, backoff)
112            if (backoff < 100)
113              backoff += 10;
114            return;
115          }
116          if (cb) cb(er)
117        })
118      }})(fs.rename)
119    }
120  
121    // if read() returns EAGAIN, then just try it again.
122    fs.read = (function (fs$read) {
123      function read (fd, buffer, offset, length, position, callback_) {
124        var callback
125        if (callback_ && typeof callback_ === 'function') {
126          var eagCounter = 0
127          callback = function (er, _, __) {
128            if (er && er.code === 'EAGAIN' && eagCounter < 10) {
129              eagCounter ++
130              return fs$read.call(fs, fd, buffer, offset, length, position, callback)
131            }
132            callback_.apply(this, arguments)
133          }
134        }
135        return fs$read.call(fs, fd, buffer, offset, length, position, callback)
136      }
137  
138      // This ensures `util.promisify` works as it does for native `fs.read`.
139      if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read)
140      return read
141    })(fs.read)
142  
143    fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) {
144      var eagCounter = 0
145      while (true) {
146        try {
147          return fs$readSync.call(fs, fd, buffer, offset, length, position)
148        } catch (er) {
149          if (er.code === 'EAGAIN' && eagCounter < 10) {
150            eagCounter ++
151            continue
152          }
153          throw er
154        }
155      }
156    }})(fs.readSync)
157  
158    function patchLchmod (fs) {
159      fs.lchmod = function (path, mode, callback) {
160        fs.open( path
161               , constants.O_WRONLY | constants.O_SYMLINK
162               , mode
163               , function (err, fd) {
164          if (err) {
165            if (callback) callback(err)
166            return
167          }
168          // prefer to return the chmod error, if one occurs,
169          // but still try to close, and report closing errors if they occur.
170          fs.fchmod(fd, mode, function (err) {
171            fs.close(fd, function(err2) {
172              if (callback) callback(err || err2)
173            })
174          })
175        })
176      }
177  
178      fs.lchmodSync = function (path, mode) {
179        var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode)
180  
181        // prefer to return the chmod error, if one occurs,
182        // but still try to close, and report closing errors if they occur.
183        var threw = true
184        var ret
185        try {
186          ret = fs.fchmodSync(fd, mode)
187          threw = false
188        } finally {
189          if (threw) {
190            try {
191              fs.closeSync(fd)
192            } catch (er) {}
193          } else {
194            fs.closeSync(fd)
195          }
196        }
197        return ret
198      }
199    }
200  
201    function patchLutimes (fs) {
202      if (constants.hasOwnProperty("O_SYMLINK")) {
203        fs.lutimes = function (path, at, mt, cb) {
204          fs.open(path, constants.O_SYMLINK, function (er, fd) {
205            if (er) {
206              if (cb) cb(er)
207              return
208            }
209            fs.futimes(fd, at, mt, function (er) {
210              fs.close(fd, function (er2) {
211                if (cb) cb(er || er2)
212              })
213            })
214          })
215        }
216  
217        fs.lutimesSync = function (path, at, mt) {
218          var fd = fs.openSync(path, constants.O_SYMLINK)
219          var ret
220          var threw = true
221          try {
222            ret = fs.futimesSync(fd, at, mt)
223            threw = false
224          } finally {
225            if (threw) {
226              try {
227                fs.closeSync(fd)
228              } catch (er) {}
229            } else {
230              fs.closeSync(fd)
231            }
232          }
233          return ret
234        }
235  
236      } else {
237        fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) }
238        fs.lutimesSync = function () {}
239      }
240    }
241  
242    function chmodFix (orig) {
243      if (!orig) return orig
244      return function (target, mode, cb) {
245        return orig.call(fs, target, mode, function (er) {
246          if (chownErOk(er)) er = null
247          if (cb) cb.apply(this, arguments)
248        })
249      }
250    }
251  
252    function chmodFixSync (orig) {
253      if (!orig) return orig
254      return function (target, mode) {
255        try {
256          return orig.call(fs, target, mode)
257        } catch (er) {
258          if (!chownErOk(er)) throw er
259        }
260      }
261    }
262  
263  
264    function chownFix (orig) {
265      if (!orig) return orig
266      return function (target, uid, gid, cb) {
267        return orig.call(fs, target, uid, gid, function (er) {
268          if (chownErOk(er)) er = null
269          if (cb) cb.apply(this, arguments)
270        })
271      }
272    }
273  
274    function chownFixSync (orig) {
275      if (!orig) return orig
276      return function (target, uid, gid) {
277        try {
278          return orig.call(fs, target, uid, gid)
279        } catch (er) {
280          if (!chownErOk(er)) throw er
281        }
282      }
283    }
284  
285    function statFix (orig) {
286      if (!orig) return orig
287      // Older versions of Node erroneously returned signed integers for
288      // uid + gid.
289      return function (target, options, cb) {
290        if (typeof options === 'function') {
291          cb = options
292          options = null
293        }
294        function callback (er, stats) {
295          if (stats) {
296            if (stats.uid < 0) stats.uid += 0x100000000
297            if (stats.gid < 0) stats.gid += 0x100000000
298          }
299          if (cb) cb.apply(this, arguments)
300        }
301        return options ? orig.call(fs, target, options, callback)
302          : orig.call(fs, target, callback)
303      }
304    }
305  
306    function statFixSync (orig) {
307      if (!orig) return orig
308      // Older versions of Node erroneously returned signed integers for
309      // uid + gid.
310      return function (target, options) {
311        var stats = options ? orig.call(fs, target, options)
312          : orig.call(fs, target)
313        if (stats.uid < 0) stats.uid += 0x100000000
314        if (stats.gid < 0) stats.gid += 0x100000000
315        return stats;
316      }
317    }
318  
319    // ENOSYS means that the fs doesn't support the op. Just ignore
320    // that, because it doesn't matter.
321    //
322    // if there's no getuid, or if getuid() is something other
323    // than 0, and the error is EINVAL or EPERM, then just ignore
324    // it.
325    //
326    // This specific case is a silent failure in cp, install, tar,
327    // and most other unix tools that manage permissions.
328    //
329    // When running as root, or if other types of errors are
330    // encountered, then it's strict.
331    function chownErOk (er) {
332      if (!er)
333        return true
334  
335      if (er.code === "ENOSYS")
336        return true
337  
338      var nonroot = !process.getuid || process.getuid() !== 0
339      if (nonroot) {
340        if (er.code === "EINVAL" || er.code === "EPERM")
341          return true
342      }
343  
344      return false
345    }
346  }