1package fakes
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"io"
8	"os"
9	gopath "path"
10	"path/filepath"
11	"sort"
12	"strings"
13	"sync"
14	"syscall"
15	"time"
16
17	gouuid "github.com/nu7hatch/gouuid"
18
19	bosherr "github.com/cloudfoundry/bosh-utils/errors"
20	boshsys "github.com/cloudfoundry/bosh-utils/system"
21)
22
23type FakeFileType string
24
25type removeAllFn func(path string) error
26
27type globFn func(pattern string) ([]string, error)
28
29const (
30	FakeFileTypeFile    FakeFileType = "file"
31	FakeFileTypeSymlink FakeFileType = "symlink"
32	FakeFileTypeDir     FakeFileType = "dir"
33)
34
35type FakeFileSystem struct {
36	fileRegistry *FakeFileStatsRegistry
37	filesLock    sync.Mutex
38
39	HomeDirUsername string
40	HomeDirHomePath string
41
42	ExpandPathPath     string
43	ExpandPathExpanded string
44	ExpandPathErr      error
45
46	openFileRegistry *FakeFileRegistry
47	OpenFileErr      error
48
49	ReadFileError             error
50	ReadFileWithOptsCallCount int
51	readFileErrorByPath       map[string]error
52
53	WriteFileError            error
54	WriteFileErrors           map[string]error
55	WriteFileCallCount        int
56	WriteFileQuietlyCallCount int
57
58	SymlinkError error
59
60	MkdirAllError       error
61	mkdirAllErrorByPath map[string]error
62	MkdirAllCallCount   int
63
64	ChangeTempRootErr error
65
66	ChownErr       error
67	ChownCallCount int
68	ChmodErr       error
69	ChmodCallCount int
70
71	CopyFileError     error
72	CopyFileCallCount int
73
74	CopyDirError error
75
76	RenameError    error
77	RenameOldPaths []string
78	RenameNewPaths []string
79
80	RemoveAllStub removeAllFn
81
82	ReadAndFollowLinkError error
83	ReadlinkError          error
84
85	StatWithOptsCallCount int
86	StatCallCount         int
87
88	TempFileError           error
89	TempFileErrorsByPrefix  map[string]error
90	ReturnTempFile          boshsys.File
91	ReturnTempFiles         []boshsys.File
92	ReturnTempFilesByPrefix map[string]boshsys.File
93
94	TempDirDir   string
95	TempDirDirs  []string
96	TempDirError error
97
98	GlobErr  error
99	GlobStub globFn
100	GlobErrs map[string]error
101	globsMap map[string][][]string
102
103	WalkErr error
104
105	TempRootPath   string
106	strictTempRoot bool
107}
108
109type FakeFileStats struct {
110	FileType FakeFileType
111
112	FileMode  os.FileMode
113	Flags     int
114	Username  string
115	Groupname string
116
117	ModTime time.Time
118	Open    bool
119
120	SymlinkTarget string
121
122	Content []byte
123}
124
125func (stats FakeFileStats) StringContents() string {
126	return string(stats.Content)
127}
128
129type FakeFileInfo struct {
130	os.FileInfo
131	file FakeFile
132}
133
134func (fi FakeFileInfo) Mode() os.FileMode {
135	return fi.file.Stats.FileMode
136}
137
138func (fi FakeFileInfo) ModTime() time.Time {
139	return fi.file.Stats.ModTime
140}
141
142func (fi FakeFileInfo) Size() int64 {
143	return int64(len(fi.file.Contents))
144}
145
146func (fi FakeFileInfo) IsDir() bool {
147	return fi.file.Stats.FileType == FakeFileTypeDir
148}
149
150type FakeFile struct {
151	path string
152	fs   *FakeFileSystem
153
154	Stats *FakeFileStats
155
156	WriteErr error
157	Contents []byte
158
159	ReadErr   error
160	ReadAtErr error
161	readIndex int64
162
163	CloseErr error
164
165	StatErr error
166}
167
168func NewFakeFile(path string, fs *FakeFileSystem) *FakeFile {
169	fakeFile := &FakeFile{
170		path: path,
171		fs:   fs,
172	}
173	me := fs.fileRegistry.Get(path)
174	if me != nil {
175		fakeFile.Contents = me.Content
176		fakeFile.Stats = me
177		fakeFile.Stats.Open = true
178	}
179	return fakeFile
180}
181
182func (f *FakeFile) Name() string {
183	return f.path
184}
185
186func (f *FakeFile) Write(contents []byte) (int, error) {
187	if f.WriteErr != nil {
188		return 0, f.WriteErr
189	}
190
191	f.fs.filesLock.Lock()
192	defer f.fs.filesLock.Unlock()
193
194	stats := f.fs.getOrCreateFile(f.path)
195	stats.Content = contents
196
197	f.Contents = contents
198	return len(contents), nil
199}
200
201func (f *FakeFile) Read(b []byte) (int, error) {
202	if f.readIndex >= int64(len(f.Contents)) {
203		return 0, io.EOF
204	}
205	copy(b, f.Contents)
206	f.readIndex = int64(len(f.Contents))
207	return len(f.Contents), f.ReadErr
208}
209
210func (f *FakeFile) ReadAt(b []byte, offset int64) (int, error) {
211	copy(b, f.Contents[offset:])
212	return len(f.Contents[offset:]), f.ReadAtErr
213}
214
215func (f *FakeFile) WriteAt(b []byte, offset int64) (int, error) {
216	return len(b), nil
217}
218
219func (f *FakeFile) Seek(int64, int) (int64, error) {
220	return 0, nil
221}
222
223func (f *FakeFile) Close() error {
224	if f.Stats != nil {
225		f.Stats.Open = false
226	}
227	return f.CloseErr
228}
229
230func (f FakeFile) Stat() (os.FileInfo, error) {
231	return FakeFileInfo{file: f}, f.StatErr
232}
233
234func NewFakeFileSystem() *FakeFileSystem {
235	return &FakeFileSystem{
236		fileRegistry:           NewFakeFileStatsRegistry(),
237		openFileRegistry:       NewFakeFileRegistry(),
238		GlobErrs:               map[string]error{},
239		globsMap:               map[string][][]string{},
240		readFileErrorByPath:    map[string]error{},
241		mkdirAllErrorByPath:    map[string]error{},
242		WriteFileErrors:        map[string]error{},
243		TempFileErrorsByPrefix: map[string]error{},
244	}
245}
246
247func (fs *FakeFileSystem) GetFileTestStat(path string) *FakeFileStats {
248	fs.filesLock.Lock()
249	defer fs.filesLock.Unlock()
250
251	return fs.fileRegistry.Get(path)
252}
253
254func (fs *FakeFileSystem) HomeDir(username string) (string, error) {
255	fs.HomeDirUsername = username
256	return fs.HomeDirHomePath, nil
257}
258
259func (fs *FakeFileSystem) ExpandPath(path string) (string, error) {
260	fs.ExpandPathPath = path
261	if fs.ExpandPathExpanded == "" {
262		return fs.ExpandPathPath, fs.ExpandPathErr
263	}
264
265	return fs.ExpandPathExpanded, fs.ExpandPathErr
266}
267
268func (fs *FakeFileSystem) RegisterMkdirAllError(path string, err error) {
269	path = gopath.Join(path)
270	if _, ok := fs.mkdirAllErrorByPath[path]; ok {
271		panic(fmt.Sprintf("MkdirAll error is already set for path: %s", path))
272	}
273	fs.mkdirAllErrorByPath[path] = err
274}
275
276func (fs *FakeFileSystem) MkdirAll(path string, perm os.FileMode) error {
277	fs.MkdirAllCallCount++
278	fs.filesLock.Lock()
279	defer fs.filesLock.Unlock()
280
281	if fs.MkdirAllError != nil {
282		return fs.MkdirAllError
283	}
284
285	path = gopath.Join(path)
286
287	if fs.mkdirAllErrorByPath[path] != nil {
288		return fs.mkdirAllErrorByPath[path]
289	}
290
291	return fs.mkdir(path, perm)
292}
293
294func (fs *FakeFileSystem) mkdir(path string, perm os.FileMode) error {
295	if path == "." {
296		return nil
297	}
298
299	if !atRoot(path) {
300		parent := filepath.Dir(path)
301		// We can't use any functions which require the filesystem lock.
302		parentStats := fs.fileRegistry.Get(parent)
303
304		if parentStats != nil && parentStats.FileType == FakeFileTypeFile {
305			return fmt.Errorf("cannot create a directory in a file (%s)", path)
306		}
307
308		// Parent does not exist
309		if parentStats == nil {
310			if err := fs.mkdir(parent, perm); err != nil {
311				return err
312			}
313		}
314	}
315
316	stats := fs.getOrCreateFile(path)
317	stats.FileMode = perm
318	stats.FileType = FakeFileTypeDir
319	fs.fileRegistry.Register(path, stats)
320	return nil
321}
322
323func atRoot(path string) bool {
324	switch path {
325	case "/":
326		return true
327	case filepath.VolumeName(path) + "\\":
328		return true
329	default:
330		return false
331	}
332}
333
334func (fs *FakeFileSystem) RegisterOpenFile(path string, file *FakeFile) {
335	path = gopath.Join(path)
336	fs.openFileRegistry.Register(path, file)
337}
338
339func (fs *FakeFileSystem) FindFileStats(path string) (*FakeFileStats, error) {
340	if stats := fs.fileRegistry.Get(path); stats != nil {
341		return stats, nil
342	}
343	return nil, fmt.Errorf("Path does not exist: %s", path)
344}
345
346func (fs *FakeFileSystem) OpenFile(path string, flag int, perm os.FileMode) (boshsys.File, error) {
347	fs.filesLock.Lock()
348	defer fs.filesLock.Unlock()
349
350	if fs.OpenFileErr != nil {
351		return nil, fs.OpenFileErr
352	}
353
354	// Make sure to record a reference for FileExist, etc. to work
355	stats := fs.getOrCreateFile(path)
356	stats.FileMode = perm
357	stats.Flags = flag
358	stats.FileType = FakeFileTypeFile
359
360	openFile := fs.openFileRegistry.Get(path)
361	if openFile != nil {
362		return openFile, nil
363	}
364	file := NewFakeFile(path, fs)
365
366	fs.RegisterOpenFile(path, file)
367	return file, nil
368}
369
370func (fs *FakeFileSystem) Stat(path string) (os.FileInfo, error) {
371	fs.StatCallCount++
372	return fs.StatHelper(path)
373}
374
375func (fs *FakeFileSystem) StatWithOpts(path string, opts boshsys.StatOpts) (os.FileInfo, error) {
376	fs.StatWithOptsCallCount++
377	return fs.StatHelper(path)
378}
379
380func (fs *FakeFileSystem) StatHelper(path string) (os.FileInfo, error) {
381	fs.filesLock.Lock()
382	defer fs.filesLock.Unlock()
383
384	openFile := fs.openFileRegistry.Get(path)
385	if openFile != nil {
386		return openFile.Stat()
387	}
388
389	stats := fs.fileRegistry.Get(path)
390	if stats == nil {
391		panic(fmt.Sprintf("Unexpected Stat call for path '%s' that does not exist", path))
392	}
393
394	if stats.FileType == FakeFileTypeSymlink {
395		targetStats := fs.fileRegistry.Get(stats.SymlinkTarget)
396		if targetStats == nil {
397			return nil, fmt.Errorf("stat: %s: no such file or directory", path)
398		}
399
400		stats = targetStats
401	}
402
403	return NewFakeFile(path, fs).Stat()
404}
405func (fs *FakeFileSystem) Readlink(symlinkPath string) (string, error) {
406	targetPath, err := fs.readlink(symlinkPath)
407	if err != nil {
408		return targetPath, err
409	}
410
411	//Converts internal path formatting (which is UNIX/Linux based) to native OS file system path
412	//This emulates the real behavior of how the real file system returns symlink
413	if strings.HasPrefix(targetPath, "/") {
414		absFilePath, err := filepath.Abs(targetPath)
415		return absFilePath, err
416	}
417
418	return targetPath, err
419}
420
421func (fs *FakeFileSystem) readlink(path string) (string, error) {
422	if fs.ReadlinkError != nil {
423		return "", fs.ReadlinkError
424	}
425
426	fs.filesLock.Lock()
427	defer fs.filesLock.Unlock()
428
429	stats := fs.fileRegistry.Get(path)
430	if stats == nil {
431		return "", os.ErrNotExist
432	}
433
434	if stats.FileType != FakeFileTypeSymlink {
435		return "", errors.New(fmt.Sprintf("cannot readlink of non-symlink"))
436	}
437
438	return stats.SymlinkTarget, nil
439}
440
441func (fs *FakeFileSystem) Lstat(path string) (os.FileInfo, error) {
442	fs.filesLock.Lock()
443	defer fs.filesLock.Unlock()
444
445	openFile := fs.openFileRegistry.Get(path)
446	if openFile != nil {
447		return openFile.Stat()
448	}
449
450	stats := fs.fileRegistry.Get(path)
451	if stats == nil {
452		panic(fmt.Sprintf("Unexpected Stat call for path '%s' that does not exist", path))
453	}
454
455	return NewFakeFile(path, fs).Stat()
456}
457
458func (fs *FakeFileSystem) Chown(path, username string) error {
459	fs.ChownCallCount++
460	fs.filesLock.Lock()
461	defer fs.filesLock.Unlock()
462
463	// check early to avoid requiring file presence
464	if fs.ChownErr != nil {
465		return fs.ChownErr
466	}
467
468	stats := fs.fileRegistry.Get(path)
469	if stats == nil {
470		return fmt.Errorf("Path does not exist: %s", path)
471	}
472
473	parts := strings.Split(username, ":")
474	stats.Username = parts[0]
475	stats.Groupname = parts[0]
476	if len(parts) > 1 {
477		stats.Groupname = parts[1]
478	}
479	return nil
480}
481
482func (fs *FakeFileSystem) Chmod(path string, perm os.FileMode) error {
483	fs.ChmodCallCount++
484	fs.filesLock.Lock()
485	defer fs.filesLock.Unlock()
486
487	// check early to avoid requiring file presence
488	if fs.ChmodErr != nil {
489		return fs.ChmodErr
490	}
491
492	stats := fs.fileRegistry.Get(path)
493	if stats == nil {
494		return fmt.Errorf("Path does not exist: %s", path)
495	}
496
497	stats.FileMode = perm
498	return nil
499}
500
501func (fs *FakeFileSystem) WriteFileString(path, content string) error {
502	return fs.WriteFile(path, []byte(content))
503}
504
505func (fs *FakeFileSystem) WriteFileQuietly(path string, content []byte) error {
506	fs.WriteFileQuietlyCallCount++
507	return fs.writeFile(path, content)
508}
509
510func (fs *FakeFileSystem) WriteFile(path string, content []byte) error {
511	fs.WriteFileCallCount++
512	return fs.writeFile(path, content)
513}
514
515func (fs *FakeFileSystem) writeFile(path string, content []byte) error {
516	fs.filesLock.Lock()
517	defer fs.filesLock.Unlock()
518
519	err := fs.WriteFileError
520	if err != nil {
521		return err
522	}
523
524	err = fs.WriteFileErrors[path]
525	if err != nil {
526		return err
527	}
528
529	path = fs.fileRegistry.UnifiedPath(path)
530	parent := gopath.Dir(path)
531	if parent != "." {
532		fs.writeDir(parent)
533	}
534
535	stats := fs.getOrCreateFile(path)
536	stats.FileType = FakeFileTypeFile
537	stats.Content = content
538	return nil
539}
540
541func (fs *FakeFileSystem) writeDir(path string) error {
542	parent := gopath.Dir(path)
543
544	grandparent := gopath.Dir(parent)
545	if grandparent != parent {
546		fs.writeDir(parent)
547	}
548
549	stats := fs.getOrCreateFile(path)
550	stats.FileType = FakeFileTypeDir
551	return nil
552}
553
554func (fs *FakeFileSystem) ConvergeFileContents(path string, content []byte, opts ...boshsys.ConvergeFileContentsOpts) (bool, error) {
555	fs.filesLock.Lock()
556	defer fs.filesLock.Unlock()
557
558	if fs.WriteFileError != nil {
559		return false, fs.WriteFileError
560	}
561
562	err := fs.WriteFileErrors[path]
563	if err != nil {
564		return false, err
565	}
566
567	if len(opts) > 0 && opts[0].DryRun {
568		stats := fs.fileRegistry.Get(path)
569		if stats == nil {
570			return true, nil
571		}
572		return bytes.Compare(stats.Content, content) != 0, nil
573	}
574
575	stats := fs.getOrCreateFile(path)
576	stats.FileType = FakeFileTypeFile
577
578	if bytes.Compare(stats.Content, content) != 0 {
579		stats.Content = content
580		return true, nil
581	}
582
583	return false, nil
584}
585
586func (fs *FakeFileSystem) ReadFileString(path string) (string, error) {
587	bytes, err := fs.ReadFile(path)
588	if err != nil {
589		return "", err
590	}
591
592	return string(bytes), nil
593}
594
595func (fs *FakeFileSystem) RegisterReadFileError(path string, err error) {
596	if _, ok := fs.readFileErrorByPath[path]; ok {
597		panic(fmt.Sprintf("ReadFile error is already set for path: %s", path))
598	}
599	fs.readFileErrorByPath[path] = err
600}
601
602func (fs *FakeFileSystem) UnregisterReadFileError(path string) {
603	delete(fs.readFileErrorByPath, path)
604}
605
606func (fs *FakeFileSystem) ReadFileWithOpts(path string, opts boshsys.ReadOpts) ([]byte, error) {
607	fs.ReadFileWithOptsCallCount++
608	return fs.ReadFile(path)
609}
610
611func (fs *FakeFileSystem) ReadFile(path string) ([]byte, error) {
612	stats := fs.GetFileTestStat(path)
613	if stats != nil {
614		if fs.ReadFileError != nil {
615			return nil, fs.ReadFileError
616		}
617
618		if fs.readFileErrorByPath[path] != nil {
619			return nil, fs.readFileErrorByPath[path]
620		}
621
622		return stats.Content, nil
623	}
624
625	return nil, bosherr.ComplexError{
626		Err: bosherr.Error("Not found"),
627		Cause: &os.PathError{
628			Op:   "open",
629			Path: path,
630			Err:  syscall.ENOENT,
631		},
632	}
633}
634
635func (fs *FakeFileSystem) FileExists(path string) bool {
636	return fs.GetFileTestStat(path) != nil
637}
638
639func (fs *FakeFileSystem) Rename(oldPath, newPath string) error {
640	fs.filesLock.Lock()
641	defer fs.filesLock.Unlock()
642
643	if fs.RenameError != nil {
644		return fs.RenameError
645	}
646
647	oldPath = fs.fileRegistry.UnifiedPath(oldPath)
648	newPath = fs.fileRegistry.UnifiedPath(newPath)
649
650	parentDir := gopath.Dir(newPath)
651	if parentDir != "." && fs.fileRegistry.Get(parentDir) == nil {
652		return errors.New("Parent directory does not exist")
653	}
654
655	stats := fs.fileRegistry.Get(oldPath)
656	if stats == nil {
657		return errors.New("Old path did not exist")
658	}
659
660	fs.RenameOldPaths = append(fs.RenameOldPaths, oldPath)
661	fs.RenameNewPaths = append(fs.RenameNewPaths, newPath)
662
663	for filePath, fileStats := range fs.fileRegistry.GetAll() {
664		if filePath == oldPath {
665			fs.fileRegistry.Register(newPath, fileStats)
666		} else if strings.HasPrefix(filePath, fmt.Sprintf("%s/", oldPath)) {
667			dstPath := gopath.Join(newPath, filePath[len(oldPath):])
668			fs.fileRegistry.Register(dstPath, fileStats)
669		}
670	}
671
672	// Ignore error from RemoveAll
673	fs.removeAll(oldPath)
674
675	return nil
676}
677
678func (fs *FakeFileSystem) Symlink(oldPath, newPath string) (err error) {
679	fs.filesLock.Lock()
680	defer fs.filesLock.Unlock()
681
682	if fs.SymlinkError == nil {
683		stats := fs.getOrCreateFile(newPath)
684		stats.FileMode |= os.ModeSymlink
685		stats.FileType = FakeFileTypeSymlink
686		stats.SymlinkTarget = fs.fileRegistry.UnifiedPath(oldPath)
687		return
688	}
689
690	err = fs.SymlinkError
691	return
692}
693
694func (fs *FakeFileSystem) ReadAndFollowLink(symlinkPath string) (string, error) {
695	targetPath, err := fs.readAndFollowLink(symlinkPath)
696	if err != nil {
697		return targetPath, err
698	}
699
700	//Converts internal path formatting (which is UNIX/Linux based) to native OS file system path
701	//This emulates the real behavior of how the real file system returns symlink
702	if strings.HasPrefix(targetPath, "/") {
703		absFilePath, err := filepath.Abs(targetPath)
704		return absFilePath, err
705	}
706
707	return targetPath, err
708}
709
710func (fs *FakeFileSystem) readAndFollowLink(symlinkPath string) (string, error) {
711	if fs.ReadAndFollowLinkError != nil {
712		return "", fs.ReadAndFollowLinkError
713	}
714
715	if symlinkPath == "\\" {
716		symlinkPath = "/"
717	}
718
719	if symlinkPath == "" ||
720		symlinkPath == "/" ||
721		symlinkPath == filepath.VolumeName(symlinkPath)+"\\" {
722		return symlinkPath, nil
723	}
724
725	if symlinkPath == "." {
726		return fs.fileRegistry.UnifiedPath("."), nil
727	}
728
729	symlinkPath = filepath.Join(symlinkPath)
730
731	stat := fs.GetFileTestStat(symlinkPath)
732	if stat == nil {
733		return "", os.ErrNotExist
734	}
735
736	if stat.FileType != FakeFileTypeSymlink {
737		dirPath, err := fs.readAndFollowLink(filepath.Dir(symlinkPath))
738		if err != nil {
739			return "", err
740		}
741
742		return gopath.Join(dirPath, filepath.Base(symlinkPath)), nil
743	}
744
745	if gopath.IsAbs(stat.SymlinkTarget) {
746		return fs.readAndFollowLink(stat.SymlinkTarget)
747	}
748
749	dirPath, err := fs.readAndFollowLink(filepath.Dir(symlinkPath))
750	if err != nil {
751		return "", err
752	}
753
754	return fs.readAndFollowLink(gopath.Join(dirPath, stat.SymlinkTarget))
755}
756
757func (fs *FakeFileSystem) CopyFile(srcPath, dstPath string) error {
758	fs.CopyFileCallCount++
759	fs.filesLock.Lock()
760	defer fs.filesLock.Unlock()
761
762	if fs.CopyFileError != nil {
763		return fs.CopyFileError
764	}
765
766	srcFile := fs.fileRegistry.Get(srcPath)
767	if srcFile == nil {
768		return errors.New(fmt.Sprintf("%s doesn't exist", srcPath))
769	}
770
771	fs.fileRegistry.Register(dstPath, srcFile)
772	return nil
773}
774
775func (fs *FakeFileSystem) CopyDir(srcPath, dstPath string) error {
776	fs.filesLock.Lock()
777	defer fs.filesLock.Unlock()
778
779	if fs.CopyDirError != nil {
780		return fs.CopyDirError
781	}
782
783	srcPath = fs.fileRegistry.UnifiedPath(srcPath)
784	dstPath = fs.fileRegistry.UnifiedPath(dstPath)
785
786	for filePath, fileStats := range fs.fileRegistry.GetAll() {
787		if filePath == srcPath {
788			fs.fileRegistry.Register(dstPath, fileStats)
789		} else if strings.HasPrefix(filePath, fmt.Sprintf("%s/", srcPath)) {
790			dstPath := gopath.Join(dstPath, filePath[len(srcPath):])
791			fs.fileRegistry.Register(dstPath, fileStats)
792		}
793	}
794
795	return nil
796}
797
798func (fs *FakeFileSystem) ChangeTempRoot(tempRootPath string) error {
799	if fs.ChangeTempRootErr != nil {
800		return fs.ChangeTempRootErr
801	}
802	fs.TempRootPath = tempRootPath
803	return nil
804}
805
806func (fs *FakeFileSystem) EnableStrictTempRootBehavior() {
807	fs.strictTempRoot = true
808}
809
810func (fs *FakeFileSystem) TempFile(prefix string) (file boshsys.File, err error) {
811	fs.filesLock.Lock()
812	defer fs.filesLock.Unlock()
813
814	if fs.TempFileError != nil {
815		return nil, fs.TempFileError
816	}
817
818	if fs.TempFileErrorsByPrefix[prefix] != nil {
819		return nil, fs.TempFileErrorsByPrefix[prefix]
820	}
821
822	if fs.strictTempRoot && fs.TempRootPath == "" {
823		return nil, errors.New("Temp file was requested without having set a temp root")
824	}
825
826	if fs.ReturnTempFilesByPrefix != nil {
827		file = fs.ReturnTempFilesByPrefix[prefix]
828	} else if fs.ReturnTempFile != nil {
829		file = fs.ReturnTempFile
830	} else if len(fs.ReturnTempFiles) != 0 {
831		file = fs.ReturnTempFiles[0]
832		fs.ReturnTempFiles = fs.ReturnTempFiles[1:]
833	} else {
834		file, err = os.Open(os.DevNull)
835		if err != nil {
836			err = bosherr.WrapError(err, fmt.Sprintf("Opening %s", os.DevNull))
837			return
838		}
839	}
840
841	// Make sure to record a reference for FileExist, etc. to work
842	stats := fs.getOrCreateFile(file.Name())
843	stats.FileType = FakeFileTypeFile
844	return
845}
846
847func (fs *FakeFileSystem) TempDir(prefix string) (string, error) {
848	fs.filesLock.Lock()
849	defer fs.filesLock.Unlock()
850
851	if fs.TempDirError != nil {
852		return "", fs.TempDirError
853	}
854
855	if fs.strictTempRoot && fs.TempRootPath == "" {
856		return "", errors.New("Temp file was requested without having set a temp root")
857	}
858
859	var path string
860	if len(fs.TempDirDir) > 0 {
861		path = fs.TempDirDir
862	} else if fs.TempDirDirs != nil {
863		if len(fs.TempDirDirs) == 0 {
864			return "", errors.New("Failed to create new temp dir: TempDirDirs is empty")
865		}
866		path = fs.TempDirDirs[0]
867		fs.TempDirDirs = fs.TempDirDirs[1:]
868	} else {
869		uuid, err := gouuid.NewV4()
870		if err != nil {
871			return "", err
872		}
873
874		path = uuid.String()
875	}
876
877	// Make sure to record a reference for FileExist, etc. to work
878	stats := fs.getOrCreateFile(path)
879	stats.FileType = FakeFileTypeDir
880
881	return path, nil
882}
883
884func (fs *FakeFileSystem) RemoveAll(path string) error {
885	if path == "" {
886		panic("RemoveAll requires path")
887	}
888
889	if fs.RemoveAllStub != nil {
890		err := fs.RemoveAllStub(path)
891		if err != nil {
892			return err
893		}
894	}
895
896	fs.filesLock.Lock()
897	defer fs.filesLock.Unlock()
898
899	path = fs.fileRegistry.UnifiedPath(path)
900	return fs.removeAll(path)
901}
902
903func (fs *FakeFileSystem) removeAll(path string) error {
904	fileInfo := fs.fileRegistry.Get(path)
905	if fileInfo != nil {
906		fs.fileRegistry.Remove(path)
907		if fileInfo.FileType != FakeFileTypeDir {
908			return nil
909		}
910	}
911
912	// path must be a dir
913	path = path + "/"
914
915	filesToRemove := []string{}
916	for name := range fs.fileRegistry.GetAll() {
917		if strings.HasPrefix(name, path) {
918			filesToRemove = append(filesToRemove, name)
919		}
920	}
921	for _, name := range filesToRemove {
922		fs.fileRegistry.Remove(name)
923	}
924
925	return nil
926}
927
928func (fs *FakeFileSystem) Glob(pattern string) (matches []string, err error) {
929	if fs.GlobStub != nil {
930		matches, err = fs.GlobStub(pattern)
931		if err != nil {
932			return nil, err
933		} else {
934			return matches, nil
935		}
936	}
937
938	remainingMatches, found := fs.globsMap[pattern]
939	if found {
940		matches = remainingMatches[0]
941		if len(remainingMatches) > 1 {
942			fs.globsMap[pattern] = remainingMatches[1:]
943		}
944	} else {
945		matches = []string{}
946	}
947	if err, ok := fs.GlobErrs[pattern]; ok {
948		return matches, err
949	}
950	return matches, fs.GlobErr
951}
952
953func (fs *FakeFileSystem) RecursiveGlob(pattern string) (matches []string, err error) {
954	return fs.Glob(pattern)
955}
956
957func (fs *FakeFileSystem) Walk(root string, walkFunc filepath.WalkFunc) error {
958	if fs.WalkErr != nil {
959		return walkFunc("", nil, fs.WalkErr)
960	}
961
962	var paths []string
963	for path := range fs.fileRegistry.GetAll() {
964		paths = append(paths, path)
965	}
966	sort.Strings(paths)
967
968	root = gopath.Join(root) + "/"
969	for _, path := range paths {
970		fileStats := fs.fileRegistry.Get(path)
971		if strings.HasPrefix(path, root) {
972			fakeFile := NewFakeFile(path, fs)
973			fakeFile.Stats = fileStats
974			fileInfo, _ := fakeFile.Stat()
975			err := walkFunc(path, fileInfo, nil)
976			if err != nil {
977				return err
978			}
979		}
980	}
981
982	return nil
983}
984
985func (fs *FakeFileSystem) SetGlob(pattern string, matches ...[]string) {
986	fs.globsMap[pattern] = matches
987}
988
989func (fs *FakeFileSystem) getOrCreateFile(path string) *FakeFileStats {
990	stats := fs.fileRegistry.Get(path)
991	if stats == nil {
992		stats = new(FakeFileStats)
993		fs.fileRegistry.Register(path, stats)
994	}
995	return stats
996}
997
998type FakeFileStatsRegistry struct {
999	files map[string]*FakeFileStats
1000}
1001
1002func NewFakeFileStatsRegistry() *FakeFileStatsRegistry {
1003	return &FakeFileStatsRegistry{
1004		files: map[string]*FakeFileStats{},
1005	}
1006}
1007
1008func (fsr *FakeFileStatsRegistry) Register(path string, stats *FakeFileStats) {
1009	fsr.files[fsr.UnifiedPath(path)] = stats
1010}
1011
1012func (fsr *FakeFileStatsRegistry) Get(path string) *FakeFileStats {
1013	return fsr.files[fsr.UnifiedPath(path)]
1014}
1015
1016func (fsr *FakeFileStatsRegistry) GetAll() map[string]*FakeFileStats {
1017	return fsr.files
1018}
1019
1020func (fsr *FakeFileStatsRegistry) Remove(path string) {
1021	delete(fsr.files, fsr.UnifiedPath(path))
1022}
1023
1024func (fsr *FakeFileStatsRegistry) UnifiedPath(path string) string {
1025	path = strings.TrimPrefix(path, filepath.VolumeName(path))
1026	return filepath.ToSlash(gopath.Join(path))
1027}
1028
1029type FakeFileRegistry struct {
1030	files map[string]*FakeFile
1031}
1032
1033func NewFakeFileRegistry() *FakeFileRegistry {
1034	return &FakeFileRegistry{
1035		files: map[string]*FakeFile{},
1036	}
1037}
1038
1039func (ffr *FakeFileRegistry) Register(path string, file *FakeFile) {
1040	ffr.files[ffr.UnifiedPath(path)] = file
1041}
1042
1043func (ffr *FakeFileRegistry) Get(path string) *FakeFile {
1044	return ffr.files[ffr.UnifiedPath(path)]
1045}
1046
1047func (ffr *FakeFileRegistry) UnifiedPath(path string) string {
1048	path = strings.TrimPrefix(path, filepath.VolumeName(path))
1049	return filepath.ToSlash(gopath.Join(path))
1050}
1051