mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
@@ -357,3 +357,41 @@ func (r *FUSELookupIn) MarshalUnsafe(buf []byte) {
|
||||
func (r *FUSELookupIn) SizeBytes() int {
|
||||
return len(r.Name) + 1
|
||||
}
|
||||
|
||||
// MAX_NON_LFS indicates the maximum offset without large file support.
|
||||
const MAX_NON_LFS = ((1 << 31) - 1)
|
||||
|
||||
// flags returned by OPEN request.
|
||||
const (
|
||||
// FOPEN_DIRECT_IO indicates bypassing page cache for this opened file.
|
||||
FOPEN_DIRECT_IO = 1 << 0
|
||||
// FOPEN_KEEP_CACHE avoids invalidate of data cache on open.
|
||||
FOPEN_KEEP_CACHE = 1 << 1
|
||||
// FOPEN_NONSEEKABLE indicates the file cannot be seeked.
|
||||
FOPEN_NONSEEKABLE = 1 << 2
|
||||
)
|
||||
|
||||
// FUSEOpenIn is the request sent by the kernel to the daemon,
|
||||
// to negotiate flags and get file handle.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEOpenIn struct {
|
||||
// Flags of this open request.
|
||||
Flags uint32
|
||||
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSEOpenOut is the reply sent by the daemon to the kernel
|
||||
// for FUSEOpenIn.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEOpenOut struct {
|
||||
// Fh is the file handler for opened file.
|
||||
Fh uint64
|
||||
|
||||
// OpenFlag for the opened file.
|
||||
OpenFlag uint32
|
||||
|
||||
_ uint32
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ go_library(
|
||||
srcs = [
|
||||
"connection.go",
|
||||
"dev.go",
|
||||
"file.go",
|
||||
"fusefs.go",
|
||||
"init.go",
|
||||
"inode_refs.go",
|
||||
|
||||
@@ -78,8 +78,13 @@ type Response struct {
|
||||
type connection struct {
|
||||
fd *DeviceFD
|
||||
|
||||
// mu protect access to struct memebers.
|
||||
mu sync.Mutex
|
||||
|
||||
// attributeVersion is the version of connection's attributes.
|
||||
attributeVersion uint64
|
||||
|
||||
// The following FUSE_INIT flags are currently unsupported by this implementation:
|
||||
// - FUSE_ATOMIC_O_TRUNC: requires open(..., O_TRUNC)
|
||||
// - FUSE_EXPORT_SUPPORT
|
||||
// - FUSE_HANDLE_KILLPRIV
|
||||
// - FUSE_POSIX_LOCKS: requires POSIX locks
|
||||
@@ -113,6 +118,11 @@ type connection struct {
|
||||
// TODO(gvisor.dev/issue/3185): abort all queued requests.
|
||||
aborted bool
|
||||
|
||||
// atomicOTrunc is true when FUSE does not send a separate SETATTR request
|
||||
// before open with O_TRUNC flag.
|
||||
// Negotiated and only set in INIT.
|
||||
atomicOTrunc bool
|
||||
|
||||
// connInitError if FUSE_INIT encountered error (major version mismatch).
|
||||
// Only set in INIT.
|
||||
connInitError bool
|
||||
@@ -189,6 +199,10 @@ type connection struct {
|
||||
// dontMask if filestestem does not apply umask to creation modes.
|
||||
// Negotiated in INIT.
|
||||
dontMask bool
|
||||
|
||||
// noOpen if FUSE server doesn't support open operation.
|
||||
// This flag only influence performance, not correctness of the program.
|
||||
noOpen bool
|
||||
}
|
||||
|
||||
// newFUSEConnection creates a FUSE connection to fd.
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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.
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// fileDescription implements vfs.FileDescriptionImpl for fuse.
|
||||
type fileDescription struct {
|
||||
vfsfd vfs.FileDescription
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.DentryMetadataFileDescriptionImpl
|
||||
vfs.NoLockFD
|
||||
|
||||
// the file handle used in userspace.
|
||||
Fh uint64
|
||||
|
||||
// Nonseekable is indicate cannot perform seek on a file.
|
||||
Nonseekable bool
|
||||
|
||||
// DirectIO suggest fuse to use direct io operation.
|
||||
DirectIO bool
|
||||
|
||||
// OpenFlag is the flag returned by open.
|
||||
OpenFlag uint32
|
||||
}
|
||||
|
||||
func (fd *fileDescription) dentry() *kernfs.Dentry {
|
||||
return fd.vfsfd.Dentry().Impl().(*kernfs.Dentry)
|
||||
}
|
||||
|
||||
func (fd *fileDescription) inode() *inode {
|
||||
return fd.dentry().Inode().(*inode)
|
||||
}
|
||||
|
||||
func (fd *fileDescription) filesystem() *vfs.Filesystem {
|
||||
return fd.vfsfd.VirtualDentry().Mount().Filesystem()
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *fileDescription) Release(ctx context.Context) {}
|
||||
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *fileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *fileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *fileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *fileDescription) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *fileDescription) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Stat implements FileDescriptionImpl.Stat.
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
fs := fd.filesystem()
|
||||
inode := fd.inode()
|
||||
return inode.Stat(ctx, fs, opts)
|
||||
}
|
||||
|
||||
// SetStat implements FileDescriptionImpl.SetStat.
|
||||
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
return nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package fuse
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
@@ -212,6 +213,18 @@ type inode struct {
|
||||
|
||||
// the owning filesystem. fs is immutable.
|
||||
fs *filesystem
|
||||
|
||||
// size of the file.
|
||||
size uint64
|
||||
|
||||
// attributeVersion is the version of inode's attributes.
|
||||
attributeVersion uint64
|
||||
|
||||
// attributeTime is the remaining vaild time of attributes.
|
||||
attributeTime uint64
|
||||
|
||||
// version of the inode.
|
||||
version uint64
|
||||
}
|
||||
|
||||
func (fs *filesystem) newRootInode(creds *auth.Credentials, mode linux.FileMode) *kernfs.Dentry {
|
||||
@@ -237,13 +250,87 @@ func (fs *filesystem) newInode(nodeID uint64, attr linux.FUSEAttr) *kernfs.Dentr
|
||||
|
||||
// Open implements kernfs.Inode.Open.
|
||||
func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &i.OrderedChildren, &i.locks, &opts, kernfs.GenericDirectoryFDOptions{
|
||||
SeekEnd: kernfs.SeekEndStaticEntries,
|
||||
})
|
||||
if err != nil {
|
||||
isDir := i.InodeAttrs.Mode().IsDir()
|
||||
// return error if specified to open directory but inode is not a directory.
|
||||
if !isDir && opts.Mode.IsDir() {
|
||||
return nil, syserror.ENOTDIR
|
||||
}
|
||||
if opts.Flags&linux.O_LARGEFILE == 0 && atomic.LoadUint64(&i.size) > linux.MAX_NON_LFS {
|
||||
return nil, syserror.EOVERFLOW
|
||||
}
|
||||
|
||||
// FOPEN_KEEP_CACHE is the defualt flag for noOpen.
|
||||
fd := fileDescription{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 {
|
||||
kernelTask := kernel.TaskFromContext(ctx)
|
||||
if kernelTask == nil {
|
||||
log.Warningf("fusefs.Inode.Open: couldn't get kernel task from context")
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
var opcode linux.FUSEOpcode
|
||||
if isDir {
|
||||
opcode = linux.FUSE_OPENDIR
|
||||
} else {
|
||||
opcode = linux.FUSE_OPEN
|
||||
}
|
||||
in := linux.FUSEOpenIn{Flags: opts.Flags & ^uint32(linux.O_CREAT|linux.O_EXCL|linux.O_NOCTTY)}
|
||||
if !i.fs.conn.atomicOTrunc {
|
||||
in.Flags &= ^uint32(linux.O_TRUNC)
|
||||
}
|
||||
req, err := i.fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), i.NodeID, opcode, &in)
|
||||
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 == syserror.ENOSYS && !isDir {
|
||||
i.fs.conn.noOpen = true
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
out := linux.FUSEOpenOut{}
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd.OpenFlag = out.OpenFlag
|
||||
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
|
||||
fdOptions := &vfs.FileDescriptionOptions{}
|
||||
if fd.OpenFlag&linux.FOPEN_NONSEEKABLE != 0 {
|
||||
fdOptions.DenyPRead = true
|
||||
fdOptions.DenyPWrite = true
|
||||
fd.Nonseekable = true
|
||||
}
|
||||
|
||||
// If we don't send SETATTR before open (which is indicated by atomicOTrunc)
|
||||
// and O_TRUNC is set, update the inode's version number and clean existing data
|
||||
// by setting the file size to 0.
|
||||
if i.fs.conn.atomicOTrunc && opts.Flags&linux.O_TRUNC != 0 {
|
||||
i.fs.conn.mu.Lock()
|
||||
i.fs.conn.attributeVersion++
|
||||
i.attributeVersion = i.fs.conn.attributeVersion
|
||||
atomic.StoreUint64(&i.size, 0)
|
||||
i.fs.conn.mu.Unlock()
|
||||
i.attributeTime = 0
|
||||
}
|
||||
|
||||
if err := fd.vfsfd.Init(&fd, opts.Flags, rp.Mount(), vfsd, fdOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
|
||||
// Lookup implements kernfs.Inode.Lookup.
|
||||
|
||||
@@ -6,3 +6,8 @@ syscall_test(
|
||||
fuse = "True",
|
||||
test = "//test/fuse/linux:stat_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
fuse = "True",
|
||||
test = "//test/fuse/linux:open_test",
|
||||
)
|
||||
|
||||
@@ -18,6 +18,19 @@ cc_binary(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "open_test",
|
||||
testonly = 1,
|
||||
srcs = ["open_test.cc"],
|
||||
deps = [
|
||||
gtest,
|
||||
":fuse_base",
|
||||
"//test/util:fuse_util",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "fuse_base",
|
||||
testonly = 1,
|
||||
|
||||
@@ -117,6 +117,16 @@ uint32_t FuseTest::GetServerTotalReceivedBytes() {
|
||||
static_cast<uint32_t>(FuseTestCmd::kGetTotalReceivedBytes));
|
||||
}
|
||||
|
||||
// Sends the `kSkipRequest` command to the FUSE server, which would skip
|
||||
// current stored request data.
|
||||
void FuseTest::SkipServerActualRequest() {
|
||||
uint32_t cmd = static_cast<uint32_t>(FuseTestCmd::kSkipRequest);
|
||||
EXPECT_THAT(RetryEINTR(write)(sock_[0], &cmd, sizeof(cmd)),
|
||||
SyscallSucceedsWithValue(sizeof(cmd)));
|
||||
|
||||
WaitServerComplete();
|
||||
}
|
||||
|
||||
// Sends the `kSetInodeLookup` command, expected mode, and the path of the
|
||||
// inode to create under the mount point.
|
||||
void FuseTest::SetServerInodeLookup(const std::string& path, mode_t mode) {
|
||||
@@ -284,6 +294,9 @@ void FuseTest::ServerHandleCommand() {
|
||||
case FuseTestCmd::kGetNumUnsentResponses:
|
||||
ServerSendData(static_cast<uint32_t>(responses_.RemainingBlocks()));
|
||||
break;
|
||||
case FuseTestCmd::kSkipRequest:
|
||||
ServerSkipReceivedRequest();
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Unknown FuseTestCmd " << cmd;
|
||||
break;
|
||||
@@ -314,32 +327,7 @@ void FuseTest::ServerReceiveInodeLookup() {
|
||||
.len = out_len,
|
||||
.error = 0,
|
||||
};
|
||||
struct fuse_entry_out out_payload = {
|
||||
.nodeid = nodeid_,
|
||||
.generation = 0,
|
||||
.entry_valid = 0,
|
||||
.attr_valid = 0,
|
||||
.entry_valid_nsec = 0,
|
||||
.attr_valid_nsec = 0,
|
||||
.attr =
|
||||
(struct fuse_attr){
|
||||
.ino = nodeid_,
|
||||
.size = 512,
|
||||
.blocks = 4,
|
||||
.atime = 0,
|
||||
.mtime = 0,
|
||||
.ctime = 0,
|
||||
.atimensec = 0,
|
||||
.mtimensec = 0,
|
||||
.ctimensec = 0,
|
||||
.mode = mode,
|
||||
.nlink = 2,
|
||||
.uid = 1234,
|
||||
.gid = 4321,
|
||||
.rdev = 12,
|
||||
.blksize = 4096,
|
||||
},
|
||||
};
|
||||
struct fuse_entry_out out_payload = DefaultEntryOut(mode, nodeid_);
|
||||
// Since this is only used in test, nodeid_ is simply increased by 1 to
|
||||
// comply with the unqiueness of different path.
|
||||
++nodeid_;
|
||||
@@ -363,6 +351,15 @@ void FuseTest::ServerSendReceivedRequest() {
|
||||
SyscallSucceedsWithValue(mem_block.len));
|
||||
}
|
||||
|
||||
// Skip the request pointed by current cursor.
|
||||
void FuseTest::ServerSkipReceivedRequest() {
|
||||
if (requests_.End()) {
|
||||
FAIL() << "No more received request.";
|
||||
return;
|
||||
}
|
||||
requests_.Next();
|
||||
}
|
||||
|
||||
// Handles FUSE request. Reads request from /dev/fuse, checks if it has the
|
||||
// same opcode as expected, and responds with the saved fake FUSE response.
|
||||
// The FUSE request is copied to the serial buffer and can be retrieved one-
|
||||
@@ -390,6 +387,7 @@ void FuseTest::ServerProcessFuseRequest() {
|
||||
|
||||
requests_.AddMemBlock(in_header->opcode, buf.data(), len);
|
||||
|
||||
if (in_header->opcode == FUSE_RELEASE) return;
|
||||
// Check if there is a corresponding response.
|
||||
if (responses_.End()) {
|
||||
GTEST_NONFATAL_FAILURE_("No more FUSE response is expected");
|
||||
|
||||
@@ -42,6 +42,7 @@ enum class FuseTestCmd {
|
||||
kGetNumUnconsumedRequests,
|
||||
kGetNumUnsentResponses,
|
||||
kGetTotalReceivedBytes,
|
||||
kSkipRequest,
|
||||
};
|
||||
|
||||
// Holds the information of a memory block in a serial buffer.
|
||||
@@ -158,6 +159,10 @@ class FuseTest : public ::testing::Test {
|
||||
// bytes from /dev/fuse.
|
||||
uint32_t GetServerTotalReceivedBytes();
|
||||
|
||||
// Called by the testing thread to ask the FUSE server to skip stored
|
||||
// request data.
|
||||
void SkipServerActualRequest();
|
||||
|
||||
protected:
|
||||
TempPath mount_point_;
|
||||
|
||||
@@ -211,6 +216,10 @@ class FuseTest : public ::testing::Test {
|
||||
// Sends a uint32_t data via socket.
|
||||
inline void ServerSendData(uint32_t data);
|
||||
|
||||
// The FUSE server side's corresponding code of `SkipServerActualRequest()`.
|
||||
// Handles `kSkipRequest` command. Skip the request pointed by current cursor.
|
||||
void ServerSkipReceivedRequest();
|
||||
|
||||
// Handles FUSE request sent to /dev/fuse by its saved responses.
|
||||
void ServerProcessFuseRequest();
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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 <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/fuse/linux/fuse_base.h"
|
||||
#include "test/util/fuse_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
class OpenTest : public FuseTest {
|
||||
protected:
|
||||
const std::string test_file_ = "test_file";
|
||||
const mode_t regular_file_ = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
|
||||
|
||||
struct fuse_open_out out_payload_ = {
|
||||
.fh = 1,
|
||||
.open_flags = O_RDWR,
|
||||
};
|
||||
};
|
||||
|
||||
TEST_F(OpenTest, RegularFile) {
|
||||
const std::string test_file_path =
|
||||
JoinPath(mount_point_.path().c_str(), test_file_);
|
||||
SetServerInodeLookup(test_file_, regular_file_);
|
||||
|
||||
struct fuse_out_header out_header = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
|
||||
};
|
||||
auto iov_out = FuseGenerateIovecs(out_header, out_payload_);
|
||||
SetServerResponse(FUSE_OPEN, iov_out);
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR));
|
||||
|
||||
struct fuse_in_header in_header;
|
||||
struct fuse_open_in in_payload;
|
||||
auto iov_in = FuseGenerateIovecs(in_header, in_payload);
|
||||
GetServerActualRequest(iov_in);
|
||||
|
||||
EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload));
|
||||
EXPECT_EQ(in_header.opcode, FUSE_OPEN);
|
||||
EXPECT_EQ(in_payload.flags, O_RDWR);
|
||||
EXPECT_THAT(fcntl(fd.get(), F_GETFL), SyscallSucceedsWithValue(O_RDWR));
|
||||
}
|
||||
|
||||
TEST_F(OpenTest, SetNoOpen) {
|
||||
const std::string test_file_path =
|
||||
JoinPath(mount_point_.path().c_str(), test_file_);
|
||||
SetServerInodeLookup(test_file_, regular_file_);
|
||||
|
||||
// ENOSYS indicates open is not implemented.
|
||||
struct fuse_out_header out_header = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
|
||||
.error = -ENOSYS,
|
||||
};
|
||||
auto iov_out = FuseGenerateIovecs(out_header, out_payload_);
|
||||
SetServerResponse(FUSE_OPEN, iov_out);
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR));
|
||||
SkipServerActualRequest();
|
||||
|
||||
// check open doesn't send new request.
|
||||
uint32_t recieved_before = GetServerTotalReceivedBytes();
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_path.c_str(), O_RDWR));
|
||||
EXPECT_EQ(GetServerTotalReceivedBytes(), recieved_before);
|
||||
}
|
||||
|
||||
TEST_F(OpenTest, OpenFail) {
|
||||
struct fuse_out_header out_header = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
|
||||
.error = -ENOENT,
|
||||
};
|
||||
|
||||
auto iov_out = FuseGenerateIovecs(out_header, out_payload_);
|
||||
SetServerResponse(FUSE_OPENDIR, iov_out);
|
||||
ASSERT_THAT(open(mount_point_.path().c_str(), O_RDWR),
|
||||
SyscallFailsWithErrno(ENOENT));
|
||||
|
||||
struct fuse_in_header in_header;
|
||||
struct fuse_open_in in_payload;
|
||||
auto iov_in = FuseGenerateIovecs(in_header, in_payload);
|
||||
GetServerActualRequest(iov_in);
|
||||
|
||||
EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload));
|
||||
EXPECT_EQ(in_header.opcode, FUSE_OPENDIR);
|
||||
EXPECT_EQ(in_payload.flags, O_RDWR);
|
||||
}
|
||||
|
||||
TEST_F(OpenTest, DirectoryFlagOnRegularFile) {
|
||||
const std::string test_file_path =
|
||||
JoinPath(mount_point_.path().c_str(), test_file_);
|
||||
|
||||
SetServerInodeLookup(test_file_, regular_file_);
|
||||
ASSERT_THAT(open(test_file_path.c_str(), O_RDWR | O_DIRECTORY),
|
||||
SyscallFailsWithErrno(ENOTDIR));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -48,6 +48,7 @@ cc_library(
|
||||
cc_library(
|
||||
name = "fuse_util",
|
||||
testonly = 1,
|
||||
srcs = ["fuse_util.cc"],
|
||||
hdrs = ["fuse_util.h"],
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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 "test/util/fuse_util.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
// Create response body with specified mode and nodeID.
|
||||
fuse_entry_out DefaultEntryOut(mode_t mode, uint64_t node_id) {
|
||||
const int time_sec = 1595436289;
|
||||
const int time_nsec = 134150844;
|
||||
struct fuse_entry_out default_entry_out = {
|
||||
.nodeid = node_id,
|
||||
.generation = 0,
|
||||
.entry_valid = time_sec,
|
||||
.attr_valid = time_sec,
|
||||
.entry_valid_nsec = time_nsec,
|
||||
.attr_valid_nsec = time_nsec,
|
||||
.attr =
|
||||
(struct fuse_attr){
|
||||
.ino = node_id,
|
||||
.size = 512,
|
||||
.blocks = 4,
|
||||
.atime = time_sec,
|
||||
.mtime = time_sec,
|
||||
.ctime = time_sec,
|
||||
.atimensec = time_nsec,
|
||||
.mtimensec = time_nsec,
|
||||
.ctimensec = time_nsec,
|
||||
.mode = mode,
|
||||
.nlink = 2,
|
||||
.uid = 1234,
|
||||
.gid = 4321,
|
||||
.rdev = 12,
|
||||
.blksize = 4096,
|
||||
},
|
||||
};
|
||||
return default_entry_out;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -15,6 +15,7 @@
|
||||
#ifndef GVISOR_TEST_UTIL_FUSE_UTIL_H_
|
||||
#define GVISOR_TEST_UTIL_FUSE_UTIL_H_
|
||||
|
||||
#include <linux/fuse.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <string>
|
||||
@@ -62,6 +63,9 @@ std::vector<struct iovec> FuseGenerateIovecs(T &first, Types &...args) {
|
||||
return first_iovec;
|
||||
}
|
||||
|
||||
// Return a fuse_entry_out FUSE server response body.
|
||||
fuse_entry_out DefaultEntryOut(mode_t mode, uint64_t nodeId);
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
#endif // GVISOR_TEST_UTIL_FUSE_UTIL_H_
|
||||
|
||||
Reference in New Issue
Block a user