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
|
// Translate ciphertext size in `out.Attr.Size` to plaintext size
|
||||||
n.translateSize(dirfd, cName, &out.Attr)
|
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
|
return ch, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package fusefrontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/hanwen/go-fuse/v2/fs"
|
"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 {
|
func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode {
|
||||||
rn := n.rootNode()
|
rn := n.rootNode()
|
||||||
// Get stable inode number based on underlying (device,ino) pair
|
// Get stable inode number based on underlying (device,ino) pair
|
||||||
// (or set to zero in case of `-sharestorage`)
|
|
||||||
rn.inoMap.TranslateStat(st)
|
rn.inoMap.TranslateStat(st)
|
||||||
out.Attr.FromStat(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
|
// Create child node
|
||||||
id := fs.StableAttr{
|
id := fs.StableAttr{
|
||||||
Mode: uint32(st.Mode),
|
Mode: uint32(st.Mode),
|
||||||
Gen: 1,
|
Gen: gen,
|
||||||
Ino: st.Ino,
|
Ino: st.Ino,
|
||||||
}
|
}
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
|
|
|
@ -50,7 +50,13 @@ type RootNode struct {
|
||||||
dirCache dirCache
|
dirCache dirCache
|
||||||
// inoMap translates inode numbers from different devices to unique inode
|
// inoMap translates inode numbers from different devices to unique inode
|
||||||
// numbers.
|
// 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 {
|
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(),
|
inoMap: inomap.New(),
|
||||||
dirCache: dirCache{ivLen: ivLen},
|
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
|
return rn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,14 +104,3 @@ func (m *InoMap) TranslateStat(st *syscall.Stat_t) {
|
||||||
in := QInoFromStat(st)
|
in := QInoFromStat(st)
|
||||||
st.Ino = m.Translate(in)
|
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
|
// TestSharedstorage checks that `-sharedstorage` shows stable inode numbers to
|
||||||
// numbers (no hard link tracking)
|
// userpsace despite having hard link tracking disabled
|
||||||
func TestSharedstorage(t *testing.T) {
|
func TestSharedstorage(t *testing.T) {
|
||||||
dir := test_helpers.InitFS(t)
|
dir := test_helpers.InitFS(t)
|
||||||
mnt := dir + ".mnt"
|
mnt := dir + ".mnt"
|
||||||
|
@ -944,7 +944,7 @@ func TestSharedstorage(t *testing.T) {
|
||||||
if err := os.Link(foo1, foo2); err != nil {
|
if err := os.Link(foo1, foo2); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var st1, st2, st3 syscall.Stat_t
|
var st1, st2 syscall.Stat_t
|
||||||
if err := syscall.Stat(foo1, &st1); err != nil {
|
if err := syscall.Stat(foo1, &st1); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -952,12 +952,8 @@ func TestSharedstorage(t *testing.T) {
|
||||||
if err := syscall.Stat(foo2, &st2); err != nil {
|
if err := syscall.Stat(foo2, &st2); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Stat()'ing again should give us again a new inode number
|
if st1.Ino != st2.Ino {
|
||||||
if err := syscall.Stat(foo2, &st3); err != nil {
|
t.Errorf("unstable inode number: changed from %d to %d", st1.Ino, st2.Ino)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if st1.Ino == st2.Ino || st2.Ino == st3.Ino || st1.Ino == st3.Ino {
|
|
||||||
t.Error(st1.Ino, st2.Ino, st3.Ino)
|
|
||||||
}
|
}
|
||||||
// Check that we we don't have stat caching. New length should show up
|
// Check that we we don't have stat caching. New length should show up
|
||||||
// on the hard link immediately.
|
// on the hard link immediately.
|
||||||
|
|
Loading…
Reference in New Issue