Refactor FUSE inode implementation.

This change replaces the FUSE inode implementations with a custom
inode type that implementsthe kernfs.Inode interface. It also cleans
up some of the attribute related functions and gets the open_test to
pass.

PiperOrigin-RevId: 506465698
This commit is contained in:
Lucas Manning
2023-02-01 17:20:22 -08:00
committed by gVisor bot
parent 9f8cbc0892
commit 298b5f3612
11 changed files with 932 additions and 723 deletions
+13 -3
View File
@@ -348,11 +348,11 @@ type FUSEAttr struct {
_ uint32
}
// FUSEGetAttrOut is the reply sent by the daemon to the kernel
// for FUSEGetAttrIn.
// FUSEAttrOut is the reply sent by the daemon to the kernel
// for FUSEGetAttrIn and FUSESetAttrIn.
//
// +marshal
type FUSEGetAttrOut struct {
type FUSEAttrOut struct {
// AttrValid and AttrValidNsec describe the attribute cache duration
AttrValid uint64
@@ -1054,3 +1054,13 @@ type FUSEFsyncIn struct {
// padding
_ uint32
}
// FUSEAccessIn is the request sent by the kernel to the daemon when checking
// permissions on a file.
//
// +marshal
type FUSEAccessIn struct {
Mask uint32
// padding
_ uint32
}
+5
View File
@@ -36,6 +36,7 @@ go_library(
"directory.go",
"file.go",
"fusefs.go",
"inode.go",
"inode_refs.go",
"read_write.go",
"register.go",
@@ -58,8 +59,12 @@ go_library(
"//pkg/safemem",
"//pkg/sentry/fsimpl/devtmpfs",
"//pkg/sentry/fsimpl/kernfs",
"//pkg/sentry/fsutil",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/pipe",
"//pkg/sentry/kernel/time",
"//pkg/sentry/memmap",
"//pkg/sentry/vfs",
"//pkg/sync",
"//pkg/usermem",
+1 -1
View File
@@ -181,7 +181,7 @@ type connection struct {
dontMask bool
// noOpen if FUSE server doesn't support open operation.
// This flag only influence performance, not correctness of the program.
// This flag only influences performance, not correctness of the program.
noOpen bool
}
-23
View File
@@ -145,7 +145,6 @@ func (fd *DeviceFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.R
if !fd.connected() {
return 0, linuxerr.EPERM
}
// We require that any Read done on this filesystem have a sane minimum
// read buffer. It must have the capacity for the fixed parts of any request
// header (Linux uses the request header and the FUSEWriteIn header for this
@@ -162,14 +161,6 @@ func (fd *DeviceFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.R
if dst.NumBytes() < int64(minBuffSize) {
return 0, linuxerr.EINVAL
}
return fd.readLocked(ctx, dst, opts)
}
// readLocked implements the reading of the fuse device while locked with DeviceFD.mu.
//
// Preconditions: dst is large enough for any reasonable request.
// +checklocks:fd.mu
func (fd *DeviceFD) readLocked(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
// Find the first valid request. For the normal case this loop only executes
// once.
var req *Request
@@ -209,7 +200,6 @@ func (fd *DeviceFD) readLocked(ctx context.Context, dst usermem.IOSequence, opts
fd.numActiveRequests--
delete(fd.completions, req.hdr.Unique)
}
return int64(n), nil
}
@@ -231,12 +221,6 @@ func (fd *DeviceFD) PWrite(ctx context.Context, src usermem.IOSequence, offset i
func (fd *DeviceFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
fd.mu.Lock()
defer fd.mu.Unlock()
return fd.writeLocked(ctx, src, opts)
}
// writeLocked implements writing to the fuse device while locked with DeviceFD.mu.
// +checklocks:fd.mu
func (fd *DeviceFD) writeLocked(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
if !fd.connected() {
return 0, linuxerr.EPERM
}
@@ -273,13 +257,6 @@ func (fd *DeviceFD) writeLocked(ctx context.Context, src usermem.IOSequence, opt
func (fd *DeviceFD) Readiness(mask waiter.EventMask) waiter.EventMask {
fd.mu.Lock()
defer fd.mu.Unlock()
return fd.readinessLocked(mask)
}
// readinessLocked implements checking the readiness of the fuse device while
// locked with DeviceFD.mu.
// +checklocks:fd.mu
func (fd *DeviceFD) readinessLocked(mask waiter.EventMask) waiter.EventMask {
var ready waiter.EventMask
if !fd.connected() {
+20 -11
View File
@@ -36,10 +36,10 @@ type fileDescription struct {
// the file handle used in userspace.
Fh uint64
// Nonseekable is indicate cannot perform seek on a file.
// Nonseekable indicates we cannot perform seek on a file.
Nonseekable bool
// DirectIO suggest fuse to use direct io operation.
// DirectIO suggests that fuse use direct IO operations.
DirectIO bool
// OpenFlag is the flag returned by open.
@@ -77,16 +77,19 @@ func (fd *fileDescription) Release(ctx context.Context) {
Fh: fd.Fh,
Flags: fd.statusFlags(),
}
// TODO(gvisor.dev/issue/3245): add logic when we support file lock owner.
// TODO(gvisor.dev/issue/3245): add logic when we support file lock owners.
inode := fd.inode()
inode.attrMu.Lock()
defer inode.attrMu.Unlock()
var opcode linux.FUSEOpcode
if fd.inode().Mode().IsDir() {
if inode.filemode().IsDir() {
opcode = linux.FUSE_RELEASEDIR
} else {
opcode = linux.FUSE_RELEASE
}
kernelTask := kernel.TaskFromContext(ctx)
// Ignoring errors and FUSE server reply is analogous to Linux's behavior.
req := conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), fd.inode().nodeID, opcode, &in)
// Ignoring errors and FUSE server replies is analogous to Linux's behavior.
req := conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), inode.nodeID, opcode, &in)
// The reply will be ignored since no callback is defined in asyncCallBack().
conn.CallAsync(kernelTask, req)
}
@@ -127,15 +130,21 @@ func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linu
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
fs := fd.filesystem()
creds := auth.CredentialsFromContext(ctx)
return fd.inode().setAttr(ctx, fs, creds, opts, true, fd.Fh)
inode := fd.inode()
inode.attrMu.Lock()
defer inode.attrMu.Unlock()
return inode.setAttr(ctx, fs, creds, opts, fhOptions{useFh: true, fh: fd.Fh})
}
// Sync implements vfs.FileDescriptionImpl.Sync.
func (fd *fileDescription) Sync(ctx context.Context) error {
if fd.inode().Mode().IsDir() {
inode := fd.inode()
inode.attrMu.Lock()
defer inode.attrMu.Unlock()
if inode.filemode().IsDir() {
return linuxerr.EPERM
}
conn := fd.inode().fs.conn
conn := inode.fs.conn
// no need to proceed if FUSE server doesn't implement Open.
if conn.noOpen {
return linuxerr.EINVAL
@@ -146,8 +155,8 @@ func (fd *fileDescription) Sync(ctx context.Context) error {
Fh: fd.Fh,
FsyncFlags: fd.statusFlags(),
}
// Ignoring errors and FUSE server reply is analogous to Linux's behavior.
req := conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), fd.inode().nodeID, linux.FUSE_FSYNC, &in)
// Ignoring errors and FUSE server replies is analogous to Linux's behavior.
req := conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), inode.nodeID, linux.FUSE_FSYNC, &in)
// The reply will be ignored since no callback is defined in asyncCallBack().
conn.CallAsync(kernelTask, req)
return nil
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+9 -12
View File
@@ -44,7 +44,7 @@ func (fs *filesystem) ReadInPages(ctx context.Context, fd *regularFileFD, off ui
// Round up to a multiple of page size.
readSize, _ := hostarch.PageRoundUp(uint64(size))
// One request cannnot exceed either maxRead or maxPages.
// One request cannot exceed either maxRead or maxPages.
maxPages := fs.conn.maxRead >> hostarch.PageShift
if maxPages > uint32(fs.conn.maxPages) {
maxPages = uint32(fs.conn.maxPages)
@@ -109,7 +109,7 @@ func (fs *filesystem) ReadInPages(ctx context.Context, fd *regularFileFD, off ui
pagesRead += pagesCanRead
}
defer fs.ReadCallback(ctx, fd, off, size, sizeRead, attributeVersion)
defer fs.ReadCallback(ctx, fd.inode(), off, size, sizeRead, attributeVersion) // +checklocksforce: fd.inode() locks are held during fd operations.
// No bytes returned: offset >= EOF.
if len(outs) == 0 {
@@ -121,14 +121,13 @@ func (fs *filesystem) ReadInPages(ctx context.Context, fd *regularFileFD, off ui
// ReadCallback updates several information after receiving a read response.
// Due to readahead, sizeRead can be larger than size.
func (fs *filesystem) ReadCallback(ctx context.Context, fd *regularFileFD, off uint64, size uint32, sizeRead uint32, attributeVersion uint64) {
//
// +checklocks:i.attrMu
func (fs *filesystem) ReadCallback(ctx context.Context, i *inode, off uint64, size uint32, sizeRead uint32, attributeVersion uint64) {
// TODO(gvisor.dev/issue/3247): support async read.
// If this is called by an async read, correctly process it.
// May need to update the signature.
i := fd.inode()
i.InodeAttrs.TouchAtime(ctx, fd.vfsfd.Mount())
i.touchAtime()
// Reached EOF.
if sizeRead < size {
// TODO(gvisor.dev/issue/3630): If we have writeback cache, then we need to fill this hole.
@@ -137,8 +136,8 @@ func (fs *filesystem) ReadCallback(ctx context.Context, fd *regularFileFD, off u
// Update existing size.
newSize := off + uint64(sizeRead)
fs.conn.mu.Lock()
if attributeVersion == i.attributeVersion.Load() && newSize < i.size.Load() {
i.attributeVersion.Store(i.fs.conn.attributeVersion.Add(1))
if attributeVersion == i.attrVersion.Load() && newSize < i.size.Load() {
i.attrVersion.Store(i.fs.conn.attributeVersion.Add(1))
i.size.Store(newSize)
}
fs.conn.mu.Unlock()
@@ -156,7 +155,7 @@ func (fs *filesystem) Write(ctx context.Context, fd *regularFileFD, off uint64,
return 0, linuxerr.EINVAL
}
// One request cannnot exceed either maxWrite or maxPages.
// One request cannot exceed either maxWrite or maxPages.
maxWrite := uint32(fs.conn.maxPages) << hostarch.PageShift
if maxWrite > fs.conn.maxWrite {
maxWrite = fs.conn.maxWrite
@@ -230,7 +229,5 @@ func (fs *filesystem) Write(ctx context.Context, fd *regularFileFD, off uint64,
break
}
}
inode.InodeAttrs.TouchCMtime(ctx)
return written, nil
}
+34 -9
View File
@@ -35,6 +35,30 @@ type regularFileFD struct {
offMu sync.Mutex
}
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
fd.offMu.Lock()
defer fd.offMu.Unlock()
inode := fd.inode()
inode.attrMu.Lock()
defer inode.attrMu.Unlock()
switch whence {
case linux.SEEK_SET:
// use offset as specified
case linux.SEEK_CUR:
offset += fd.off
case linux.SEEK_END:
offset += int64(inode.size.Load())
default:
return 0, linuxerr.EINVAL
}
if offset < 0 {
return 0, linuxerr.EINVAL
}
fd.off = offset
return offset, nil
}
// PRead implements vfs.FileDescriptionImpl.PRead.
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
if offset < 0 {
@@ -61,6 +85,8 @@ func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offs
// TODO(gvisor.dev/issue/3678): Add direct IO support.
inode := fd.inode()
inode.attrMu.Lock()
defer inode.attrMu.Unlock()
// Reading beyond EOF, update file size if outdated.
if uint64(offset+size) > inode.size.Load() {
@@ -140,7 +166,7 @@ func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts
// pwrite returns the number of bytes written, final offset and error. The
// final offset should be ignored by PWrite.
func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (written, finalOff int64, err error) {
func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, int64, error) {
if offset < 0 {
return 0, offset, linuxerr.EINVAL
}
@@ -153,8 +179,8 @@ func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, off
}
inode := fd.inode()
inode.metadataMu.Lock()
defer inode.metadataMu.Unlock()
inode.attrMu.Lock()
defer inode.attrMu.Unlock()
// If the file is opened with O_APPEND, update offset to file size.
// Note: since our Open() implements the interface of kernfs,
@@ -177,7 +203,7 @@ func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, off
return 0, offset, linuxerr.EINVAL
}
srclen, err = vfs.CheckLimit(ctx, offset, srclen)
srclen, err := vfs.CheckLimit(ctx, offset, srclen)
if err != nil {
return 0, offset, err
}
@@ -217,13 +243,12 @@ func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, off
return 0, offset, linuxerr.EIO
}
written = int64(n)
finalOff = offset + written
written := int64(n)
finalOff := offset + written
if finalOff > int64(inode.size.Load()) {
inode.size.Store(uint64(finalOff))
inode.fs.conn.attributeVersion.Add(1)
}
return
inode.touchCMtime()
return written, finalOff, nil
}
+1 -1
View File
@@ -33,7 +33,7 @@ func main() {
log.Warningf("could not create loopback root: %v", err)
os.Exit(1)
}
opts := &fuse.MountOptions{DirectMount: true, Debug: true}
opts := &fuse.MountOptions{DirectMount: true, Debug: true, Options: []string{"default_permissions"}}
rawFS := fs.NewNodeFS(loopbackRoot, &fs.Options{Logger: golog.Default()})
server, err := fuse.NewServer(rawFS, "/tmp", opts)
if err != nil {
+1
View File
@@ -386,6 +386,7 @@ syscall_test(
add_overlay = True,
shard_count = more_shards,
test = "//test/syscalls/linux:open_test",
use_fusefs = True,
)
syscall_test(