blob: 9c138cd109fd8617b4862cee557f3661968c4160 [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
Colin Crossae7fd6b2017-12-21 16:44:26 -080078type dirEntryInfo struct {
79 name string
80 mode os.FileMode
81 modeExists bool
82}
83
Colin Crossa88c8832017-12-21 15:39:26 -080084var _ DirEntryInfo = os.FileInfo(nil)
85
Colin Crossae7fd6b2017-12-21 16:44:26 -080086func (d *dirEntryInfo) Name() string { return d.name }
87func (d *dirEntryInfo) Mode() os.FileMode { return d.mode }
88func (d *dirEntryInfo) IsDir() bool { return d.mode.IsDir() }
89func (d *dirEntryInfo) String() string { return d.name + ": " + d.mode.String() }
90
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070091// osFs implements FileSystem using the local disk.
92type osFs struct{}
93
Colin Crossa88c8832017-12-21 15:39:26 -080094var _ FileSystem = (*osFs)(nil)
95
Jeff Gastonf1fd45e2017-08-09 18:25:28 -070096func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
97
98func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
99 return os.Lstat(path)
100}
101
Colin Crossa88c8832017-12-21 15:39:26 -0800102func (osFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
Colin Crossae7fd6b2017-12-21 16:44:26 -0800103 entries, err := readdir(path)
Colin Crossa88c8832017-12-21 15:39:26 -0800104 if err != nil {
105 return nil, err
106 }
107 for _, entry := range entries {
108 contents = append(contents, entry)
109 }
110
111 return contents, nil
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700112}
Colin Crossa88c8832017-12-21 15:39:26 -0800113
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700114func (osFs) Rename(oldPath string, newPath string) error {
115 return os.Rename(oldPath, newPath)
116}
117
118func (osFs) WriteFile(path string, data []byte, perm os.FileMode) error {
119 return ioutil.WriteFile(path, data, perm)
120}
121
122func (osFs) Remove(path string) error {
123 return os.Remove(path)
124}
125
126func (osFs) RemoveAll(path string) error {
127 return os.RemoveAll(path)
128}
129
130func (osFs) ViewId() (id string) {
131 user, err := user.Current()
132 if err != nil {
133 return ""
134 }
135 username := user.Username
136
137 hostname, err := os.Hostname()
138 if err != nil {
139 return ""
140 }
141
142 return username + "@" + hostname
143}
144
145type Clock struct {
146 time time.Time
147}
148
149func NewClock(startTime time.Time) *Clock {
150 return &Clock{time: startTime}
151
152}
153
154func (c *Clock) Tick() {
155 c.time = c.time.Add(time.Microsecond)
156}
157
158func (c *Clock) Time() time.Time {
159 return c.time
160}
161
162// given "/a/b/c/d", pathSplit returns ("/a/b/c", "d")
163func pathSplit(path string) (dir string, leaf string) {
164 dir, leaf = filepath.Split(path)
165 if dir != "/" && len(dir) > 0 {
166 dir = dir[:len(dir)-1]
167 }
168 return dir, leaf
169}
170
171// MockFs supports singlethreaded writes and multithreaded reads
172type MockFs struct {
173 // configuration
174 viewId string //
175 deviceNumber uint64
176
177 // implementation
178 root mockDir
179 Clock *Clock
180 workDir string
181 nextInodeNumber uint64
182
183 // history of requests, for tests to check
184 StatCalls []string
185 ReadDirCalls []string
186 aggregatesLock sync.Mutex
187}
188
Colin Crossa88c8832017-12-21 15:39:26 -0800189var _ FileSystem = (*MockFs)(nil)
190
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700191type mockInode struct {
192 modTime time.Time
193 permTime time.Time
194 sys interface{}
195 inodeNumber uint64
Jeff Gastonb629e182017-08-14 16:49:18 -0700196 readErr error
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700197}
198
199func (m mockInode) ModTime() time.Time {
200 return m.modTime
201}
202
203func (m mockInode) Sys() interface{} {
204 return m.sys
205}
206
207type mockFile struct {
208 bytes []byte
209
210 mockInode
211}
212
213type mockLink struct {
214 target string
215
216 mockInode
217}
218
219type mockDir struct {
220 mockInode
221
222 subdirs map[string]*mockDir
223 files map[string]*mockFile
224 symlinks map[string]*mockLink
225}
226
227func (m *MockFs) resolve(path string, followLastLink bool) (result string, err error) {
228 if !filepath.IsAbs(path) {
229 path = filepath.Join(m.workDir, path)
230 }
231 path = filepath.Clean(path)
232
233 return m.followLinks(path, followLastLink, 10)
234}
235
236// note that followLinks can return a file path that doesn't exist
237func (m *MockFs) followLinks(path string, followLastLink bool, count int) (canonicalPath string, err error) {
238 if path == "/" {
239 return path, nil
240 }
241
242 parentPath, leaf := pathSplit(path)
243 if parentPath == path {
244 err = fmt.Errorf("Internal error: %v yields itself as a parent", path)
245 panic(err.Error())
246 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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700258 if parentNode.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700259 return "", &os.PathError{
260 Op: "read",
261 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700262 Err: parentNode.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700263 }
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
Jeff Gastonb629e182017-08-14 16:49:18 -0700277 if link.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700278 return "", &os.PathError{
279 Op: "read",
280 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700281 Err: link.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700282 }
283 }
284
285 target := m.followLink(link, parentPath)
286 return m.followLinks(target, followLastLink, count-1)
287 }
288 return path, nil
289}
290
291func (m *MockFs) followLink(link *mockLink, parentPath string) (result string) {
292 return filepath.Clean(filepath.Join(parentPath, link.target))
293}
294
295func (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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700314 if file.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700315 return nil, &os.PathError{
316 Op: "open",
317 Path: fileName,
Jeff Gastonb629e182017-08-14 16:49:18 -0700318 Err: file.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700319 }
320 }
321 return file, nil
322}
323
324func (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
345func (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
375type 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
385func (m *mockFileInfo) Name() string {
386 return m.path
387}
388
389func (m *mockFileInfo) Size() int64 {
390 return m.size
391}
392
393func (m *mockFileInfo) Mode() os.FileMode {
394 return 0
395}
396
397func (m *mockFileInfo) ModTime() time.Time {
398 return m.modTime
399}
400
401func (m *mockFileInfo) IsDir() bool {
402 return m.isDir
403}
404
405func (m *mockFileInfo) Sys() interface{} {
406 return nil
407}
408
409func (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
422func (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
434func (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
446func (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
489func (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}
496func (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}
503func (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
Colin Crossa88c8832017-12-21 15:39:26 -0800512func (m *MockFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700513 // 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 }
Colin Crossa88c8832017-12-21 15:39:26 -0800523 results := []DirEntryInfo{}
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700524 dir, err := m.getDir(path, false)
525 if err != nil {
526 return nil, err
527 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700528 if dir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700529 return nil, &os.PathError{
530 Op: "read",
531 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700532 Err: dir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700533 }
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
551func (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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700569 if sourceParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700570 return &os.PathError{
571 Op: "move",
572 Path: sourcePath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700573 Err: sourceParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700574 }
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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700591 if destParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700592 return &os.PathError{
593 Op: "move",
594 Path: destParentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700595 Err: destParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700596 }
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
665func (m *MockFs) newInodeNumber() uint64 {
666 result := m.nextInodeNumber
667 m.nextInodeNumber++
668 return result
669}
670
671func (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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700685 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700686 return &os.PathError{
687 Op: "write",
688 Path: parentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700689 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700690 }
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 {
Jeff Gastonb629e182017-08-14 16:49:18 -0700699 readErr := parentDir.files[baseName].readErr
700 if readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700701 return &os.PathError{
702 Op: "write",
703 Path: filePath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700704 Err: readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700705 }
706 }
707 }
708 file := parentDir.files[baseName]
709 file.bytes = data
710 file.modTime = m.Clock.Time()
711 return nil
712}
713
714func (m *MockFs) newFile() *mockFile {
715 newFile := &mockFile{}
716 newFile.inodeNumber = m.newInodeNumber()
717 newFile.modTime = m.Clock.Time()
718 newFile.permTime = newFile.modTime
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700719 return newFile
720}
721
722func (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
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700731 return newDir
732}
733
734func (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
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700741
742 return newLink
743}
744func (m *MockFs) MkDirs(path string) error {
745 _, err := m.getDir(path, true)
746 return err
747}
748
749// getDir doesn't support symlinks
750func (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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700764 if parent.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700765 return nil, &os.PathError{
766 Op: "stat",
767 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700768 Err: parent.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700769 }
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
799func (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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700816 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700817 return &os.PathError{
818 Op: "remove",
819 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700820 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700821 }
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
849func (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)
Jeff Gastonb629e182017-08-14 16:49:18 -0700857 if newParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700858 return &os.PathError{
859 Op: "link",
860 Path: newPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700861 Err: newParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700862 }
863 }
864 if err != nil {
865 return err
866 }
867 newParentDir.symlinks[leaf] = m.newLink(oldPath)
868 return nil
869}
870
871func (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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700891 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700892 return &os.PathError{
893 Op: "removeAll",
894 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700895 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700896 }
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
920func (m *MockFs) SetReadable(path string, readable bool) error {
Jeff Gastonb629e182017-08-14 16:49:18 -0700921 var readErr error
922 if !readable {
923 readErr = os.ErrPermission
924 }
925 return m.SetReadErr(path, readErr)
926}
927
928func (m *MockFs) SetReadErr(path string, readErr error) error {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700929 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 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700938 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700939 return &os.PathError{
940 Op: "chmod",
941 Path: parentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700942 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700943 }
944 }
945
946 inode, err := m.getInode(parentDir, leaf)
947 if err != nil {
948 return err
949 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700950 inode.readErr = readErr
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700951 inode.permTime = m.Clock.Time()
952 return nil
953}
954
955func (m *MockFs) ClearMetrics() {
956 m.ReadDirCalls = []string{}
957 m.StatCalls = []string{}
958}
959
960func (m *MockFs) ViewId() (id string) {
961 return m.viewId
962}
963
964func (m *MockFs) SetViewId(id string) {
965 m.viewId = id
966}
967func (m *MockFs) SetDeviceNumber(deviceNumber uint64) {
968 m.deviceNumber = deviceNumber
969}