mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
9f8cbc0892
commit
298b5f3612
+13
-3
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -386,6 +386,7 @@ syscall_test(
|
||||
add_overlay = True,
|
||||
shard_count = more_shards,
|
||||
test = "//test/syscalls/linux:open_test",
|
||||
use_fusefs = True,
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
|
||||
Reference in New Issue
Block a user