1// Copyright 2016 the Go-FUSE Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package unionfs
6
7import (
8	"crypto/md5"
9	"fmt"
10	"log"
11	"os"
12	"path"
13	"path/filepath"
14	"strings"
15	"sync"
16	"syscall"
17	"time"
18
19	"github.com/hanwen/go-fuse/v2/fuse"
20	"github.com/hanwen/go-fuse/v2/fuse/nodefs"
21	"github.com/hanwen/go-fuse/v2/fuse/pathfs"
22)
23
24func filePathHash(path string) string {
25	dir, base := filepath.Split(path)
26
27	h := md5.New()
28	h.Write([]byte(dir))
29	return fmt.Sprintf("%x-%s", h.Sum(nil)[:8], base)
30}
31
32/*
33
34 UnionFs implements a user-space union file system, which is
35 stateless but efficient even if the writable branch is on NFS.
36
37
38 Assumptions:
39
40 * It uses a list of branches, the first of which (index 0) is thought
41 to be writable, and the rest read-only.
42
43 * It assumes that the number of deleted files is small relative to
44 the total tree size.
45
46
47 Implementation notes.
48
49 * It overlays arbitrary writable FileSystems with any number of
50   readonly FileSystems.
51
52 * Deleting a file will put a file named
53 /DELETIONS/HASH-OF-FULL-FILENAME into the writable overlay,
54 containing the full filename itself.
55
56 This is optimized for NFS usage: we want to minimize the number of
57 NFS operations, which are slow.  By putting all whiteouts in one
58 place, we can cheaply fetch the list of all deleted files.  Even
59 without caching on our side, the kernel's negative dentry cache can
60 answer is-deleted queries quickly.
61
62*/
63type unionFS struct {
64	pathfs.FileSystem
65
66	// The same, but as interfaces.
67	fileSystems []pathfs.FileSystem
68
69	// A file-existence cache.
70	deletionCache *dirCache
71
72	// A file -> branch cache.
73	branchCache *TimedCache
74
75	// Map of files to hide.
76	hiddenFiles map[string]bool
77
78	options *UnionFsOptions
79	nodeFs  *pathfs.PathNodeFs
80}
81
82type UnionFsOptions struct {
83	BranchCacheTTL   time.Duration
84	DeletionCacheTTL time.Duration
85	DeletionDirName  string
86	HiddenFiles      []string
87}
88
89const (
90	_DROP_CACHE = ".drop_cache"
91)
92
93func NewUnionFs(fileSystems []pathfs.FileSystem, options UnionFsOptions) (pathfs.FileSystem, error) {
94	g := &unionFS{
95		options:     &options,
96		fileSystems: fileSystems,
97		FileSystem:  pathfs.NewDefaultFileSystem(),
98	}
99
100	writable := g.fileSystems[0]
101	code := g.createDeletionStore()
102	if !code.Ok() {
103		return nil, fmt.Errorf("could not create deletion path %v: %v", options.DeletionDirName, code)
104	}
105
106	g.deletionCache = newDirCache(writable, options.DeletionDirName, options.DeletionCacheTTL)
107	g.branchCache = NewTimedCache(
108		func(n string) (interface{}, bool) { return g.getBranchAttrNoCache(n), true },
109		options.BranchCacheTTL)
110
111	g.hiddenFiles = make(map[string]bool)
112	for _, name := range options.HiddenFiles {
113		g.hiddenFiles[name] = true
114	}
115
116	return g, nil
117}
118
119func (fs *unionFS) OnMount(nodeFs *pathfs.PathNodeFs) {
120	fs.nodeFs = nodeFs
121}
122
123////////////////
124// Deal with all the caches.
125
126// The isDeleted() method tells us if a path has a marker in the deletion store.
127// It may return an error code if the store could not be accessed.
128func (fs *unionFS) isDeleted(name string) (deleted bool, code fuse.Status) {
129	marker := fs.deletionPath(name)
130	haveCache, found := fs.deletionCache.HasEntry(filepath.Base(marker))
131	if haveCache {
132		return found, fuse.OK
133	}
134
135	_, code = fs.fileSystems[0].GetAttr(marker, nil)
136
137	if code == fuse.OK {
138		return true, code
139	}
140	if code == fuse.ENOENT {
141		return false, fuse.OK
142	}
143
144	log.Printf("error accessing deletion marker %s: %v", marker, code)
145	return false, fuse.Status(syscall.EROFS)
146}
147
148func (fs *unionFS) createDeletionStore() (code fuse.Status) {
149	writable := fs.fileSystems[0]
150	fi, code := writable.GetAttr(fs.options.DeletionDirName, nil)
151	if code == fuse.ENOENT {
152		code = writable.Mkdir(fs.options.DeletionDirName, 0755, nil)
153		if code.Ok() {
154			fi, code = writable.GetAttr(fs.options.DeletionDirName, nil)
155		}
156	}
157
158	if !code.Ok() || !fi.IsDir() {
159		code = fuse.Status(syscall.EROFS)
160	}
161
162	return code
163}
164
165func (fs *unionFS) getBranch(name string) branchResult {
166	name = stripSlash(name)
167	r := fs.branchCache.Get(name)
168	return r.(branchResult)
169}
170
171func (fs *unionFS) setBranch(name string, r branchResult) {
172	if !r.valid() {
173		log.Panicf("entry %q setting illegal branchResult %v", name, r)
174	}
175	fs.branchCache.Set(name, r)
176}
177
178type branchResult struct {
179	attr   *fuse.Attr
180	code   fuse.Status
181	branch int
182}
183
184func (r *branchResult) valid() bool {
185	return (r.branch >= 0 && r.attr != nil && r.code.Ok()) ||
186		(r.branch < 0 && r.attr == nil && !r.code.Ok())
187}
188
189func (fs branchResult) String() string {
190	return fmt.Sprintf("{%v %v branch %d}", fs.attr, fs.code, fs.branch)
191}
192
193func (fs *unionFS) getBranchAttrNoCache(name string) branchResult {
194	name = stripSlash(name)
195
196	parent, base := path.Split(name)
197	parent = stripSlash(parent)
198
199	parentBranch := 0
200	if base != "" {
201		parentBranch = fs.getBranch(parent).branch
202	}
203	for i, fs := range fs.fileSystems {
204		if i < parentBranch {
205			continue
206		}
207
208		a, s := fs.GetAttr(name, nil)
209		if s.Ok() {
210			if i > 0 {
211				// Needed to make hardlinks work.
212				a.Ino = 0
213			}
214			return branchResult{
215				attr:   a,
216				code:   s,
217				branch: i,
218			}
219		} else {
220			if s != fuse.ENOENT {
221				log.Printf("getattr: %v:  Got error %v from branch %v", name, s, i)
222			}
223		}
224	}
225	return branchResult{nil, fuse.ENOENT, -1}
226}
227
228////////////////
229// Deletion.
230
231func (fs *unionFS) deletionPath(name string) string {
232	return filepath.Join(fs.options.DeletionDirName, filePathHash(name))
233}
234
235func (fs *unionFS) removeDeletion(name string) {
236	marker := fs.deletionPath(name)
237
238	// os.Remove tries to be smart and issues a Remove() and
239	// Rmdir() sequentially.  We want to skip the 2nd system call,
240	// so use syscall.Unlink() directly.
241
242	code := fs.fileSystems[0].Unlink(marker, nil)
243	if !code.Ok() && code != fuse.ENOENT {
244		log.Printf("error unlinking %s: %v", marker, code)
245	}
246
247	// Update in-memory cache as last step, so we avoid caching a
248	// state from before the storage update.
249	fs.deletionCache.RemoveEntry(path.Base(marker))
250}
251
252func (fs *unionFS) putDeletion(name string) (code fuse.Status) {
253	code = fs.createDeletionStore()
254	if !code.Ok() {
255		return code
256	}
257
258	marker := fs.deletionPath(name)
259
260	// Is there a WriteStringToFileOrDie ?
261	writable := fs.fileSystems[0]
262	fi, code := writable.GetAttr(marker, nil)
263	if code.Ok() && fi.Size == uint64(len(name)) {
264		return fuse.OK
265	}
266
267	var f nodefs.File
268	if code == fuse.ENOENT {
269		f, code = writable.Create(marker, uint32(os.O_TRUNC|os.O_WRONLY), 0644, nil)
270	} else {
271		writable.Chmod(marker, 0644, nil)
272		f, code = writable.Open(marker, uint32(os.O_TRUNC|os.O_WRONLY), nil)
273	}
274	if !code.Ok() {
275		log.Printf("could not create deletion file %v: %v", marker, code)
276		return fuse.EPERM
277	}
278	defer f.Release()
279	defer f.Flush()
280	n, code := f.Write([]byte(name), 0)
281	if int(n) != len(name) || !code.Ok() {
282		panic(fmt.Sprintf("Error for writing %v: %v, %v (exp %v) %v", name, marker, n, len(name), code))
283	}
284
285	// Update the in-memory deletion cache as the last step,
286	// to ensure that the new state stays in memory
287	fs.deletionCache.AddEntry(path.Base(marker))
288
289	return fuse.OK
290}
291
292////////////////
293// Promotion.
294
295func (fs *unionFS) Promote(name string, srcResult branchResult, context *fuse.Context) (code fuse.Status) {
296	writable := fs.fileSystems[0]
297	sourceFs := fs.fileSystems[srcResult.branch]
298
299	// Promote directories.
300	fs.promoteDirsTo(name)
301
302	if srcResult.attr.IsRegular() {
303		code = pathfs.CopyFile(sourceFs, writable, name, name, context)
304
305		if code.Ok() {
306			code = writable.Chmod(name, srcResult.attr.Mode&07777|0200, context)
307		}
308		if code.Ok() {
309			aTime := srcResult.attr.AccessTime()
310			mTime := srcResult.attr.ModTime()
311			code = writable.Utimens(name, &aTime, &mTime, context)
312		}
313
314		files := fs.nodeFs.AllFiles(name, 0)
315		for _, fileWrapper := range files {
316			if !code.Ok() {
317				break
318			}
319			var uf *unionFsFile
320			f := fileWrapper.File
321			for f != nil {
322				ok := false
323				uf, ok = f.(*unionFsFile)
324				if ok {
325					break
326				}
327				f = f.InnerFile()
328			}
329			if uf == nil {
330				panic("no unionFsFile found inside")
331			}
332
333			if uf.layer > 0 {
334				uf.layer = 0
335				f := uf.File
336				uf.File, code = fs.fileSystems[0].Open(name, fileWrapper.OpenFlags, context)
337				f.Flush()
338				f.Release()
339			}
340		}
341	} else if srcResult.attr.IsSymlink() {
342		link := ""
343		link, code = sourceFs.Readlink(name, context)
344		if !code.Ok() {
345			log.Println("can't read link in source fs", name)
346		} else {
347			code = writable.Symlink(link, name, context)
348		}
349	} else if srcResult.attr.IsDir() {
350		code = writable.Mkdir(name, srcResult.attr.Mode&07777|0200, context)
351	} else {
352		log.Println("Unknown file type:", srcResult.attr)
353		return fuse.ENOSYS
354	}
355
356	if !code.Ok() {
357		fs.branchCache.GetFresh(name)
358		return code
359	} else {
360		r := fs.getBranch(name)
361		r.branch = 0
362		fs.setBranch(name, r)
363	}
364
365	return fuse.OK
366}
367
368////////////////////////////////////////////////////////////////
369// Below: implement interface for a FileSystem.
370
371func (fs *unionFS) Link(orig string, newName string, context *fuse.Context) (code fuse.Status) {
372	origResult := fs.getBranch(orig)
373	code = origResult.code
374	if code.Ok() && origResult.branch > 0 {
375		code = fs.Promote(orig, origResult, context)
376	}
377	if code.Ok() && origResult.branch > 0 {
378		// Hairy: for the link to be hooked up to the existing
379		// inode, PathNodeFs must see a client inode for the
380		// original.  We force a refresh of the attribute (so
381		// the Ino is filled in.), and then force PathNodeFs
382		// to see the Inode number.
383		fs.branchCache.GetFresh(orig)
384		inode := fs.nodeFs.Node(orig)
385		var a fuse.Attr
386		inode.Node().GetAttr(&a, nil, nil)
387	}
388	if code.Ok() {
389		code = fs.promoteDirsTo(newName)
390	}
391	if code.Ok() {
392		code = fs.fileSystems[0].Link(orig, newName, context)
393	}
394	if code.Ok() {
395		fs.removeDeletion(newName)
396		fs.branchCache.GetFresh(newName)
397	}
398	return code
399}
400
401func (fs *unionFS) Rmdir(path string, context *fuse.Context) (code fuse.Status) {
402	r := fs.getBranch(path)
403	if r.code != fuse.OK {
404		return r.code
405	}
406	if !r.attr.IsDir() {
407		return fuse.Status(syscall.ENOTDIR)
408	}
409
410	stream, code := fs.OpenDir(path, context)
411	found := false
412	for _ = range stream {
413		found = true
414	}
415	if found {
416		return fuse.Status(syscall.ENOTEMPTY)
417	}
418
419	if r.branch > 0 {
420		code = fs.putDeletion(path)
421		return code
422	}
423	code = fs.fileSystems[0].Rmdir(path, context)
424	if code != fuse.OK {
425		return code
426	}
427
428	r = fs.branchCache.GetFresh(path).(branchResult)
429	if r.branch > 0 {
430		code = fs.putDeletion(path)
431	}
432	return code
433}
434
435func (fs *unionFS) Mkdir(path string, mode uint32, context *fuse.Context) (code fuse.Status) {
436	deleted, code := fs.isDeleted(path)
437	if !code.Ok() {
438		return code
439	}
440
441	if !deleted {
442		r := fs.getBranch(path)
443		if r.code != fuse.ENOENT {
444			return fuse.Status(syscall.EEXIST)
445		}
446	}
447
448	code = fs.promoteDirsTo(path)
449	if code.Ok() {
450		code = fs.fileSystems[0].Mkdir(path, mode, context)
451	}
452	if code.Ok() {
453		fs.removeDeletion(path)
454		attr := &fuse.Attr{
455			Mode: fuse.S_IFDIR | mode,
456		}
457		fs.setBranch(path, branchResult{attr, fuse.OK, 0})
458	}
459
460	var stream []fuse.DirEntry
461	stream, code = fs.OpenDir(path, context)
462	if code.Ok() {
463		// This shouldn't happen, but let's be safe.
464		for _, entry := range stream {
465			fs.putDeletion(filepath.Join(path, entry.Name))
466		}
467	}
468
469	return code
470}
471
472func (fs *unionFS) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) {
473	code = fs.promoteDirsTo(linkName)
474	if code.Ok() {
475		code = fs.fileSystems[0].Symlink(pointedTo, linkName, context)
476	}
477	if code.Ok() {
478		fs.removeDeletion(linkName)
479		fs.branchCache.GetFresh(linkName)
480	}
481	return code
482}
483
484func (fs *unionFS) Truncate(path string, size uint64, context *fuse.Context) (code fuse.Status) {
485	if path == _DROP_CACHE {
486		return fuse.OK
487	}
488
489	r := fs.getBranch(path)
490	if r.branch > 0 {
491		code = fs.Promote(path, r, context)
492		r.branch = 0
493	}
494
495	if code.Ok() {
496		code = fs.fileSystems[0].Truncate(path, size, context)
497	}
498	if code.Ok() {
499		newAttr := *r.attr
500
501		r.attr = &newAttr
502		r.attr.Size = size
503		now := time.Now()
504		r.attr.SetTimes(nil, &now, &now)
505		fs.setBranch(path, r)
506	}
507	return code
508}
509
510func (fs *unionFS) Utimens(name string, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status) {
511	name = stripSlash(name)
512	r := fs.getBranch(name)
513
514	code = r.code
515	if code.Ok() && r.branch > 0 {
516		code = fs.Promote(name, r, context)
517		r.branch = 0
518	}
519	if code.Ok() {
520		code = fs.fileSystems[0].Utimens(name, atime, mtime, context)
521	}
522	if code.Ok() {
523		now := time.Now()
524		newAttr := *r.attr
525		r.attr = &newAttr
526		r.attr.SetTimes(atime, mtime, &now)
527		fs.setBranch(name, r)
528	}
529	return code
530}
531
532func (fs *unionFS) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
533	name = stripSlash(name)
534	r := fs.getBranch(name)
535	if r.attr == nil || r.code != fuse.OK {
536		return r.code
537	}
538
539	newAttr := *r.attr
540	r.attr = &newAttr
541
542	if os.Geteuid() != 0 {
543		return fuse.EPERM
544	}
545
546	if r.attr.Uid != uid || r.attr.Gid != gid {
547		if r.branch > 0 {
548			code := fs.Promote(name, r, context)
549			if code != fuse.OK {
550				return code
551			}
552			r.branch = 0
553		}
554		fs.fileSystems[0].Chown(name, uid, gid, context)
555	}
556	r.attr.Uid = uid
557	r.attr.Gid = gid
558	now := time.Now()
559	r.attr.SetTimes(nil, nil, &now)
560	fs.setBranch(name, r)
561	return fuse.OK
562}
563
564func (fs *unionFS) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
565	name = stripSlash(name)
566	r := fs.getBranch(name)
567	if r.attr == nil {
568		return r.code
569	}
570	newAttr := *r.attr
571	r.attr = &newAttr
572	if r.code != fuse.OK {
573		return r.code
574	}
575
576	permMask := uint32(07777)
577
578	// Always be writable.
579	oldMode := r.attr.Mode & permMask
580
581	if oldMode != mode {
582		if r.branch > 0 {
583			code := fs.Promote(name, r, context)
584			if code != fuse.OK {
585				return code
586			}
587			r.branch = 0
588		}
589		fs.fileSystems[0].Chmod(name, mode, context)
590	}
591	r.attr.Mode = (r.attr.Mode &^ permMask) | mode
592	now := time.Now()
593	r.attr.SetTimes(nil, nil, &now)
594	fs.setBranch(name, r)
595	return fuse.OK
596}
597
598func (fs *unionFS) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
599	// We always allow writing.
600	mode = mode &^ fuse.W_OK
601	if name == "" || name == _DROP_CACHE {
602		return fuse.OK
603	}
604	r := fs.getBranch(name)
605	if r.branch >= 0 {
606		return fs.fileSystems[r.branch].Access(name, mode, context)
607	}
608	return fuse.ENOENT
609}
610
611func (fs *unionFS) Unlink(name string, context *fuse.Context) (code fuse.Status) {
612	r := fs.getBranch(name)
613	if r.branch == 0 {
614		code = fs.fileSystems[0].Unlink(name, context)
615		if code != fuse.OK {
616			return code
617		}
618		r = fs.branchCache.GetFresh(name).(branchResult)
619	}
620
621	if r.branch > 0 {
622		// It would be nice to do the putDeletion async.
623		code = fs.putDeletion(name)
624	}
625	return code
626}
627
628func (fs *unionFS) Readlink(name string, context *fuse.Context) (out string, code fuse.Status) {
629	r := fs.getBranch(name)
630	if r.branch >= 0 {
631		return fs.fileSystems[r.branch].Readlink(name, context)
632	}
633	return "", fuse.ENOENT
634}
635
636func stripSlash(fn string) string {
637	return strings.TrimRight(fn, string(filepath.Separator))
638}
639
640func (fs *unionFS) promoteDirsTo(filename string) fuse.Status {
641	dirName, _ := filepath.Split(filename)
642	dirName = stripSlash(dirName)
643
644	var todo []string
645	var results []branchResult
646	for dirName != "" {
647		r := fs.getBranch(dirName)
648
649		if !r.code.Ok() {
650			log.Println("path component does not exist", filename, dirName)
651		}
652		if !r.attr.IsDir() {
653			log.Println("path component is not a directory.", dirName, r)
654			return fuse.EPERM
655		}
656		if r.branch == 0 {
657			break
658		}
659		todo = append(todo, dirName)
660		results = append(results, r)
661		dirName, _ = filepath.Split(dirName)
662		dirName = stripSlash(dirName)
663	}
664
665	for i := range todo {
666		j := len(todo) - i - 1
667		d := todo[j]
668		r := results[j]
669		code := fs.fileSystems[0].Mkdir(d, r.attr.Mode&07777|0200, nil)
670		if code != fuse.OK {
671			log.Println("Error creating dir leading to path", d, code, fs.fileSystems[0])
672			return fuse.EPERM
673		}
674
675		aTime := r.attr.AccessTime()
676		mTime := r.attr.ModTime()
677		fs.fileSystems[0].Utimens(d, &aTime, &mTime, nil)
678		r.branch = 0
679		fs.setBranch(d, r)
680	}
681	return fuse.OK
682}
683
684func (fs *unionFS) Create(name string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
685	writable := fs.fileSystems[0]
686
687	code = fs.promoteDirsTo(name)
688	if code != fuse.OK {
689		return nil, code
690	}
691	fuseFile, code = writable.Create(name, flags, mode, context)
692	if code.Ok() {
693		fuseFile = fs.newUnionFsFile(fuseFile, 0)
694		fs.removeDeletion(name)
695
696		now := time.Now()
697		a := fuse.Attr{
698			Mode: fuse.S_IFREG | mode,
699		}
700		a.SetTimes(nil, &now, &now)
701		fs.setBranch(name, branchResult{&a, fuse.OK, 0})
702	}
703	return fuseFile, code
704}
705
706func (fs *unionFS) GetAttr(name string, context *fuse.Context) (a *fuse.Attr, s fuse.Status) {
707	_, hidden := fs.hiddenFiles[name]
708	if hidden {
709		return nil, fuse.ENOENT
710	}
711	if name == _DROP_CACHE {
712		return &fuse.Attr{
713			Mode: fuse.S_IFREG | 0777,
714		}, fuse.OK
715	}
716	if name == fs.options.DeletionDirName {
717		return nil, fuse.ENOENT
718	}
719	isDel, s := fs.isDeleted(name)
720	if !s.Ok() {
721		return nil, s
722	}
723
724	if isDel {
725		return nil, fuse.ENOENT
726	}
727	r := fs.getBranch(name)
728	if r.branch < 0 {
729		return nil, fuse.ENOENT
730	}
731	fi := *r.attr
732	// Make everything appear writable.
733	fi.Mode |= 0200
734	return &fi, r.code
735}
736
737func (fs *unionFS) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
738	if name == _DROP_CACHE {
739		return nil, fuse.ENOATTR
740	}
741	r := fs.getBranch(name)
742	if r.branch >= 0 {
743		return fs.fileSystems[r.branch].GetXAttr(name, attr, context)
744	}
745	return nil, fuse.ENOENT
746}
747
748func (fs *unionFS) OpenDir(directory string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
749	dirBranch := fs.getBranch(directory)
750	if dirBranch.branch < 0 {
751		return nil, fuse.ENOENT
752	}
753
754	// We could try to use the cache, but we have a delay, so
755	// might as well get the fresh results async.
756	var wg sync.WaitGroup
757	var deletions map[string]struct{}
758
759	wg.Add(1)
760	go func() {
761		deletions = newDirnameMap(fs.fileSystems[0], fs.options.DeletionDirName)
762		wg.Done()
763	}()
764
765	entries := make([]map[string]uint32, len(fs.fileSystems))
766	for i := range fs.fileSystems {
767		entries[i] = make(map[string]uint32)
768	}
769
770	statuses := make([]fuse.Status, len(fs.fileSystems))
771	for i, l := range fs.fileSystems {
772		if i >= dirBranch.branch {
773			wg.Add(1)
774			go func(j int, pfs pathfs.FileSystem) {
775				ch, s := pfs.OpenDir(directory, context)
776				statuses[j] = s
777				for _, v := range ch {
778					entries[j][v.Name] = v.Mode
779				}
780				wg.Done()
781			}(i, l)
782		}
783	}
784
785	wg.Wait()
786	if deletions == nil {
787		_, code := fs.fileSystems[0].GetAttr(fs.options.DeletionDirName, context)
788		if code == fuse.ENOENT {
789			deletions = map[string]struct{}{}
790		} else {
791			return nil, fuse.Status(syscall.EROFS)
792		}
793	}
794
795	results := entries[0]
796
797	// TODO(hanwen): should we do anything with the return
798	// statuses?
799	for i, m := range entries {
800		if statuses[i] != fuse.OK {
801			continue
802		}
803		if i == 0 {
804			// We don't need to further process the first
805			// branch: it has no deleted files.
806			continue
807		}
808		for k, v := range m {
809			_, ok := results[k]
810			if ok {
811				continue
812			}
813
814			_, deleted := deletions[filePathHash(filepath.Join(directory, k))]
815			if !deleted {
816				results[k] = v
817			}
818		}
819	}
820	if directory == "" {
821		delete(results, fs.options.DeletionDirName)
822		for name, _ := range fs.hiddenFiles {
823			delete(results, name)
824		}
825	}
826
827	stream = make([]fuse.DirEntry, 0, len(results))
828	for k, v := range results {
829		stream = append(stream, fuse.DirEntry{
830			Name: k,
831			Mode: v,
832		})
833	}
834	return stream, fuse.OK
835}
836
837// recursivePromote promotes path, and if a directory, everything
838// below that directory.  It returns a list of all promoted paths, in
839// full, including the path itself.
840func (fs *unionFS) recursivePromote(path string, pathResult branchResult, context *fuse.Context) (names []string, code fuse.Status) {
841	names = []string{}
842	if pathResult.branch > 0 {
843		code = fs.Promote(path, pathResult, context)
844	}
845
846	if code.Ok() {
847		names = append(names, path)
848	}
849
850	if code.Ok() && pathResult.attr != nil && pathResult.attr.IsDir() {
851		var stream []fuse.DirEntry
852		stream, code = fs.OpenDir(path, context)
853		for _, e := range stream {
854			if !code.Ok() {
855				break
856			}
857			subnames := []string{}
858			p := filepath.Join(path, e.Name)
859			r := fs.getBranch(p)
860			subnames, code = fs.recursivePromote(p, r, context)
861			names = append(names, subnames...)
862		}
863	}
864
865	if !code.Ok() {
866		names = nil
867	}
868	return names, code
869}
870
871func (fs *unionFS) renameDirectory(srcResult branchResult, srcDir string, dstDir string, context *fuse.Context) (code fuse.Status) {
872	names := []string{}
873	if code.Ok() {
874		names, code = fs.recursivePromote(srcDir, srcResult, context)
875	}
876	if code.Ok() {
877		code = fs.promoteDirsTo(dstDir)
878	}
879
880	if code.Ok() {
881		writable := fs.fileSystems[0]
882		code = writable.Rename(srcDir, dstDir, context)
883	}
884
885	if code.Ok() {
886		for _, srcName := range names {
887			relative := strings.TrimLeft(srcName[len(srcDir):], string(filepath.Separator))
888			dst := filepath.Join(dstDir, relative)
889			fs.removeDeletion(dst)
890
891			srcResult := fs.getBranch(srcName)
892			srcResult.branch = 0
893			fs.setBranch(dst, srcResult)
894
895			srcResult = fs.branchCache.GetFresh(srcName).(branchResult)
896			if srcResult.branch > 0 {
897				code = fs.putDeletion(srcName)
898			}
899		}
900	}
901	return code
902}
903
904func (fs *unionFS) Rename(src string, dst string, context *fuse.Context) fuse.Status {
905	srcResult := fs.getBranch(src)
906	if !srcResult.code.Ok() {
907		return srcResult.code
908	}
909
910	if srcResult.attr.IsDir() {
911		return fs.renameDirectory(srcResult, src, dst, context)
912	}
913
914	if srcResult.branch > 0 {
915		if code := fs.Promote(src, srcResult, context); !code.Ok() {
916			return code
917		}
918	}
919	if code := fs.promoteDirsTo(dst); !code.Ok() {
920		return code
921	}
922
923	if code := fs.fileSystems[0].Rename(src, dst, context); !code.Ok() {
924		return code
925	}
926
927	fs.removeDeletion(dst)
928	// Rename is racy; avoid racing with unionFsFile.Release().
929	fs.branchCache.DropEntry(dst)
930
931	srcResult = fs.branchCache.GetFresh(src).(branchResult)
932	if srcResult.branch > 0 {
933		return fs.putDeletion(src)
934	}
935	return fuse.OK
936}
937
938func (fs *unionFS) DropBranchCache(names []string) {
939	fs.branchCache.DropAll(names)
940}
941
942func (fs *unionFS) DropDeletionCache() {
943	fs.deletionCache.DropCache()
944}
945
946func (fs *unionFS) DropSubFsCaches() {
947	for _, fs := range fs.fileSystems {
948		a, code := fs.GetAttr(_DROP_CACHE, nil)
949		if code.Ok() && a.IsRegular() {
950			f, _ := fs.Open(_DROP_CACHE, uint32(os.O_WRONLY), nil)
951			if f != nil {
952				f.Flush()
953				f.Release()
954			}
955		}
956	}
957}
958
959func (fs *unionFS) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
960	if name == _DROP_CACHE {
961		if flags&fuse.O_ANYWRITE != 0 {
962			log.Println("Forced cache drop on", fs)
963			fs.DropBranchCache(nil)
964			fs.DropDeletionCache()
965			fs.DropSubFsCaches()
966			fs.nodeFs.ForgetClientInodes()
967		}
968		return nodefs.NewDevNullFile(), fuse.OK
969	}
970	r := fs.getBranch(name)
971	if r.branch < 0 {
972		// This should not happen, as a GetAttr() should have
973		// already verified existence.
974		log.Println("UnionFs: open of non-existent file:", name)
975		return nil, fuse.ENOENT
976	}
977	if flags&fuse.O_ANYWRITE != 0 && r.branch > 0 {
978		code := fs.Promote(name, r, context)
979		if code != fuse.OK {
980			return nil, code
981		}
982		r.branch = 0
983		now := time.Now()
984		r.attr.SetTimes(nil, &now, nil)
985		fs.setBranch(name, r)
986	}
987	fuseFile, status = fs.fileSystems[r.branch].Open(name, uint32(flags), context)
988	if fuseFile != nil {
989		fuseFile = fs.newUnionFsFile(fuseFile, r.branch)
990	}
991	return fuseFile, status
992}
993
994func (fs *unionFS) String() string {
995	names := []string{}
996	for _, fs := range fs.fileSystems {
997		names = append(names, fs.String())
998	}
999	return fmt.Sprintf("UnionFs(%v)", names)
1000}
1001
1002func (fs *unionFS) StatFs(name string) *fuse.StatfsOut {
1003	return fs.fileSystems[0].StatFs("")
1004}
1005
1006type unionFsFile struct {
1007	nodefs.File
1008	ufs   *unionFS
1009	node  *nodefs.Inode
1010	layer int
1011}
1012
1013func (fs *unionFsFile) String() string {
1014	return fmt.Sprintf("unionFsFile(%s)", fs.File.String())
1015}
1016
1017func (fs *unionFS) newUnionFsFile(f nodefs.File, branch int) *unionFsFile {
1018	return &unionFsFile{
1019		File:  f,
1020		ufs:   fs,
1021		layer: branch,
1022	}
1023}
1024
1025func (fs *unionFsFile) InnerFile() (file nodefs.File) {
1026	return fs.File
1027}
1028
1029// We can't hook on Release. Release has no response, so it is not
1030// ordered wrt any following calls.
1031func (fs *unionFsFile) Flush() (code fuse.Status) {
1032	code = fs.File.Flush()
1033	path := fs.ufs.nodeFs.Path(fs.node)
1034	fs.ufs.branchCache.GetFresh(path)
1035	return code
1036}
1037
1038func (fs *unionFsFile) SetInode(node *nodefs.Inode) {
1039	fs.node = node
1040}
1041
1042func (fs *unionFsFile) GetAttr(out *fuse.Attr) fuse.Status {
1043	code := fs.File.GetAttr(out)
1044	if code.Ok() {
1045		out.Mode |= 0200
1046	}
1047	return code
1048}
1049