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