mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Fixes #7086,#6964,#3413,#7001.
Also adds fuse fsync, rename, flock support.
This commit is contained in:
@@ -232,6 +232,43 @@ type FUSEInitOut struct {
|
||||
_ [8]uint32
|
||||
}
|
||||
|
||||
// FUSEStatfsOut is the reply sent by the daemon to the kernel
|
||||
// for FUSE_STATFS.
|
||||
// from https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/fuse.h#L252
|
||||
//
|
||||
// +marshal
|
||||
type FUSEStatfsOut struct {
|
||||
// Blocks is the maximum number of data blocks the filesystem may store, in
|
||||
// units of BlockSize.
|
||||
Blocks uint64
|
||||
|
||||
// BlocksFree is the number of free data blocks, in units of BlockSize.
|
||||
BlocksFree uint64
|
||||
|
||||
// BlocksAvailable is the number of data blocks free for use by
|
||||
// unprivileged users, in units of BlockSize.
|
||||
BlocksAvailable uint64
|
||||
|
||||
// Files is the number of used file nodes on the filesystem.
|
||||
Files uint64
|
||||
|
||||
// FileFress is the number of free file nodes on the filesystem.
|
||||
FilesFree uint64
|
||||
|
||||
// BlockSize is the optimal transfer block size in bytes.
|
||||
BlockSize uint32
|
||||
|
||||
// NameLength is the maximum file name length.
|
||||
NameLength uint32
|
||||
|
||||
// FragmentSize is equivalent to BlockSize.
|
||||
FragmentSize uint32
|
||||
|
||||
_ uint32
|
||||
|
||||
Spare [6]uint32
|
||||
}
|
||||
|
||||
// FUSE_GETATTR_FH is currently the only flag of FUSEGetAttrIn.GetAttrFlags.
|
||||
// If it is set, the file handle (FUSEGetAttrIn.Fh) is used to indicate the
|
||||
// object instead of the node id attribute in the request header.
|
||||
@@ -436,6 +473,15 @@ type FUSEOpenOut struct {
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSECreateOut is the reply sent by the daemon to the kernel
|
||||
// for FUSECreateMeta.
|
||||
//
|
||||
// +marshal
|
||||
type FUSECreateOut struct {
|
||||
FUSEEntryOut
|
||||
FUSEOpenOut
|
||||
}
|
||||
|
||||
// FUSE_READ flags, consistent with the ones in include/uapi/linux/fuse.h.
|
||||
const (
|
||||
FUSE_READ_LOCKOWNER = 1 << 1
|
||||
@@ -474,6 +520,7 @@ type FUSEReadIn struct {
|
||||
//
|
||||
// The second part of the payload is the
|
||||
// binary bytes of the data to be written.
|
||||
// See FUSEWritePayloadIn that combines header & payload.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEWriteIn struct {
|
||||
@@ -498,6 +545,36 @@ type FUSEWriteIn struct {
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSEWritePayloadIn combines header - FUSEWriteIn and payload
|
||||
// in a single marshallable struct when sending request by the
|
||||
// kernel to the daemon
|
||||
//
|
||||
// +marshal dynamic
|
||||
type FUSEWritePayloadIn struct {
|
||||
Header FUSEWriteIn
|
||||
Payload primitive.ByteSlice
|
||||
}
|
||||
|
||||
// SizeBytes implements marshal.Marshallable.SizeBytes.
|
||||
func (r *FUSEWritePayloadIn) SizeBytes() int {
|
||||
if r == nil {
|
||||
return (*FUSEWriteIn)(nil).SizeBytes()
|
||||
}
|
||||
return r.Header.SizeBytes() + r.Payload.SizeBytes()
|
||||
}
|
||||
|
||||
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
|
||||
func (r *FUSEWritePayloadIn) MarshalBytes(dst []byte) []byte {
|
||||
dst = r.Header.MarshalUnsafe(dst)
|
||||
dst = r.Payload.MarshalUnsafe(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
|
||||
func (r *FUSEWritePayloadIn) UnmarshalBytes(src []byte) []byte {
|
||||
panic("Unimplemented, FUSEWritePayloadIn is never unmarshalled")
|
||||
}
|
||||
|
||||
// FUSEWriteOut is the payload of the reply sent by the daemon to the kernel
|
||||
// for a FUSE_WRITE request.
|
||||
//
|
||||
@@ -543,6 +620,32 @@ type FUSECreateMeta struct {
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSERenameIn sent by the kernel for FUSE_RENAME
|
||||
//
|
||||
// +marshal dynamic
|
||||
type FUSERenameIn struct {
|
||||
Newdir primitive.Uint64
|
||||
Oldname CString
|
||||
Newname CString
|
||||
}
|
||||
|
||||
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
|
||||
func (r *FUSERenameIn) MarshalBytes(dst []byte) []byte {
|
||||
dst = r.Newdir.MarshalBytes(dst)
|
||||
dst = r.Oldname.MarshalBytes(dst)
|
||||
return r.Newname.MarshalBytes(dst)
|
||||
}
|
||||
|
||||
// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
|
||||
func (r *FUSERenameIn) UnmarshalBytes(buf []byte) []byte {
|
||||
panic("Unimplemented, FUSERmDirIn is never unmarshalled")
|
||||
}
|
||||
|
||||
// SizeBytes implements marshal.Marshallable.SizeBytes.
|
||||
func (r *FUSERenameIn) SizeBytes() int {
|
||||
return r.Newdir.SizeBytes() + r.Oldname.SizeBytes() + r.Newname.SizeBytes()
|
||||
}
|
||||
|
||||
// FUSECreateIn contains all the arguments sent by the kernel to the daemon, to
|
||||
// atomically create and open a new regular file.
|
||||
//
|
||||
@@ -929,3 +1032,16 @@ func (r *FUSEUnlinkIn) UnmarshalBytes(buf []byte) []byte {
|
||||
func (r *FUSEUnlinkIn) SizeBytes() int {
|
||||
return r.Name.SizeBytes()
|
||||
}
|
||||
|
||||
// FUSEFsyncIn is the request sent by the kernel to the daemon
|
||||
// when trying to fsync a file.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEFsyncIn struct {
|
||||
Fh uint64
|
||||
|
||||
FsyncFlags uint32
|
||||
|
||||
// padding
|
||||
_ uint32
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ go_library(
|
||||
"//pkg/hostarch",
|
||||
"//pkg/log",
|
||||
"//pkg/marshal",
|
||||
"//pkg/marshal/primitive",
|
||||
"//pkg/refs",
|
||||
"//pkg/refsvfs2",
|
||||
"//pkg/safemem",
|
||||
|
||||
@@ -168,7 +168,7 @@ func (fd *DeviceFD) readLocked(ctx context.Context, dst usermem.IOSequence, opts
|
||||
for !fd.queue.Empty() {
|
||||
req = fd.queue.Front()
|
||||
|
||||
if int64(req.hdr.Len)+int64(len(req.payload)) <= dst.NumBytes() {
|
||||
if int64(req.hdr.Len) <= dst.NumBytes() {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -207,17 +207,6 @@ func (fd *DeviceFD) readLocked(ctx context.Context, dst usermem.IOSequence, opts
|
||||
return 0, linuxerr.EIO
|
||||
}
|
||||
|
||||
if req.hdr.Opcode == linux.FUSE_WRITE {
|
||||
written, err := dst.DropFirst(n).CopyOut(ctx, req.payload)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if written != len(req.payload) {
|
||||
return 0, linuxerr.EIO
|
||||
}
|
||||
n += int(written)
|
||||
}
|
||||
|
||||
// Fully done with this req, remove it from the queue.
|
||||
fd.queue.Remove(req)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package fuse
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
@@ -29,7 +30,7 @@ type fileDescription struct {
|
||||
vfsfd vfs.FileDescription
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.DentryMetadataFileDescriptionImpl
|
||||
vfs.NoLockFD
|
||||
vfs.LockFD
|
||||
|
||||
// the file handle used in userspace.
|
||||
Fh uint64
|
||||
@@ -127,3 +128,26 @@ func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions)
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
return fd.inode().setAttr(ctx, fs, creds, opts, true, fd.Fh)
|
||||
}
|
||||
|
||||
// Sync implements vfs.FileDescriptionImpl.Sync.
|
||||
func (fd *fileDescription) Sync(ctx context.Context) error {
|
||||
if fd.inode().Mode().IsDir() {
|
||||
return linuxerr.EPERM
|
||||
}
|
||||
conn := fd.inode().fs.conn
|
||||
// no need to proceed if FUSE server doesn't implement Open.
|
||||
if conn.noOpen {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
kernelTask := kernel.TaskFromContext(ctx)
|
||||
|
||||
in := linux.FUSEFsyncIn{
|
||||
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)
|
||||
// The reply will be ignored since no callback is defined in asyncCallBack().
|
||||
conn.CallAsync(kernelTask, req)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/marshal"
|
||||
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
@@ -299,6 +300,15 @@ func (fs *filesystem) MountOptions() string {
|
||||
return fs.opts.mopts
|
||||
}
|
||||
|
||||
// Fh data returned by newEntry
|
||||
type NewFhData struct {
|
||||
// file handler
|
||||
fh uint64
|
||||
|
||||
// Flags of the file.
|
||||
flags uint32
|
||||
}
|
||||
|
||||
// inode implements kernfs.Inode.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -334,6 +344,10 @@ type inode struct {
|
||||
|
||||
// link is result of following a symbolic link.
|
||||
link string
|
||||
|
||||
// if newEntry got a new Fh from server it saves it here, until returned by Open
|
||||
isNewFh bool
|
||||
newFhData NewFhData
|
||||
}
|
||||
|
||||
func (fs *filesystem) newRoot(ctx context.Context, creds *auth.Credentials, mode linux.FileMode) *kernfs.Dentry {
|
||||
@@ -410,11 +424,17 @@ func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentr
|
||||
fd = &(regularFD.fileDescription)
|
||||
fdImpl = regularFD
|
||||
}
|
||||
fd.LockFD.Init(&i.locks)
|
||||
// FOPEN_KEEP_CACHE is the defualt flag for noOpen.
|
||||
fd.OpenFlag = linux.FOPEN_KEEP_CACHE
|
||||
|
||||
// Only send open request when FUSE server support open or is opening a directory.
|
||||
if !i.fs.conn.noOpen || isDir {
|
||||
if i.isNewFh {
|
||||
// use Fh from NewEntry
|
||||
fd.OpenFlag = i.newFhData.flags
|
||||
fd.Fh = i.newFhData.fh
|
||||
i.isNewFh = false
|
||||
} else if !i.fs.conn.noOpen || isDir {
|
||||
kernelTask := kernel.TaskFromContext(ctx)
|
||||
if kernelTask == nil {
|
||||
log.Warningf("fusefs.Inode.Open: couldn't get kernel task from context")
|
||||
@@ -452,13 +472,12 @@ func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentr
|
||||
|
||||
// Process the reply.
|
||||
fd.OpenFlag = out.OpenFlag
|
||||
if isDir {
|
||||
fd.OpenFlag &= ^uint32(linux.FOPEN_DIRECT_IO)
|
||||
}
|
||||
|
||||
fd.Fh = out.Fh
|
||||
}
|
||||
}
|
||||
if isDir {
|
||||
fd.OpenFlag &= ^uint32(linux.FOPEN_DIRECT_IO)
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/3234): invalidate mmap after implemented it for FUSE Inode
|
||||
fd.DirectIO = fd.OpenFlag&linux.FOPEN_DIRECT_IO != 0
|
||||
@@ -590,6 +609,28 @@ func (i *inode) RmDir(ctx context.Context, name string, child kernfs.Inode) erro
|
||||
return res.Error()
|
||||
}
|
||||
|
||||
func (i *inode) Rename(ctx context.Context, oldname, newname string, child, dstDir kernfs.Inode) error {
|
||||
fusefs := i.fs
|
||||
task, creds := kernel.TaskFromContext(ctx), auth.CredentialsFromContext(ctx)
|
||||
|
||||
dstDirInode, ok := dstDir.(*inode)
|
||||
if !ok {
|
||||
return linuxerr.EXDEV
|
||||
}
|
||||
|
||||
in := linux.FUSERenameIn{
|
||||
Newdir: primitive.Uint64(dstDirInode.nodeID),
|
||||
Oldname: linux.CString(oldname),
|
||||
Newname: linux.CString(newname),
|
||||
}
|
||||
req := fusefs.conn.NewRequest(creds, uint32(task.ThreadID()), i.nodeID, linux.FUSE_RENAME, &in)
|
||||
res, err := i.fs.conn.Call(task, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return res.Error()
|
||||
}
|
||||
|
||||
// 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) (kernfs.Inode, error) {
|
||||
@@ -606,14 +647,31 @@ func (i *inode) newEntry(ctx context.Context, name string, fileType linux.FileMo
|
||||
if err := res.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := linux.FUSEEntryOut{}
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return nil, err
|
||||
out := linux.FUSECreateOut{}
|
||||
if opcode == linux.FUSE_CREATE {
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := res.UnmarshalPayload(&out.FUSEEntryOut); 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, linuxerr.EIO
|
||||
}
|
||||
child := i.fs.newInode(ctx, out.NodeID, out.Attr)
|
||||
if opcode == linux.FUSE_CREATE {
|
||||
// File handler is returned by fuse server at a time of file create.
|
||||
// Save it temporary in a created child, so Open could return it when invoked
|
||||
// to be sure after fh is consumed reset 'isNewFh' flag of inode
|
||||
childI, ok := child.(*inode)
|
||||
if ok {
|
||||
childI.isNewFh = true
|
||||
childI.newFhData.fh = out.FUSEOpenOut.Fh
|
||||
childI.newFhData.flags = out.FUSEOpenOut.OpenFlag
|
||||
}
|
||||
}
|
||||
return child, nil
|
||||
}
|
||||
|
||||
@@ -798,8 +856,39 @@ func (i *inode) DecRef(ctx context.Context) {
|
||||
|
||||
// StatFS implements kernfs.Inode.StatFS.
|
||||
func (i *inode) StatFS(ctx context.Context, fs *vfs.Filesystem) (linux.Statfs, error) {
|
||||
// TODO(gvisor.dev/issues/3413): Complete the implementation of statfs.
|
||||
return vfs.GenericStatFS(linux.FUSE_SUPER_MAGIC), nil
|
||||
task := kernel.TaskFromContext(ctx)
|
||||
if task == nil {
|
||||
log.Warningf("couldn't get kernel task from context")
|
||||
return linux.Statfs{}, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
req := i.fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(task.ThreadID()), i.nodeID,
|
||||
linux.FUSE_STATFS, &linux.FUSEEmptyIn{},
|
||||
)
|
||||
res, err := i.fs.conn.Call(task, req)
|
||||
if err != nil {
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
if err := res.Error(); err != nil {
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
|
||||
var out linux.FUSEStatfsOut
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
|
||||
return linux.Statfs{
|
||||
Type: linux.FUSE_SUPER_MAGIC,
|
||||
Blocks: uint64(out.Blocks),
|
||||
BlocksFree: out.BlocksFree,
|
||||
BlocksAvailable: out.BlocksAvailable,
|
||||
Files: out.Files,
|
||||
FilesFree: out.FilesFree,
|
||||
BlockSize: int64(out.BlockSize),
|
||||
NameLength: uint64(out.NameLength),
|
||||
FragmentSize: int64(out.FragmentSize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fattrMaskFromStats converts vfs.SetStatOptions.Stat.Mask to linux stats mask
|
||||
|
||||
@@ -165,14 +165,16 @@ func (fs *filesystem) Write(ctx context.Context, fd *regularFileFD, off uint64,
|
||||
}
|
||||
|
||||
// Reuse the same struct for unmarshalling to avoid unnecessary memory allocation.
|
||||
in := linux.FUSEWriteIn{
|
||||
Fh: fd.Fh,
|
||||
// TODO(gvisor.dev/issue/3245): file lock
|
||||
LockOwner: 0,
|
||||
// TODO(gvisor.dev/issue/3245): |= linux.FUSE_READ_LOCKOWNER
|
||||
// TODO(gvisor.dev/issue/3237): |= linux.FUSE_WRITE_CACHE (not added yet)
|
||||
WriteFlags: 0,
|
||||
Flags: fd.statusFlags(),
|
||||
in := linux.FUSEWritePayloadIn {
|
||||
Header: linux.FUSEWriteIn{
|
||||
Fh: fd.Fh,
|
||||
// TODO(gvisor.dev/issue/3245): file lock
|
||||
LockOwner: 0,
|
||||
// TODO(gvisor.dev/issue/3245): |= linux.FUSE_READ_LOCKOWNER
|
||||
// TODO(gvisor.dev/issue/3237): |= linux.FUSE_WRITE_CACHE (not added yet)
|
||||
WriteFlags: 0,
|
||||
Flags: fd.statusFlags(),
|
||||
},
|
||||
}
|
||||
|
||||
inode := fd.inode()
|
||||
@@ -197,11 +199,11 @@ func (fs *filesystem) Write(ctx context.Context, fd *regularFileFD, off uint64,
|
||||
toWrite = maxWrite
|
||||
}
|
||||
|
||||
in.Offset = off + uint64(written)
|
||||
in.Size = toWrite
|
||||
in.Header.Offset = off + uint64(written)
|
||||
in.Header.Size = toWrite
|
||||
in.Payload = data[written : written+toWrite]
|
||||
|
||||
req := fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(t.ThreadID()), inode.nodeID, linux.FUSE_WRITE, &in)
|
||||
req.payload = data[written : written+toWrite]
|
||||
|
||||
// TODO(gvisor.dev/issue/3247): support async write.
|
||||
|
||||
|
||||
@@ -95,10 +95,6 @@ type Request struct {
|
||||
hdr *linux.FUSEHeaderIn
|
||||
data []byte
|
||||
|
||||
// payload for this request: extra bytes to write after
|
||||
// the data slice. Used by FUSE_WRITE.
|
||||
payload []byte
|
||||
|
||||
// If this request is async.
|
||||
async bool
|
||||
// If we don't care its response.
|
||||
|
||||
@@ -17,6 +17,7 @@ package vfs
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
@@ -102,7 +103,13 @@ func (vfs *VirtualFilesystem) MustRegisterFilesystemType(name string, fsType Fil
|
||||
func (vfs *VirtualFilesystem) getFilesystemType(name string) *registeredFilesystemType {
|
||||
vfs.fsTypesMu.RLock()
|
||||
defer vfs.fsTypesMu.RUnlock()
|
||||
return vfs.fsTypes[name]
|
||||
fsname := name
|
||||
// Fetch a meaningful part of name if there is a dot in the name
|
||||
// and use left part of a string as fname.
|
||||
if strings.Index(name, ".") != -1 {
|
||||
fsname = strings.Split(name, ".")[0]
|
||||
}
|
||||
return vfs.fsTypes[fsname]
|
||||
}
|
||||
|
||||
// GenerateProcFilesystems emits the contents of /proc/filesystems for vfs to
|
||||
|
||||
@@ -8,6 +8,10 @@ package(licenses = ["notice"])
|
||||
# fuse = "True",
|
||||
# test = "//test/fuse/linux:stat_test",
|
||||
# )
|
||||
# syscall_test(
|
||||
# fuse = "True",
|
||||
# test = "//test/fuse/linux:statfs_test",
|
||||
# )
|
||||
#
|
||||
# syscall_test(
|
||||
# fuse = "True",
|
||||
|
||||
@@ -20,6 +20,21 @@ cc_binary(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "statfs_test",
|
||||
testonly = 1,
|
||||
srcs = ["statfs_test.cc"],
|
||||
deps = [
|
||||
gtest,
|
||||
":fuse_fd_util",
|
||||
"//test/util:cleanup",
|
||||
"//test/util:fs_util",
|
||||
"//test/util:fuse_util",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "open_test",
|
||||
testonly = 1,
|
||||
|
||||
@@ -40,6 +40,19 @@ TEST(FuseMount, Success) {
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse", 0, mopts, 0));
|
||||
}
|
||||
|
||||
TEST(FuseMount, SuccessFstype) {
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY));
|
||||
std::string mopts =
|
||||
absl::StrFormat("fd=%d,user_id=%d,group_id=%d,rootmode=0777", fd.get(),
|
||||
getuid(), getgid());
|
||||
|
||||
const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
|
||||
const auto mount =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse.testfs", 0, mopts, 0));
|
||||
}
|
||||
|
||||
TEST(FuseMount, FDNotParsable) {
|
||||
int devfd;
|
||||
EXPECT_THAT(devfd = open("/dev/fuse", O_RDWR), SyscallSucceeds());
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/fuse.h>
|
||||
#include <linux/magic.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/vfs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/fuse/linux/fuse_fd_util.h"
|
||||
#include "test/util/cleanup.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/fuse_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
#define FUSE_SUPER_MAGIC 0x65735546
|
||||
|
||||
class StatfsTest : public FuseFdTest {
|
||||
public:
|
||||
void SetUp() override {
|
||||
FuseFdTest::SetUp();
|
||||
}
|
||||
|
||||
protected:
|
||||
const mode_t dir_mode_ = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
|
||||
bool StatsfsAreEqual(struct statfs expected, struct statfs actual) {
|
||||
return memcmp(&expected, &actual, sizeof(struct statfs)) == 0;
|
||||
}
|
||||
|
||||
const mode_t expected_mode = S_IFREG | S_IRUSR | S_IWUSR;
|
||||
const uint64_t fh = 23;
|
||||
};
|
||||
|
||||
TEST_F(StatfsTest, StatfsNormal) {
|
||||
SetServerInodeLookup(mount_point_.path(), dir_mode_);
|
||||
|
||||
struct fuse_out_header out_header = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_statfs_out),
|
||||
};
|
||||
struct fuse_statfs_out out_payload = {
|
||||
.st = fuse_kstatfs {
|
||||
.blocks = 0x6000,
|
||||
.bfree = 0x6000,
|
||||
.bavail = 0x6000,
|
||||
.bsize = 4096,
|
||||
.namelen = 0x10000,
|
||||
},
|
||||
};
|
||||
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
|
||||
SetServerResponse(FUSE_STATFS, iov_out);
|
||||
|
||||
// Make syscall.
|
||||
struct statfs st;
|
||||
EXPECT_THAT(statfs(mount_point_.path().c_str(), &st), SyscallSucceeds());
|
||||
|
||||
// Check filesystem operation result.
|
||||
struct statfs expected_stat = {
|
||||
.f_type = FUSE_SUPER_MAGIC,
|
||||
.f_bsize = out_payload.st.bsize,
|
||||
.f_blocks = out_payload.st.blocks,
|
||||
.f_bfree = out_payload.st.bfree,
|
||||
.f_bavail = out_payload.st.bavail,
|
||||
.f_namelen = out_payload.st.namelen,
|
||||
};
|
||||
EXPECT_TRUE(StatsfsAreEqual(st, expected_stat));
|
||||
|
||||
// Check FUSE request.
|
||||
struct fuse_in_header in_header;
|
||||
auto iov_in = FuseGenerateIovecs(in_header);
|
||||
|
||||
GetServerActualRequest(iov_in);
|
||||
EXPECT_EQ(in_header.opcode, FUSE_STATFS);
|
||||
}
|
||||
|
||||
TEST_F(StatfsTest, NotFound) {
|
||||
struct fuse_out_header out_header = {
|
||||
.len = sizeof(struct fuse_out_header),
|
||||
.error = -ENOENT,
|
||||
};
|
||||
auto iov_out = FuseGenerateIovecs(out_header);
|
||||
SetServerResponse(FUSE_STATFS, iov_out);
|
||||
|
||||
// Make syscall.
|
||||
struct statfs statfs_buf;
|
||||
EXPECT_THAT(statfs(mount_point_.path().c_str(), &statfs_buf),
|
||||
SyscallFailsWithErrno(ENOENT));
|
||||
|
||||
// Check FUSE request.
|
||||
struct fuse_in_header in_header;
|
||||
auto iov_in = FuseGenerateIovecs(in_header);
|
||||
|
||||
GetServerActualRequest(iov_in);
|
||||
EXPECT_EQ(in_header.opcode, FUSE_STATFS);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -120,7 +120,7 @@ TEST_F(WriteTest, WriteNormal) {
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
sizeof(in_header_write) + sizeof(in_payload_write) + n_write);
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, 0);
|
||||
EXPECT_EQ(in_payload_write.size, n_write);
|
||||
@@ -157,7 +157,7 @@ TEST_F(WriteTest, WriteShort) {
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
sizeof(in_header_write) + sizeof(in_payload_write) + n_write);
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, 0);
|
||||
EXPECT_EQ(in_payload_write.size, n_write);
|
||||
@@ -193,7 +193,7 @@ TEST_F(WriteTest, WriteShortZero) {
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
sizeof(in_header_write) + sizeof(in_payload_write) + n_write);
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, 0);
|
||||
EXPECT_EQ(in_payload_write.size, n_write);
|
||||
@@ -240,7 +240,7 @@ TEST_F(WriteTest, PWrite) {
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
sizeof(in_header_write) + sizeof(in_payload_write) + n_write);
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, offset_write);
|
||||
EXPECT_EQ(in_payload_write.size, n_write);
|
||||
@@ -287,7 +287,7 @@ TEST_F(WriteTestSmallMaxWrite, WriteSmallMaxWrie) {
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
sizeof(in_header_write) + sizeof(in_payload_write) + size_fragment);
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, i * size_fragment);
|
||||
EXPECT_EQ(in_payload_write.size, size_fragment);
|
||||
|
||||
Reference in New Issue
Block a user