Source file src/cmd/go/internal/fsys/glob.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fsys
     6  
     7  import (
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  )
    14  
    15  // Copied from path/filepath.
    16  
    17  // Glob is like filepath.Glob but uses the overlay file system.
    18  func Glob(pattern string) (matches []string, err error) {
    19  	Trace("Glob", pattern)
    20  	// Check pattern is well-formed.
    21  	if _, err := filepath.Match(pattern, ""); err != nil {
    22  		return nil, err
    23  	}
    24  	if !hasMeta(pattern) {
    25  		if _, err = Lstat(pattern); err != nil {
    26  			return nil, nil
    27  		}
    28  		return []string{pattern}, nil
    29  	}
    30  
    31  	dir, file := filepath.Split(pattern)
    32  	volumeLen := 0
    33  	if runtime.GOOS == "windows" {
    34  		volumeLen, dir = cleanGlobPathWindows(dir)
    35  	} else {
    36  		dir = cleanGlobPath(dir)
    37  	}
    38  
    39  	if !hasMeta(dir[volumeLen:]) {
    40  		return glob(dir, file, nil)
    41  	}
    42  
    43  	// Prevent infinite recursion. See issue 15879.
    44  	if dir == pattern {
    45  		return nil, filepath.ErrBadPattern
    46  	}
    47  
    48  	var m []string
    49  	m, err = Glob(dir)
    50  	if err != nil {
    51  		return
    52  	}
    53  	for _, d := range m {
    54  		matches, err = glob(d, file, matches)
    55  		if err != nil {
    56  			return
    57  		}
    58  	}
    59  	return
    60  }
    61  
    62  // cleanGlobPath prepares path for glob matching.
    63  func cleanGlobPath(path string) string {
    64  	switch path {
    65  	case "":
    66  		return "."
    67  	case string(filepath.Separator):
    68  		// do nothing to the path
    69  		return path
    70  	default:
    71  		return path[0 : len(path)-1] // chop off trailing separator
    72  	}
    73  }
    74  
    75  func volumeNameLen(path string) int {
    76  	isSlash := func(c uint8) bool {
    77  		return c == '\\' || c == '/'
    78  	}
    79  	if len(path) < 2 {
    80  		return 0
    81  	}
    82  	// with drive letter
    83  	c := path[0]
    84  	if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
    85  		return 2
    86  	}
    87  	// is it UNC? https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
    88  	if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
    89  		!isSlash(path[2]) && path[2] != '.' {
    90  		// first, leading `\\` and next shouldn't be `\`. its server name.
    91  		for n := 3; n < l-1; n++ {
    92  			// second, next '\' shouldn't be repeated.
    93  			if isSlash(path[n]) {
    94  				n++
    95  				// third, following something characters. its share name.
    96  				if !isSlash(path[n]) {
    97  					if path[n] == '.' {
    98  						break
    99  					}
   100  					for ; n < l; n++ {
   101  						if isSlash(path[n]) {
   102  							break
   103  						}
   104  					}
   105  					return n
   106  				}
   107  				break
   108  			}
   109  		}
   110  	}
   111  	return 0
   112  }
   113  
   114  // cleanGlobPathWindows is windows version of cleanGlobPath.
   115  func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) {
   116  	vollen := volumeNameLen(path)
   117  	switch {
   118  	case path == "":
   119  		return 0, "."
   120  	case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/
   121  		// do nothing to the path
   122  		return vollen + 1, path
   123  	case vollen == len(path) && len(path) == 2: // C:
   124  		return vollen, path + "." // convert C: into C:.
   125  	default:
   126  		if vollen >= len(path) {
   127  			vollen = len(path) - 1
   128  		}
   129  		return vollen, path[0 : len(path)-1] // chop off trailing separator
   130  	}
   131  }
   132  
   133  // glob searches for files matching pattern in the directory dir
   134  // and appends them to matches. If the directory cannot be
   135  // opened, it returns the existing matches. New matches are
   136  // added in lexicographical order.
   137  func glob(dir, pattern string, matches []string) (m []string, e error) {
   138  	m = matches
   139  	fi, err := Stat(dir)
   140  	if err != nil {
   141  		return // ignore I/O error
   142  	}
   143  	if !fi.IsDir() {
   144  		return // ignore I/O error
   145  	}
   146  
   147  	list, err := ReadDir(dir)
   148  	if err != nil {
   149  		return // ignore I/O error
   150  	}
   151  
   152  	names := make([]string, 0, len(list))
   153  	for _, info := range list {
   154  		names = append(names, info.Name())
   155  	}
   156  	sort.Strings(names)
   157  
   158  	for _, n := range names {
   159  		matched, err := filepath.Match(pattern, n)
   160  		if err != nil {
   161  			return m, err
   162  		}
   163  		if matched {
   164  			m = append(m, filepath.Join(dir, n))
   165  		}
   166  	}
   167  	return
   168  }
   169  
   170  // hasMeta reports whether path contains any of the magic characters
   171  // recognized by filepath.Match.
   172  func hasMeta(path string) bool {
   173  	magicChars := `*?[`
   174  	if runtime.GOOS != "windows" {
   175  		magicChars = `*?[\`
   176  	}
   177  	return strings.ContainsAny(path, magicChars)
   178  }
   179  

View as plain text