blob: 082adc7b1f0ba622038e90d025ec2d1ade43b4d5 [file] [log] [blame] [edit]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fsys
import (
"os"
"path/filepath"
"runtime"
"sort"
"strings"
)
// Copied from path/filepath.
// Glob is like filepath.Glob but uses the overlay file system.
func Glob(pattern string) (matches []string, err error) {
Trace("Glob", pattern)
// Check pattern is well-formed.
if _, err := filepath.Match(pattern, ""); err != nil {
return nil, err
}
if !hasMeta(pattern) {
if _, err = Lstat(pattern); err != nil {
return nil, nil
}
return []string{pattern}, nil
}
dir, file := filepath.Split(pattern)
volumeLen := 0
if runtime.GOOS == "windows" {
volumeLen, dir = cleanGlobPathWindows(dir)
} else {
dir = cleanGlobPath(dir)
}
if !hasMeta(dir[volumeLen:]) {
return glob(dir, file, nil)
}
// Prevent infinite recursion. See issue 15879.
if dir == pattern {
return nil, filepath.ErrBadPattern
}
var m []string
m, err = Glob(dir)
if err != nil {
return
}
for _, d := range m {
matches, err = glob(d, file, matches)
if err != nil {
return
}
}
return
}
// cleanGlobPath prepares path for glob matching.
func cleanGlobPath(path string) string {
switch path {
case "":
return "."
case string(filepath.Separator):
// do nothing to the path
return path
default:
return path[0 : len(path)-1] // chop off trailing separator
}
}
func volumeNameLen(path string) int {
isSlash := func(c uint8) bool {
return c == '\\' || c == '/'
}
if len(path) < 2 {
return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
return 2
}
// is it UNC? https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
!isSlash(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if isSlash(path[n]) {
n++
// third, following something characters. its share name.
if !isSlash(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if isSlash(path[n]) {
break
}
}
return n
}
break
}
}
}
return 0
}
// cleanGlobPathWindows is windows version of cleanGlobPath.
func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) {
vollen := volumeNameLen(path)
switch {
case path == "":
return 0, "."
case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/
// do nothing to the path
return vollen + 1, path
case vollen == len(path) && len(path) == 2: // C:
return vollen, path + "." // convert C: into C:.
default:
if vollen >= len(path) {
vollen = len(path) - 1
}
return vollen, path[0 : len(path)-1] // chop off trailing separator
}
}
// glob searches for files matching pattern in the directory dir
// and appends them to matches. If the directory cannot be
// opened, it returns the existing matches. New matches are
// added in lexicographical order.
func glob(dir, pattern string, matches []string) (m []string, e error) {
m = matches
fi, err := Stat(dir)
if err != nil {
return // ignore I/O error
}
if !fi.IsDir() {
return // ignore I/O error
}
list, err := ReadDir(dir)
if err != nil {
return // ignore I/O error
}
names := make([]string, 0, len(list))
for _, info := range list {
names = append(names, info.Name())
}
sort.Strings(names)
for _, n := range names {
matched, err := filepath.Match(pattern, n)
if err != nil {
return m, err
}
if matched {
m = append(m, filepath.Join(dir, n))
}
}
return
}
// hasMeta reports whether path contains any of the magic characters
// recognized by filepath.Match.
func hasMeta(path string) bool {
magicChars := `*?[`
if runtime.GOOS != "windows" {
magicChars = `*?[\`
}
return strings.ContainsAny(path, magicChars)
}