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