mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement FUSE_CREATE
FUSE_CREATE is called when issuing creat(2) or open(2) with O_CREAT. It creates a new file on the FUSE filesystem. Fixes #3825
This commit is contained in:
@@ -445,6 +445,48 @@ type FUSEReleaseIn struct {
|
||||
LockOwner uint64
|
||||
}
|
||||
|
||||
// FUSECreateMeta contains all the static fields of FUSECreateIn,
|
||||
// which is used for FUSE_CREATE.
|
||||
//
|
||||
// +marshal
|
||||
type FUSECreateMeta struct {
|
||||
// Flags of the creating file.
|
||||
Flags uint32
|
||||
|
||||
// Mode is the mode of the creating file.
|
||||
Mode uint32
|
||||
|
||||
// Umask is the current file mode creation mask.
|
||||
Umask uint32
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// FUSECreateIn contains all the arguments sent by the kernel to the daemon, to
|
||||
// atomically create and open a new regular file.
|
||||
//
|
||||
// Dynamically-sized objects cannot be marshalled.
|
||||
type FUSECreateIn struct {
|
||||
marshal.StubMarshallable
|
||||
|
||||
// CreateMeta contains mode, rdev and umash field for FUSE_MKNODS.
|
||||
CreateMeta FUSECreateMeta
|
||||
|
||||
// Name is the name of the node to create.
|
||||
Name string
|
||||
}
|
||||
|
||||
// MarshalBytes serializes r.CreateMeta and r.Name to the dst buffer.
|
||||
func (r *FUSECreateIn) MarshalBytes(buf []byte) {
|
||||
r.CreateMeta.MarshalBytes(buf[:r.CreateMeta.SizeBytes()])
|
||||
copy(buf[r.CreateMeta.SizeBytes():], r.Name)
|
||||
}
|
||||
|
||||
// SizeBytes is the size of the memory representation of FUSECreateIn.
|
||||
// 1 extra byte for null-terminated string.
|
||||
func (r *FUSECreateIn) SizeBytes() int {
|
||||
return r.CreateMeta.SizeBytes() + len(r.Name) + 1
|
||||
}
|
||||
|
||||
// FUSEMknodMeta contains all the static fields of FUSEMknodIn,
|
||||
// which is used for FUSE_MKNOD.
|
||||
//
|
||||
|
||||
@@ -392,6 +392,24 @@ func (inode) Valid(ctx context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewFile implements kernfs.Inode.NewFile.
|
||||
func (i *inode) NewFile(ctx context.Context, name string, opts vfs.OpenOptions) (*vfs.Dentry, error) {
|
||||
kernelTask := kernel.TaskFromContext(ctx)
|
||||
if kernelTask == nil {
|
||||
log.Warningf("fusefs.Inode.NewFile: couldn't get kernel task from context", i.NodeID)
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
in := linux.FUSECreateIn{
|
||||
CreateMeta: linux.FUSECreateMeta{
|
||||
Flags: opts.Flags,
|
||||
Mode: uint32(opts.Mode) | linux.S_IFREG,
|
||||
Umask: uint32(kernelTask.FSContext().Umask()),
|
||||
},
|
||||
Name: name,
|
||||
}
|
||||
return i.newEntry(ctx, name, linux.S_IFREG, linux.FUSE_CREATE, &in)
|
||||
}
|
||||
|
||||
// NewNode implements kernfs.Inode.NewNode.
|
||||
func (i *inode) NewNode(ctx context.Context, name string, opts vfs.MknodOptions) (*vfs.Dentry, error) {
|
||||
in := linux.FUSEMknodIn{
|
||||
|
||||
@@ -52,6 +52,10 @@ syscall_test(
|
||||
test = "//test/fuse/linux:readdir_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
fuse = "True",
|
||||
test = "//test/fuse/linux:create_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
|
||||
+16
-1
@@ -152,4 +152,19 @@ cc_binary(
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "create_test",
|
||||
testonly = 1,
|
||||
srcs = ["create_test.cc"],
|
||||
deps = [
|
||||
gtest,
|
||||
":fuse_base",
|
||||
"//test/util:fs_util",
|
||||
"//test/util:fuse_util",
|
||||
"//test/util:temp_umask",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// 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/fs_util.h"
|
||||
#include "test/util/fuse_util.h"
|
||||
#include "test/util/temp_umask.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
class CreateTest : public FuseTest {
|
||||
protected:
|
||||
const std::string test_file_name_ = "test_file";
|
||||
const mode_t mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
|
||||
};
|
||||
|
||||
TEST_F(CreateTest, CreateFile) {
|
||||
const std::string test_file_path =
|
||||
JoinPath(mount_point_.path().c_str(), test_file_name_);
|
||||
|
||||
// Ensure the file doesn't exist.
|
||||
struct fuse_out_header out_header = {
|
||||
.len = sizeof(struct fuse_out_header),
|
||||
.error = -ENOENT,
|
||||
};
|
||||
auto iov_out = FuseGenerateIovecs(out_header);
|
||||
SetServerResponse(FUSE_LOOKUP, iov_out);
|
||||
|
||||
// creat(2) is equal to open(2) with open_flags O_CREAT | O_WRONLY | O_TRUNC.
|
||||
const mode_t new_mask = S_IWGRP | S_IWOTH;
|
||||
const int open_flags = O_CREAT | O_WRONLY | O_TRUNC;
|
||||
out_header.error = 0;
|
||||
out_header.len = sizeof(struct fuse_out_header) +
|
||||
sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out);
|
||||
struct fuse_entry_out entry_payload = DefaultEntryOut(mode & ~new_mask, 2);
|
||||
struct fuse_open_out out_payload = {
|
||||
.fh = 1,
|
||||
.open_flags = open_flags,
|
||||
};
|
||||
iov_out = FuseGenerateIovecs(out_header, entry_payload, out_payload);
|
||||
SetServerResponse(FUSE_CREATE, iov_out);
|
||||
|
||||
// kernfs generates a successive FUSE_OPEN after the file is created. Linux's
|
||||
// fuse kernel module will not send this FUSE_OPEN after creat(2).
|
||||
out_header.len =
|
||||
sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out);
|
||||
iov_out = FuseGenerateIovecs(out_header, out_payload);
|
||||
SetServerResponse(FUSE_OPEN, iov_out);
|
||||
|
||||
int fd;
|
||||
TempUmask mask(new_mask);
|
||||
EXPECT_THAT(fd = creat(test_file_path.c_str(), mode), SyscallSucceeds());
|
||||
EXPECT_THAT(fcntl(fd, F_GETFL),
|
||||
SyscallSucceedsWithValue(open_flags & O_ACCMODE));
|
||||
|
||||
struct fuse_in_header in_header;
|
||||
struct fuse_create_in in_payload;
|
||||
std::vector<char> name(test_file_name_.size() + 1);
|
||||
auto iov_in = FuseGenerateIovecs(in_header, in_payload, name);
|
||||
|
||||
// Skip the request of FUSE_LOOKUP.
|
||||
SkipServerActualRequest();
|
||||
|
||||
// Get the first FUSE_CREATE.
|
||||
GetServerActualRequest(iov_in);
|
||||
EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload) +
|
||||
test_file_name_.size() + 1);
|
||||
EXPECT_EQ(in_header.opcode, FUSE_CREATE);
|
||||
EXPECT_EQ(in_payload.flags, open_flags);
|
||||
EXPECT_EQ(in_payload.mode, mode & ~new_mask);
|
||||
EXPECT_EQ(in_payload.umask, new_mask);
|
||||
EXPECT_EQ(std::string(name.data()), test_file_name_);
|
||||
|
||||
// Get the successive FUSE_OPEN.
|
||||
struct fuse_open_in in_payload_open;
|
||||
iov_in = FuseGenerateIovecs(in_header, in_payload_open);
|
||||
GetServerActualRequest(iov_in);
|
||||
EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload_open));
|
||||
EXPECT_EQ(in_header.opcode, FUSE_OPEN);
|
||||
EXPECT_EQ(in_payload_open.flags, open_flags & O_ACCMODE);
|
||||
|
||||
EXPECT_THAT(close(fd), SyscallSucceeds());
|
||||
// Skip the FUSE_RELEASE.
|
||||
SkipServerActualRequest();
|
||||
}
|
||||
|
||||
TEST_F(CreateTest, CreateFileAlreadyExists) {
|
||||
const std::string test_file_path =
|
||||
JoinPath(mount_point_.path().c_str(), test_file_name_);
|
||||
|
||||
const int open_flags = O_CREAT | O_EXCL;
|
||||
|
||||
SetServerInodeLookup(test_file_name_);
|
||||
|
||||
EXPECT_THAT(open(test_file_path.c_str(), mode, open_flags),
|
||||
SyscallFailsWithErrno(EEXIST));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
Reference in New Issue
Block a user