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