From d76e7aadb48e5efd6af6ad42e9146f65f8e89fb8 Mon Sep 17 00:00:00 2001
From: Jakob Unterwurzacher
Date: Wed, 21 Sep 2016 21:25:55 +0200
Subject: reverse: use dynamic inode numbers

...with stable mappings for hard-linked files.
---
 internal/fusefrontend_reverse/ino_map.go | 24 +++++++++++++
 internal/fusefrontend_reverse/rfs.go     | 59 +++++++++++++++++++++++++++++---
 2 files changed, 78 insertions(+), 5 deletions(-)
 create mode 100644 internal/fusefrontend_reverse/ino_map.go

(limited to 'internal')

diff --git a/internal/fusefrontend_reverse/ino_map.go b/internal/fusefrontend_reverse/ino_map.go
new file mode 100644
index 0000000..5217732
--- /dev/null
+++ b/internal/fusefrontend_reverse/ino_map.go
@@ -0,0 +1,24 @@
+package fusefrontend_reverse
+
+import (
+	"sync/atomic"
+)
+
+func NewInoGen() *inoGenT {
+	var ino uint64 = 1
+	return &inoGenT{&ino}
+}
+
+type inoGenT struct {
+	ino *uint64
+}
+
+// Get the next inode counter value
+func (i *inoGenT) next() uint64 {
+	return atomic.AddUint64(i.ino, 1)
+}
+
+type devIno struct {
+	dev uint64
+	ino uint64
+}
diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go
index 4b04b86..4f516fd 100644
--- a/internal/fusefrontend_reverse/rfs.go
+++ b/internal/fusefrontend_reverse/rfs.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"sync"
 	"syscall"
 
 	"github.com/hanwen/go-fuse/fuse"
@@ -32,6 +33,12 @@ type reverseFS struct {
 	nameTransform *nametransform.NameTransform
 	// Content encryption helper
 	contentEnc *contentenc.ContentEnc
+	// Inode number generator
+	inoGen *inoGenT
+	// Maps backing files device+inode pairs to user-facing unique inode numbers
+	inoMap map[devIno]uint64
+	// Protects map access
+	inoMapLock sync.Mutex
 }
 
 // Encrypted FUSE overlay filesystem
@@ -47,6 +54,8 @@ func NewFS(args fusefrontend.Args) *reverseFS {
 		args:          args,
 		nameTransform: nameTransform,
 		contentEnc:    contentEnc,
+		inoGen:        NewInoGen(),
+		inoMap:        map[devIno]uint64{},
 	}
 }
 
@@ -85,11 +94,11 @@ func (rfs *reverseFS) dirIVAttr(relPath string, context *fuse.Context) (*fuse.At
 		fmt.Printf("not exec")
 		return nil, fuse.EPERM
 	}
-	// All good. Let's fake the file.
-	// We use the inode number of the parent dir (can this cause problems?).
+	// All good. Let's fake the file. We use the timestamps from the parent dir.
 	a.Mode = DirIVMode
 	a.Size = nametransform.DirIVLen
 	a.Nlink = 1
+	a.Ino = rfs.inoGen.next()
 
 	return a, fuse.OK
 }
@@ -99,10 +108,45 @@ func isDirIV(relPath string) bool {
 	return filepath.Base(relPath) == nametransform.DirIVFilename
 }
 
+func (rfs *reverseFS) inoAwareStat(relPlainPath string) (*fuse.Attr, fuse.Status) {
+	absPath, err := rfs.abs(relPlainPath, nil)
+	if err != nil {
+		return nil, fuse.ToStatus(err)
+	}
+	var fi os.FileInfo
+	if relPlainPath == "" {
+		// Look through symlinks for the root dir
+		fi, err = os.Stat(absPath)
+	} else {
+		fi, err = os.Lstat(absPath)
+	}
+	if err != nil {
+		return nil, fuse.ToStatus(err)
+	}
+	st := fi.Sys().(*syscall.Stat_t)
+	// The file has hard links. We have to give it a stable inode number so
+	// tar or rsync can find them.
+	if fi.Mode().IsRegular() && st.Nlink > 1 {
+		di := devIno{st.Dev, st.Ino}
+		rfs.inoMapLock.Lock()
+		stableIno := rfs.inoMap[di]
+		if stableIno == 0 {
+			rfs.inoMap[di] = rfs.inoGen.next()
+		}
+		rfs.inoMapLock.Unlock()
+		st.Ino = stableIno
+	} else {
+		st.Ino = rfs.inoGen.next()
+	}
+	a := &fuse.Attr{}
+	a.FromStat(st)
+	return a, fuse.OK
+}
+
 // GetAttr - FUSE call
 func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
 	if relPath == configfile.ConfDefaultName {
-		return rfs.loopbackfs.GetAttr(configfile.ConfReverseName, context)
+		return rfs.inoAwareStat(configfile.ConfReverseName)
 	}
 	if isDirIV(relPath) {
 		return rfs.dirIVAttr(relPath, context)
@@ -110,11 +154,11 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
 	if rfs.isFiltered(relPath) {
 		return nil, fuse.EPERM
 	}
-	relPath, err := rfs.decryptPath(relPath)
+	cPath, err := rfs.decryptPath(relPath)
 	if err != nil {
 		return nil, fuse.ToStatus(err)
 	}
-	a, status := rfs.loopbackfs.GetAttr(relPath, context)
+	a, status := rfs.inoAwareStat(cPath)
 	if !status.Ok() {
 		return nil, status
 	}
@@ -189,3 +233,8 @@ func (rfs *reverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.
 
 	return entries, fuse.OK
 }
+
+// StatFs - FUSE call
+func (rfs *reverseFS) StatFs(name string) *fuse.StatfsOut {
+	return rfs.loopbackfs.StatFs(name)
+}
-- 
cgit v1.2.3