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:
parent
eecbcbb090
commit
1bc1db620b
@ -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
|
||||
}
|
||||
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user