Home | History | Annotate | Download | only in fs
      1 // Copyright 2017 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package fs
     16 
     17 import (
     18 	"bytes"
     19 	"errors"
     20 	"fmt"
     21 	"io"
     22 	"io/ioutil"
     23 	"os"
     24 	"os/user"
     25 	"path/filepath"
     26 	"sync"
     27 	"time"
     28 )
     29 
     30 var OsFs FileSystem = osFs{}
     31 
     32 func 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 
     50 type 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 []DirEntryInfo, 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 // 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.
     72 type DirEntryInfo interface {
     73 	Name() string
     74 	Mode() os.FileMode // the file type encoded as an os.FileMode
     75 	IsDir() bool
     76 }
     77 
     78 type dirEntryInfo struct {
     79 	name       string
     80 	mode       os.FileMode
     81 	modeExists bool
     82 }
     83 
     84 var _ DirEntryInfo = os.FileInfo(nil)
     85 
     86 func (d *dirEntryInfo) Name() string      { return d.name }
     87 func (d *dirEntryInfo) Mode() os.FileMode { return d.mode }
     88 func (d *dirEntryInfo) IsDir() bool       { return d.mode.IsDir() }
     89 func (d *dirEntryInfo) String() string    { return d.name + ": " + d.mode.String() }
     90 
     91 // osFs implements FileSystem using the local disk.
     92 type osFs struct{}
     93 
     94 var _ FileSystem = (*osFs)(nil)
     95 
     96 func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
     97 
     98 func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
     99 	return os.Lstat(path)
    100 }
    101 
    102 func (osFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
    103 	entries, err := readdir(path)
    104 	if err != nil {
    105 		return nil, err
    106 	}
    107 	for _, entry := range entries {
    108 		contents = append(contents, entry)
    109 	}
    110 
    111 	return contents, nil
    112 }
    113 
    114 func (osFs) Rename(oldPath string, newPath string) error {
    115 	return os.Rename(oldPath, newPath)
    116 }
    117 
    118 func (osFs) WriteFile(path string, data []byte, perm os.FileMode) error {
    119 	return ioutil.WriteFile(path, data, perm)
    120 }
    121 
    122 func (osFs) Remove(path string) error {
    123 	return os.Remove(path)
    124 }
    125 
    126 func (osFs) RemoveAll(path string) error {
    127 	return os.RemoveAll(path)
    128 }
    129 
    130 func (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 
    145 type Clock struct {
    146 	time time.Time
    147 }
    148 
    149 func NewClock(startTime time.Time) *Clock {
    150 	return &Clock{time: startTime}
    151 
    152 }
    153 
    154 func (c *Clock) Tick() {
    155 	c.time = c.time.Add(time.Microsecond)
    156 }
    157 
    158 func (c *Clock) Time() time.Time {
    159 	return c.time
    160 }
    161 
    162 // given "/a/b/c/d", pathSplit returns ("/a/b/c", "d")
    163 func 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
    172 type 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 
    189 var _ FileSystem = (*MockFs)(nil)
    190 
    191 type mockInode struct {
    192 	modTime     time.Time
    193 	permTime    time.Time
    194 	sys         interface{}
    195 	inodeNumber uint64
    196 	readErr     error
    197 }
    198 
    199 func (m mockInode) ModTime() time.Time {
    200 	return m.modTime
    201 }
    202 
    203 func (m mockInode) Sys() interface{} {
    204 	return m.sys
    205 }
    206 
    207 type mockFile struct {
    208 	bytes []byte
    209 
    210 	mockInode
    211 }
    212 
    213 type mockLink struct {
    214 	target string
    215 
    216 	mockInode
    217 }
    218 
    219 type mockDir struct {
    220 	mockInode
    221 
    222 	subdirs  map[string]*mockDir
    223 	files    map[string]*mockFile
    224 	symlinks map[string]*mockLink
    225 }
    226 
    227 func (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
    237 func (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 	}
    258 	if parentNode.readErr != nil {
    259 		return "", &os.PathError{
    260 			Op:   "read",
    261 			Path: path,
    262 			Err:  parentNode.readErr,
    263 		}
    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 
    277 		if link.readErr != nil {
    278 			return "", &os.PathError{
    279 				Op:   "read",
    280 				Path: path,
    281 				Err:  link.readErr,
    282 			}
    283 		}
    284 
    285 		target := m.followLink(link, parentPath)
    286 		return m.followLinks(target, followLastLink, count-1)
    287 	}
    288 	return path, nil
    289 }
    290 
    291 func (m *MockFs) followLink(link *mockLink, parentPath string) (result string) {
    292 	return filepath.Clean(filepath.Join(parentPath, link.target))
    293 }
    294 
    295 func (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 	}
    314 	if file.readErr != nil {
    315 		return nil, &os.PathError{
    316 			Op:   "open",
    317 			Path: fileName,
    318 			Err:  file.readErr,
    319 		}
    320 	}
    321 	return file, nil
    322 }
    323 
    324 func (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 
    345 func (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
    375 type 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 
    385 func (m *mockFileInfo) Name() string {
    386 	return m.path
    387 }
    388 
    389 func (m *mockFileInfo) Size() int64 {
    390 	return m.size
    391 }
    392 
    393 func (m *mockFileInfo) Mode() os.FileMode {
    394 	return 0
    395 }
    396 
    397 func (m *mockFileInfo) ModTime() time.Time {
    398 	return m.modTime
    399 }
    400 
    401 func (m *mockFileInfo) IsDir() bool {
    402 	return m.isDir
    403 }
    404 
    405 func (m *mockFileInfo) Sys() interface{} {
    406 	return nil
    407 }
    408 
    409 func (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 
    422 func (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 
    434 func (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 
    446 func (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 
    489 func (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 }
    496 func (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 }
    503 func (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 
    512 func (m *MockFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
    513 	// 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 	}
    523 	results := []DirEntryInfo{}
    524 	dir, err := m.getDir(path, false)
    525 	if err != nil {
    526 		return nil, err
    527 	}
    528 	if dir.readErr != nil {
    529 		return nil, &os.PathError{
    530 			Op:   "read",
    531 			Path: path,
    532 			Err:  dir.readErr,
    533 		}
    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 
    551 func (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 	}
    569 	if sourceParentDir.readErr != nil {
    570 		return &os.PathError{
    571 			Op:   "move",
    572 			Path: sourcePath,
    573 			Err:  sourceParentDir.readErr,
    574 		}
    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 	}
    591 	if destParentDir.readErr != nil {
    592 		return &os.PathError{
    593 			Op:   "move",
    594 			Path: destParentPath,
    595 			Err:  destParentDir.readErr,
    596 		}
    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 
    665 func (m *MockFs) newInodeNumber() uint64 {
    666 	result := m.nextInodeNumber
    667 	m.nextInodeNumber++
    668 	return result
    669 }
    670 
    671 func (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 	}
    685 	if parentDir.readErr != nil {
    686 		return &os.PathError{
    687 			Op:   "write",
    688 			Path: parentPath,
    689 			Err:  parentDir.readErr,
    690 		}
    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 {
    699 		readErr := parentDir.files[baseName].readErr
    700 		if readErr != nil {
    701 			return &os.PathError{
    702 				Op:   "write",
    703 				Path: filePath,
    704 				Err:  readErr,
    705 			}
    706 		}
    707 	}
    708 	file := parentDir.files[baseName]
    709 	file.bytes = data
    710 	file.modTime = m.Clock.Time()
    711 	return nil
    712 }
    713 
    714 func (m *MockFs) newFile() *mockFile {
    715 	newFile := &mockFile{}
    716 	newFile.inodeNumber = m.newInodeNumber()
    717 	newFile.modTime = m.Clock.Time()
    718 	newFile.permTime = newFile.modTime
    719 	return newFile
    720 }
    721 
    722 func (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
    731 	return newDir
    732 }
    733 
    734 func (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
    741 
    742 	return newLink
    743 }
    744 func (m *MockFs) MkDirs(path string) error {
    745 	_, err := m.getDir(path, true)
    746 	return err
    747 }
    748 
    749 // getDir doesn't support symlinks
    750 func (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 	}
    764 	if parent.readErr != nil {
    765 		return nil, &os.PathError{
    766 			Op:   "stat",
    767 			Path: path,
    768 			Err:  parent.readErr,
    769 		}
    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 
    799 func (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 	}
    816 	if parentDir.readErr != nil {
    817 		return &os.PathError{
    818 			Op:   "remove",
    819 			Path: path,
    820 			Err:  parentDir.readErr,
    821 		}
    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 
    849 func (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)
    857 	if newParentDir.readErr != nil {
    858 		return &os.PathError{
    859 			Op:   "link",
    860 			Path: newPath,
    861 			Err:  newParentDir.readErr,
    862 		}
    863 	}
    864 	if err != nil {
    865 		return err
    866 	}
    867 	newParentDir.symlinks[leaf] = m.newLink(oldPath)
    868 	return nil
    869 }
    870 
    871 func (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 	}
    891 	if parentDir.readErr != nil {
    892 		return &os.PathError{
    893 			Op:   "removeAll",
    894 			Path: path,
    895 			Err:  parentDir.readErr,
    896 		}
    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 
    920 func (m *MockFs) SetReadable(path string, readable bool) error {
    921 	var readErr error
    922 	if !readable {
    923 		readErr = os.ErrPermission
    924 	}
    925 	return m.SetReadErr(path, readErr)
    926 }
    927 
    928 func (m *MockFs) SetReadErr(path string, readErr error) error {
    929 	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 	}
    938 	if parentDir.readErr != nil {
    939 		return &os.PathError{
    940 			Op:   "chmod",
    941 			Path: parentPath,
    942 			Err:  parentDir.readErr,
    943 		}
    944 	}
    945 
    946 	inode, err := m.getInode(parentDir, leaf)
    947 	if err != nil {
    948 		return err
    949 	}
    950 	inode.readErr = readErr
    951 	inode.permTime = m.Clock.Time()
    952 	return nil
    953 }
    954 
    955 func (m *MockFs) ClearMetrics() {
    956 	m.ReadDirCalls = []string{}
    957 	m.StatCalls = []string{}
    958 }
    959 
    960 func (m *MockFs) ViewId() (id string) {
    961 	return m.viewId
    962 }
    963 
    964 func (m *MockFs) SetViewId(id string) {
    965 	m.viewId = id
    966 }
    967 func (m *MockFs) SetDeviceNumber(deviceNumber uint64) {
    968 	m.deviceNumber = deviceNumber
    969 }
    970