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) +} diff --git a/main.go b/main.go index 68f6a44..ea9efad 100644 --- a/main.go +++ b/main.go @@ -349,6 +349,9 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi mOpts.Options = append(mOpts.Options, "fsname="+args.cipherdir) // Second column, "Type", will be shown as "fuse." + Name mOpts.Name = "gocryptfs" + if args.reverse { + mOpts.Name += "-reverse" + } // The kernel enforces read-only operation, we just have to pass "ro". // Reverse mounts are always read-only