From: Russ Cox Date: Fri, 15 Nov 2024 17:52:10 +0000 (-0500) Subject: cmd/go/internal/fsys: move glob, walk code into new files X-Git-Tag: go1.24rc1~283 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=0b4cde34ac39c02586697787be83230f2ff06d90;p=gostls13.git cmd/go/internal/fsys: move glob, walk code into new files The Glob and Walk code does not depend on any of the fsys internals; it simply uses ReadDir as an opaque abstraction. Move it to separate files so that when working on the actual overlay abstraction, it is out of sight, out of mind. Change-Id: Ifa98feaaaafe5c1d8d8edce82de4fd0c78f599c4 Reviewed-on: https://go-review.googlesource.com/c/go/+/628696 Auto-Submit: Russ Cox Reviewed-by: Sam Thanawalla TryBot-Bypass: Russ Cox Reviewed-by: Michael Matloob --- diff --git a/src/cmd/go/internal/fsys/fsys.go b/src/cmd/go/internal/fsys/fsys.go index 11293120f6..7c2e997bda 100644 --- a/src/cmd/go/internal/fsys/fsys.go +++ b/src/cmd/go/internal/fsys/fsys.go @@ -17,7 +17,6 @@ import ( "os" pathpkg "path" "path/filepath" - "runtime" "runtime/debug" "sort" "strings" @@ -487,45 +486,6 @@ func IsDirWithGoFiles(dir string) (bool, error) { return false, firstErr } -// walk recursively descends path, calling walkFn. Copied, with some -// modifications from path/filepath.walk. -func walk(path string, info fs.FileInfo, walkFn filepath.WalkFunc) error { - if err := walkFn(path, info, nil); err != nil || !info.IsDir() { - return err - } - - fis, err := ReadDir(path) - if err != nil { - return walkFn(path, info, err) - } - - for _, fi := range fis { - filename := filepath.Join(path, fi.Name()) - if err := walk(filename, fi, walkFn); err != nil { - if !fi.IsDir() || err != filepath.SkipDir { - return err - } - } - } - return nil -} - -// Walk walks the file tree rooted at root, calling walkFn for each file or -// directory in the tree, including root. -func Walk(root string, walkFn filepath.WalkFunc) error { - Trace("Walk", root) - info, err := Lstat(root) - if err != nil { - err = walkFn(root, nil, err) - } else { - err = walk(root, info, walkFn) - } - if err == filepath.SkipDir { - return nil - } - return err -} - // Lstat implements a version of os.Lstat that operates on the overlay filesystem. func Lstat(path string) (fs.FileInfo, error) { Trace("Lstat", path) @@ -625,166 +585,3 @@ func (f fakeDir) Sys() any { return nil } func (f fakeDir) String() string { return fs.FormatFileInfo(f) } - -// 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) -} diff --git a/src/cmd/go/internal/fsys/glob.go b/src/cmd/go/internal/fsys/glob.go new file mode 100644 index 0000000000..082adc7b1f --- /dev/null +++ b/src/cmd/go/internal/fsys/glob.go @@ -0,0 +1,178 @@ +// 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) +} diff --git a/src/cmd/go/internal/fsys/walk.go b/src/cmd/go/internal/fsys/walk.go new file mode 100644 index 0000000000..23d739518a --- /dev/null +++ b/src/cmd/go/internal/fsys/walk.go @@ -0,0 +1,49 @@ +// 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 ( + "io/fs" + "path/filepath" +) + +// Walk walks the file tree rooted at root, calling walkFn for each file or +// directory in the tree, including root. +func Walk(root string, walkFn filepath.WalkFunc) error { + Trace("Walk", root) + info, err := Lstat(root) + if err != nil { + err = walkFn(root, nil, err) + } else { + err = walk(root, info, walkFn) + } + if err == filepath.SkipDir { + return nil + } + return err +} + +// walk recursively descends path, calling walkFn. Copied, with some +// modifications from path/filepath.walk. +func walk(path string, info fs.FileInfo, walkFn filepath.WalkFunc) error { + if err := walkFn(path, info, nil); err != nil || !info.IsDir() { + return err + } + + fis, err := ReadDir(path) + if err != nil { + return walkFn(path, info, err) + } + + for _, fi := range fis { + filename := filepath.Join(path, fi.Name()) + if err := walk(filename, fi, walkFn); err != nil { + if !fi.IsDir() || err != filepath.SkipDir { + return err + } + } + } + return nil +}