pkg/glob: When enumerating files, keep going if Lstat fails.

When encountering files that can't be lstat'ed, simply ignore them instead of
terminating the entire globbing process. This is consistent with how directories
that can't be read are already silently ignored.

This fixes #1674.
This commit is contained in:
Qi Xiao 2023-04-10 23:18:02 +01:00
parent 5f0eebab1f
commit bda2879228
3 changed files with 19 additions and 6 deletions

View File

@ -63,7 +63,8 @@ func isDrive(s string) bool {
// glob finds all filenames matching the given Segments in the given dir, and
// calls the callback on all of them. If the callback returns false, globbing is
// interrupted, and glob returns false. Otherwise it returns true.
// interrupted, and glob returns false. Otherwise it returns true. Files that
// can't be lstat'ed and directories that can't be read are ignored silently.
func glob(segs []Segment, dir string, cb func(PathInfo) bool) bool {
// Consume non-wildcard path elements simply by following the path. This may
// seem like an optimization, but is actually required for "." and ".." to
@ -98,7 +99,7 @@ func glob(segs []Segment, dir string, cb func(PathInfo) bool) bool {
infos, err := readDir(dir)
if err != nil {
// TODO(xiaq): Silently drop the error.
// Ignore directories that can't be read.
return true
}
@ -166,12 +167,17 @@ func glob(segs []Segment, dir string, cb func(PathInfo) bool) bool {
for _, info := range infos {
name := info.Name()
if matchElement(segs, name) {
dirname := dir + name
info, err := os.Lstat(dirname)
fullname := dir + name
info, err := os.Lstat(fullname)
if err != nil {
return true
// Either the file was removed between ReadDir and Lstat, or the
// OS has some special rule that prevents it from being lstat'ed
// (see b.elv.sh/1674 for a known case on macOS; SELinux and
// FreeBSD's MAC might be able to do the same). In either case,
// ignore the file.
continue
}
if !cb(PathInfo{dirname, info}) {
if !cb(PathInfo{fullname, info}) {
return false
}
}

View File

@ -60,6 +60,8 @@ var globCases = []globCase{
{"xxxx/*", []string{}},
{"a/*/", []string{}},
// TODO: Add more tests for situations where Lstat fails.
// TODO Test cases against dotfiles.
}

View File

@ -1285,6 +1285,11 @@ Note the following caveats:
- Likewise, you always need to use `**` to match slashes, even if the matcher
includes `/`. For example `*[set:abc/]` is the same as `*[set:abc]`.
Files that the Elvish runtime doesn't have appropriate access to are omitted
silently. For example, if the runtime doesn't have appropriate access to either
the `d` directory or the `d/y.cc` file, the result of `*/y.cc` may omit
`d/y.cc`.
## Order of evaluation
An expression can use a combination of indexing, tilde expansion, wildcard and