...

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

Documentation: cmd/go/internal/fsys

     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 is an abstraction for reading files that
     6  // allows for virtual overlays on top of the files on disk.
     7  package fsys
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"internal/godebug"
    14  	"io"
    15  	"io/fs"
    16  	"log"
    17  	"os"
    18  	pathpkg "path"
    19  	"path/filepath"
    20  	"runtime"
    21  	"runtime/debug"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  // Trace emits a trace event for the operation and file path to the trace log,
    29  // but only when $GODEBUG contains gofsystrace=1.
    30  // The traces are appended to the file named by the $GODEBUG setting gofsystracelog, or else standard error.
    31  // For debugging, if the $GODEBUG setting gofsystracestack is non-empty, then trace events for paths
    32  // matching that glob pattern (using path.Match) will be followed by a full stack trace.
    33  func Trace(op, path string) {
    34  	if !doTrace {
    35  		return
    36  	}
    37  	traceMu.Lock()
    38  	defer traceMu.Unlock()
    39  	fmt.Fprintf(traceFile, "%d gofsystrace %s %s\n", os.Getpid(), op, path)
    40  	if pattern := gofsystracestack.Value(); pattern != "" {
    41  		if match, _ := pathpkg.Match(pattern, path); match {
    42  			traceFile.Write(debug.Stack())
    43  		}
    44  	}
    45  }
    46  
    47  var (
    48  	doTrace   bool
    49  	traceFile *os.File
    50  	traceMu   sync.Mutex
    51  
    52  	gofsystrace      = godebug.New("#gofsystrace")
    53  	gofsystracelog   = godebug.New("#gofsystracelog")
    54  	gofsystracestack = godebug.New("#gofsystracestack")
    55  )
    56  
    57  func init() {
    58  	if gofsystrace.Value() != "1" {
    59  		return
    60  	}
    61  	doTrace = true
    62  	if f := gofsystracelog.Value(); f != "" {
    63  		// Note: No buffering on writes to this file, so no need to worry about closing it at exit.
    64  		var err error
    65  		traceFile, err = os.OpenFile(f, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
    66  		if err != nil {
    67  			log.Fatal(err)
    68  		}
    69  	} else {
    70  		traceFile = os.Stderr
    71  	}
    72  }
    73  
    74  // OverlayFile is the path to a text file in the OverlayJSON format.
    75  // It is the value of the -overlay flag.
    76  var OverlayFile string
    77  
    78  // OverlayJSON is the format overlay files are expected to be in.
    79  // The Replace map maps from overlaid paths to replacement paths:
    80  // the Go command will forward all reads trying to open
    81  // each overlaid path to its replacement path, or consider the overlaid
    82  // path not to exist if the replacement path is empty.
    83  type OverlayJSON struct {
    84  	Replace map[string]string
    85  }
    86  
    87  type node struct {
    88  	actualFilePath string           // empty if a directory
    89  	children       map[string]*node // path element → file or directory
    90  }
    91  
    92  func (n *node) isDir() bool {
    93  	return n.actualFilePath == "" && n.children != nil
    94  }
    95  
    96  func (n *node) isDeleted() bool {
    97  	return n.actualFilePath == "" && n.children == nil
    98  }
    99  
   100  // TODO(matloob): encapsulate these in an io/fs-like interface
   101  var overlay map[string]*node // path -> file or directory node
   102  var cwd string               // copy of base.Cwd() to avoid dependency
   103  
   104  // canonicalize a path for looking it up in the overlay.
   105  // Important: filepath.Join(cwd, path) doesn't always produce
   106  // the correct absolute path if path is relative, because on
   107  // Windows producing the correct absolute path requires making
   108  // a syscall. So this should only be used when looking up paths
   109  // in the overlay, or canonicalizing the paths in the overlay.
   110  func canonicalize(path string) string {
   111  	if path == "" {
   112  		return ""
   113  	}
   114  	if filepath.IsAbs(path) {
   115  		return filepath.Clean(path)
   116  	}
   117  
   118  	if v := filepath.VolumeName(cwd); v != "" && path[0] == filepath.Separator {
   119  		// On Windows filepath.Join(cwd, path) doesn't always work. In general
   120  		// filepath.Abs needs to make a syscall on Windows. Elsewhere in cmd/go
   121  		// use filepath.Join(cwd, path), but cmd/go specifically supports Windows
   122  		// paths that start with "\" which implies the path is relative to the
   123  		// volume of the working directory. See golang.org/issue/8130.
   124  		return filepath.Join(v, path)
   125  	}
   126  
   127  	// Make the path absolute.
   128  	return filepath.Join(cwd, path)
   129  }
   130  
   131  // Init initializes the overlay, if one is being used.
   132  func Init(wd string) error {
   133  	if overlay != nil {
   134  		// already initialized
   135  		return nil
   136  	}
   137  
   138  	cwd = wd
   139  
   140  	if OverlayFile == "" {
   141  		return nil
   142  	}
   143  
   144  	Trace("ReadFile", OverlayFile)
   145  	b, err := os.ReadFile(OverlayFile)
   146  	if err != nil {
   147  		return fmt.Errorf("reading overlay file: %v", err)
   148  	}
   149  
   150  	var overlayJSON OverlayJSON
   151  	if err := json.Unmarshal(b, &overlayJSON); err != nil {
   152  		return fmt.Errorf("parsing overlay JSON: %v", err)
   153  	}
   154  
   155  	return initFromJSON(overlayJSON)
   156  }
   157  
   158  func initFromJSON(overlayJSON OverlayJSON) error {
   159  	// Canonicalize the paths in the overlay map.
   160  	// Use reverseCanonicalized to check for collisions:
   161  	// no two 'from' paths should canonicalize to the same path.
   162  	overlay = make(map[string]*node)
   163  	reverseCanonicalized := make(map[string]string) // inverse of canonicalize operation, to check for duplicates
   164  	// Build a table of file and directory nodes from the replacement map.
   165  
   166  	// Remove any potential non-determinism from iterating over map by sorting it.
   167  	replaceFrom := make([]string, 0, len(overlayJSON.Replace))
   168  	for k := range overlayJSON.Replace {
   169  		replaceFrom = append(replaceFrom, k)
   170  	}
   171  	sort.Strings(replaceFrom)
   172  
   173  	for _, from := range replaceFrom {
   174  		to := overlayJSON.Replace[from]
   175  		// Canonicalize paths and check for a collision.
   176  		if from == "" {
   177  			return fmt.Errorf("empty string key in overlay file Replace map")
   178  		}
   179  		cfrom := canonicalize(from)
   180  		if to != "" {
   181  			// Don't canonicalize "", meaning to delete a file, because then it will turn into ".".
   182  			to = canonicalize(to)
   183  		}
   184  		if otherFrom, seen := reverseCanonicalized[cfrom]; seen {
   185  			return fmt.Errorf(
   186  				"paths %q and %q both canonicalize to %q in overlay file Replace map", otherFrom, from, cfrom)
   187  		}
   188  		reverseCanonicalized[cfrom] = from
   189  		from = cfrom
   190  
   191  		// Create node for overlaid file.
   192  		dir, base := filepath.Dir(from), filepath.Base(from)
   193  		if n, ok := overlay[from]; ok {
   194  			// All 'from' paths in the overlay are file paths. Since the from paths
   195  			// are in a map, they are unique, so if the node already exists we added
   196  			// it below when we create parent directory nodes. That is, that
   197  			// both a file and a path to one of its parent directories exist as keys
   198  			// in the Replace map.
   199  			//
   200  			// This only applies if the overlay directory has any files or directories
   201  			// in it: placeholder directories that only contain deleted files don't
   202  			// count. They are safe to be overwritten with actual files.
   203  			for _, f := range n.children {
   204  				if !f.isDeleted() {
   205  					return fmt.Errorf("invalid overlay: path %v is used as both file and directory", from)
   206  				}
   207  			}
   208  		}
   209  		overlay[from] = &node{actualFilePath: to}
   210  
   211  		// Add parent directory nodes to overlay structure.
   212  		childNode := overlay[from]
   213  		for {
   214  			dirNode := overlay[dir]
   215  			if dirNode == nil || dirNode.isDeleted() {
   216  				dirNode = &node{children: make(map[string]*node)}
   217  				overlay[dir] = dirNode
   218  			}
   219  			if childNode.isDeleted() {
   220  				// Only create one parent for a deleted file:
   221  				// the directory only conditionally exists if
   222  				// there are any non-deleted children, so
   223  				// we don't create their parents.
   224  				if dirNode.isDir() {
   225  					dirNode.children[base] = childNode
   226  				}
   227  				break
   228  			}
   229  			if !dirNode.isDir() {
   230  				// This path already exists as a file, so it can't be a parent
   231  				// directory. See comment at error above.
   232  				return fmt.Errorf("invalid overlay: path %v is used as both file and directory", dir)
   233  			}
   234  			dirNode.children[base] = childNode
   235  			parent := filepath.Dir(dir)
   236  			if parent == dir {
   237  				break // reached the top; there is no parent
   238  			}
   239  			dir, base = parent, filepath.Base(dir)
   240  			childNode = dirNode
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  // IsDir returns true if path is a directory on disk or in the
   248  // overlay.
   249  func IsDir(path string) (bool, error) {
   250  	Trace("IsDir", path)
   251  	path = canonicalize(path)
   252  
   253  	if _, ok := parentIsOverlayFile(path); ok {
   254  		return false, nil
   255  	}
   256  
   257  	if n, ok := overlay[path]; ok {
   258  		return n.isDir(), nil
   259  	}
   260  
   261  	fi, err := os.Stat(path)
   262  	if err != nil {
   263  		return false, err
   264  	}
   265  
   266  	return fi.IsDir(), nil
   267  }
   268  
   269  // parentIsOverlayFile returns whether name or any of
   270  // its parents are files in the overlay, and the first parent found,
   271  // including name itself, that's a file in the overlay.
   272  func parentIsOverlayFile(name string) (string, bool) {
   273  	if overlay != nil {
   274  		// Check if name can't possibly be a directory because
   275  		// it or one of its parents is overlaid with a file.
   276  		// TODO(matloob): Maybe save this to avoid doing it every time?
   277  		prefix := name
   278  		for {
   279  			node := overlay[prefix]
   280  			if node != nil && !node.isDir() {
   281  				return prefix, true
   282  			}
   283  			parent := filepath.Dir(prefix)
   284  			if parent == prefix {
   285  				break
   286  			}
   287  			prefix = parent
   288  		}
   289  	}
   290  
   291  	return "", false
   292  }
   293  
   294  // errNotDir is used to communicate from ReadDir to IsDirWithGoFiles
   295  // that the argument is not a directory, so that IsDirWithGoFiles doesn't
   296  // return an error.
   297  var errNotDir = errors.New("not a directory")
   298  
   299  func nonFileInOverlayError(overlayPath string) error {
   300  	return fmt.Errorf("replacement path %q is a directory, not a file", overlayPath)
   301  }
   302  
   303  // readDir reads a dir on disk, returning an error that is errNotDir if the dir is not a directory.
   304  // Unfortunately, the error returned by os.ReadDir if dir is not a directory
   305  // can vary depending on the OS (Linux, Mac, Windows return ENOTDIR; BSD returns EINVAL).
   306  func readDir(dir string) ([]fs.FileInfo, error) {
   307  	entries, err := os.ReadDir(dir)
   308  	if err != nil {
   309  		if os.IsNotExist(err) {
   310  			return nil, err
   311  		}
   312  		if dirfi, staterr := os.Stat(dir); staterr == nil && !dirfi.IsDir() {
   313  			return nil, &fs.PathError{Op: "ReadDir", Path: dir, Err: errNotDir}
   314  		}
   315  		return nil, err
   316  	}
   317  
   318  	fis := make([]fs.FileInfo, 0, len(entries))
   319  	for _, entry := range entries {
   320  		info, err := entry.Info()
   321  		if err != nil {
   322  			continue
   323  		}
   324  		fis = append(fis, info)
   325  	}
   326  	return fis, nil
   327  }
   328  
   329  // ReadDir provides a slice of fs.FileInfo entries corresponding
   330  // to the overlaid files in the directory.
   331  func ReadDir(dir string) ([]fs.FileInfo, error) {
   332  	Trace("ReadDir", dir)
   333  	dir = canonicalize(dir)
   334  	if _, ok := parentIsOverlayFile(dir); ok {
   335  		return nil, &fs.PathError{Op: "ReadDir", Path: dir, Err: errNotDir}
   336  	}
   337  
   338  	dirNode := overlay[dir]
   339  	if dirNode == nil {
   340  		return readDir(dir)
   341  	}
   342  	if dirNode.isDeleted() {
   343  		return nil, &fs.PathError{Op: "ReadDir", Path: dir, Err: fs.ErrNotExist}
   344  	}
   345  	diskfis, err := readDir(dir)
   346  	if err != nil && !os.IsNotExist(err) && !errors.Is(err, errNotDir) {
   347  		return nil, err
   348  	}
   349  
   350  	// Stat files in overlay to make composite list of fileinfos
   351  	files := make(map[string]fs.FileInfo)
   352  	for _, f := range diskfis {
   353  		files[f.Name()] = f
   354  	}
   355  	for name, to := range dirNode.children {
   356  		switch {
   357  		case to.isDir():
   358  			files[name] = fakeDir(name)
   359  		case to.isDeleted():
   360  			delete(files, name)
   361  		default:
   362  			// To keep the data model simple, if the overlay contains a symlink we
   363  			// always stat through it (using Stat, not Lstat). That way we don't need
   364  			// to worry about the interaction between Lstat and directories: if a
   365  			// symlink in the overlay points to a directory, we reject it like an
   366  			// ordinary directory.
   367  			fi, err := os.Stat(to.actualFilePath)
   368  			if err != nil {
   369  				files[name] = missingFile(name)
   370  				continue
   371  			} else if fi.IsDir() {
   372  				return nil, &fs.PathError{Op: "Stat", Path: filepath.Join(dir, name), Err: nonFileInOverlayError(to.actualFilePath)}
   373  			}
   374  			// Add a fileinfo for the overlaid file, so that it has
   375  			// the original file's name, but the overlaid file's metadata.
   376  			files[name] = fakeFile{name, fi}
   377  		}
   378  	}
   379  	sortedFiles := diskfis[:0]
   380  	for _, f := range files {
   381  		sortedFiles = append(sortedFiles, f)
   382  	}
   383  	sort.Slice(sortedFiles, func(i, j int) bool { return sortedFiles[i].Name() < sortedFiles[j].Name() })
   384  	return sortedFiles, nil
   385  }
   386  
   387  // OverlayPath returns the path to the overlaid contents of the
   388  // file, the empty string if the overlay deletes the file, or path
   389  // itself if the file is not in the overlay, the file is a directory
   390  // in the overlay, or there is no overlay.
   391  // It returns true if the path is overlaid with a regular file
   392  // or deleted, and false otherwise.
   393  func OverlayPath(path string) (string, bool) {
   394  	if p, ok := overlay[canonicalize(path)]; ok && !p.isDir() {
   395  		return p.actualFilePath, ok
   396  	}
   397  
   398  	return path, false
   399  }
   400  
   401  // Open opens the file at or overlaid on the given path.
   402  func Open(path string) (*os.File, error) {
   403  	Trace("Open", path)
   404  	return openFile(path, os.O_RDONLY, 0)
   405  }
   406  
   407  func openFile(path string, flag int, perm os.FileMode) (*os.File, error) {
   408  	cpath := canonicalize(path)
   409  	if node, ok := overlay[cpath]; ok {
   410  		// Opening a file in the overlay.
   411  		if node.isDir() {
   412  			return nil, &fs.PathError{Op: "OpenFile", Path: path, Err: errors.New("fsys.OpenFile doesn't support opening directories yet")}
   413  		}
   414  		// We can't open overlaid paths for write.
   415  		if perm != os.FileMode(os.O_RDONLY) {
   416  			return nil, &fs.PathError{Op: "OpenFile", Path: path, Err: errors.New("overlaid files can't be opened for write")}
   417  		}
   418  		return os.OpenFile(node.actualFilePath, flag, perm)
   419  	}
   420  	if parent, ok := parentIsOverlayFile(filepath.Dir(cpath)); ok {
   421  		// The file is deleted explicitly in the Replace map,
   422  		// or implicitly because one of its parent directories was
   423  		// replaced by a file.
   424  		return nil, &fs.PathError{
   425  			Op:   "Open",
   426  			Path: path,
   427  			Err:  fmt.Errorf("file %s does not exist: parent directory %s is replaced by a file in overlay", path, parent),
   428  		}
   429  	}
   430  	return os.OpenFile(cpath, flag, perm)
   431  }
   432  
   433  // ReadFile reads the file at or overlaid on the given path.
   434  func ReadFile(path string) ([]byte, error) {
   435  	f, err := Open(path)
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  	defer f.Close()
   440  
   441  	return io.ReadAll(f)
   442  }
   443  
   444  // IsDirWithGoFiles reports whether dir is a directory containing Go files
   445  // either on disk or in the overlay.
   446  func IsDirWithGoFiles(dir string) (bool, error) {
   447  	Trace("IsDirWithGoFiles", dir)
   448  	fis, err := ReadDir(dir)
   449  	if os.IsNotExist(err) || errors.Is(err, errNotDir) {
   450  		return false, nil
   451  	}
   452  	if err != nil {
   453  		return false, err
   454  	}
   455  
   456  	var firstErr error
   457  	for _, fi := range fis {
   458  		if fi.IsDir() {
   459  			continue
   460  		}
   461  
   462  		// TODO(matloob): this enforces that the "from" in the map
   463  		// has a .go suffix, but the actual destination file
   464  		// doesn't need to have a .go suffix. Is this okay with the
   465  		// compiler?
   466  		if !strings.HasSuffix(fi.Name(), ".go") {
   467  			continue
   468  		}
   469  		if fi.Mode().IsRegular() {
   470  			return true, nil
   471  		}
   472  
   473  		// fi is the result of an Lstat, so it doesn't follow symlinks.
   474  		// But it's okay if the file is a symlink pointing to a regular
   475  		// file, so use os.Stat to follow symlinks and check that.
   476  		actualFilePath, _ := OverlayPath(filepath.Join(dir, fi.Name()))
   477  		fi, err := os.Stat(actualFilePath)
   478  		if err == nil && fi.Mode().IsRegular() {
   479  			return true, nil
   480  		}
   481  		if err != nil && firstErr == nil {
   482  			firstErr = err
   483  		}
   484  	}
   485  
   486  	// No go files found in directory.
   487  	return false, firstErr
   488  }
   489  
   490  // walk recursively descends path, calling walkFn. Copied, with some
   491  // modifications from path/filepath.walk.
   492  func walk(path string, info fs.FileInfo, walkFn filepath.WalkFunc) error {
   493  	if err := walkFn(path, info, nil); err != nil || !info.IsDir() {
   494  		return err
   495  	}
   496  
   497  	fis, err := ReadDir(path)
   498  	if err != nil {
   499  		return walkFn(path, info, err)
   500  	}
   501  
   502  	for _, fi := range fis {
   503  		filename := filepath.Join(path, fi.Name())
   504  		if err := walk(filename, fi, walkFn); err != nil {
   505  			if !fi.IsDir() || err != filepath.SkipDir {
   506  				return err
   507  			}
   508  		}
   509  	}
   510  	return nil
   511  }
   512  
   513  // Walk walks the file tree rooted at root, calling walkFn for each file or
   514  // directory in the tree, including root.
   515  func Walk(root string, walkFn filepath.WalkFunc) error {
   516  	Trace("Walk", root)
   517  	info, err := Lstat(root)
   518  	if err != nil {
   519  		err = walkFn(root, nil, err)
   520  	} else {
   521  		err = walk(root, info, walkFn)
   522  	}
   523  	if err == filepath.SkipDir {
   524  		return nil
   525  	}
   526  	return err
   527  }
   528  
   529  // Lstat implements a version of os.Lstat that operates on the overlay filesystem.
   530  func Lstat(path string) (fs.FileInfo, error) {
   531  	Trace("Lstat", path)
   532  	return overlayStat(path, os.Lstat, "lstat")
   533  }
   534  
   535  // Stat implements a version of os.Stat that operates on the overlay filesystem.
   536  func Stat(path string) (fs.FileInfo, error) {
   537  	Trace("Stat", path)
   538  	return overlayStat(path, os.Stat, "stat")
   539  }
   540  
   541  // overlayStat implements lstat or Stat (depending on whether os.Lstat or os.Stat is passed in).
   542  func overlayStat(path string, osStat func(string) (fs.FileInfo, error), opName string) (fs.FileInfo, error) {
   543  	cpath := canonicalize(path)
   544  
   545  	if _, ok := parentIsOverlayFile(filepath.Dir(cpath)); ok {
   546  		return nil, &fs.PathError{Op: opName, Path: cpath, Err: fs.ErrNotExist}
   547  	}
   548  
   549  	node, ok := overlay[cpath]
   550  	if !ok {
   551  		// The file or directory is not overlaid.
   552  		return osStat(path)
   553  	}
   554  
   555  	switch {
   556  	case node.isDeleted():
   557  		return nil, &fs.PathError{Op: opName, Path: cpath, Err: fs.ErrNotExist}
   558  	case node.isDir():
   559  		return fakeDir(filepath.Base(path)), nil
   560  	default:
   561  		// To keep the data model simple, if the overlay contains a symlink we
   562  		// always stat through it (using Stat, not Lstat). That way we don't need to
   563  		// worry about the interaction between Lstat and directories: if a symlink
   564  		// in the overlay points to a directory, we reject it like an ordinary
   565  		// directory.
   566  		fi, err := os.Stat(node.actualFilePath)
   567  		if err != nil {
   568  			return nil, err
   569  		}
   570  		if fi.IsDir() {
   571  			return nil, &fs.PathError{Op: opName, Path: cpath, Err: nonFileInOverlayError(node.actualFilePath)}
   572  		}
   573  		return fakeFile{name: filepath.Base(path), real: fi}, nil
   574  	}
   575  }
   576  
   577  // fakeFile provides an fs.FileInfo implementation for an overlaid file,
   578  // so that the file has the name of the overlaid file, but takes all
   579  // other characteristics of the replacement file.
   580  type fakeFile struct {
   581  	name string
   582  	real fs.FileInfo
   583  }
   584  
   585  func (f fakeFile) Name() string       { return f.name }
   586  func (f fakeFile) Size() int64        { return f.real.Size() }
   587  func (f fakeFile) Mode() fs.FileMode  { return f.real.Mode() }
   588  func (f fakeFile) ModTime() time.Time { return f.real.ModTime() }
   589  func (f fakeFile) IsDir() bool        { return f.real.IsDir() }
   590  func (f fakeFile) Sys() any           { return f.real.Sys() }
   591  
   592  func (f fakeFile) String() string {
   593  	return fs.FormatFileInfo(f)
   594  }
   595  
   596  // missingFile provides an fs.FileInfo for an overlaid file where the
   597  // destination file in the overlay doesn't exist. It returns zero values
   598  // for the fileInfo methods other than Name, set to the file's name, and Mode
   599  // set to ModeIrregular.
   600  type missingFile string
   601  
   602  func (f missingFile) Name() string       { return string(f) }
   603  func (f missingFile) Size() int64        { return 0 }
   604  func (f missingFile) Mode() fs.FileMode  { return fs.ModeIrregular }
   605  func (f missingFile) ModTime() time.Time { return time.Unix(0, 0) }
   606  func (f missingFile) IsDir() bool        { return false }
   607  func (f missingFile) Sys() any           { return nil }
   608  
   609  func (f missingFile) String() string {
   610  	return fs.FormatFileInfo(f)
   611  }
   612  
   613  // fakeDir provides an fs.FileInfo implementation for directories that are
   614  // implicitly created by overlaid files. Each directory in the
   615  // path of an overlaid file is considered to exist in the overlay filesystem.
   616  type fakeDir string
   617  
   618  func (f fakeDir) Name() string       { return string(f) }
   619  func (f fakeDir) Size() int64        { return 0 }
   620  func (f fakeDir) Mode() fs.FileMode  { return fs.ModeDir | 0500 }
   621  func (f fakeDir) ModTime() time.Time { return time.Unix(0, 0) }
   622  func (f fakeDir) IsDir() bool        { return true }
   623  func (f fakeDir) Sys() any           { return nil }
   624  
   625  func (f fakeDir) String() string {
   626  	return fs.FormatFileInfo(f)
   627  }
   628  
   629  // Glob is like filepath.Glob but uses the overlay file system.
   630  func Glob(pattern string) (matches []string, err error) {
   631  	Trace("Glob", pattern)
   632  	// Check pattern is well-formed.
   633  	if _, err := filepath.Match(pattern, ""); err != nil {
   634  		return nil, err
   635  	}
   636  	if !hasMeta(pattern) {
   637  		if _, err = Lstat(pattern); err != nil {
   638  			return nil, nil
   639  		}
   640  		return []string{pattern}, nil
   641  	}
   642  
   643  	dir, file := filepath.Split(pattern)
   644  	volumeLen := 0
   645  	if runtime.GOOS == "windows" {
   646  		volumeLen, dir = cleanGlobPathWindows(dir)
   647  	} else {
   648  		dir = cleanGlobPath(dir)
   649  	}
   650  
   651  	if !hasMeta(dir[volumeLen:]) {
   652  		return glob(dir, file, nil)
   653  	}
   654  
   655  	// Prevent infinite recursion. See issue 15879.
   656  	if dir == pattern {
   657  		return nil, filepath.ErrBadPattern
   658  	}
   659  
   660  	var m []string
   661  	m, err = Glob(dir)
   662  	if err != nil {
   663  		return
   664  	}
   665  	for _, d := range m {
   666  		matches, err = glob(d, file, matches)
   667  		if err != nil {
   668  			return
   669  		}
   670  	}
   671  	return
   672  }
   673  
   674  // cleanGlobPath prepares path for glob matching.
   675  func cleanGlobPath(path string) string {
   676  	switch path {
   677  	case "":
   678  		return "."
   679  	case string(filepath.Separator):
   680  		// do nothing to the path
   681  		return path
   682  	default:
   683  		return path[0 : len(path)-1] // chop off trailing separator
   684  	}
   685  }
   686  
   687  func volumeNameLen(path string) int {
   688  	isSlash := func(c uint8) bool {
   689  		return c == '\\' || c == '/'
   690  	}
   691  	if len(path) < 2 {
   692  		return 0
   693  	}
   694  	// with drive letter
   695  	c := path[0]
   696  	if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
   697  		return 2
   698  	}
   699  	// is it UNC? https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
   700  	if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
   701  		!isSlash(path[2]) && path[2] != '.' {
   702  		// first, leading `\\` and next shouldn't be `\`. its server name.
   703  		for n := 3; n < l-1; n++ {
   704  			// second, next '\' shouldn't be repeated.
   705  			if isSlash(path[n]) {
   706  				n++
   707  				// third, following something characters. its share name.
   708  				if !isSlash(path[n]) {
   709  					if path[n] == '.' {
   710  						break
   711  					}
   712  					for ; n < l; n++ {
   713  						if isSlash(path[n]) {
   714  							break
   715  						}
   716  					}
   717  					return n
   718  				}
   719  				break
   720  			}
   721  		}
   722  	}
   723  	return 0
   724  }
   725  
   726  // cleanGlobPathWindows is windows version of cleanGlobPath.
   727  func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) {
   728  	vollen := volumeNameLen(path)
   729  	switch {
   730  	case path == "":
   731  		return 0, "."
   732  	case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/
   733  		// do nothing to the path
   734  		return vollen + 1, path
   735  	case vollen == len(path) && len(path) == 2: // C:
   736  		return vollen, path + "." // convert C: into C:.
   737  	default:
   738  		if vollen >= len(path) {
   739  			vollen = len(path) - 1
   740  		}
   741  		return vollen, path[0 : len(path)-1] // chop off trailing separator
   742  	}
   743  }
   744  
   745  // glob searches for files matching pattern in the directory dir
   746  // and appends them to matches. If the directory cannot be
   747  // opened, it returns the existing matches. New matches are
   748  // added in lexicographical order.
   749  func glob(dir, pattern string, matches []string) (m []string, e error) {
   750  	m = matches
   751  	fi, err := Stat(dir)
   752  	if err != nil {
   753  		return // ignore I/O error
   754  	}
   755  	if !fi.IsDir() {
   756  		return // ignore I/O error
   757  	}
   758  
   759  	list, err := ReadDir(dir)
   760  	if err != nil {
   761  		return // ignore I/O error
   762  	}
   763  
   764  	var names []string
   765  	for _, info := range list {
   766  		names = append(names, info.Name())
   767  	}
   768  	sort.Strings(names)
   769  
   770  	for _, n := range names {
   771  		matched, err := filepath.Match(pattern, n)
   772  		if err != nil {
   773  			return m, err
   774  		}
   775  		if matched {
   776  			m = append(m, filepath.Join(dir, n))
   777  		}
   778  	}
   779  	return
   780  }
   781  
   782  // hasMeta reports whether path contains any of the magic characters
   783  // recognized by filepath.Match.
   784  func hasMeta(path string) bool {
   785  	magicChars := `*?[`
   786  	if runtime.GOOS != "windows" {
   787  		magicChars = `*?[\`
   788  	}
   789  	return strings.ContainsAny(path, magicChars)
   790  }
   791  

View as plain text