1// Copyright 2017 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 fs 16 17import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "os/user" 25 "path/filepath" 26 "sync" 27 "time" 28) 29 30var OsFs FileSystem = osFs{} 31 32func NewMockFs(files map[string][]byte) *MockFs { 33 workDir := "/cwd" 34 fs := &MockFs{ 35 Clock: NewClock(time.Unix(2, 2)), 36 workDir: workDir, 37 } 38 fs.root = *fs.newDir() 39 fs.MkDirs(workDir) 40 41 for path, bytes := range files { 42 dir := filepath.Dir(path) 43 fs.MkDirs(dir) 44 fs.WriteFile(path, bytes, 0777) 45 } 46 47 return fs 48} 49 50type FileSystem interface { 51 // getting information about files 52 Open(name string) (file io.ReadCloser, err error) 53 Lstat(path string) (stats os.FileInfo, err error) 54 ReadDir(path string) (contents []DirEntryInfo, err error) 55 56 InodeNumber(info os.FileInfo) (number uint64, err error) 57 DeviceNumber(info os.FileInfo) (number uint64, err error) 58 PermTime(info os.FileInfo) (time time.Time, err error) 59 60 // changing contents of the filesystem 61 Rename(oldPath string, newPath string) (err error) 62 WriteFile(path string, data []byte, perm os.FileMode) (err error) 63 Remove(path string) (err error) 64 RemoveAll(path string) (err error) 65 66 // metadata about the filesystem 67 ViewId() (id string) // Some unique id of the user accessing the filesystem 68} 69 70// DentryInfo is a subset of the functionality available through os.FileInfo that might be able 71// to be gleaned through only a syscall.Getdents without requiring a syscall.Lstat of every file. 72type DirEntryInfo interface { 73 Name() string 74 Mode() os.FileMode // the file type encoded as an os.FileMode 75 IsDir() bool 76} 77 78type dirEntryInfo struct { 79 name string 80 mode os.FileMode 81 modeExists bool 82} 83 84var _ DirEntryInfo = os.FileInfo(nil) 85 86func (d *dirEntryInfo) Name() string { return d.name } 87func (d *dirEntryInfo) Mode() os.FileMode { return d.mode } 88func (d *dirEntryInfo) IsDir() bool { return d.mode.IsDir() } 89func (d *dirEntryInfo) String() string { return d.name + ": " + d.mode.String() } 90 91// osFs implements FileSystem using the local disk. 92type osFs struct{} 93 94var _ FileSystem = (*osFs)(nil) 95 96func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) } 97 98func (osFs) Lstat(path string) (stats os.FileInfo, err error) { 99 return os.Lstat(path) 100} 101 102func (osFs) ReadDir(path string) (contents []DirEntryInfo, err error) { 103 entries, err := readdir(path) 104 if err != nil { 105 return nil, err 106 } 107 for _, entry := range entries { 108 contents = append(contents, entry) 109 } 110 111 return contents, nil 112} 113 114func (osFs) Rename(oldPath string, newPath string) error { 115 return os.Rename(oldPath, newPath) 116} 117 118func (osFs) WriteFile(path string, data []byte, perm os.FileMode) error { 119 return ioutil.WriteFile(path, data, perm) 120} 121 122func (osFs) Remove(path string) error { 123 return os.Remove(path) 124} 125 126func (osFs) RemoveAll(path string) error { 127 return os.RemoveAll(path) 128} 129 130func (osFs) ViewId() (id string) { 131 user, err := user.Current() 132 if err != nil { 133 return "" 134 } 135 username := user.Username 136 137 hostname, err := os.Hostname() 138 if err != nil { 139 return "" 140 } 141 142 return username + "@" + hostname 143} 144 145type Clock struct { 146 time time.Time 147} 148 149func NewClock(startTime time.Time) *Clock { 150 return &Clock{time: startTime} 151 152} 153 154func (c *Clock) Tick() { 155 c.time = c.time.Add(time.Microsecond) 156} 157 158func (c *Clock) Time() time.Time { 159 return c.time 160} 161 162// given "/a/b/c/d", pathSplit returns ("/a/b/c", "d") 163func pathSplit(path string) (dir string, leaf string) { 164 dir, leaf = filepath.Split(path) 165 if dir != "/" && len(dir) > 0 { 166 dir = dir[:len(dir)-1] 167 } 168 return dir, leaf 169} 170 171// MockFs supports singlethreaded writes and multithreaded reads 172type MockFs struct { 173 // configuration 174 viewId string // 175 deviceNumber uint64 176 177 // implementation 178 root mockDir 179 Clock *Clock 180 workDir string 181 nextInodeNumber uint64 182 183 // history of requests, for tests to check 184 StatCalls []string 185 ReadDirCalls []string 186 aggregatesLock sync.Mutex 187} 188 189var _ FileSystem = (*MockFs)(nil) 190 191type mockInode struct { 192 modTime time.Time 193 permTime time.Time 194 sys interface{} 195 inodeNumber uint64 196 readErr error 197} 198 199func (m mockInode) ModTime() time.Time { 200 return m.modTime 201} 202 203func (m mockInode) Sys() interface{} { 204 return m.sys 205} 206 207type mockFile struct { 208 bytes []byte 209 210 mockInode 211} 212 213type mockLink struct { 214 target string 215 216 mockInode 217} 218 219type mockDir struct { 220 mockInode 221 222 subdirs map[string]*mockDir 223 files map[string]*mockFile 224 symlinks map[string]*mockLink 225} 226 227func (m *MockFs) resolve(path string, followLastLink bool) (result string, err error) { 228 if !filepath.IsAbs(path) { 229 path = filepath.Join(m.workDir, path) 230 } 231 path = filepath.Clean(path) 232 233 return m.followLinks(path, followLastLink, 10) 234} 235 236// note that followLinks can return a file path that doesn't exist 237func (m *MockFs) followLinks(path string, followLastLink bool, count int) (canonicalPath string, err error) { 238 if path == "/" { 239 return path, nil 240 } 241 242 parentPath, leaf := pathSplit(path) 243 if parentPath == path { 244 err = fmt.Errorf("Internal error: %v yields itself as a parent", path) 245 panic(err.Error()) 246 } 247 248 parentPath, err = m.followLinks(parentPath, true, count) 249 if err != nil { 250 return "", err 251 } 252 253 parentNode, err := m.getDir(parentPath, false) 254 if err != nil { 255 return "", err 256 } 257 if parentNode.readErr != nil { 258 return "", &os.PathError{ 259 Op: "read", 260 Path: path, 261 Err: parentNode.readErr, 262 } 263 } 264 265 link, isLink := parentNode.symlinks[leaf] 266 if isLink && followLastLink { 267 if count <= 0 { 268 // probably a loop 269 return "", &os.PathError{ 270 Op: "read", 271 Path: path, 272 Err: fmt.Errorf("too many levels of symbolic links"), 273 } 274 } 275 276 if link.readErr != nil { 277 return "", &os.PathError{ 278 Op: "read", 279 Path: path, 280 Err: link.readErr, 281 } 282 } 283 284 target := m.followLink(link, parentPath) 285 return m.followLinks(target, followLastLink, count-1) 286 } 287 return path, nil 288} 289 290func (m *MockFs) followLink(link *mockLink, parentPath string) (result string) { 291 return filepath.Clean(filepath.Join(parentPath, link.target)) 292} 293 294func (m *MockFs) getFile(parentDir *mockDir, fileName string) (file *mockFile, err error) { 295 file, isFile := parentDir.files[fileName] 296 if !isFile { 297 _, isDir := parentDir.subdirs[fileName] 298 _, isLink := parentDir.symlinks[fileName] 299 if isDir || isLink { 300 return nil, &os.PathError{ 301 Op: "open", 302 Path: fileName, 303 Err: os.ErrInvalid, 304 } 305 } 306 307 return nil, &os.PathError{ 308 Op: "open", 309 Path: fileName, 310 Err: os.ErrNotExist, 311 } 312 } 313 if file.readErr != nil { 314 return nil, &os.PathError{ 315 Op: "open", 316 Path: fileName, 317 Err: file.readErr, 318 } 319 } 320 return file, nil 321} 322 323func (m *MockFs) getInode(parentDir *mockDir, name string) (inode *mockInode, err error) { 324 file, isFile := parentDir.files[name] 325 if isFile { 326 return &file.mockInode, nil 327 } 328 link, isLink := parentDir.symlinks[name] 329 if isLink { 330 return &link.mockInode, nil 331 } 332 dir, isDir := parentDir.subdirs[name] 333 if isDir { 334 return &dir.mockInode, nil 335 } 336 return nil, &os.PathError{ 337 Op: "stat", 338 Path: name, 339 Err: os.ErrNotExist, 340 } 341 342} 343 344func (m *MockFs) Open(path string) (io.ReadCloser, error) { 345 path, err := m.resolve(path, true) 346 if err != nil { 347 return nil, err 348 } 349 350 if err != nil { 351 return nil, err 352 } 353 354 parentPath, base := pathSplit(path) 355 parentDir, err := m.getDir(parentPath, false) 356 if err != nil { 357 return nil, err 358 } 359 file, err := m.getFile(parentDir, base) 360 if err != nil { 361 return nil, err 362 } 363 return struct { 364 io.Closer 365 *bytes.Reader 366 }{ 367 ioutil.NopCloser(nil), 368 bytes.NewReader(file.bytes), 369 }, nil 370 371} 372 373// a mockFileInfo is for exporting file stats in a way that satisfies the FileInfo interface 374type mockFileInfo struct { 375 path string 376 size int64 377 modTime time.Time // time at which the inode's contents were modified 378 permTime time.Time // time at which the inode's permissions were modified 379 isDir bool 380 inodeNumber uint64 381 deviceNumber uint64 382} 383 384func (m *mockFileInfo) Name() string { 385 return m.path 386} 387 388func (m *mockFileInfo) Size() int64 { 389 return m.size 390} 391 392func (m *mockFileInfo) Mode() os.FileMode { 393 return 0 394} 395 396func (m *mockFileInfo) ModTime() time.Time { 397 return m.modTime 398} 399 400func (m *mockFileInfo) IsDir() bool { 401 return m.isDir 402} 403 404func (m *mockFileInfo) Sys() interface{} { 405 return nil 406} 407 408func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) { 409 return &mockFileInfo{ 410 path: path, 411 size: 1, 412 modTime: d.modTime, 413 permTime: d.permTime, 414 isDir: true, 415 inodeNumber: d.inodeNumber, 416 deviceNumber: m.deviceNumber, 417 } 418 419} 420 421func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) { 422 return &mockFileInfo{ 423 path: path, 424 size: 1, 425 modTime: f.modTime, 426 permTime: f.permTime, 427 isDir: false, 428 inodeNumber: f.inodeNumber, 429 deviceNumber: m.deviceNumber, 430 } 431} 432 433func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) { 434 return &mockFileInfo{ 435 path: path, 436 size: 1, 437 modTime: l.modTime, 438 permTime: l.permTime, 439 isDir: false, 440 inodeNumber: l.inodeNumber, 441 deviceNumber: m.deviceNumber, 442 } 443} 444 445func (m *MockFs) Lstat(path string) (stats os.FileInfo, err error) { 446 // update aggregates 447 m.aggregatesLock.Lock() 448 m.StatCalls = append(m.StatCalls, path) 449 m.aggregatesLock.Unlock() 450 451 // resolve symlinks 452 path, err = m.resolve(path, false) 453 if err != nil { 454 return nil, err 455 } 456 457 // special case for root dir 458 if path == "/" { 459 return m.dirToFileInfo(&m.root, "/"), nil 460 } 461 462 // determine type and handle appropriately 463 parentPath, baseName := pathSplit(path) 464 dir, err := m.getDir(parentPath, false) 465 if err != nil { 466 return nil, err 467 } 468 subdir, subdirExists := dir.subdirs[baseName] 469 if subdirExists { 470 return m.dirToFileInfo(subdir, path), nil 471 } 472 file, fileExists := dir.files[baseName] 473 if fileExists { 474 return m.fileToFileInfo(file, path), nil 475 } 476 link, linkExists := dir.symlinks[baseName] 477 if linkExists { 478 return m.linkToFileInfo(link, path), nil 479 } 480 // not found 481 return nil, &os.PathError{ 482 Op: "stat", 483 Path: path, 484 Err: os.ErrNotExist, 485 } 486} 487 488func (m *MockFs) InodeNumber(info os.FileInfo) (number uint64, err error) { 489 mockInfo, ok := info.(*mockFileInfo) 490 if ok { 491 return mockInfo.inodeNumber, nil 492 } 493 return 0, fmt.Errorf("%v is not a mockFileInfo", info) 494} 495func (m *MockFs) DeviceNumber(info os.FileInfo) (number uint64, err error) { 496 mockInfo, ok := info.(*mockFileInfo) 497 if ok { 498 return mockInfo.deviceNumber, nil 499 } 500 return 0, fmt.Errorf("%v is not a mockFileInfo", info) 501} 502func (m *MockFs) PermTime(info os.FileInfo) (when time.Time, err error) { 503 mockInfo, ok := info.(*mockFileInfo) 504 if ok { 505 return mockInfo.permTime, nil 506 } 507 return time.Date(0, 0, 0, 0, 0, 0, 0, nil), 508 fmt.Errorf("%v is not a mockFileInfo", info) 509} 510 511func (m *MockFs) ReadDir(path string) (contents []DirEntryInfo, err error) { 512 // update aggregates 513 m.aggregatesLock.Lock() 514 m.ReadDirCalls = append(m.ReadDirCalls, path) 515 m.aggregatesLock.Unlock() 516 517 // locate directory 518 path, err = m.resolve(path, true) 519 if err != nil { 520 return nil, err 521 } 522 results := []DirEntryInfo{} 523 dir, err := m.getDir(path, false) 524 if err != nil { 525 return nil, err 526 } 527 if dir.readErr != nil { 528 return nil, &os.PathError{ 529 Op: "read", 530 Path: path, 531 Err: dir.readErr, 532 } 533 } 534 // describe its contents 535 for name, subdir := range dir.subdirs { 536 dirInfo := m.dirToFileInfo(subdir, name) 537 results = append(results, dirInfo) 538 } 539 for name, file := range dir.files { 540 info := m.fileToFileInfo(file, name) 541 results = append(results, info) 542 } 543 for name, link := range dir.symlinks { 544 info := m.linkToFileInfo(link, name) 545 results = append(results, info) 546 } 547 return results, nil 548} 549 550func (m *MockFs) Rename(sourcePath string, destPath string) error { 551 // validate source parent exists 552 sourcePath, err := m.resolve(sourcePath, false) 553 if err != nil { 554 return err 555 } 556 sourceParentPath := filepath.Dir(sourcePath) 557 sourceParentDir, err := m.getDir(sourceParentPath, false) 558 if err != nil { 559 return err 560 } 561 if sourceParentDir == nil { 562 return &os.PathError{ 563 Op: "move", 564 Path: sourcePath, 565 Err: os.ErrNotExist, 566 } 567 } 568 if sourceParentDir.readErr != nil { 569 return &os.PathError{ 570 Op: "move", 571 Path: sourcePath, 572 Err: sourceParentDir.readErr, 573 } 574 } 575 576 // validate dest parent exists 577 destPath, err = m.resolve(destPath, false) 578 destParentPath := filepath.Dir(destPath) 579 destParentDir, err := m.getDir(destParentPath, false) 580 if err != nil { 581 return err 582 } 583 if destParentDir == nil { 584 return &os.PathError{ 585 Op: "move", 586 Path: destParentPath, 587 Err: os.ErrNotExist, 588 } 589 } 590 if destParentDir.readErr != nil { 591 return &os.PathError{ 592 Op: "move", 593 Path: destParentPath, 594 Err: destParentDir.readErr, 595 } 596 } 597 // check the source and dest themselves 598 sourceBase := filepath.Base(sourcePath) 599 destBase := filepath.Base(destPath) 600 601 file, sourceIsFile := sourceParentDir.files[sourceBase] 602 dir, sourceIsDir := sourceParentDir.subdirs[sourceBase] 603 link, sourceIsLink := sourceParentDir.symlinks[sourceBase] 604 605 // validate that the source exists 606 if !sourceIsFile && !sourceIsDir && !sourceIsLink { 607 return &os.PathError{ 608 Op: "move", 609 Path: sourcePath, 610 Err: os.ErrNotExist, 611 } 612 613 } 614 615 // validate the destination doesn't already exist as an incompatible type 616 _, destWasFile := destParentDir.files[destBase] 617 _, destWasDir := destParentDir.subdirs[destBase] 618 _, destWasLink := destParentDir.symlinks[destBase] 619 620 if destWasDir { 621 return &os.PathError{ 622 Op: "move", 623 Path: destPath, 624 Err: errors.New("destination exists as a directory"), 625 } 626 } 627 628 if sourceIsDir && (destWasFile || destWasLink) { 629 return &os.PathError{ 630 Op: "move", 631 Path: destPath, 632 Err: errors.New("destination exists as a file"), 633 } 634 } 635 636 if destWasFile { 637 delete(destParentDir.files, destBase) 638 } 639 if destWasDir { 640 delete(destParentDir.subdirs, destBase) 641 } 642 if destWasLink { 643 delete(destParentDir.symlinks, destBase) 644 } 645 646 if sourceIsFile { 647 destParentDir.files[destBase] = file 648 delete(sourceParentDir.files, sourceBase) 649 } 650 if sourceIsDir { 651 destParentDir.subdirs[destBase] = dir 652 delete(sourceParentDir.subdirs, sourceBase) 653 } 654 if sourceIsLink { 655 destParentDir.symlinks[destBase] = link 656 delete(destParentDir.symlinks, sourceBase) 657 } 658 659 destParentDir.modTime = m.Clock.Time() 660 sourceParentDir.modTime = m.Clock.Time() 661 return nil 662} 663 664func (m *MockFs) newInodeNumber() uint64 { 665 result := m.nextInodeNumber 666 m.nextInodeNumber++ 667 return result 668} 669 670func (m *MockFs) WriteFile(filePath string, data []byte, perm os.FileMode) error { 671 filePath, err := m.resolve(filePath, true) 672 if err != nil { 673 return err 674 } 675 parentPath := filepath.Dir(filePath) 676 parentDir, err := m.getDir(parentPath, false) 677 if err != nil || parentDir == nil { 678 return &os.PathError{ 679 Op: "write", 680 Path: parentPath, 681 Err: os.ErrNotExist, 682 } 683 } 684 if parentDir.readErr != nil { 685 return &os.PathError{ 686 Op: "write", 687 Path: parentPath, 688 Err: parentDir.readErr, 689 } 690 } 691 692 baseName := filepath.Base(filePath) 693 _, exists := parentDir.files[baseName] 694 if !exists { 695 parentDir.modTime = m.Clock.Time() 696 parentDir.files[baseName] = m.newFile() 697 } else { 698 readErr := parentDir.files[baseName].readErr 699 if readErr != nil { 700 return &os.PathError{ 701 Op: "write", 702 Path: filePath, 703 Err: readErr, 704 } 705 } 706 } 707 file := parentDir.files[baseName] 708 file.bytes = data 709 file.modTime = m.Clock.Time() 710 return nil 711} 712 713func (m *MockFs) newFile() *mockFile { 714 newFile := &mockFile{} 715 newFile.inodeNumber = m.newInodeNumber() 716 newFile.modTime = m.Clock.Time() 717 newFile.permTime = newFile.modTime 718 return newFile 719} 720 721func (m *MockFs) newDir() *mockDir { 722 newDir := &mockDir{ 723 subdirs: make(map[string]*mockDir, 0), 724 files: make(map[string]*mockFile, 0), 725 symlinks: make(map[string]*mockLink, 0), 726 } 727 newDir.inodeNumber = m.newInodeNumber() 728 newDir.modTime = m.Clock.Time() 729 newDir.permTime = newDir.modTime 730 return newDir 731} 732 733func (m *MockFs) newLink(target string) *mockLink { 734 newLink := &mockLink{ 735 target: target, 736 } 737 newLink.inodeNumber = m.newInodeNumber() 738 newLink.modTime = m.Clock.Time() 739 newLink.permTime = newLink.modTime 740 741 return newLink 742} 743func (m *MockFs) MkDirs(path string) error { 744 _, err := m.getDir(path, true) 745 return err 746} 747 748// getDir doesn't support symlinks 749func (m *MockFs) getDir(path string, createIfMissing bool) (dir *mockDir, err error) { 750 cleanedPath := filepath.Clean(path) 751 if cleanedPath == "/" { 752 return &m.root, nil 753 } 754 755 parentPath, leaf := pathSplit(cleanedPath) 756 if len(parentPath) >= len(path) { 757 return &m.root, nil 758 } 759 parent, err := m.getDir(parentPath, createIfMissing) 760 if err != nil { 761 return nil, err 762 } 763 if parent.readErr != nil { 764 return nil, &os.PathError{ 765 Op: "stat", 766 Path: path, 767 Err: parent.readErr, 768 } 769 } 770 childDir, dirExists := parent.subdirs[leaf] 771 if !dirExists { 772 if createIfMissing { 773 // confirm that a file with the same name doesn't already exist 774 _, fileExists := parent.files[leaf] 775 if fileExists { 776 return nil, &os.PathError{ 777 Op: "mkdir", 778 Path: path, 779 Err: os.ErrExist, 780 } 781 } 782 // create this directory 783 childDir = m.newDir() 784 parent.subdirs[leaf] = childDir 785 parent.modTime = m.Clock.Time() 786 } else { 787 return nil, &os.PathError{ 788 Op: "stat", 789 Path: path, 790 Err: os.ErrNotExist, 791 } 792 } 793 } 794 return childDir, nil 795 796} 797 798func (m *MockFs) Remove(path string) (err error) { 799 path, err = m.resolve(path, false) 800 parentPath, leaf := pathSplit(path) 801 if len(leaf) == 0 { 802 return fmt.Errorf("Cannot remove %v\n", path) 803 } 804 parentDir, err := m.getDir(parentPath, false) 805 if err != nil { 806 return err 807 } 808 if parentDir == nil { 809 return &os.PathError{ 810 Op: "remove", 811 Path: path, 812 Err: os.ErrNotExist, 813 } 814 } 815 if parentDir.readErr != nil { 816 return &os.PathError{ 817 Op: "remove", 818 Path: path, 819 Err: parentDir.readErr, 820 } 821 } 822 _, isDir := parentDir.subdirs[leaf] 823 if isDir { 824 return &os.PathError{ 825 Op: "remove", 826 Path: path, 827 Err: os.ErrInvalid, 828 } 829 } 830 _, isLink := parentDir.symlinks[leaf] 831 if isLink { 832 delete(parentDir.symlinks, leaf) 833 } else { 834 _, isFile := parentDir.files[leaf] 835 if !isFile { 836 return &os.PathError{ 837 Op: "remove", 838 Path: path, 839 Err: os.ErrNotExist, 840 } 841 } 842 delete(parentDir.files, leaf) 843 } 844 parentDir.modTime = m.Clock.Time() 845 return nil 846} 847 848func (m *MockFs) Symlink(oldPath string, newPath string) (err error) { 849 newPath, err = m.resolve(newPath, false) 850 if err != nil { 851 return err 852 } 853 854 newParentPath, leaf := pathSplit(newPath) 855 newParentDir, err := m.getDir(newParentPath, false) 856 if newParentDir.readErr != nil { 857 return &os.PathError{ 858 Op: "link", 859 Path: newPath, 860 Err: newParentDir.readErr, 861 } 862 } 863 if err != nil { 864 return err 865 } 866 newParentDir.symlinks[leaf] = m.newLink(oldPath) 867 return nil 868} 869 870func (m *MockFs) RemoveAll(path string) (err error) { 871 path, err = m.resolve(path, false) 872 if err != nil { 873 return err 874 } 875 parentPath, leaf := pathSplit(path) 876 if len(leaf) == 0 { 877 return fmt.Errorf("Cannot remove %v\n", path) 878 } 879 parentDir, err := m.getDir(parentPath, false) 880 if err != nil { 881 return err 882 } 883 if parentDir == nil { 884 return &os.PathError{ 885 Op: "removeAll", 886 Path: path, 887 Err: os.ErrNotExist, 888 } 889 } 890 if parentDir.readErr != nil { 891 return &os.PathError{ 892 Op: "removeAll", 893 Path: path, 894 Err: parentDir.readErr, 895 } 896 897 } 898 _, isFile := parentDir.files[leaf] 899 _, isLink := parentDir.symlinks[leaf] 900 if isFile || isLink { 901 return m.Remove(path) 902 } 903 _, isDir := parentDir.subdirs[leaf] 904 if !isDir { 905 if !isDir { 906 return &os.PathError{ 907 Op: "removeAll", 908 Path: path, 909 Err: os.ErrNotExist, 910 } 911 } 912 } 913 914 delete(parentDir.subdirs, leaf) 915 parentDir.modTime = m.Clock.Time() 916 return nil 917} 918 919func (m *MockFs) SetReadable(path string, readable bool) error { 920 var readErr error 921 if !readable { 922 readErr = os.ErrPermission 923 } 924 return m.SetReadErr(path, readErr) 925} 926 927func (m *MockFs) SetReadErr(path string, readErr error) error { 928 path, err := m.resolve(path, false) 929 if err != nil { 930 return err 931 } 932 parentPath, leaf := filepath.Split(path) 933 parentDir, err := m.getDir(parentPath, false) 934 if err != nil { 935 return err 936 } 937 if parentDir.readErr != nil { 938 return &os.PathError{ 939 Op: "chmod", 940 Path: parentPath, 941 Err: parentDir.readErr, 942 } 943 } 944 945 inode, err := m.getInode(parentDir, leaf) 946 if err != nil { 947 return err 948 } 949 inode.readErr = readErr 950 inode.permTime = m.Clock.Time() 951 return nil 952} 953 954func (m *MockFs) ClearMetrics() { 955 m.ReadDirCalls = []string{} 956 m.StatCalls = []string{} 957} 958 959func (m *MockFs) ViewId() (id string) { 960 return m.viewId 961} 962 963func (m *MockFs) SetViewId(id string) { 964 m.viewId = id 965} 966func (m *MockFs) SetDeviceNumber(deviceNumber uint64) { 967 m.deviceNumber = deviceNumber 968} 969