From b7ccfa5084e252f3b159aa3d44c007b889cd4287 Mon Sep 17 00:00:00 2001 From: Yaroslav Litvinov Date: Wed, 26 Jan 2022 00:48:59 +0200 Subject: [PATCH] Fixes #7086,#6964,#3413,#7001. Also adds fuse fsync, rename, flock support. --- pkg/abi/linux/fuse.go | 116 +++++++++++++++++++ pkg/sentry/fsimpl/fuse/BUILD | 1 + pkg/sentry/fsimpl/fuse/dev.go | 13 +-- pkg/sentry/fsimpl/fuse/file.go | 26 ++++- pkg/sentry/fsimpl/fuse/fusefs.go | 109 ++++++++++++++++-- pkg/sentry/fsimpl/fuse/read_write.go | 24 ++-- pkg/sentry/fsimpl/fuse/request_response.go | 4 - pkg/sentry/vfs/filesystem_type.go | 9 +- test/fuse/BUILD | 4 + test/fuse/linux/BUILD | 15 +++ test/fuse/linux/mount_test.cc | 13 +++ test/fuse/linux/statfs_test.cc | 123 +++++++++++++++++++++ test/fuse/linux/write_test.cc | 10 +- 13 files changed, 423 insertions(+), 44 deletions(-) create mode 100644 test/fuse/linux/statfs_test.cc diff --git a/pkg/abi/linux/fuse.go b/pkg/abi/linux/fuse.go index 71a6f95e6..cda1aae39 100644 --- a/pkg/abi/linux/fuse.go +++ b/pkg/abi/linux/fuse.go @@ -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 +} diff --git a/pkg/sentry/fsimpl/fuse/BUILD b/pkg/sentry/fsimpl/fuse/BUILD index 07400d273..a7c792110 100644 --- a/pkg/sentry/fsimpl/fuse/BUILD +++ b/pkg/sentry/fsimpl/fuse/BUILD @@ -51,6 +51,7 @@ go_library( "//pkg/hostarch", "//pkg/log", "//pkg/marshal", + "//pkg/marshal/primitive", "//pkg/refs", "//pkg/refsvfs2", "//pkg/safemem", diff --git a/pkg/sentry/fsimpl/fuse/dev.go b/pkg/sentry/fsimpl/fuse/dev.go index 55d86b933..3c7ae8616 100644 --- a/pkg/sentry/fsimpl/fuse/dev.go +++ b/pkg/sentry/fsimpl/fuse/dev.go @@ -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) diff --git a/pkg/sentry/fsimpl/fuse/file.go b/pkg/sentry/fsimpl/fuse/file.go index e138b11f8..ba720f70c 100644 --- a/pkg/sentry/fsimpl/fuse/file.go +++ b/pkg/sentry/fsimpl/fuse/file.go @@ -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 +} diff --git a/pkg/sentry/fsimpl/fuse/fusefs.go b/pkg/sentry/fsimpl/fuse/fusefs.go index 039dc6996..ce97c4a6b 100644 --- a/pkg/sentry/fsimpl/fuse/fusefs.go +++ b/pkg/sentry/fsimpl/fuse/fusefs.go @@ -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 diff --git a/pkg/sentry/fsimpl/fuse/read_write.go b/pkg/sentry/fsimpl/fuse/read_write.go index fe119aa43..2d3fedc07 100644 --- a/pkg/sentry/fsimpl/fuse/read_write.go +++ b/pkg/sentry/fsimpl/fuse/read_write.go @@ -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. diff --git a/pkg/sentry/fsimpl/fuse/request_response.go b/pkg/sentry/fsimpl/fuse/request_response.go index fa774f67a..3170c3cde 100644 --- a/pkg/sentry/fsimpl/fuse/request_response.go +++ b/pkg/sentry/fsimpl/fuse/request_response.go @@ -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. diff --git a/pkg/sentry/vfs/filesystem_type.go b/pkg/sentry/vfs/filesystem_type.go index 70d6eb85e..aedb26bbb 100644 --- a/pkg/sentry/vfs/filesystem_type.go +++ b/pkg/sentry/vfs/filesystem_type.go @@ -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 diff --git a/test/fuse/BUILD b/test/fuse/BUILD index 2885ffdb2..158867181 100644 --- a/test/fuse/BUILD +++ b/test/fuse/BUILD @@ -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", diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD index 2f745bd47..4e6701025 100644 --- a/test/fuse/linux/BUILD +++ b/test/fuse/linux/BUILD @@ -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, diff --git a/test/fuse/linux/mount_test.cc b/test/fuse/linux/mount_test.cc index 276f842ea..ad862479f 100644 --- a/test/fuse/linux/mount_test.cc +++ b/test/fuse/linux/mount_test.cc @@ -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()); diff --git a/test/fuse/linux/statfs_test.cc b/test/fuse/linux/statfs_test.cc new file mode 100644 index 000000000..b40746da3 --- /dev/null +++ b/test/fuse/linux/statfs_test.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 diff --git a/test/fuse/linux/write_test.cc b/test/fuse/linux/write_test.cc index 1a62beb96..b173293b6 100644 --- a/test/fuse/linux/write_test.cc +++ b/test/fuse/linux/write_test.cc @@ -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);