| // 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) |
| } |