• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
7package fsys
8
9import (
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.
33func 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
47var (
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
57func 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.
76var 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.
83type OverlayJSON struct {
84	Replace map[string]string
85}
86
87type node struct {
88	actualFilePath string           // empty if a directory
89	children       map[string]*node // path element → file or directory
90}
91
92func (n *node) isDir() bool {
93	return n.actualFilePath == "" && n.children != nil
94}
95
96func (n *node) isDeleted() bool {
97	return n.actualFilePath == "" && n.children == nil
98}
99
100// TODO(matloob): encapsulate these in an io/fs-like interface
101var overlay map[string]*node // path -> file or directory node
102var 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.
110func 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.
132func 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
158func 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.
249func 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.
272func 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.
297var errNotDir = errors.New("not a directory")
298
299func 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).
306func 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.
331func 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.
393func 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.
402func Open(path string) (*os.File, error) {
403	Trace("Open", path)
404	return openFile(path, os.O_RDONLY, 0)
405}
406
407func 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.
434func 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.
446func 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.
492func 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.
515func 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.
530func 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.
536func 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).
542func 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.
580type fakeFile struct {
581	name string
582	real fs.FileInfo
583}
584
585func (f fakeFile) Name() string       { return f.name }
586func (f fakeFile) Size() int64        { return f.real.Size() }
587func (f fakeFile) Mode() fs.FileMode  { return f.real.Mode() }
588func (f fakeFile) ModTime() time.Time { return f.real.ModTime() }
589func (f fakeFile) IsDir() bool        { return f.real.IsDir() }
590func (f fakeFile) Sys() any           { return f.real.Sys() }
591
592func (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.
600type missingFile string
601
602func (f missingFile) Name() string       { return string(f) }
603func (f missingFile) Size() int64        { return 0 }
604func (f missingFile) Mode() fs.FileMode  { return fs.ModeIrregular }
605func (f missingFile) ModTime() time.Time { return time.Unix(0, 0) }
606func (f missingFile) IsDir() bool        { return false }
607func (f missingFile) Sys() any           { return nil }
608
609func (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.
616type fakeDir string
617
618func (f fakeDir) Name() string       { return string(f) }
619func (f fakeDir) Size() int64        { return 0 }
620func (f fakeDir) Mode() fs.FileMode  { return fs.ModeDir | 0500 }
621func (f fakeDir) ModTime() time.Time { return time.Unix(0, 0) }
622func (f fakeDir) IsDir() bool        { return true }
623func (f fakeDir) Sys() any           { return nil }
624
625func (f fakeDir) String() string {
626	return fs.FormatFileInfo(f)
627}
628
629// Glob is like filepath.Glob but uses the overlay file system.
630func 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.
675func 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
687func 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.
727func 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.
749func 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.
784func hasMeta(path string) bool {
785	magicChars := `*?[`
786	if runtime.GOOS != "windows" {
787		magicChars = `*?[\`
788	}
789	return strings.ContainsAny(path, magicChars)
790}
791