Implement FUSE_SETATTR

This commit implements FUSE_SETATTR command. When a system call modifies
the metadata of a regular file or a folder by chown(2), chmod(2),
truncate(2), utime(2), or utimes(2), they should be translated to
corresponding FUSE_SETATTR command and sent to the FUSE server.

Fixes #3332
This commit is contained in:
Craig Chi
2020-09-16 12:19:30 -07:00
committed by Andrei Vagin
parent 4181e8c974
commit bf8efe8cdf
7 changed files with 516 additions and 2 deletions
+67
View File
@@ -749,3 +749,70 @@ func (r *FUSEDirent) UnmarshalBytes(src []byte) {
name.UnmarshalBytes(src[:r.Meta.NameLen])
r.Name = string(name)
}
// FATTR_* consts are the attribute flags defined in include/uapi/linux/fuse.h.
// These should be or-ed together for setattr to know what has been changed.
const (
FATTR_MODE = (1 << 0)
FATTR_UID = (1 << 1)
FATTR_GID = (1 << 2)
FATTR_SIZE = (1 << 3)
FATTR_ATIME = (1 << 4)
FATTR_MTIME = (1 << 5)
FATTR_FH = (1 << 6)
FATTR_ATIME_NOW = (1 << 7)
FATTR_MTIME_NOW = (1 << 8)
FATTR_LOCKOWNER = (1 << 9)
FATTR_CTIME = (1 << 10)
)
// FUSESetAttrIn is the request sent by the kernel to the daemon,
// to set the attribute(s) of a file.
//
// +marshal
type FUSESetAttrIn struct {
// Valid indicates which attributes are modified by this request.
Valid uint32
_ uint32
// Fh is used to identify the file if FATTR_FH is set in Valid.
Fh uint64
// Size is the size that the request wants to change to.
Size uint64
// LockOwner is the owner of the lock that the request wants to change to.
LockOwner uint64
// Atime is the access time that the request wants to change to.
Atime uint64
// Mtime is the modification time that the request wants to change to.
Mtime uint64
// Ctime is the status change time that the request wants to change to.
Ctime uint64
// AtimeNsec is the nano second part of Atime.
AtimeNsec uint32
// MtimeNsec is the nano second part of Mtime.
MtimeNsec uint32
// CtimeNsec is the nano second part of Ctime.
CtimeNsec uint32
// Mode is the file mode that the request wants to change to.
Mode uint32
_ uint32
// UID is the user ID of the owner that the request wants to change to.
UID uint32
// GID is the group ID of the owner that the request wants to change to.
GID uint32
_ uint32
}
+1 -1
View File
@@ -123,5 +123,5 @@ func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linu
// SetStat implements FileDescriptionImpl.SetStat.
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
creds := auth.CredentialsFromContext(ctx)
return fd.inode().SetStat(ctx, fd.inode().fs.VFSFilesystem(), creds, opts)
return fd.inode().setAttr(ctx, fd.inode().fs.VFSFilesystem(), creds, opts, true, fd.Fh)
}
+82 -1
View File
@@ -660,7 +660,7 @@ func (i *inode) getAttr(ctx context.Context, fs *vfs.Filesystem, opts vfs.StatOp
}
// Set the metadata of kernfs.InodeAttrs.
if err := i.SetStat(ctx, fs, creds, vfs.SetStatOptions{
if err := i.SetInodeStat(ctx, fs, creds, vfs.SetStatOptions{
Stat: statFromFUSEAttr(out.Attr, linux.STATX_ALL, i.fs.devMinor),
}); err != nil {
return linux.FUSEAttr{}, err
@@ -703,3 +703,84 @@ func (i *inode) StatFS(ctx context.Context, fs *vfs.Filesystem) (linux.Statfs, e
// TODO(gvisor.dev/issues/3413): Complete the implementation of statfs.
return vfs.GenericStatFS(linux.FUSE_SUPER_MAGIC), nil
}
// fattrMaskFromStats converts vfs.SetStatOptions.Stat.Mask to linux stats mask
// aligned with the attribute mask defined in include/linux/fs.h.
func fattrMaskFromStats(mask uint32) uint32 {
var fuseAttrMask uint32
maskMap := map[uint32]uint32{
linux.STATX_MODE: linux.FATTR_MODE,
linux.STATX_UID: linux.FATTR_UID,
linux.STATX_GID: linux.FATTR_GID,
linux.STATX_SIZE: linux.FATTR_SIZE,
linux.STATX_ATIME: linux.FATTR_ATIME,
linux.STATX_MTIME: linux.FATTR_MTIME,
linux.STATX_CTIME: linux.FATTR_CTIME,
}
for statxMask, fattrMask := range maskMap {
if mask&statxMask != 0 {
fuseAttrMask |= fattrMask
}
}
return fuseAttrMask
}
// SetStat implements kernfs.Inode.SetStat.
func (i *inode) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
return i.setAttr(ctx, fs, creds, opts, false, 0)
}
func (i *inode) setAttr(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions, useFh bool, fh uint64) error {
conn := i.fs.conn
task := kernel.TaskFromContext(ctx)
if task == nil {
log.Warningf("couldn't get kernel task from context")
return syserror.EINVAL
}
// We should retain the original file type when assigning new mode.
fileType := uint16(i.Mode()) & linux.S_IFMT
fattrMask := fattrMaskFromStats(opts.Stat.Mask)
if useFh {
fattrMask |= linux.FATTR_FH
}
in := linux.FUSESetAttrIn{
Valid: fattrMask,
Fh: fh,
Size: opts.Stat.Size,
Atime: uint64(opts.Stat.Atime.Sec),
Mtime: uint64(opts.Stat.Mtime.Sec),
Ctime: uint64(opts.Stat.Ctime.Sec),
AtimeNsec: opts.Stat.Atime.Nsec,
MtimeNsec: opts.Stat.Mtime.Nsec,
CtimeNsec: opts.Stat.Ctime.Nsec,
Mode: uint32(fileType | opts.Stat.Mode),
UID: opts.Stat.UID,
GID: opts.Stat.GID,
}
req, err := conn.NewRequest(creds, uint32(task.ThreadID()), i.NodeID, linux.FUSE_SETATTR, &in)
if err != nil {
return err
}
res, err := conn.Call(task, req)
if err != nil {
return err
}
if err := res.Error(); err != nil {
return err
}
out := linux.FUSEGetAttrOut{}
if err := res.UnmarshalPayload(&out); err != nil {
return err
}
// Set the metadata of kernfs.InodeAttrs.
if err := i.SetInodeStat(ctx, fs, creds, vfs.SetStatOptions{
Stat: statFromFUSEAttr(out.Attr, linux.STATX_ALL, i.fs.devMinor),
}); err != nil {
return err
}
return nil
}
@@ -256,6 +256,13 @@ func (a *InodeAttrs) Stat(context.Context, *vfs.Filesystem, vfs.StatOptions) (li
// SetStat implements Inode.SetStat.
func (a *InodeAttrs) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
return a.SetInodeStat(ctx, fs, creds, opts)
}
// SetInodeStat sets the corresponding attributes from opts to InodeAttrs.
// This function can be used by other kernfs-based filesystem implementation to
// sets the unexported attributes into kernfs.InodeAttrs.
func (a *InodeAttrs) SetInodeStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
if opts.Stat.Mask == 0 {
return nil
}
+5
View File
@@ -62,6 +62,11 @@ syscall_test(
test = "//test/fuse/linux:create_test",
)
syscall_test(
fuse = "True",
test = "//test/fuse/linux:setstat_test",
)
syscall_test(
size = "large",
add_overlay = True,
+16
View File
@@ -100,6 +100,22 @@ cc_binary(
],
)
cc_binary(
name = "setstat_test",
testonly = 1,
srcs = ["setstat_test.cc"],
deps = [
gtest,
":fuse_fd_util",
"//test/util:cleanup",
"//test/util:fs_util",
"//test/util:fuse_util",
"//test/util:temp_umask",
"//test/util:test_main",
"//test/util:test_util",
],
)
cc_binary(
name = "rmdir_test",
testonly = 1,
+338
View File
@@ -0,0 +1,338 @@
// 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 <sys/uio.h>
#include <unistd.h>
#include <utime.h>
#include <string>
#include <vector>
#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 {
class SetStatTest : public FuseFdTest {
public:
void SetUp() override {
FuseFdTest::SetUp();
test_dir_path_ = JoinPath(mount_point_.path(), test_dir_);
test_file_path_ = JoinPath(mount_point_.path(), test_file_);
}
protected:
const uint64_t fh = 23;
const std::string test_dir_ = "testdir";
const std::string test_file_ = "testfile";
const mode_t test_dir_mode_ = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR;
const mode_t test_file_mode_ = S_IFREG | S_IRUSR | S_IWUSR | S_IXUSR;
std::string test_dir_path_;
std::string test_file_path_;
};
TEST_F(SetStatTest, ChmodDir) {
// Set up fixture.
SetServerInodeLookup(test_dir_, test_dir_mode_);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
mode_t set_mode = S_IRGRP | S_IWGRP | S_IXGRP;
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(set_mode, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
EXPECT_THAT(chmod(test_dir_path_.c_str(), set_mode), SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_MODE);
EXPECT_EQ(in_payload.mode, S_IFDIR | set_mode);
}
TEST_F(SetStatTest, ChownDir) {
// Set up fixture.
SetServerInodeLookup(test_dir_, test_dir_mode_);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(test_dir_mode_, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
EXPECT_THAT(chown(test_dir_path_.c_str(), 1025, 1025), SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_UID | FATTR_GID);
EXPECT_EQ(in_payload.uid, 1025);
EXPECT_EQ(in_payload.gid, 1025);
}
TEST_F(SetStatTest, TruncateFile) {
// Set up fixture.
SetServerInodeLookup(test_file_, test_file_mode_);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(S_IFREG | S_IRUSR | S_IWUSR, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
EXPECT_THAT(truncate(test_file_path_.c_str(), 321), SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_SIZE);
EXPECT_EQ(in_payload.size, 321);
}
TEST_F(SetStatTest, UtimeFile) {
// Set up fixture.
SetServerInodeLookup(test_file_, test_file_mode_);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(S_IFREG | S_IRUSR | S_IWUSR, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
time_t expected_atime = 1597159766, expected_mtime = 1597159765;
struct utimbuf times = {
.actime = expected_atime,
.modtime = expected_mtime,
};
EXPECT_THAT(utime(test_file_path_.c_str(), &times), SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_ATIME | FATTR_MTIME);
EXPECT_EQ(in_payload.atime, expected_atime);
EXPECT_EQ(in_payload.mtime, expected_mtime);
}
TEST_F(SetStatTest, UtimesFile) {
// Set up fixture.
SetServerInodeLookup(test_file_, test_file_mode_);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(test_file_mode_, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
struct timeval expected_times[2] = {
{
.tv_sec = 1597159766,
.tv_usec = 234945,
},
{
.tv_sec = 1597159765,
.tv_usec = 232341,
},
};
EXPECT_THAT(utimes(test_file_path_.c_str(), expected_times),
SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_ATIME | FATTR_MTIME);
EXPECT_EQ(in_payload.atime, expected_times[0].tv_sec);
EXPECT_EQ(in_payload.atimensec, expected_times[0].tv_usec * 1000);
EXPECT_EQ(in_payload.mtime, expected_times[1].tv_sec);
EXPECT_EQ(in_payload.mtimensec, expected_times[1].tv_usec * 1000);
}
TEST_F(SetStatTest, FtruncateFile) {
// Set up fixture.
SetServerInodeLookup(test_file_, test_file_mode_);
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDWR, fh));
auto close_fd = CloseFD(fd);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(test_file_mode_, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
EXPECT_THAT(ftruncate(fd.get(), 321), SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_SIZE | FATTR_FH);
EXPECT_EQ(in_payload.fh, fh);
EXPECT_EQ(in_payload.size, 321);
}
TEST_F(SetStatTest, FchmodFile) {
// Set up fixture.
SetServerInodeLookup(test_file_, test_file_mode_);
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDWR, fh));
auto close_fd = CloseFD(fd);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
mode_t set_mode = S_IROTH | S_IWOTH | S_IXOTH;
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(set_mode, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
EXPECT_THAT(fchmod(fd.get(), set_mode), SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_MODE | FATTR_FH);
EXPECT_EQ(in_payload.fh, fh);
EXPECT_EQ(in_payload.mode, S_IFREG | set_mode);
}
TEST_F(SetStatTest, FchownFile) {
// Set up fixture.
SetServerInodeLookup(test_file_, test_file_mode_);
auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenPath(test_file_path_, O_RDWR, fh));
auto close_fd = CloseFD(fd);
struct fuse_out_header out_header = {
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
.error = 0,
};
struct fuse_attr_out out_payload = {
.attr = DefaultFuseAttr(S_IFREG | S_IRUSR | S_IWUSR | S_IXUSR, 2),
};
auto iov_out = FuseGenerateIovecs(out_header, out_payload);
SetServerResponse(FUSE_SETATTR, iov_out);
// Make syscall.
EXPECT_THAT(fchown(fd.get(), 1025, 1025), SyscallSucceeds());
// Check FUSE request.
struct fuse_in_header in_header;
struct fuse_setattr_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_SETATTR);
EXPECT_EQ(in_header.uid, 0);
EXPECT_EQ(in_header.gid, 0);
EXPECT_EQ(in_payload.valid, FATTR_UID | FATTR_GID | FATTR_FH);
EXPECT_EQ(in_payload.fh, fh);
EXPECT_EQ(in_payload.uid, 1025);
EXPECT_EQ(in_payload.gid, 1025);
}
} // namespace
} // namespace testing
} // namespace gvisor