mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement FUSE_LOOKUP
Fixes #3231 Co-authored-by: Boyuan He <heboyuan@google.com>
This commit is contained in:
committed by
Andrei Vagin
co-authored by
Boyuan He
parent
717b661c45
commit
d6ee3ae6d7
@@ -14,12 +14,17 @@
|
||||
|
||||
package linux
|
||||
|
||||
import "gvisor.dev/gvisor/tools/go_marshal/marshal"
|
||||
|
||||
// +marshal
|
||||
type FUSEOpcode uint32
|
||||
|
||||
// +marshal
|
||||
type FUSEOpID uint64
|
||||
|
||||
// FUSE_ROOT_ID is the id of root inode.
|
||||
const FUSE_ROOT_ID = 1
|
||||
|
||||
// Opcodes for FUSE operations. Analogous to the opcodes in include/linux/fuse.h.
|
||||
const (
|
||||
FUSE_LOOKUP FUSEOpcode = 1
|
||||
@@ -301,3 +306,54 @@ type FUSEGetAttrOut struct {
|
||||
// Attr contains the metadata returned from the FUSE server
|
||||
Attr FUSEAttr
|
||||
}
|
||||
|
||||
// FUSEEntryOut is the reply sent by the daemon to the kernel
|
||||
// for FUSE_MKNOD, FUSE_MKDIR, FUSE_SYMLINK, FUSE_LINK and
|
||||
// FUSE_LOOKUP.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEEntryOut struct {
|
||||
// NodeID is the ID for current inode.
|
||||
NodeID uint64
|
||||
|
||||
// Generation is the generation number of inode.
|
||||
// Used to identify an inode that have different ID at different time.
|
||||
Generation uint64
|
||||
|
||||
// EntryValid indicates timeout for an entry.
|
||||
EntryValid uint64
|
||||
|
||||
// AttrValid indicates timeout for an entry's attributes.
|
||||
AttrValid uint64
|
||||
|
||||
// EntryValidNsec indicates timeout for an entry in nanosecond.
|
||||
EntryValidNSec uint32
|
||||
|
||||
// AttrValidNsec indicates timeout for an entry's attributes in nanosecond.
|
||||
AttrValidNSec uint32
|
||||
|
||||
// Attr contains the attributes of an entry.
|
||||
Attr FUSEAttr
|
||||
}
|
||||
|
||||
// FUSELookupIn is the request sent by the kernel to the daemon
|
||||
// to look up a file name.
|
||||
//
|
||||
// Dynamically-sized objects cannot be marshalled.
|
||||
type FUSELookupIn struct {
|
||||
marshal.StubMarshallable
|
||||
|
||||
// Name is a file name to be looked up.
|
||||
Name string
|
||||
}
|
||||
|
||||
// MarshalUnsafe serializes r.name to the dst buffer.
|
||||
func (r *FUSELookupIn) MarshalUnsafe(buf []byte) {
|
||||
copy(buf, []byte(r.Name))
|
||||
}
|
||||
|
||||
// SizeBytes is the size of the memory representation of FUSELookupIn.
|
||||
// 1 extra byte for null-terminated string.
|
||||
func (r *FUSELookupIn) SizeBytes() int {
|
||||
return len(r.Name) + 1
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/tools/go_marshal/marshal"
|
||||
)
|
||||
|
||||
// Name is the default filesystem name.
|
||||
@@ -165,7 +166,7 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
}
|
||||
|
||||
// root is the fusefs root directory.
|
||||
root := fs.newInode(creds, fsopts.rootMode)
|
||||
root := fs.newRootInode(creds, fsopts.rootMode)
|
||||
|
||||
return fs.VFSFilesystem(), root.VFSDentry(), nil
|
||||
}
|
||||
@@ -205,14 +206,28 @@ type inode struct {
|
||||
kernfs.InodeNotSymlink
|
||||
kernfs.OrderedChildren
|
||||
|
||||
locks vfs.FileLocks
|
||||
|
||||
NodeID uint64
|
||||
dentry kernfs.Dentry
|
||||
locks vfs.FileLocks
|
||||
|
||||
// the owning filesystem. fs is immutable.
|
||||
fs *filesystem
|
||||
}
|
||||
|
||||
func (fs *filesystem) newInode(creds *auth.Credentials, mode linux.FileMode) *kernfs.Dentry {
|
||||
i := &inode{}
|
||||
i.InodeAttrs.Init(creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0755)
|
||||
func (fs *filesystem) newRootInode(creds *auth.Credentials, mode linux.FileMode) *kernfs.Dentry {
|
||||
i := &inode{fs: fs}
|
||||
i.InodeAttrs.Init(creds, linux.UNNAMED_MAJOR, fs.devMinor, 1, linux.ModeDirectory|0755)
|
||||
i.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
|
||||
i.dentry.Init(i)
|
||||
i.NodeID = 1
|
||||
|
||||
return &i.dentry
|
||||
}
|
||||
|
||||
func (fs *filesystem) newInode(nodeID uint64, attr linux.FUSEAttr) *kernfs.Dentry {
|
||||
i := &inode{fs: fs, NodeID: nodeID}
|
||||
creds := auth.Credentials{EffectiveKGID: auth.KGID(attr.UID), EffectiveKUID: auth.KUID(attr.UID)}
|
||||
i.InodeAttrs.Init(&creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.FileMode(attr.Mode))
|
||||
i.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
|
||||
i.EnableLeakCheck()
|
||||
i.dentry.Init(i)
|
||||
@@ -231,6 +246,57 @@ func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentr
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
// Lookup implements kernfs.Inode.Lookup.
|
||||
func (i *inode) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
|
||||
in := linux.FUSELookupIn{Name: name}
|
||||
return i.newEntry(ctx, name, 0, linux.FUSE_LOOKUP, &in)
|
||||
}
|
||||
|
||||
// IterDirents implements Inode.IterDirents.
|
||||
func (inode) IterDirents(ctx context.Context, callback vfs.IterDirentsCallback, offset, relOffset int64) (int64, error) {
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// Valid implements Inode.Valid.
|
||||
func (inode) Valid(ctx context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// newEntry calls FUSE server for entry creation and allocates corresponding entry according to response.
|
||||
// Shared by FUSE_MKNOD, FUSE_MKDIR, FUSE_SYMLINK, FUSE_LINK and FUSE_LOOKUP.
|
||||
func (i *inode) newEntry(ctx context.Context, name string, fileType linux.FileMode, opcode linux.FUSEOpcode, payload marshal.Marshallable) (*vfs.Dentry, error) {
|
||||
kernelTask := kernel.TaskFromContext(ctx)
|
||||
if kernelTask == nil {
|
||||
log.Warningf("fusefs.Inode.newEntry: couldn't get kernel task from context", i.NodeID)
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
req, err := i.fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), i.NodeID, opcode, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := i.fs.conn.Call(kernelTask, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := res.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := linux.FUSEEntryOut{}
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opcode != linux.FUSE_LOOKUP && ((out.Attr.Mode&linux.S_IFMT)^uint32(fileType) != 0 || out.NodeID == 0 || out.NodeID == linux.FUSE_ROOT_ID) {
|
||||
return nil, syserror.EIO
|
||||
}
|
||||
child := i.fs.newInode(out.NodeID, out.Attr)
|
||||
if opcode == linux.FUSE_LOOKUP {
|
||||
i.dentry.InsertChildLocked(name, child)
|
||||
} else {
|
||||
i.dentry.InsertChild(name, child)
|
||||
}
|
||||
return child.VFSDentry(), nil
|
||||
}
|
||||
|
||||
// statFromFUSEAttr makes attributes from linux.FUSEAttr to linux.Statx. The
|
||||
// opts.Sync attribute is ignored since the synchronization is handled by the
|
||||
// FUSE server.
|
||||
@@ -299,7 +365,7 @@ func (i *inode) Stat(ctx context.Context, fs *vfs.Filesystem, opts vfs.StatOptio
|
||||
// finally be translated into vfs.FilesystemImpl.StatAt() (see
|
||||
// pkg/sentry/syscalls/linux/vfs2/stat.go), resulting in the same flow
|
||||
// as stat(2). Thus GetAttrFlags and Fh variable will never be used in VFS2.
|
||||
req, err := conn.NewRequest(creds, uint32(task.ThreadID()), i.Ino(), linux.FUSE_GETATTR, &in)
|
||||
req, err := conn.NewRequest(creds, uint32(task.ThreadID()), i.NodeID, linux.FUSE_GETATTR, &in)
|
||||
if err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ func (fs *Filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.Vir
|
||||
}
|
||||
// Reference on childVFSD dropped by a corresponding Valid.
|
||||
child = childVFSD.Impl().(*Dentry)
|
||||
parent.insertChildLocked(name, child)
|
||||
parent.InsertChildLocked(name, child)
|
||||
}
|
||||
return child, nil
|
||||
}
|
||||
|
||||
@@ -246,15 +246,15 @@ func (d *Dentry) OnZeroWatches(context.Context) {}
|
||||
// Precondition: d must represent a directory inode.
|
||||
func (d *Dentry) InsertChild(name string, child *Dentry) {
|
||||
d.dirMu.Lock()
|
||||
d.insertChildLocked(name, child)
|
||||
d.InsertChildLocked(name, child)
|
||||
d.dirMu.Unlock()
|
||||
}
|
||||
|
||||
// insertChildLocked is equivalent to InsertChild, with additional
|
||||
// InsertChildLocked is equivalent to InsertChild, with additional
|
||||
// preconditions.
|
||||
//
|
||||
// Precondition: d.dirMu must be locked.
|
||||
func (d *Dentry) insertChildLocked(name string, child *Dentry) {
|
||||
func (d *Dentry) InsertChildLocked(name string, child *Dentry) {
|
||||
if !d.isDir() {
|
||||
panic(fmt.Sprintf("InsertChild called on non-directory Dentry: %+v.", d))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user