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