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