Implement FUSE_LOOKUP

Fixes #3231

Co-authored-by: Boyuan He <heboyuan@google.com>
This commit is contained in:
Andrei Vagin
2020-09-16 12:19:30 -07:00
committed by Andrei Vagin
co-authored by Boyuan He
parent 717b661c45
commit d6ee3ae6d7
4 changed files with 133 additions and 11 deletions
+56
View File
@@ -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
}
+73 -7
View File
@@ -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
}
+1 -1
View File
@@ -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
}
+3 -3
View File
@@ -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))
}