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