github.com/visualfc/goembed@v0.3.3/fsys/fsys.go (about)

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