Commit 74afa852 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

nodefs: use {Gen, Ino} as file ID

This lets filesystems control Generation produced. It also unifies
OpaqueID (used for implementors) NodeID (used for kernel comms) and
the Ino field (reported in Stat/Lstat calls).

For loopback, there is a minor inconvenience, which is that the file
is determined by (Dev, Ino). Get around this by xoring Dev and Ino.
parent 0ba4db2f
...@@ -14,11 +14,6 @@ import ( ...@@ -14,11 +14,6 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
type mapEntry struct {
generation uint64
inode *Inode
}
type fileEntry struct { type fileEntry struct {
file File file File
...@@ -33,14 +28,12 @@ type rawBridge struct { ...@@ -33,14 +28,12 @@ type rawBridge struct {
// mu protects the following data. Locks for inodes must be // mu protects the following data. Locks for inodes must be
// taken before rawBridge.mu // taken before rawBridge.mu
mu sync.Mutex mu sync.Mutex
nodes []mapEntry nodes map[uint64]*Inode
free []uint64 automaticIno uint64
files []fileEntry files []fileEntry
freeFiles []uint64 freeFiles []uint64
byFileID map[FileID]*Inode
} }
// newInode creates creates new inode pointing to node. // newInode creates creates new inode pointing to node.
...@@ -49,27 +42,35 @@ func (b *rawBridge) newInode(node Node, mode uint32, id FileID, persistent bool) ...@@ -49,27 +42,35 @@ func (b *rawBridge) newInode(node Node, mode uint32, id FileID, persistent bool)
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
if !id.Zero() { if id.Reserved() {
// the same node can be looked up through 2 paths in parallel, eg. log.Panicf("using reserved ID %d for inode number", id.Ino)
// }
// root
// / \ if id.Ino == 0 {
// dir1 dir2 id.Ino = b.automaticIno
// \ / b.automaticIno++
// file }
//
// dir1.Lookup("file") and dir2.Lookup("file") are executed // the same node can be looked up through 2 paths in parallel, eg.
// simultaneously. The matching FileIDs ensure that we return the //
// same node. // root
old := b.byFileID[id] // / \
if old != nil { // dir1 dir2
return old // \ /
} // file
//
// dir1.Lookup("file") and dir2.Lookup("file") are executed
// simultaneously. The matching FileIDs ensure that we return the
// same node.
old := b.nodes[id.Ino]
if old != nil {
return old
} }
inode := &Inode{ inode := &Inode{
mode: mode ^ 07777, mode: mode ^ 07777,
node: node, node: node,
nodeID: id,
bridge: b, bridge: b,
persistent: persistent, persistent: persistent,
parents: make(map[parentData]struct{}), parents: make(map[parentData]struct{}),
...@@ -78,10 +79,7 @@ func (b *rawBridge) newInode(node Node, mode uint32, id FileID, persistent bool) ...@@ -78,10 +79,7 @@ func (b *rawBridge) newInode(node Node, mode uint32, id FileID, persistent bool)
inode.children = make(map[string]*Inode) inode.children = make(map[string]*Inode)
} }
if !id.Zero() { b.nodes[id.Ino] = inode
b.byFileID[id] = inode
}
node.setInode(inode) node.setInode(inode)
return inode return inode
} }
...@@ -89,7 +87,7 @@ func (b *rawBridge) newInode(node Node, mode uint32, id FileID, persistent bool) ...@@ -89,7 +87,7 @@ func (b *rawBridge) newInode(node Node, mode uint32, id FileID, persistent bool)
func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem { func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem {
bridge := &rawBridge{ bridge := &rawBridge{
RawFileSystem: fuse.NewDefaultRawFileSystem(), RawFileSystem: fuse.NewDefaultRawFileSystem(),
byFileID: make(map[FileID]*Inode), automaticIno: 1 << 63,
} }
if opts != nil { if opts != nil {
...@@ -101,7 +99,6 @@ func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem { ...@@ -101,7 +99,6 @@ func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem {
} }
bridge.root = &Inode{ bridge.root = &Inode{
nodeID: 1,
lookupCount: 1, lookupCount: 1,
mode: fuse.S_IFDIR, mode: fuse.S_IFDIR,
children: make(map[string]*Inode), children: make(map[string]*Inode),
...@@ -109,11 +106,11 @@ func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem { ...@@ -109,11 +106,11 @@ func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem {
node: root, node: root,
bridge: bridge, bridge: bridge,
} }
bridge.root.nodeID.Ino = 1
root.setInode(bridge.root) root.setInode(bridge.root)
bridge.nodes = append(bridge.nodes, bridge.nodes = map[uint64]*Inode{
mapEntry{}, 1: bridge.root,
// ID 1 is always the root. }
mapEntry{inode: bridge.root})
// Fh 0 means no file handle. // Fh 0 means no file handle.
bridge.files = []fileEntry{{}} bridge.files = []fileEntry{{}}
...@@ -123,7 +120,7 @@ func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem { ...@@ -123,7 +120,7 @@ func NewNodeFS(root Node, opts *Options) fuse.RawFileSystem {
func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, fileEntry) { func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, fileEntry) {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
n, f := b.nodes[id].inode, b.files[fh] n, f := b.nodes[id], b.files[fh]
if n == nil { if n == nil {
log.Panicf("unknown node %d", id) log.Panicf("unknown node %d", id)
} }
...@@ -203,18 +200,18 @@ func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file F ...@@ -203,18 +200,18 @@ func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file F
lockNodes(parent, child) lockNodes(parent, child)
parent.setEntry(name, child) parent.setEntry(name, child)
b.mu.Lock() b.mu.Lock()
child.lookupCount++ child.lookupCount++
if child.nodeID == 0 {
b.registerInode(child)
}
var fh uint64 var fh uint64
if file != nil { if file != nil {
fh = b.registerFile(file) fh = b.registerFile(file)
} }
out.NodeId = child.nodeID out.NodeId = child.nodeID.Ino
out.Generation = b.nodes[out.NodeId].generation out.Generation = child.nodeID.Gen
out.Attr.Ino = child.nodeID.Ino
b.mu.Unlock() b.mu.Unlock()
unlockNodes(parent, child) unlockNodes(parent, child)
return fh return fh
...@@ -229,25 +226,6 @@ func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) { ...@@ -229,25 +226,6 @@ func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) {
} }
} }
// registerInode sets an nodeID in the child. Must have bridge.mu and
// child.mu
func (b *rawBridge) registerInode(child *Inode) {
if l := len(b.free); l > 0 {
last := b.free[l-1]
b.free = b.free[:l-1]
child.nodeID = last
b.nodes[last].inode = child
b.nodes[last].generation++
} else {
last := len(b.nodes)
b.nodes = append(b.nodes, mapEntry{
inode: child,
})
child.nodeID = uint64(last)
}
}
func (b *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOut) (code fuse.Status) { func (b *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOut) (code fuse.Status) {
ctx := context.TODO() ctx := context.TODO()
parent, _ := b.inode(input.NodeId, 0) parent, _ := b.inode(input.NodeId, 0)
...@@ -269,6 +247,9 @@ func (b *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOu ...@@ -269,6 +247,9 @@ func (b *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOu
out.Attr = temp.Attr out.Attr = temp.Attr
out.AttrValid = temp.AttrValid out.AttrValid = temp.AttrValid
out.AttrValidNsec = temp.AttrValidNsec out.AttrValidNsec = temp.AttrValidNsec
out.Attr.Ino = child.nodeID.Ino
out.Generation = child.nodeID.Gen
out.NodeId = child.nodeID.Ino
b.setEntryOutTimeout(&out.EntryOut) b.setEntryOutTimeout(&out.EntryOut)
if out.Attr.Mode&^07777 != fuse.S_IFREG { if out.Attr.Mode&^07777 != fuse.S_IFREG {
...@@ -282,11 +263,6 @@ func (b *rawBridge) Forget(nodeid, nlookup uint64) { ...@@ -282,11 +263,6 @@ func (b *rawBridge) Forget(nodeid, nlookup uint64) {
n.removeRef(nlookup, false) n.removeRef(nlookup, false)
} }
func (b *rawBridge) unregisterNode(nodeid uint64) {
b.free = append(b.free, nodeid)
b.nodes[nodeid].inode = nil
}
func (b *rawBridge) SetDebug(debug bool) {} func (b *rawBridge) SetDebug(debug bool) {}
func (b *rawBridge) GetAttr(input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Status { func (b *rawBridge) GetAttr(input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Status {
...@@ -299,6 +275,7 @@ func (b *rawBridge) GetAttr(input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Statu ...@@ -299,6 +275,7 @@ func (b *rawBridge) GetAttr(input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Statu
code := n.node.GetAttr(context.TODO(), f, out) code := n.node.GetAttr(context.TODO(), f, out)
b.setAttrTimeout(out) b.setAttrTimeout(out)
out.Ino = input.NodeId
return code return code
} }
...@@ -372,6 +349,7 @@ func (b *rawBridge) SetAttr(input *fuse.SetAttrIn, out *fuse.AttrOut) (code fuse ...@@ -372,6 +349,7 @@ func (b *rawBridge) SetAttr(input *fuse.SetAttrIn, out *fuse.AttrOut) (code fuse
// the changes we effect here. // the changes we effect here.
code = n.node.GetAttr(ctx, f, out) code = n.node.GetAttr(ctx, f, out)
b.setAttrTimeout(out) b.setAttrTimeout(out)
out.Ino = n.nodeID.Ino
return code return code
} }
......
...@@ -21,20 +21,25 @@ type parentData struct { ...@@ -21,20 +21,25 @@ type parentData struct {
} }
// FileID provides a identifier for file objects defined by FUSE // FileID provides a identifier for file objects defined by FUSE
// filesystems. The identifier is divided into a (Device, Inode) pair, // filesystems.
// so files in underlying file systems can easily be represented, but
// FUSE filesystems are free to use any other information as 128-bit
// key.
//
// XXX name: PersistentID ? NodeID ?
type FileID struct { type FileID struct {
Dev uint64 // The inode number must be unique among the currently live
// objects in the file system. It is used to communicate to
// the kernel about this file object. The values uint64(-1),
// and 1 are reserved. When using Ino==0, a unique, sequential
// number is assigned (starting at 2^63)
Ino uint64 Ino uint64
// When reusing a previously used inode number for a new
// object, the new object must have a different Gen
// number. This is irrelevant if the FS is not exported over
// NFS
Gen uint64
} }
// Zero returns if the FileID is zeroed out // Zero returns if the FileID is zeroed out
func (i *FileID) Zero() bool { func (i *FileID) Reserved() bool {
return i.Dev == 0 && i.Ino == 0 return i.Ino == 0 || i.Ino == 1 || i.Ino == ^uint64(0)
} }
// Inode is a node in VFS tree. Inodes are one-to-one mapped to Node // Inode is a node in VFS tree. Inodes are one-to-one mapped to Node
...@@ -45,7 +50,7 @@ type Inode struct { ...@@ -45,7 +50,7 @@ type Inode struct {
// The filetype bits from the mode. // The filetype bits from the mode.
mode uint32 mode uint32
opaqueID FileID nodeID FileID
node Node node Node
bridge *rawBridge bridge *rawBridge
...@@ -57,10 +62,6 @@ type Inode struct { ...@@ -57,10 +62,6 @@ type Inode struct {
// lockNodes/unlockNodes // lockNodes/unlockNodes
mu sync.Mutex mu sync.Mutex
// ID of the inode for talking to the kernel, 0 if the kernel
// does not know this inode.
nodeID uint64
// persistent indicates that this node should not be removed // persistent indicates that this node should not be removed
// from the tree, even if there are no live references. This // from the tree, even if there are no live references. This
// must be set on creation, and can only be changed to false // must be set on creation, and can only be changed to false
...@@ -86,7 +87,7 @@ type Inode struct { ...@@ -86,7 +87,7 @@ type Inode struct {
func (n *Inode) debugString() string { func (n *Inode) debugString() string {
var ss []string var ss []string
for nm, ch := range n.children { for nm, ch := range n.children {
ss = append(ss, fmt.Sprintf("%q=%d(%d)", nm, ch.nodeID, ch.opaqueID)) ss = append(ss, fmt.Sprintf("%q=%d", nm, ch.nodeID.Ino))
} }
return fmt.Sprintf("%d: %s", n.nodeID, strings.Join(ss, ",")) return fmt.Sprintf("%d: %s", n.nodeID, strings.Join(ss, ","))
...@@ -172,9 +173,9 @@ func unlockNodes(ns ...*Inode) { ...@@ -172,9 +173,9 @@ func unlockNodes(ns ...*Inode) {
// kernel has no way of reviving forgotten nodes by its own // kernel has no way of reviving forgotten nodes by its own
// initiative. // initiative.
func (n *Inode) Forgotten() bool { func (n *Inode) Forgotten() bool {
n.bridge.mu.Lock() n.mu.Lock()
defer n.bridge.mu.Unlock() defer n.mu.Unlock()
return n.nodeID == 0 return n.lookupCount == 0 && len(n.parents) == 0 && !n.persistent
} }
// Node returns the Node object implementing the file system operations. // Node returns the Node object implementing the file system operations.
...@@ -333,13 +334,7 @@ retry: ...@@ -333,13 +334,7 @@ retry:
} }
n.bridge.mu.Lock() n.bridge.mu.Lock()
if n.nodeID != 0 { delete(n.bridge.nodes, n.nodeID.Ino)
n.bridge.unregisterNode(n.nodeID)
n.nodeID = 0
}
if !n.opaqueID.Zero() {
delete(n.bridge.byFileID, n.opaqueID)
}
n.bridge.mu.Unlock() n.bridge.mu.Unlock()
unlockNodes(lockme...) unlockNodes(lockme...)
......
...@@ -72,14 +72,8 @@ func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryO ...@@ -72,14 +72,8 @@ func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryO
} }
out.Attr.FromStat(&st) out.Attr.FromStat(&st)
opaque := FileID{
Dev: uint64(out.Attr.Rdev),
Ino: out.Attr.Ino,
}
node := n.rootNode.newLoopbackNode() node := n.rootNode.newLoopbackNode()
ch := n.inode().NewInode(node, out.Attr.Mode, opaque) ch := n.inode().NewInode(node, out.Attr.Mode, idFromStat(&st))
return ch, fuse.OK return ch, fuse.OK
} }
...@@ -98,11 +92,7 @@ func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32 ...@@ -98,11 +92,7 @@ func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32
out.Attr.FromStat(&st) out.Attr.FromStat(&st)
node := n.rootNode.newLoopbackNode() node := n.rootNode.newLoopbackNode()
opaque := FileID{ ch := n.inode().NewInode(node, out.Attr.Mode, idFromStat(&st))
Dev: uint64(out.Attr.Rdev),
Ino: out.Attr.Ino,
}
ch := n.inode().NewInode(node, out.Attr.Mode, opaque)
return ch, fuse.OK return ch, fuse.OK
} }
...@@ -123,11 +113,7 @@ func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out ...@@ -123,11 +113,7 @@ func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out
out.Attr.FromStat(&st) out.Attr.FromStat(&st)
node := n.rootNode.newLoopbackNode() node := n.rootNode.newLoopbackNode()
opaque := FileID{ ch := n.inode().NewInode(node, out.Attr.Mode, idFromStat(&st))
Dev: uint64(out.Attr.Rdev),
Ino: out.Attr.Ino,
}
ch := n.inode().NewInode(node, out.Attr.Mode, opaque)
return ch, fuse.OK return ch, fuse.OK
} }
...@@ -163,6 +149,15 @@ func (n *loopbackNode) Rename(ctx context.Context, name string, newParent Node, ...@@ -163,6 +149,15 @@ func (n *loopbackNode) Rename(ctx context.Context, name string, newParent Node,
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
func idFromStat(st *syscall.Stat_t) FileID {
return FileID{
Gen: 1,
// This should work well for traditional backing FSes,
// not so much for other go-fuse FS-es
Ino: uint64(st.Dev)<<32 ^ st.Ino,
}
}
func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32) (inode *Inode, fh File, fuseFlags uint32, code fuse.Status) { func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32) (inode *Inode, fh File, fuseFlags uint32, code fuse.Status) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
...@@ -178,12 +173,7 @@ func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mo ...@@ -178,12 +173,7 @@ func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mo
} }
node := n.rootNode.newLoopbackNode() node := n.rootNode.newLoopbackNode()
opaque := FileID{ ch := n.inode().NewInode(node, st.Mode, idFromStat(&st))
Dev: st.Rdev,
Ino: st.Ino,
}
ch := n.inode().NewInode(node, st.Mode, opaque)
lf := newLoopbackFile(f) lf := newLoopbackFile(f)
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment