github.com/bir3/gocompiler@v0.9.2202/src/cmd/gocmd/internal/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 "github.com/bir3/gocompiler/src/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 func (f fakeFile) String() string { 587 return fs.FormatFileInfo(f) 588 } 589 590 // missingFile provides an fs.FileInfo for an overlaid file where the 591 // destination file in the overlay doesn't exist. It returns zero values 592 // for the fileInfo methods other than Name, set to the file's name, and Mode 593 // set to ModeIrregular. 594 type missingFile string 595 596 func (f missingFile) Name() string { return string(f) } 597 func (f missingFile) Size() int64 { return 0 } 598 func (f missingFile) Mode() fs.FileMode { return fs.ModeIrregular } 599 func (f missingFile) ModTime() time.Time { return time.Unix(0, 0) } 600 func (f missingFile) IsDir() bool { return false } 601 func (f missingFile) Sys() any { return nil } 602 603 func (f missingFile) String() string { 604 return fs.FormatFileInfo(f) 605 } 606 607 // fakeDir provides an fs.FileInfo implementation for directories that are 608 // implicitly created by overlaid files. Each directory in the 609 // path of an overlaid file is considered to exist in the overlay filesystem. 610 type fakeDir string 611 612 func (f fakeDir) Name() string { return string(f) } 613 func (f fakeDir) Size() int64 { return 0 } 614 func (f fakeDir) Mode() fs.FileMode { return fs.ModeDir | 0500 } 615 func (f fakeDir) ModTime() time.Time { return time.Unix(0, 0) } 616 func (f fakeDir) IsDir() bool { return true } 617 func (f fakeDir) Sys() any { return nil } 618 619 func (f fakeDir) String() string { 620 return fs.FormatFileInfo(f) 621 } 622 623 // Glob is like filepath.Glob but uses the overlay file system. 624 func Glob(pattern string) (matches []string, err error) { 625 Trace("Glob", pattern) 626 // Check pattern is well-formed. 627 if _, err := filepath.Match(pattern, ""); err != nil { 628 return nil, err 629 } 630 if !hasMeta(pattern) { 631 if _, err = Lstat(pattern); err != nil { 632 return nil, nil 633 } 634 return []string{pattern}, nil 635 } 636 637 dir, file := filepath.Split(pattern) 638 volumeLen := 0 639 if runtime.GOOS == "windows" { 640 volumeLen, dir = cleanGlobPathWindows(dir) 641 } else { 642 dir = cleanGlobPath(dir) 643 } 644 645 if !hasMeta(dir[volumeLen:]) { 646 return glob(dir, file, nil) 647 } 648 649 // Prevent infinite recursion. See issue 15879. 650 if dir == pattern { 651 return nil, filepath.ErrBadPattern 652 } 653 654 var m []string 655 m, err = Glob(dir) 656 if err != nil { 657 return 658 } 659 for _, d := range m { 660 matches, err = glob(d, file, matches) 661 if err != nil { 662 return 663 } 664 } 665 return 666 } 667 668 // cleanGlobPath prepares path for glob matching. 669 func cleanGlobPath(path string) string { 670 switch path { 671 case "": 672 return "." 673 case string(filepath.Separator): 674 // do nothing to the path 675 return path 676 default: 677 return path[0 : len(path)-1] // chop off trailing separator 678 } 679 } 680 681 func volumeNameLen(path string) int { 682 isSlash := func(c uint8) bool { 683 return c == '\\' || c == '/' 684 } 685 if len(path) < 2 { 686 return 0 687 } 688 // with drive letter 689 c := path[0] 690 if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { 691 return 2 692 } 693 // is it UNC? https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file 694 if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) && 695 !isSlash(path[2]) && path[2] != '.' { 696 // first, leading `\\` and next shouldn't be `\`. its server name. 697 for n := 3; n < l-1; n++ { 698 // second, next '\' shouldn't be repeated. 699 if isSlash(path[n]) { 700 n++ 701 // third, following something characters. its share name. 702 if !isSlash(path[n]) { 703 if path[n] == '.' { 704 break 705 } 706 for ; n < l; n++ { 707 if isSlash(path[n]) { 708 break 709 } 710 } 711 return n 712 } 713 break 714 } 715 } 716 } 717 return 0 718 } 719 720 // cleanGlobPathWindows is windows version of cleanGlobPath. 721 func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) { 722 vollen := volumeNameLen(path) 723 switch { 724 case path == "": 725 return 0, "." 726 case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/ 727 // do nothing to the path 728 return vollen + 1, path 729 case vollen == len(path) && len(path) == 2: // C: 730 return vollen, path + "." // convert C: into C:. 731 default: 732 if vollen >= len(path) { 733 vollen = len(path) - 1 734 } 735 return vollen, path[0 : len(path)-1] // chop off trailing separator 736 } 737 } 738 739 // glob searches for files matching pattern in the directory dir 740 // and appends them to matches. If the directory cannot be 741 // opened, it returns the existing matches. New matches are 742 // added in lexicographical order. 743 func glob(dir, pattern string, matches []string) (m []string, e error) { 744 m = matches 745 fi, err := Stat(dir) 746 if err != nil { 747 return // ignore I/O error 748 } 749 if !fi.IsDir() { 750 return // ignore I/O error 751 } 752 753 list, err := ReadDir(dir) 754 if err != nil { 755 return // ignore I/O error 756 } 757 758 var names []string 759 for _, info := range list { 760 names = append(names, info.Name()) 761 } 762 sort.Strings(names) 763 764 for _, n := range names { 765 matched, err := filepath.Match(pattern, n) 766 if err != nil { 767 return m, err 768 } 769 if matched { 770 m = append(m, filepath.Join(dir, n)) 771 } 772 } 773 return 774 } 775 776 // hasMeta reports whether path contains any of the magic characters 777 // recognized by filepath.Match. 778 func hasMeta(path string) bool { 779 magicChars := `*?[` 780 if runtime.GOOS != "windows" { 781 magicChars = `*?[\` 782 } 783 return strings.ContainsAny(path, magicChars) 784 }