fusefrontend: -sharedstorage: present stable inode numbers

Use the Gen field (inode generation) to distinguish hard links
while passing the real inode numbers to userspace.

Fixes https://github.com/rfjakob/gocryptfs/issues/584
This commit is contained in:
Jakob Unterwurzacher 2021-07-31 13:24:25 +02:00
parent eecbcbb090
commit 1bc1db620b
5 changed files with 40 additions and 28 deletions

View File

@ -40,6 +40,24 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
// Translate ciphertext size in `out.Attr.Size` to plaintext size
n.translateSize(dirfd, cName, &out.Attr)
rn := n.rootNode()
if rn.args.SharedStorage {
// If we already have a child node that matches what we found on disk*
// (as reflected in `ch`), return it here.
//
// This keeps the Node ID for each directory entry stable
// (until forgotten).
//
// *We compare `name`, `Ino`, `Mode` (but not `Gen`!)
old := n.Inode.GetChild(name)
if old != nil &&
old.StableAttr().Ino == ch.StableAttr().Ino &&
// `Mode` has already been masked with syscall.S_IFMT by n.newChild()
old.StableAttr().Mode == ch.StableAttr().Mode {
return old, 0
}
}
return ch, 0
}

View File

@ -2,6 +2,7 @@ package fusefrontend
import (
"context"
"sync/atomic"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
@ -82,13 +83,20 @@ func (n *Node) rootNode() *RootNode {
func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode {
rn := n.rootNode()
// Get stable inode number based on underlying (device,ino) pair
// (or set to zero in case of `-sharestorage`)
rn.inoMap.TranslateStat(st)
out.Attr.FromStat(st)
var gen uint64 = 1
if rn.args.SharedStorage {
// Make each directory entry a unique node by using a unique generation
// value - see the comment at RootNode.gen for details.
gen = atomic.AddUint64(&rn.gen, 1)
}
// Create child node
id := fs.StableAttr{
Mode: uint32(st.Mode),
Gen: 1,
Gen: gen,
Ino: st.Ino,
}
node := &Node{}

View File

@ -50,7 +50,13 @@ type RootNode struct {
dirCache dirCache
// inoMap translates inode numbers from different devices to unique inode
// numbers.
inoMap inomap.TranslateStater
inoMap *inomap.InoMap
// gen is the node generation numbers. Normally, it is always set to 1,
// but -sharestorage uses an incrementing counter for new nodes.
// This makes each directory entry unique (even hard links),
// makes go-fuse hand out separate FUSE Node IDs for each, and prevents
// bizarre problems when inode numbers are reused behind our back.
gen uint64
}
func NewRootNode(args Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *RootNode {
@ -71,11 +77,6 @@ func NewRootNode(args Args, c *contentenc.ContentEnc, n *nametransform.NameTrans
inoMap: inomap.New(),
dirCache: dirCache{ivLen: ivLen},
}
// In `-sharedstorage` mode we always set the inode number to zero.
// This makes go-fuse generate a new inode number for each lookup.
if args.SharedStorage {
rn.inoMap = &inomap.TranslateStatZero{}
}
return rn
}

View File

@ -104,14 +104,3 @@ func (m *InoMap) TranslateStat(st *syscall.Stat_t) {
in := QInoFromStat(st)
st.Ino = m.Translate(in)
}
type TranslateStater interface {
TranslateStat(st *syscall.Stat_t)
}
// TranslateStatZero always sets st.Ino to zero. Used for `-sharedstorage`.
type TranslateStatZero struct{}
func (z TranslateStatZero) TranslateStat(st *syscall.Stat_t) {
st.Ino = 0
}

View File

@ -929,8 +929,8 @@ func TestInitNotEmpty(t *testing.T) {
}
}
// TestSharedstorage checks that `-sharedstorage` hands out arbitrary inode
// numbers (no hard link tracking)
// TestSharedstorage checks that `-sharedstorage` shows stable inode numbers to
// userpsace despite having hard link tracking disabled
func TestSharedstorage(t *testing.T) {
dir := test_helpers.InitFS(t)
mnt := dir + ".mnt"
@ -944,7 +944,7 @@ func TestSharedstorage(t *testing.T) {
if err := os.Link(foo1, foo2); err != nil {
t.Fatal(err)
}
var st1, st2, st3 syscall.Stat_t
var st1, st2 syscall.Stat_t
if err := syscall.Stat(foo1, &st1); err != nil {
t.Fatal(err)
}
@ -952,12 +952,8 @@ func TestSharedstorage(t *testing.T) {
if err := syscall.Stat(foo2, &st2); err != nil {
t.Fatal(err)
}
// Stat()'ing again should give us again a new inode number
if err := syscall.Stat(foo2, &st3); err != nil {
t.Fatal(err)
}
if st1.Ino == st2.Ino || st2.Ino == st3.Ino || st1.Ino == st3.Ino {
t.Error(st1.Ino, st2.Ino, st3.Ino)
if st1.Ino != st2.Ino {
t.Errorf("unstable inode number: changed from %d to %d", st1.Ino, st2.Ino)
}
// Check that we we don't have stat caching. New length should show up
// on the hard link immediately.