Skip to content

Commit

Permalink
Ignore stat errors that are not ENOENT
Browse files Browse the repository at this point in the history
Fix #245

This works around cases in Windows systems where we can see that a file
exists on the directory listing, but the stat/lstat call fails for some
reason other than ENOENT or ENOTDIR.
  • Loading branch information
isaacs committed Oct 7, 2016
1 parent cea11fe commit 3b5e0f5
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 15 deletions.
16 changes: 9 additions & 7 deletions glob.js
Expand Up @@ -508,15 +508,15 @@ Glob.prototype._readdirInGlobStar = function (abs, cb) {
fs.lstat(abs, lstatcb) fs.lstat(abs, lstatcb)


function lstatcb_ (er, lstat) { function lstatcb_ (er, lstat) {
if (er) if (er && er.code === 'ENOENT')
return cb() return cb()


var isSym = lstat.isSymbolicLink() var isSym = lstat && lstat.isSymbolicLink()
self.symlinks[abs] = isSym self.symlinks[abs] = isSym


// If it's not a symlink or a dir, then it's definitely a regular file. // If it's not a symlink or a dir, then it's definitely a regular file.
// don't bother doing a readdir in that case. // don't bother doing a readdir in that case.
if (!isSym && !lstat.isDirectory()) { if (!isSym && lstat && !lstat.isDirectory()) {
self.cache[abs] = 'FILE' self.cache[abs] = 'FILE'
cb() cb()
} else } else
Expand Down Expand Up @@ -769,21 +769,23 @@ Glob.prototype._stat = function (f, cb) {
} }


Glob.prototype._stat2 = function (f, abs, er, stat, cb) { Glob.prototype._stat2 = function (f, abs, er, stat, cb) {
if (er) { if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) {
this.statCache[abs] = false this.statCache[abs] = false
return cb() return cb()
} }


var needDir = f.slice(-1) === '/' var needDir = f.slice(-1) === '/'
this.statCache[abs] = stat this.statCache[abs] = stat


if (abs.slice(-1) === '/' && !stat.isDirectory()) if (abs.slice(-1) === '/' && stat && !stat.isDirectory())
return cb(null, false, stat) return cb(null, false, stat)


var c = stat.isDirectory() ? 'DIR' : 'FILE' var c = true
if (stat)
c = stat.isDirectory() ? 'DIR' : 'FILE'
this.cache[abs] = this.cache[abs] || c this.cache[abs] = this.cache[abs] || c


if (needDir && c !== 'DIR') if (needDir && c === 'FILE')
return cb() return cb()


return cb(null, c, stat) return cb(null, c, stat)
Expand Down
24 changes: 16 additions & 8 deletions sync.js
Expand Up @@ -250,16 +250,18 @@ GlobSync.prototype._readdirInGlobStar = function (abs) {
try { try {
lstat = fs.lstatSync(abs) lstat = fs.lstatSync(abs)
} catch (er) { } catch (er) {
// lstat failed, doesn't exist if (er.code === 'ENOENT') {
return null // lstat failed, doesn't exist
return null
}
} }


var isSym = lstat.isSymbolicLink() var isSym = lstat && lstat.isSymbolicLink()
this.symlinks[abs] = isSym this.symlinks[abs] = isSym


// If it's not a symlink or a dir, then it's definitely a regular file. // If it's not a symlink or a dir, then it's definitely a regular file.
// don't bother doing a readdir in that case. // don't bother doing a readdir in that case.
if (!isSym && !lstat.isDirectory()) if (!isSym && lstat && !lstat.isDirectory())
this.cache[abs] = 'FILE' this.cache[abs] = 'FILE'
else else
entries = this._readdir(abs, false) entries = this._readdir(abs, false)
Expand Down Expand Up @@ -444,10 +446,13 @@ GlobSync.prototype._stat = function (f) {
try { try {
lstat = fs.lstatSync(abs) lstat = fs.lstatSync(abs)
} catch (er) { } catch (er) {
return false if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) {
this.statCache[abs] = false
return false
}
} }


if (lstat.isSymbolicLink()) { if (lstat && lstat.isSymbolicLink()) {
try { try {
stat = fs.statSync(abs) stat = fs.statSync(abs)
} catch (er) { } catch (er) {
Expand All @@ -460,10 +465,13 @@ GlobSync.prototype._stat = function (f) {


this.statCache[abs] = stat this.statCache[abs] = stat


var c = stat.isDirectory() ? 'DIR' : 'FILE' var c = true
if (stat)
c = stat.isDirectory() ? 'DIR' : 'FILE'

this.cache[abs] = this.cache[abs] || c this.cache[abs] = this.cache[abs] || c


if (needDir && c !== 'DIR') if (needDir && c === 'FILE')
return false return false


return c return c
Expand Down
107 changes: 107 additions & 0 deletions test/eperm-stat.js
@@ -0,0 +1,107 @@
require("./global-leakage.js")
var dir = __dirname + '/fixtures'

var fs = require('fs')
var expect = [
'a/abcdef',
'a/abcdef/g',
'a/abcdef/g/h',
'a/abcfed',
'a/abcfed/g',
'a/abcfed/g/h'
]

var lstat = fs.lstat
var lstatSync = fs.lstatSync
var badPaths = /\ba[\\\/]?$|\babcdef\b/

fs.lstat = function (path, cb) {
// synthetically generate a non-ENOENT error
if (badPaths.test(path)) {
var er = new Error('synthetic')
er.code = 'EPERM'
return process.nextTick(cb.bind(null, er))
}

return lstat.call(fs, path, cb)
}

fs.lstatSync = function (path) {
// synthetically generate a non-ENOENT error
if (badPaths.test(path)) {
var er = new Error('synthetic')
er.code = 'EPERM'
throw er
}

return lstatSync.call(fs, path)
}

var glob = require('../')
var t = require('tap')

t.test('stat errors other than ENOENT are ok', function (t) {
t.plan(2)
t.test('async', function (t) {
glob('a/*abc*/**', { stat: true, cwd: dir }, function (er, matches) {
if (er)
throw er
t.same(matches, expect)
t.end()
})
})

t.test('sync', function (t) {
var matches = glob.sync('a/*abc*/**', { stat: true, cwd: dir })
t.same(matches, expect)
t.end()
})
})

t.test('globstar with error in root', function (t) {
var expect = [
'a',
'a/abcdef',
'a/abcdef/g',
'a/abcdef/g/h',
'a/abcfed',
'a/abcfed/g',
'a/abcfed/g/h',
'a/b',
'a/b/c',
'a/b/c/d',
'a/bc',
'a/bc/e',
'a/bc/e/f',
'a/c',
'a/c/d',
'a/c/d/c',
'a/c/d/c/b',
'a/cb',
'a/cb/e',
'a/cb/e/f',
'a/symlink',
'a/symlink/a',
'a/symlink/a/b',
'a/symlink/a/b/c',
'a/x',
'a/z'
]

var pattern = 'a/**'
t.plan(2)
t.test('async', function (t) {
glob(pattern, { cwd: dir }, function (er, matches) {
if (er)
throw er
t.same(matches, expect)
t.end()
})
})

t.test('sync', function (t) {
var matches = glob.sync(pattern, { cwd: dir })
t.same(matches, expect)
t.end()
})
})

0 comments on commit 3b5e0f5

Please sign in to comment.