blob: 071f7640f8657694b330ac4378b8efaf196b205e [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())
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700246 }
247
248 parentPath, err = m.followLinks(parentPath, true, count)
249 if err != nil {
250 return "", err
251 }
252
253 parentNode, err := m.getDir(parentPath, false)
254 if err != nil {
255 return "", err
256 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700257 if parentNode.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700258 return "", &os.PathError{
259 Op: "read",
260 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700261 Err: parentNode.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700262 }
263 }
264
265 link, isLink := parentNode.symlinks[leaf]
266 if isLink && followLastLink {
267 if count <= 0 {
268 // probably a loop
269 return "", &os.PathError{
270 Op: "read",
271 Path: path,
272 Err: fmt.Errorf("too many levels of symbolic links"),
273 }
274 }
275
Jeff Gastonb629e182017-08-14 16:49:18 -0700276 if link.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700277 return "", &os.PathError{
278 Op: "read",
279 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700280 Err: link.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700281 }
282 }
283
284 target := m.followLink(link, parentPath)
285 return m.followLinks(target, followLastLink, count-1)
286 }
287 return path, nil
288}
289
290func (m *MockFs) followLink(link *mockLink, parentPath string) (result string) {
291 return filepath.Clean(filepath.Join(parentPath, link.target))
292}
293
294func (m *MockFs) getFile(parentDir *mockDir, fileName string) (file *mockFile, err error) {
295 file, isFile := parentDir.files[fileName]
296 if !isFile {
297 _, isDir := parentDir.subdirs[fileName]
298 _, isLink := parentDir.symlinks[fileName]
299 if isDir || isLink {
300 return nil, &os.PathError{
301 Op: "open",
302 Path: fileName,
303 Err: os.ErrInvalid,
304 }
305 }
306
307 return nil, &os.PathError{
308 Op: "open",
309 Path: fileName,
310 Err: os.ErrNotExist,
311 }
312 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700313 if file.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700314 return nil, &os.PathError{
315 Op: "open",
316 Path: fileName,
Jeff Gastonb629e182017-08-14 16:49:18 -0700317 Err: file.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700318 }
319 }
320 return file, nil
321}
322
323func (m *MockFs) getInode(parentDir *mockDir, name string) (inode *mockInode, err error) {
324 file, isFile := parentDir.files[name]
325 if isFile {
326 return &file.mockInode, nil
327 }
328 link, isLink := parentDir.symlinks[name]
329 if isLink {
330 return &link.mockInode, nil
331 }
332 dir, isDir := parentDir.subdirs[name]
333 if isDir {
334 return &dir.mockInode, nil
335 }
336 return nil, &os.PathError{
337 Op: "stat",
338 Path: name,
339 Err: os.ErrNotExist,
340 }
341
342}
343
344func (m *MockFs) Open(path string) (io.ReadCloser, error) {
345 path, err := m.resolve(path, true)
346 if err != nil {
347 return nil, err
348 }
349
350 if err != nil {
351 return nil, err
352 }
353
354 parentPath, base := pathSplit(path)
355 parentDir, err := m.getDir(parentPath, false)
356 if err != nil {
357 return nil, err
358 }
359 file, err := m.getFile(parentDir, base)
360 if err != nil {
361 return nil, err
362 }
363 return struct {
364 io.Closer
365 *bytes.Reader
366 }{
367 ioutil.NopCloser(nil),
368 bytes.NewReader(file.bytes),
369 }, nil
370
371}
372
373// a mockFileInfo is for exporting file stats in a way that satisfies the FileInfo interface
374type mockFileInfo struct {
375 path string
376 size int64
377 modTime time.Time // time at which the inode's contents were modified
378 permTime time.Time // time at which the inode's permissions were modified
379 isDir bool
380 inodeNumber uint64
381 deviceNumber uint64
382}
383
384func (m *mockFileInfo) Name() string {
385 return m.path
386}
387
388func (m *mockFileInfo) Size() int64 {
389 return m.size
390}
391
392func (m *mockFileInfo) Mode() os.FileMode {
393 return 0
394}
395
396func (m *mockFileInfo) ModTime() time.Time {
397 return m.modTime
398}
399
400func (m *mockFileInfo) IsDir() bool {
401 return m.isDir
402}
403
404func (m *mockFileInfo) Sys() interface{} {
405 return nil
406}
407
408func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) {
409 return &mockFileInfo{
410 path: path,
411 size: 1,
412 modTime: d.modTime,
413 permTime: d.permTime,
414 isDir: true,
415 inodeNumber: d.inodeNumber,
416 deviceNumber: m.deviceNumber,
417 }
418
419}
420
421func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) {
422 return &mockFileInfo{
423 path: path,
424 size: 1,
425 modTime: f.modTime,
426 permTime: f.permTime,
427 isDir: false,
428 inodeNumber: f.inodeNumber,
429 deviceNumber: m.deviceNumber,
430 }
431}
432
433func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) {
434 return &mockFileInfo{
435 path: path,
436 size: 1,
437 modTime: l.modTime,
438 permTime: l.permTime,
439 isDir: false,
440 inodeNumber: l.inodeNumber,
441 deviceNumber: m.deviceNumber,
442 }
443}
444
445func (m *MockFs) Lstat(path string) (stats os.FileInfo, err error) {
446 // update aggregates
447 m.aggregatesLock.Lock()
448 m.StatCalls = append(m.StatCalls, path)
449 m.aggregatesLock.Unlock()
450
451 // resolve symlinks
452 path, err = m.resolve(path, false)
453 if err != nil {
454 return nil, err
455 }
456
457 // special case for root dir
458 if path == "/" {
459 return m.dirToFileInfo(&m.root, "/"), nil
460 }
461
462 // determine type and handle appropriately
463 parentPath, baseName := pathSplit(path)
464 dir, err := m.getDir(parentPath, false)
465 if err != nil {
466 return nil, err
467 }
468 subdir, subdirExists := dir.subdirs[baseName]
469 if subdirExists {
470 return m.dirToFileInfo(subdir, path), nil
471 }
472 file, fileExists := dir.files[baseName]
473 if fileExists {
474 return m.fileToFileInfo(file, path), nil
475 }
476 link, linkExists := dir.symlinks[baseName]
477 if linkExists {
478 return m.linkToFileInfo(link, path), nil
479 }
480 // not found
481 return nil, &os.PathError{
482 Op: "stat",
483 Path: path,
484 Err: os.ErrNotExist,
485 }
486}
487
488func (m *MockFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
489 mockInfo, ok := info.(*mockFileInfo)
490 if ok {
491 return mockInfo.inodeNumber, nil
492 }
493 return 0, fmt.Errorf("%v is not a mockFileInfo", info)
494}
495func (m *MockFs) DeviceNumber(info os.FileInfo) (number uint64, err error) {
496 mockInfo, ok := info.(*mockFileInfo)
497 if ok {
498 return mockInfo.deviceNumber, nil
499 }
500 return 0, fmt.Errorf("%v is not a mockFileInfo", info)
501}
502func (m *MockFs) PermTime(info os.FileInfo) (when time.Time, err error) {
503 mockInfo, ok := info.(*mockFileInfo)
504 if ok {
505 return mockInfo.permTime, nil
506 }
507 return time.Date(0, 0, 0, 0, 0, 0, 0, nil),
508 fmt.Errorf("%v is not a mockFileInfo", info)
509}
510
Colin Crossa88c8832017-12-21 15:39:26 -0800511func (m *MockFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700512 // update aggregates
513 m.aggregatesLock.Lock()
514 m.ReadDirCalls = append(m.ReadDirCalls, path)
515 m.aggregatesLock.Unlock()
516
517 // locate directory
518 path, err = m.resolve(path, true)
519 if err != nil {
520 return nil, err
521 }
Colin Crossa88c8832017-12-21 15:39:26 -0800522 results := []DirEntryInfo{}
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700523 dir, err := m.getDir(path, false)
524 if err != nil {
525 return nil, err
526 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700527 if dir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700528 return nil, &os.PathError{
529 Op: "read",
530 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700531 Err: dir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700532 }
533 }
534 // describe its contents
535 for name, subdir := range dir.subdirs {
536 dirInfo := m.dirToFileInfo(subdir, name)
537 results = append(results, dirInfo)
538 }
539 for name, file := range dir.files {
540 info := m.fileToFileInfo(file, name)
541 results = append(results, info)
542 }
543 for name, link := range dir.symlinks {
544 info := m.linkToFileInfo(link, name)
545 results = append(results, info)
546 }
547 return results, nil
548}
549
550func (m *MockFs) Rename(sourcePath string, destPath string) error {
551 // validate source parent exists
552 sourcePath, err := m.resolve(sourcePath, false)
553 if err != nil {
554 return err
555 }
556 sourceParentPath := filepath.Dir(sourcePath)
557 sourceParentDir, err := m.getDir(sourceParentPath, false)
558 if err != nil {
559 return err
560 }
561 if sourceParentDir == nil {
562 return &os.PathError{
563 Op: "move",
564 Path: sourcePath,
565 Err: os.ErrNotExist,
566 }
567 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700568 if sourceParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700569 return &os.PathError{
570 Op: "move",
571 Path: sourcePath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700572 Err: sourceParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700573 }
574 }
575
576 // validate dest parent exists
577 destPath, err = m.resolve(destPath, false)
578 destParentPath := filepath.Dir(destPath)
579 destParentDir, err := m.getDir(destParentPath, false)
580 if err != nil {
581 return err
582 }
583 if destParentDir == nil {
584 return &os.PathError{
585 Op: "move",
586 Path: destParentPath,
587 Err: os.ErrNotExist,
588 }
589 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700590 if destParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700591 return &os.PathError{
592 Op: "move",
593 Path: destParentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700594 Err: destParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700595 }
596 }
597 // check the source and dest themselves
598 sourceBase := filepath.Base(sourcePath)
599 destBase := filepath.Base(destPath)
600
601 file, sourceIsFile := sourceParentDir.files[sourceBase]
602 dir, sourceIsDir := sourceParentDir.subdirs[sourceBase]
603 link, sourceIsLink := sourceParentDir.symlinks[sourceBase]
604
605 // validate that the source exists
606 if !sourceIsFile && !sourceIsDir && !sourceIsLink {
607 return &os.PathError{
608 Op: "move",
609 Path: sourcePath,
610 Err: os.ErrNotExist,
611 }
612
613 }
614
615 // validate the destination doesn't already exist as an incompatible type
616 _, destWasFile := destParentDir.files[destBase]
617 _, destWasDir := destParentDir.subdirs[destBase]
618 _, destWasLink := destParentDir.symlinks[destBase]
619
620 if destWasDir {
621 return &os.PathError{
622 Op: "move",
623 Path: destPath,
624 Err: errors.New("destination exists as a directory"),
625 }
626 }
627
628 if sourceIsDir && (destWasFile || destWasLink) {
629 return &os.PathError{
630 Op: "move",
631 Path: destPath,
632 Err: errors.New("destination exists as a file"),
633 }
634 }
635
636 if destWasFile {
637 delete(destParentDir.files, destBase)
638 }
639 if destWasDir {
640 delete(destParentDir.subdirs, destBase)
641 }
642 if destWasLink {
643 delete(destParentDir.symlinks, destBase)
644 }
645
646 if sourceIsFile {
647 destParentDir.files[destBase] = file
648 delete(sourceParentDir.files, sourceBase)
649 }
650 if sourceIsDir {
651 destParentDir.subdirs[destBase] = dir
652 delete(sourceParentDir.subdirs, sourceBase)
653 }
654 if sourceIsLink {
655 destParentDir.symlinks[destBase] = link
656 delete(destParentDir.symlinks, sourceBase)
657 }
658
659 destParentDir.modTime = m.Clock.Time()
660 sourceParentDir.modTime = m.Clock.Time()
661 return nil
662}
663
664func (m *MockFs) newInodeNumber() uint64 {
665 result := m.nextInodeNumber
666 m.nextInodeNumber++
667 return result
668}
669
670func (m *MockFs) WriteFile(filePath string, data []byte, perm os.FileMode) error {
671 filePath, err := m.resolve(filePath, true)
672 if err != nil {
673 return err
674 }
675 parentPath := filepath.Dir(filePath)
676 parentDir, err := m.getDir(parentPath, false)
677 if err != nil || parentDir == nil {
678 return &os.PathError{
679 Op: "write",
680 Path: parentPath,
681 Err: os.ErrNotExist,
682 }
683 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700684 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700685 return &os.PathError{
686 Op: "write",
687 Path: parentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700688 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700689 }
690 }
691
692 baseName := filepath.Base(filePath)
693 _, exists := parentDir.files[baseName]
694 if !exists {
695 parentDir.modTime = m.Clock.Time()
696 parentDir.files[baseName] = m.newFile()
697 } else {
Jeff Gastonb629e182017-08-14 16:49:18 -0700698 readErr := parentDir.files[baseName].readErr
699 if readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700700 return &os.PathError{
701 Op: "write",
702 Path: filePath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700703 Err: readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700704 }
705 }
706 }
707 file := parentDir.files[baseName]
708 file.bytes = data
709 file.modTime = m.Clock.Time()
710 return nil
711}
712
713func (m *MockFs) newFile() *mockFile {
714 newFile := &mockFile{}
715 newFile.inodeNumber = m.newInodeNumber()
716 newFile.modTime = m.Clock.Time()
717 newFile.permTime = newFile.modTime
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700718 return newFile
719}
720
721func (m *MockFs) newDir() *mockDir {
722 newDir := &mockDir{
723 subdirs: make(map[string]*mockDir, 0),
724 files: make(map[string]*mockFile, 0),
725 symlinks: make(map[string]*mockLink, 0),
726 }
727 newDir.inodeNumber = m.newInodeNumber()
728 newDir.modTime = m.Clock.Time()
729 newDir.permTime = newDir.modTime
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700730 return newDir
731}
732
733func (m *MockFs) newLink(target string) *mockLink {
734 newLink := &mockLink{
735 target: target,
736 }
737 newLink.inodeNumber = m.newInodeNumber()
738 newLink.modTime = m.Clock.Time()
739 newLink.permTime = newLink.modTime
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700740
741 return newLink
742}
743func (m *MockFs) MkDirs(path string) error {
744 _, err := m.getDir(path, true)
745 return err
746}
747
748// getDir doesn't support symlinks
749func (m *MockFs) getDir(path string, createIfMissing bool) (dir *mockDir, err error) {
750 cleanedPath := filepath.Clean(path)
751 if cleanedPath == "/" {
752 return &m.root, nil
753 }
754
755 parentPath, leaf := pathSplit(cleanedPath)
756 if len(parentPath) >= len(path) {
757 return &m.root, nil
758 }
759 parent, err := m.getDir(parentPath, createIfMissing)
760 if err != nil {
761 return nil, err
762 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700763 if parent.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700764 return nil, &os.PathError{
765 Op: "stat",
766 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700767 Err: parent.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700768 }
769 }
770 childDir, dirExists := parent.subdirs[leaf]
771 if !dirExists {
772 if createIfMissing {
773 // confirm that a file with the same name doesn't already exist
774 _, fileExists := parent.files[leaf]
775 if fileExists {
776 return nil, &os.PathError{
777 Op: "mkdir",
778 Path: path,
779 Err: os.ErrExist,
780 }
781 }
782 // create this directory
783 childDir = m.newDir()
784 parent.subdirs[leaf] = childDir
785 parent.modTime = m.Clock.Time()
786 } else {
787 return nil, &os.PathError{
788 Op: "stat",
789 Path: path,
790 Err: os.ErrNotExist,
791 }
792 }
793 }
794 return childDir, nil
795
796}
797
798func (m *MockFs) Remove(path string) (err error) {
799 path, err = m.resolve(path, false)
800 parentPath, leaf := pathSplit(path)
801 if len(leaf) == 0 {
802 return fmt.Errorf("Cannot remove %v\n", path)
803 }
804 parentDir, err := m.getDir(parentPath, false)
805 if err != nil {
806 return err
807 }
808 if parentDir == nil {
809 return &os.PathError{
810 Op: "remove",
811 Path: path,
812 Err: os.ErrNotExist,
813 }
814 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700815 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700816 return &os.PathError{
817 Op: "remove",
818 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700819 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700820 }
821 }
822 _, isDir := parentDir.subdirs[leaf]
823 if isDir {
824 return &os.PathError{
825 Op: "remove",
826 Path: path,
827 Err: os.ErrInvalid,
828 }
829 }
830 _, isLink := parentDir.symlinks[leaf]
831 if isLink {
832 delete(parentDir.symlinks, leaf)
833 } else {
834 _, isFile := parentDir.files[leaf]
835 if !isFile {
836 return &os.PathError{
837 Op: "remove",
838 Path: path,
839 Err: os.ErrNotExist,
840 }
841 }
842 delete(parentDir.files, leaf)
843 }
844 parentDir.modTime = m.Clock.Time()
845 return nil
846}
847
848func (m *MockFs) Symlink(oldPath string, newPath string) (err error) {
849 newPath, err = m.resolve(newPath, false)
850 if err != nil {
851 return err
852 }
853
854 newParentPath, leaf := pathSplit(newPath)
855 newParentDir, err := m.getDir(newParentPath, false)
Jeff Gastonb629e182017-08-14 16:49:18 -0700856 if newParentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700857 return &os.PathError{
858 Op: "link",
859 Path: newPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700860 Err: newParentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700861 }
862 }
863 if err != nil {
864 return err
865 }
866 newParentDir.symlinks[leaf] = m.newLink(oldPath)
867 return nil
868}
869
870func (m *MockFs) RemoveAll(path string) (err error) {
871 path, err = m.resolve(path, false)
872 if err != nil {
873 return err
874 }
875 parentPath, leaf := pathSplit(path)
876 if len(leaf) == 0 {
877 return fmt.Errorf("Cannot remove %v\n", path)
878 }
879 parentDir, err := m.getDir(parentPath, false)
880 if err != nil {
881 return err
882 }
883 if parentDir == nil {
884 return &os.PathError{
885 Op: "removeAll",
886 Path: path,
887 Err: os.ErrNotExist,
888 }
889 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700890 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700891 return &os.PathError{
892 Op: "removeAll",
893 Path: path,
Jeff Gastonb629e182017-08-14 16:49:18 -0700894 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700895 }
896
897 }
898 _, isFile := parentDir.files[leaf]
899 _, isLink := parentDir.symlinks[leaf]
900 if isFile || isLink {
901 return m.Remove(path)
902 }
903 _, isDir := parentDir.subdirs[leaf]
904 if !isDir {
905 if !isDir {
906 return &os.PathError{
907 Op: "removeAll",
908 Path: path,
909 Err: os.ErrNotExist,
910 }
911 }
912 }
913
914 delete(parentDir.subdirs, leaf)
915 parentDir.modTime = m.Clock.Time()
916 return nil
917}
918
919func (m *MockFs) SetReadable(path string, readable bool) error {
Jeff Gastonb629e182017-08-14 16:49:18 -0700920 var readErr error
921 if !readable {
922 readErr = os.ErrPermission
923 }
924 return m.SetReadErr(path, readErr)
925}
926
927func (m *MockFs) SetReadErr(path string, readErr error) error {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700928 path, err := m.resolve(path, false)
929 if err != nil {
930 return err
931 }
932 parentPath, leaf := filepath.Split(path)
933 parentDir, err := m.getDir(parentPath, false)
934 if err != nil {
935 return err
936 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700937 if parentDir.readErr != nil {
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700938 return &os.PathError{
939 Op: "chmod",
940 Path: parentPath,
Jeff Gastonb629e182017-08-14 16:49:18 -0700941 Err: parentDir.readErr,
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700942 }
943 }
944
945 inode, err := m.getInode(parentDir, leaf)
946 if err != nil {
947 return err
948 }
Jeff Gastonb629e182017-08-14 16:49:18 -0700949 inode.readErr = readErr
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700950 inode.permTime = m.Clock.Time()
951 return nil
952}
953
954func (m *MockFs) ClearMetrics() {
955 m.ReadDirCalls = []string{}
956 m.StatCalls = []string{}
957}
958
959func (m *MockFs) ViewId() (id string) {
960 return m.viewId
961}
962
963func (m *MockFs) SetViewId(id string) {
964 m.viewId = id
965}
966func (m *MockFs) SetDeviceNumber(deviceNumber uint64) {
967 m.deviceNumber = deviceNumber
968}