1// Copyright 2016 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package pathtools 16 17import ( 18 "bytes" 19 "fmt" 20 "io" 21 "io/fs" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "sort" 26 "strings" 27 "sync" 28 "syscall" 29 "time" 30) 31 32// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go" 33 34type ShouldFollowSymlinks bool 35 36const ( 37 FollowSymlinks = ShouldFollowSymlinks(true) 38 DontFollowSymlinks = ShouldFollowSymlinks(false) 39) 40 41var OsFs FileSystem = &osFs{} 42 43func MockFs(files map[string][]byte) FileSystem { 44 fs := &mockFs{ 45 files: make(map[string][]byte, len(files)), 46 dirs: make(map[string]bool), 47 symlinks: make(map[string]string), 48 all: []string(nil), 49 } 50 51 for f, b := range files { 52 if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 { 53 fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1]) 54 continue 55 } 56 57 fs.files[filepath.Clean(f)] = b 58 dir := filepath.Dir(f) 59 for dir != "." && dir != "/" { 60 fs.dirs[dir] = true 61 dir = filepath.Dir(dir) 62 } 63 fs.dirs[dir] = true 64 } 65 66 fs.dirs["."] = true 67 fs.dirs["/"] = true 68 69 for f := range fs.files { 70 fs.all = append(fs.all, f) 71 } 72 73 for d := range fs.dirs { 74 fs.all = append(fs.all, d) 75 } 76 77 for s := range fs.symlinks { 78 fs.all = append(fs.all, s) 79 } 80 81 sort.Strings(fs.all) 82 83 return fs 84} 85 86type ReaderAtSeekerCloser interface { 87 io.Reader 88 io.ReaderAt 89 io.Seeker 90 io.Closer 91} 92 93type FileSystem interface { 94 // Open opens a file for reading. Follows symlinks. 95 Open(name string) (ReaderAtSeekerCloser, error) 96 97 // OpenFile opens a file for read/write, if the file does not exist, and the 98 // O_CREATE flag is passed, it is created with mode perm (before umask). 99 OpenFile(name string, flag int, perm fs.FileMode) (io.WriteCloser, error) 100 101 // Exists returns whether the file exists and whether it is a directory. Follows symlinks. 102 Exists(name string) (bool, bool, error) 103 104 Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) 105 glob(pattern string) (matches []string, err error) 106 107 // IsDir returns true if the path points to a directory, false it it points to a file. Follows symlinks. 108 // Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist. 109 IsDir(name string) (bool, error) 110 111 // IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does 112 // not exist. Returns os.ErrNotExist if the path does not exist. 113 IsSymlink(name string) (bool, error) 114 115 // Lstat returns info on a file without following symlinks. 116 Lstat(name string) (os.FileInfo, error) 117 118 // Lstat returns info on a file. 119 Stat(name string) (os.FileInfo, error) 120 121 // ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested. 122 ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) 123 124 // ReadDirNames returns a list of everything in a directory. 125 ReadDirNames(name string) ([]string, error) 126 127 // Readlink returns the destination of the named symbolic link. 128 Readlink(name string) (string, error) 129} 130 131// osFs implements FileSystem using the local disk. 132type osFs struct { 133 srcDir string 134 openFilesChan chan bool 135} 136 137func NewOsFs(path string) FileSystem { 138 // Darwin has a default limit of 256 open files, rate limit open files to 200 139 limit := 200 140 return &osFs{ 141 srcDir: path, 142 openFilesChan: make(chan bool, limit), 143 } 144} 145 146func (fs *osFs) acquire() { 147 if fs.openFilesChan != nil { 148 fs.openFilesChan <- true 149 } 150} 151 152func (fs *osFs) release() { 153 if fs.openFilesChan != nil { 154 <-fs.openFilesChan 155 } 156} 157 158func (fs *osFs) toAbs(path string) string { 159 if filepath.IsAbs(path) { 160 return path 161 } 162 return filepath.Join(fs.srcDir, path) 163} 164 165func (fs *osFs) removeSrcDirPrefix(path string) string { 166 if fs.srcDir == "" { 167 return path 168 } 169 rel, err := filepath.Rel(fs.srcDir, path) 170 if err != nil { 171 panic(fmt.Errorf("unexpected failure in removeSrcDirPrefix filepath.Rel(%s, %s): %s", 172 fs.srcDir, path, err)) 173 } 174 if strings.HasPrefix(rel, "../") { 175 panic(fmt.Errorf("unexpected relative path outside directory in removeSrcDirPrefix filepath.Rel(%s, %s): %s", 176 fs.srcDir, path, rel)) 177 } 178 return rel 179} 180 181func (fs *osFs) removeSrcDirPrefixes(paths []string) []string { 182 if fs.srcDir != "" { 183 for i, path := range paths { 184 paths[i] = fs.removeSrcDirPrefix(path) 185 } 186 } 187 return paths 188} 189 190// OsFile wraps an os.File to also release open file descriptors semaphore on close 191type OsFile struct { 192 *os.File 193 fs *osFs 194} 195 196// Close closes file and releases the open file descriptor semaphore 197func (f *OsFile) Close() error { 198 err := f.File.Close() 199 f.fs.release() 200 return err 201} 202 203func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) { 204 fs.acquire() 205 f, err := os.Open(fs.toAbs(name)) 206 if err != nil { 207 return nil, err 208 } 209 return &OsFile{f, fs}, nil 210} 211 212func (fs *osFs) OpenFile(name string, flag int, perm fs.FileMode) (io.WriteCloser, error) { 213 fs.acquire() 214 f, err := os.OpenFile(fs.toAbs(name), flag, perm) 215 if err != nil { 216 return nil, err 217 } 218 return &OsFile{f, fs}, nil 219} 220 221func (fs *osFs) Exists(name string) (bool, bool, error) { 222 stat, err := os.Stat(fs.toAbs(name)) 223 if err == nil { 224 return true, stat.IsDir(), nil 225 } else if os.IsNotExist(err) { 226 return false, false, nil 227 } else { 228 return false, false, err 229 } 230} 231 232func (fs *osFs) IsDir(name string) (bool, error) { 233 info, err := os.Stat(fs.toAbs(name)) 234 if err != nil { 235 return false, err 236 } 237 return info.IsDir(), nil 238} 239 240func (fs *osFs) IsSymlink(name string) (bool, error) { 241 if info, err := os.Lstat(fs.toAbs(name)); err != nil { 242 return false, err 243 } else { 244 return info.Mode()&os.ModeSymlink != 0, nil 245 } 246} 247 248func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) { 249 return startGlob(fs, pattern, excludes, follow) 250} 251 252func (fs *osFs) glob(pattern string) ([]string, error) { 253 fs.acquire() 254 defer fs.release() 255 paths, err := filepath.Glob(fs.toAbs(pattern)) 256 fs.removeSrcDirPrefixes(paths) 257 return paths, err 258} 259 260func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) { 261 return os.Lstat(fs.toAbs(path)) 262} 263 264func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) { 265 return os.Stat(fs.toAbs(path)) 266} 267 268// Returns a list of all directories under dir 269func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) { 270 return listDirsRecursive(fs, name, follow) 271} 272 273func (fs *osFs) ReadDirNames(name string) ([]string, error) { 274 fs.acquire() 275 defer fs.release() 276 dir, err := os.Open(fs.toAbs(name)) 277 if err != nil { 278 return nil, err 279 } 280 defer dir.Close() 281 282 contents, err := dir.Readdirnames(-1) 283 if err != nil { 284 return nil, err 285 } 286 287 sort.Strings(contents) 288 return contents, nil 289} 290 291func (fs *osFs) Readlink(name string) (string, error) { 292 return os.Readlink(fs.toAbs(name)) 293} 294 295type mockFs struct { 296 files map[string][]byte 297 dirs map[string]bool 298 symlinks map[string]string 299 all []string 300 lock sync.RWMutex 301} 302 303func (m *mockFs) followSymlinks(name string) string { 304 dir, file := quickSplit(name) 305 if dir != "." && dir != "/" { 306 dir = m.followSymlinks(dir) 307 } 308 name = filepath.Join(dir, file) 309 310 for i := 0; i < 255; i++ { 311 i++ 312 if i > 255 { 313 panic("symlink loop") 314 } 315 to, exists := m.symlinks[name] 316 if !exists { 317 break 318 } 319 if filepath.IsAbs(to) { 320 name = to 321 } else { 322 name = filepath.Join(dir, to) 323 } 324 } 325 return name 326} 327 328func (m *mockFs) Open(name string) (ReaderAtSeekerCloser, error) { 329 m.lock.RLock() 330 defer m.lock.RUnlock() 331 name = filepath.Clean(name) 332 name = m.followSymlinks(name) 333 if f, ok := m.files[name]; ok { 334 return struct { 335 io.Closer 336 *bytes.Reader 337 }{ 338 ioutil.NopCloser(nil), 339 bytes.NewReader(f), 340 }, nil 341 } 342 343 return nil, &os.PathError{ 344 Op: "open", 345 Path: name, 346 Err: os.ErrNotExist, 347 } 348} 349 350type MockFileWriter struct { 351 name string 352 fs *mockFs 353} 354 355func (b *MockFileWriter) Write(p []byte) (n int, err error) { 356 b.fs.lock.Lock() 357 defer b.fs.lock.Unlock() 358 b.fs.files[b.name] = append(b.fs.files[b.name], p...) 359 return n, nil 360} 361func (m *mockFs) OpenFile(name string, flag int, perm fs.FileMode) (io.WriteCloser, error) { 362 // For mockFs we simplify the logic here by just either creating a new file or 363 // truncating an existing one. 364 m.lock.Lock() 365 defer m.lock.Unlock() 366 name = filepath.Clean(name) 367 name = m.followSymlinks(name) 368 m.files[name] = []byte{} 369 return struct { 370 io.Closer 371 io.Writer 372 }{ 373 ioutil.NopCloser(nil), 374 &MockFileWriter{ 375 name: name, 376 fs: m, 377 }, 378 }, nil 379} 380 381func (m *mockFs) Exists(name string) (bool, bool, error) { 382 m.lock.RLock() 383 defer m.lock.RUnlock() 384 name = filepath.Clean(name) 385 name = m.followSymlinks(name) 386 if _, ok := m.files[name]; ok { 387 return ok, false, nil 388 } 389 if _, ok := m.dirs[name]; ok { 390 return ok, true, nil 391 } 392 return false, false, nil 393} 394 395func (m *mockFs) IsDir(name string) (bool, error) { 396 m.lock.RLock() 397 defer m.lock.RUnlock() 398 dir := filepath.Dir(name) 399 if dir != "." && dir != "/" { 400 isDir, err := m.IsDir(dir) 401 402 if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR { 403 isDir = false 404 } else if err != nil { 405 return false, err 406 } 407 408 if !isDir { 409 return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR) 410 } 411 } 412 413 name = filepath.Clean(name) 414 name = m.followSymlinks(name) 415 416 if _, ok := m.dirs[name]; ok { 417 return true, nil 418 } 419 if _, ok := m.files[name]; ok { 420 return false, nil 421 } 422 return false, os.ErrNotExist 423} 424 425func (m *mockFs) IsSymlink(name string) (bool, error) { 426 m.lock.RLock() 427 defer m.lock.RUnlock() 428 dir, file := quickSplit(name) 429 dir = m.followSymlinks(dir) 430 name = filepath.Join(dir, file) 431 432 if _, isSymlink := m.symlinks[name]; isSymlink { 433 return true, nil 434 } 435 if _, isDir := m.dirs[name]; isDir { 436 return false, nil 437 } 438 if _, isFile := m.files[name]; isFile { 439 return false, nil 440 } 441 return false, os.ErrNotExist 442} 443 444func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) { 445 return startGlob(m, pattern, excludes, follow) 446} 447 448func unescapeGlob(s string) string { 449 i := 0 450 for i < len(s) { 451 if s[i] == '\\' { 452 s = s[:i] + s[i+1:] 453 } else { 454 i++ 455 } 456 } 457 return s 458} 459 460func (m *mockFs) glob(pattern string) ([]string, error) { 461 dir, file := quickSplit(pattern) 462 463 dir = unescapeGlob(dir) 464 toDir := m.followSymlinks(dir) 465 466 var matches []string 467 for _, f := range m.all { 468 fDir, fFile := quickSplit(f) 469 if toDir == fDir { 470 match, err := filepath.Match(file, fFile) 471 if err != nil { 472 return nil, err 473 } 474 if (f == "." || f == "/") && f != pattern { 475 // filepath.Glob won't return "." or "/" unless the pattern was "." or "/" 476 match = false 477 } 478 if match { 479 matches = append(matches, filepath.Join(dir, fFile)) 480 } 481 } 482 } 483 return matches, nil 484} 485 486type mockStat struct { 487 name string 488 size int64 489 mode os.FileMode 490} 491 492func (ms *mockStat) Name() string { return ms.name } 493func (ms *mockStat) IsDir() bool { return ms.Mode().IsDir() } 494func (ms *mockStat) Size() int64 { return ms.size } 495func (ms *mockStat) Mode() os.FileMode { return ms.mode } 496func (ms *mockStat) ModTime() time.Time { return time.Time{} } 497func (ms *mockStat) Sys() interface{} { return nil } 498 499func (m *mockFs) Lstat(name string) (os.FileInfo, error) { 500 m.lock.RLock() 501 defer m.lock.RUnlock() 502 dir, file := quickSplit(name) 503 dir = m.followSymlinks(dir) 504 name = filepath.Join(dir, file) 505 506 ms := mockStat{ 507 name: file, 508 } 509 510 if symlink, isSymlink := m.symlinks[name]; isSymlink { 511 ms.mode = os.ModeSymlink 512 ms.size = int64(len(symlink)) 513 } else if _, isDir := m.dirs[name]; isDir { 514 ms.mode = os.ModeDir 515 } else if _, isFile := m.files[name]; isFile { 516 ms.mode = 0 517 ms.size = int64(len(m.files[name])) 518 } else { 519 return nil, os.ErrNotExist 520 } 521 522 return &ms, nil 523} 524 525func (m *mockFs) Stat(name string) (os.FileInfo, error) { 526 m.lock.RLock() 527 defer m.lock.RUnlock() 528 name = filepath.Clean(name) 529 origName := name 530 name = m.followSymlinks(name) 531 532 ms := mockStat{ 533 name: filepath.Base(origName), 534 size: int64(len(m.files[name])), 535 } 536 537 if _, isDir := m.dirs[name]; isDir { 538 ms.mode = os.ModeDir 539 } else if _, isFile := m.files[name]; isFile { 540 ms.mode = 0 541 ms.size = int64(len(m.files[name])) 542 } else { 543 return nil, os.ErrNotExist 544 } 545 546 return &ms, nil 547} 548 549func (m *mockFs) ReadDirNames(name string) ([]string, error) { 550 name = filepath.Clean(name) 551 name = m.followSymlinks(name) 552 553 exists, isDir, err := m.Exists(name) 554 if err != nil { 555 return nil, err 556 } 557 if !exists { 558 return nil, os.ErrNotExist 559 } 560 if !isDir { 561 return nil, os.NewSyscallError("readdir", syscall.ENOTDIR) 562 } 563 564 var ret []string 565 for _, f := range m.all { 566 dir, file := quickSplit(f) 567 if dir == name && len(file) > 0 && file[0] != '.' { 568 ret = append(ret, file) 569 } 570 } 571 return ret, nil 572} 573 574func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) { 575 return listDirsRecursive(m, name, follow) 576} 577 578func (m *mockFs) Readlink(name string) (string, error) { 579 dir, file := quickSplit(name) 580 dir = m.followSymlinks(dir) 581 582 origName := name 583 name = filepath.Join(dir, file) 584 585 if dest, isSymlink := m.symlinks[name]; isSymlink { 586 return dest, nil 587 } 588 589 if exists, _, err := m.Exists(name); err != nil { 590 return "", err 591 } else if !exists { 592 return "", os.ErrNotExist 593 } else { 594 return "", os.NewSyscallError("readlink: "+origName, syscall.EINVAL) 595 } 596} 597 598func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) { 599 name = filepath.Clean(name) 600 601 isDir, err := fs.IsDir(name) 602 if err != nil { 603 return nil, err 604 } 605 606 if !isDir { 607 return nil, nil 608 } 609 610 dirs := []string{name} 611 612 subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0) 613 if err != nil { 614 return nil, err 615 } 616 617 for _, d := range subDirs { 618 dirs = append(dirs, filepath.Join(name, d)) 619 } 620 621 return dirs, nil 622} 623 624func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) { 625 depth++ 626 if depth > 255 { 627 return nil, fmt.Errorf("too many symlinks") 628 } 629 contents, err := fs.ReadDirNames(name) 630 if err != nil { 631 return nil, err 632 } 633 634 var dirs []string 635 for _, f := range contents { 636 if f[0] == '.' { 637 continue 638 } 639 f = filepath.Join(name, f) 640 var info os.FileInfo 641 if follow == DontFollowSymlinks { 642 info, err = fs.Lstat(f) 643 if err != nil { 644 continue 645 } 646 if info.Mode()&os.ModeSymlink != 0 { 647 continue 648 } 649 } else { 650 info, err = fs.Stat(f) 651 if err != nil { 652 continue 653 } 654 } 655 if info.IsDir() { 656 dirs = append(dirs, f) 657 subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth) 658 if err != nil { 659 return nil, err 660 } 661 for _, s := range subDirs { 662 dirs = append(dirs, filepath.Join(f, s)) 663 } 664 } 665 } 666 667 for i, d := range dirs { 668 rel, err := filepath.Rel(name, d) 669 if err != nil { 670 return nil, err 671 } 672 dirs[i] = rel 673 } 674 675 return dirs, nil 676} 677