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