mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement FUSE_WRITE
This commit adds basic write(2) support for FUSE.
This commit is contained in:
+41
-26
@@ -124,32 +124,6 @@ type FUSEHeaderOut struct {
|
||||
Unique FUSEOpID
|
||||
}
|
||||
|
||||
// FUSEWriteIn is the header written by a daemon when it makes a
|
||||
// write request to the FUSE filesystem.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEWriteIn struct {
|
||||
// Fh specifies the file handle that is being written to.
|
||||
Fh uint64
|
||||
|
||||
// Offset is the offset of the write.
|
||||
Offset uint64
|
||||
|
||||
// Size is the size of data being written.
|
||||
Size uint32
|
||||
|
||||
// WriteFlags is the flags used during the write.
|
||||
WriteFlags uint32
|
||||
|
||||
// LockOwner is the ID of the lock owner.
|
||||
LockOwner uint64
|
||||
|
||||
// Flags is the flags for the request.
|
||||
Flags uint32
|
||||
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSE_INIT flags, consistent with the ones in include/uapi/linux/fuse.h.
|
||||
// Our taget version is 7.23 but we have few implemented in advance.
|
||||
const (
|
||||
@@ -427,6 +401,47 @@ type FUSEReadIn struct {
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSEWriteIn is the first part of the payload of the
|
||||
// request sent by the kernel to the daemon
|
||||
// for FUSE_WRITE (struct for FUSE version >= 7.9).
|
||||
//
|
||||
// The second part of the payload is the
|
||||
// binary bytes of the data to be written.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEWriteIn struct {
|
||||
// Fh is the file handle in userspace.
|
||||
Fh uint64
|
||||
|
||||
// Offset is the write offset.
|
||||
Offset uint64
|
||||
|
||||
// Size is the number of bytes to write.
|
||||
Size uint32
|
||||
|
||||
// ReadFlags for this FUSE_WRITE request.
|
||||
WriteFlags uint32
|
||||
|
||||
// LockOwner is the id of the lock owner if there is one.
|
||||
LockOwner uint64
|
||||
|
||||
// Flags for the underlying file.
|
||||
Flags uint32
|
||||
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSEWriteOut is the payload of the reply sent by the daemon to the kernel
|
||||
// for a FUSE_WRITE request.
|
||||
//
|
||||
// +marshal
|
||||
type FUSEWriteOut struct {
|
||||
// Size is the number of bytes written.
|
||||
Size uint32
|
||||
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSEReleaseIn is the request sent by the kernel to the daemon
|
||||
// when there is no more reference to a file.
|
||||
//
|
||||
|
||||
@@ -64,6 +64,10 @@ type Request struct {
|
||||
id linux.FUSEOpID
|
||||
hdr *linux.FUSEHeaderIn
|
||||
data []byte
|
||||
|
||||
// payload for this request: extra bytes to write after
|
||||
// the data slice. Used by FUSE_WRITE.
|
||||
payload []byte
|
||||
}
|
||||
|
||||
// Response represents an actual response from the server, including the
|
||||
|
||||
@@ -152,7 +152,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) <= dst.NumBytes() {
|
||||
if int64(req.hdr.Len)+int64(len(req.payload)) <= dst.NumBytes() {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -191,6 +191,17 @@ func (fd *DeviceFD) readLocked(ctx context.Context, dst usermem.IOSequence, opts
|
||||
return 0, syserror.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, syserror.EIO
|
||||
}
|
||||
n += int(written)
|
||||
}
|
||||
|
||||
// Fully done with this req, remove it from the queue.
|
||||
fd.queue.Remove(req)
|
||||
if req.hdr.Opcode == linux.FUSE_RELEASE {
|
||||
|
||||
@@ -18,6 +18,7 @@ package fuse
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
@@ -228,13 +229,18 @@ type inode struct {
|
||||
kernfs.InodeNotSymlink
|
||||
kernfs.OrderedChildren
|
||||
|
||||
NodeID uint64
|
||||
dentry kernfs.Dentry
|
||||
locks vfs.FileLocks
|
||||
|
||||
// the owning filesystem. fs is immutable.
|
||||
fs *filesystem
|
||||
|
||||
// metaDataMu protects the metadata of this inode.
|
||||
metadataMu sync.Mutex
|
||||
|
||||
NodeID uint64
|
||||
|
||||
locks vfs.FileLocks
|
||||
|
||||
// size of the file.
|
||||
size uint64
|
||||
|
||||
|
||||
@@ -150,3 +150,93 @@ func (fs *filesystem) ReadCallback(ctx context.Context, fd *regularFileFD, off u
|
||||
fs.conn.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Write sends FUSE_WRITE requests and return the bytes
|
||||
// written according to the response.
|
||||
//
|
||||
// Preconditions: len(data) == size.
|
||||
func (fs *filesystem) Write(ctx context.Context, fd *regularFileFD, off uint64, size uint32, data []byte) (uint32, error) {
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
log.Warningf("fusefs.Read: couldn't get kernel task from context")
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
// One request cannnot exceed either maxWrite or maxPages.
|
||||
maxWrite := uint32(fs.conn.maxPages) << usermem.PageShift
|
||||
if maxWrite > fs.conn.maxWrite {
|
||||
maxWrite = fs.conn.maxWrite
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}
|
||||
|
||||
var written uint32
|
||||
|
||||
// This loop is intended for fragmented write where the bytes to write is
|
||||
// larger than either the maxWrite or maxPages or when bigWrites is false.
|
||||
// Unless a small value for max_write is explicitly used, this loop
|
||||
// is expected to execute only once for the majority of the writes.
|
||||
for written < size {
|
||||
toWrite := size - written
|
||||
|
||||
// Limit the write size to one page.
|
||||
// Note that the bigWrites flag is obsolete,
|
||||
// latest libfuse always sets it on.
|
||||
if !fs.conn.bigWrites && toWrite > usermem.PageSize {
|
||||
toWrite = usermem.PageSize
|
||||
}
|
||||
|
||||
// Limit the write size to maxWrite.
|
||||
if toWrite > maxWrite {
|
||||
toWrite = maxWrite
|
||||
}
|
||||
|
||||
in.Offset = off + uint64(written)
|
||||
in.Size = toWrite
|
||||
|
||||
req, err := fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(t.ThreadID()), fd.inode().NodeID, linux.FUSE_WRITE, &in)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
req.payload = data[written : written+toWrite]
|
||||
|
||||
// TODO(gvisor.dev/issue/3247): support async write.
|
||||
|
||||
res, err := fs.conn.Call(t, req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := res.Error(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
out := linux.FUSEWriteOut{}
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Write more than requested? EIO.
|
||||
if out.Size > toWrite {
|
||||
return 0, syserror.EIO
|
||||
}
|
||||
|
||||
written += out.Size
|
||||
|
||||
// Break if short write. Not necessarily an error.
|
||||
if out.Size != toWrite {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
||||
|
||||
@@ -123,3 +123,108 @@ func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts
|
||||
fd.offMu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
n, _, err := fd.pwrite(ctx, src, offset, opts)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
n, off, err := fd.pwrite(ctx, src, fd.off, opts)
|
||||
fd.off = off
|
||||
fd.offMu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if offset < 0 {
|
||||
return 0, offset, syserror.EINVAL
|
||||
}
|
||||
|
||||
// Check that flags are supported.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/2601): Support select preadv2 flags.
|
||||
if opts.Flags&^linux.RWF_HIPRI != 0 {
|
||||
return 0, offset, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
inode := fd.inode()
|
||||
inode.metadataMu.Lock()
|
||||
defer inode.metadataMu.Unlock()
|
||||
|
||||
// If the file is opened with O_APPEND, update offset to file size.
|
||||
// Note: since our Open() implements the interface of kernfs,
|
||||
// and kernfs currently does not support O_APPEND, this will never
|
||||
// be true before we switch out from kernfs.
|
||||
if fd.vfsfd.StatusFlags()&linux.O_APPEND != 0 {
|
||||
// Locking inode.metadataMu is sufficient for reading size
|
||||
offset = int64(inode.size)
|
||||
}
|
||||
|
||||
srclen := src.NumBytes()
|
||||
|
||||
if srclen > math.MaxUint32 {
|
||||
// FUSE only supports uint32 for size.
|
||||
// Overflow.
|
||||
return 0, offset, syserror.EINVAL
|
||||
}
|
||||
if end := offset + srclen; end < offset {
|
||||
// Overflow.
|
||||
return 0, offset, syserror.EINVAL
|
||||
}
|
||||
|
||||
srclen, err = vfs.CheckLimit(ctx, offset, srclen)
|
||||
if err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
|
||||
if srclen == 0 {
|
||||
// Return before causing any side effects.
|
||||
return 0, offset, nil
|
||||
}
|
||||
|
||||
src = src.TakeFirst64(srclen)
|
||||
|
||||
// TODO(gvisor.dev/issue/3237): Add cache support:
|
||||
// buffer cache. Ideally we write from src to our buffer cache first.
|
||||
// The slice passed to fs.Write() should be a slice from buffer cache.
|
||||
data := make([]byte, srclen)
|
||||
// Reason for making a copy here: connection.Call() blocks on kerneltask,
|
||||
// which in turn acquires mm.activeMu lock. Functions like CopyInTo() will
|
||||
// attemp to acquire the mm.activeMu lock as well -> deadlock.
|
||||
// We must finish reading from the userspace memory before
|
||||
// t.Block() deactivates it.
|
||||
cp, err := src.CopyIn(ctx, data)
|
||||
if err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
if int64(cp) != srclen {
|
||||
return 0, offset, syserror.EIO
|
||||
}
|
||||
|
||||
n, err := fd.inode().fs.Write(ctx, fd, uint64(offset), uint32(srclen), data)
|
||||
if err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
// We have checked srclen != 0 previously.
|
||||
// If err == nil, then it's a short write and we return EIO.
|
||||
return 0, offset, syserror.EIO
|
||||
}
|
||||
|
||||
written = int64(n)
|
||||
finalOff = offset + written
|
||||
|
||||
if finalOff > int64(inode.size) {
|
||||
atomic.StoreUint64(&inode.size, uint64(finalOff))
|
||||
atomic.AddUint64(&inode.fs.conn.attributeVersion, 1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,6 +42,11 @@ syscall_test(
|
||||
test = "//test/fuse/linux:read_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
fuse = "True",
|
||||
test = "//test/fuse/linux:write_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
fuse = "True",
|
||||
test = "//test/fuse/linux:rmdir_test",
|
||||
|
||||
@@ -154,6 +154,19 @@ cc_binary(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "write_test",
|
||||
testonly = 1,
|
||||
srcs = ["write_test.cc"],
|
||||
deps = [
|
||||
gtest,
|
||||
":fuse_base",
|
||||
"//test/util:fuse_util",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "create_test",
|
||||
testonly = 1,
|
||||
|
||||
@@ -164,7 +164,8 @@ void FuseTest::UnmountFuse() {
|
||||
}
|
||||
|
||||
// Consumes the first FUSE request and returns the corresponding PosixError.
|
||||
PosixError FuseTest::ServerConsumeFuseInit() {
|
||||
PosixError FuseTest::ServerConsumeFuseInit(
|
||||
const struct fuse_init_out* out_payload) {
|
||||
std::vector<char> buf(FUSE_MIN_READ_BUFFER);
|
||||
RETURN_ERROR_IF_SYSCALL_FAIL(
|
||||
RetryEINTR(read)(dev_fd_, buf.data(), buf.size()));
|
||||
@@ -176,10 +177,8 @@ PosixError FuseTest::ServerConsumeFuseInit() {
|
||||
};
|
||||
// Returns a fake fuse_init_out with 7.0 version to avoid ECONNREFUSED
|
||||
// error in the initialization of FUSE connection.
|
||||
struct fuse_init_out out_payload = {
|
||||
.major = 7,
|
||||
};
|
||||
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
|
||||
auto iov_out = FuseGenerateIovecs(
|
||||
out_header, *const_cast<struct fuse_init_out*>(out_payload));
|
||||
|
||||
RETURN_ERROR_IF_SYSCALL_FAIL(
|
||||
RetryEINTR(writev)(dev_fd_, iov_out.data(), iov_out.size()));
|
||||
@@ -244,7 +243,7 @@ void FuseTest::ServerFuseLoop() {
|
||||
// becomes testing thread and the child thread becomes the FUSE server running
|
||||
// in background. These 2 threads are connected via socketpair. sock_[0] is
|
||||
// opened in testing thread and sock_[1] is opened in the FUSE server.
|
||||
void FuseTest::SetUpFuseServer() {
|
||||
void FuseTest::SetUpFuseServer(const struct fuse_init_out* payload) {
|
||||
ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_), SyscallSucceeds());
|
||||
|
||||
switch (fork()) {
|
||||
@@ -261,7 +260,7 @@ void FuseTest::SetUpFuseServer() {
|
||||
|
||||
// Begin child thread, i.e. the FUSE server.
|
||||
ASSERT_THAT(close(sock_[0]), SyscallSucceeds());
|
||||
ServerCompleteWith(ServerConsumeFuseInit().ok());
|
||||
ServerCompleteWith(ServerConsumeFuseInit(payload).ok());
|
||||
ServerFuseLoop();
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace testing {
|
||||
|
||||
constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0";
|
||||
|
||||
constexpr struct fuse_init_out kDefaultFUSEInitOutPayload = {.major = 7};
|
||||
|
||||
// Internal commands used to communicate between testing thread and the FUSE
|
||||
// server. See test/fuse/README.md for further detail.
|
||||
enum class FuseTestCmd {
|
||||
@@ -171,7 +173,8 @@ class FuseTest : public ::testing::Test {
|
||||
void MountFuse(const char* mountOpts = kMountOpts);
|
||||
|
||||
// Creates a socketpair for communication and forks FUSE server.
|
||||
void SetUpFuseServer();
|
||||
void SetUpFuseServer(
|
||||
const struct fuse_init_out* payload = &kDefaultFUSEInitOutPayload);
|
||||
|
||||
// Unmounts the mountpoint of the FUSE server.
|
||||
void UnmountFuse();
|
||||
@@ -194,7 +197,7 @@ class FuseTest : public ::testing::Test {
|
||||
|
||||
// Consumes the first FUSE request when mounting FUSE. Replies with a
|
||||
// response with empty payload.
|
||||
PosixError ServerConsumeFuseInit();
|
||||
PosixError ServerConsumeFuseInit(const struct fuse_init_out* payload);
|
||||
|
||||
// A command switch that dispatch different FuseTestCmd to its handler.
|
||||
void ServerHandleCommand();
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
// 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 <vector>
|
||||
|
||||
#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 WriteTest : public FuseTest {
|
||||
void SetUp() override {
|
||||
FuseTest::SetUp();
|
||||
test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_);
|
||||
}
|
||||
|
||||
// TearDown overrides the parent's function
|
||||
// to skip checking the unconsumed release request at the end.
|
||||
void TearDown() override { UnmountFuse(); }
|
||||
|
||||
protected:
|
||||
const std::string test_file_ = "test_file";
|
||||
const mode_t test_file_mode_ = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
|
||||
const uint64_t test_fh_ = 1;
|
||||
const uint32_t open_flag_ = O_RDWR;
|
||||
|
||||
std::string test_file_path_;
|
||||
|
||||
PosixErrorOr<FileDescriptor> OpenTestFile(const std::string &path,
|
||||
uint64_t size = 512) {
|
||||
SetServerInodeLookup(test_file_, test_file_mode_, size);
|
||||
|
||||
struct fuse_out_header out_header_open = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
|
||||
};
|
||||
struct fuse_open_out out_payload_open = {
|
||||
.fh = test_fh_,
|
||||
.open_flags = open_flag_,
|
||||
};
|
||||
auto iov_out_open = FuseGenerateIovecs(out_header_open, out_payload_open);
|
||||
SetServerResponse(FUSE_OPEN, iov_out_open);
|
||||
|
||||
auto res = Open(path.c_str(), open_flag_);
|
||||
if (res.ok()) {
|
||||
SkipServerActualRequest();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
class WriteTestSmallMaxWrite : public WriteTest {
|
||||
void SetUp() override {
|
||||
MountFuse();
|
||||
SetUpFuseServer(&fuse_init_payload);
|
||||
test_file_path_ = JoinPath(mount_point_.path().c_str(), test_file_);
|
||||
}
|
||||
|
||||
protected:
|
||||
const static uint32_t max_write_ = 4096;
|
||||
constexpr static struct fuse_init_out fuse_init_payload = {
|
||||
.major = 7,
|
||||
.max_write = max_write_,
|
||||
};
|
||||
|
||||
const uint32_t size_fragment = max_write_;
|
||||
};
|
||||
|
||||
TEST_F(WriteTest, WriteNormal) {
|
||||
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
||||
|
||||
// Prepare for the write.
|
||||
const int n_write = 10;
|
||||
struct fuse_out_header out_header_write = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
|
||||
};
|
||||
struct fuse_write_out out_payload_write = {
|
||||
.size = n_write,
|
||||
};
|
||||
auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
|
||||
SetServerResponse(FUSE_WRITE, iov_out_write);
|
||||
|
||||
// Issue the write.
|
||||
std::vector<char> buf(n_write);
|
||||
RandomizeBuffer(buf.data(), buf.size());
|
||||
EXPECT_THAT(write(fd.get(), buf.data(), n_write),
|
||||
SyscallSucceedsWithValue(n_write));
|
||||
|
||||
// Check the write request.
|
||||
struct fuse_in_header in_header_write;
|
||||
struct fuse_write_in in_payload_write;
|
||||
std::vector<char> payload_buf(n_write);
|
||||
auto iov_in_write =
|
||||
FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
|
||||
GetServerActualRequest(iov_in_write);
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, 0);
|
||||
EXPECT_EQ(in_payload_write.size, n_write);
|
||||
EXPECT_EQ(buf, payload_buf);
|
||||
}
|
||||
|
||||
TEST_F(WriteTest, WriteShort) {
|
||||
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
||||
|
||||
// Prepare for the write.
|
||||
const int n_write = 10, n_written = 5;
|
||||
struct fuse_out_header out_header_write = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
|
||||
};
|
||||
struct fuse_write_out out_payload_write = {
|
||||
.size = n_written,
|
||||
};
|
||||
auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
|
||||
SetServerResponse(FUSE_WRITE, iov_out_write);
|
||||
|
||||
// Issue the write.
|
||||
std::vector<char> buf(n_write);
|
||||
RandomizeBuffer(buf.data(), buf.size());
|
||||
EXPECT_THAT(write(fd.get(), buf.data(), n_write),
|
||||
SyscallSucceedsWithValue(n_written));
|
||||
|
||||
// Check the write request.
|
||||
struct fuse_in_header in_header_write;
|
||||
struct fuse_write_in in_payload_write;
|
||||
std::vector<char> payload_buf(n_write);
|
||||
auto iov_in_write =
|
||||
FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
|
||||
GetServerActualRequest(iov_in_write);
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, 0);
|
||||
EXPECT_EQ(in_payload_write.size, n_write);
|
||||
EXPECT_EQ(buf, payload_buf);
|
||||
}
|
||||
|
||||
TEST_F(WriteTest, WriteShortZero) {
|
||||
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
||||
|
||||
// Prepare for the write.
|
||||
const int n_write = 10;
|
||||
struct fuse_out_header out_header_write = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
|
||||
};
|
||||
struct fuse_write_out out_payload_write = {
|
||||
.size = 0,
|
||||
};
|
||||
auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
|
||||
SetServerResponse(FUSE_WRITE, iov_out_write);
|
||||
|
||||
// Issue the write.
|
||||
std::vector<char> buf(n_write);
|
||||
RandomizeBuffer(buf.data(), buf.size());
|
||||
EXPECT_THAT(write(fd.get(), buf.data(), n_write), SyscallFailsWithErrno(EIO));
|
||||
|
||||
// Check the write request.
|
||||
struct fuse_in_header in_header_write;
|
||||
struct fuse_write_in in_payload_write;
|
||||
std::vector<char> payload_buf(n_write);
|
||||
auto iov_in_write =
|
||||
FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
|
||||
GetServerActualRequest(iov_in_write);
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
EXPECT_EQ(in_header_write.opcode, FUSE_WRITE);
|
||||
EXPECT_EQ(in_payload_write.offset, 0);
|
||||
EXPECT_EQ(in_payload_write.size, n_write);
|
||||
EXPECT_EQ(buf, payload_buf);
|
||||
}
|
||||
|
||||
TEST_F(WriteTest, WriteZero) {
|
||||
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_));
|
||||
|
||||
// Issue the write.
|
||||
std::vector<char> buf(0);
|
||||
EXPECT_THAT(write(fd.get(), buf.data(), 0), SyscallSucceedsWithValue(0));
|
||||
}
|
||||
|
||||
TEST_F(WriteTest, PWrite) {
|
||||
const int file_size = 512;
|
||||
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, file_size));
|
||||
|
||||
// Prepare for the write.
|
||||
const int n_write = 10;
|
||||
struct fuse_out_header out_header_write = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
|
||||
};
|
||||
struct fuse_write_out out_payload_write = {
|
||||
.size = n_write,
|
||||
};
|
||||
auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
|
||||
SetServerResponse(FUSE_WRITE, iov_out_write);
|
||||
|
||||
// Issue the write.
|
||||
std::vector<char> buf(n_write);
|
||||
RandomizeBuffer(buf.data(), buf.size());
|
||||
const int offset_write = file_size >> 1;
|
||||
EXPECT_THAT(pwrite(fd.get(), buf.data(), n_write, offset_write),
|
||||
SyscallSucceedsWithValue(n_write));
|
||||
|
||||
// Check the write request.
|
||||
struct fuse_in_header in_header_write;
|
||||
struct fuse_write_in in_payload_write;
|
||||
std::vector<char> payload_buf(n_write);
|
||||
auto iov_in_write =
|
||||
FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
|
||||
GetServerActualRequest(iov_in_write);
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_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);
|
||||
EXPECT_EQ(buf, payload_buf);
|
||||
}
|
||||
|
||||
TEST_F(WriteTestSmallMaxWrite, WriteSmallMaxWrie) {
|
||||
const int n_fragment = 10;
|
||||
const int n_write = size_fragment * n_fragment;
|
||||
|
||||
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenTestFile(test_file_path_, n_write));
|
||||
|
||||
// Prepare for the write.
|
||||
struct fuse_out_header out_header_write = {
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_write_out),
|
||||
};
|
||||
struct fuse_write_out out_payload_write = {
|
||||
.size = size_fragment,
|
||||
};
|
||||
auto iov_out_write = FuseGenerateIovecs(out_header_write, out_payload_write);
|
||||
|
||||
for (int i = 0; i < n_fragment; ++i) {
|
||||
SetServerResponse(FUSE_WRITE, iov_out_write);
|
||||
}
|
||||
|
||||
// Issue the write.
|
||||
std::vector<char> buf(n_write);
|
||||
RandomizeBuffer(buf.data(), buf.size());
|
||||
EXPECT_THAT(write(fd.get(), buf.data(), n_write),
|
||||
SyscallSucceedsWithValue(n_write));
|
||||
|
||||
ASSERT_EQ(GetServerNumUnsentResponses(), 0);
|
||||
ASSERT_EQ(GetServerNumUnconsumedRequests(), n_fragment);
|
||||
|
||||
// Check the write request.
|
||||
struct fuse_in_header in_header_write;
|
||||
struct fuse_write_in in_payload_write;
|
||||
std::vector<char> payload_buf(size_fragment);
|
||||
auto iov_in_write =
|
||||
FuseGenerateIovecs(in_header_write, in_payload_write, payload_buf);
|
||||
|
||||
for (int i = 0; i < n_fragment; ++i) {
|
||||
GetServerActualRequest(iov_in_write);
|
||||
|
||||
EXPECT_EQ(in_payload_write.fh, test_fh_);
|
||||
EXPECT_EQ(in_header_write.len,
|
||||
sizeof(in_header_write) + sizeof(in_payload_write));
|
||||
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);
|
||||
|
||||
auto it = buf.begin() + i * size_fragment;
|
||||
EXPECT_EQ(std::vector<char>(it, it + size_fragment), payload_buf);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
Reference in New Issue
Block a user