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