fusefs: Upgrade FUSE protocol to version 7.9.

This commit upgrades the FUSE API to protocol 7.9 and adds unit tests for
backwards compatibility with servers built for version 7.8.  It doesn't
implement any of 7.9's new features yet.

Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-05-16 17:24:11 +00:00
parent 96192dfce0
commit 16bd2d47c7
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=347814
17 changed files with 667 additions and 32 deletions

View File

@ -334,10 +334,13 @@ static int
fuse_read_directbackend(struct vnode *vp, struct uio *uio,
struct ucred *cred, struct fuse_filehandle *fufh)
{
struct fuse_data *data;
struct fuse_dispatcher fdi;
struct fuse_read_in *fri;
int err = 0;
data = fuse_get_mpdata(vp->v_mount);
if (uio->uio_resid == 0)
return (0);
@ -359,6 +362,11 @@ fuse_read_directbackend(struct vnode *vp, struct uio *uio,
fri->offset = uio->uio_offset;
fri->size = MIN(uio->uio_resid,
fuse_get_mpdata(vp->v_mount)->max_read);
if (fuse_libabi_geq(data, 7, 9)) {
/* See comment regarding FUSE_WRITE_LOCKOWNER */
fri->read_flags = 0;
fri->flags = 0; /* TODO */
}
SDT_PROBE1(fusefs, , io, read_directbackend_start, fri);
@ -385,6 +393,7 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio,
int ioflag)
{
struct fuse_vnode_data *fvdat = VTOFUD(vp);
struct fuse_data *data;
struct fuse_write_in *fwi;
struct fuse_write_out *fwo;
struct fuse_dispatcher fdi;
@ -395,6 +404,8 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio,
int err = 0;
bool direct_io = fufh->fuse_open_flags & FOPEN_DIRECT_IO;
data = fuse_get_mpdata(vp->v_mount);
if (uio->uio_resid == 0)
return (0);
@ -404,8 +415,7 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio,
fdisp_init(&fdi, 0);
while (uio->uio_resid > 0) {
chunksize = MIN(uio->uio_resid,
fuse_get_mpdata(vp->v_mount)->max_write);
chunksize = MIN(uio->uio_resid, data->max_write);
fdi.iosize = sizeof(*fwi) + chunksize;
fdisp_make_vp(&fdi, FUSE_WRITE, vp, uio->uio_td, cred);
@ -414,7 +424,23 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio,
fwi->fh = fufh->fh_id;
fwi->offset = uio->uio_offset;
fwi->size = chunksize;
fwi_data = (char *)fdi.indata + sizeof(*fwi);
if (fuse_libabi_geq(data, 7, 9)) {
/*
* Don't set FUSE_WRITE_LOCKOWNER. It can't be set
* accurately when using POSIX AIO, libfuse doesn't use
* it, and I'm not aware of any file systems that do.
* It was an attempt to add Linux-style mandatory
* locking to the FUSE protocol, but mandatory locking
* is deprecated even on Linux. See Linux commit
* f33321141b273d60cbb3a8f56a5489baad82ba5e .
*/
fwi->write_flags = 0;
fwi->flags = 0; /* TODO */
fwi_data = (char *)fdi.indata + sizeof(*fwi);
} else {
fwi_data = (char *)fdi.indata +
FUSE_COMPAT_WRITE_IN_SIZE;
}
if ((err = uiomove(fwi_data, chunksize, uio)))
break;

View File

@ -719,8 +719,17 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
opcode = fticket_opcode(ftick);
switch (opcode) {
case FUSE_LINK:
case FUSE_LOOKUP:
err = (blen == sizeof(struct fuse_entry_out)) ? 0 : EINVAL;
case FUSE_MKDIR:
case FUSE_MKNOD:
case FUSE_SYMLINK:
if (fuse_libabi_geq(ftick->tk_data, 7, 9)) {
err = (blen == sizeof(struct fuse_entry_out)) ?
0 : EINVAL;
} else {
err = (blen == FUSE_COMPAT_ENTRY_OUT_SIZE) ? 0 : EINVAL;
}
break;
case FUSE_FORGET:
@ -728,29 +737,19 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
break;
case FUSE_GETATTR:
err = (blen == sizeof(struct fuse_attr_out)) ? 0 : EINVAL;
break;
case FUSE_SETATTR:
err = (blen == sizeof(struct fuse_attr_out)) ? 0 : EINVAL;
if (fuse_libabi_geq(ftick->tk_data, 7, 9)) {
err = (blen == sizeof(struct fuse_attr_out)) ?
0 : EINVAL;
} else {
err = (blen == FUSE_COMPAT_ATTR_OUT_SIZE) ? 0 : EINVAL;
}
break;
case FUSE_READLINK:
err = (PAGE_SIZE >= blen) ? 0 : EINVAL;
break;
case FUSE_SYMLINK:
err = (blen == sizeof(struct fuse_entry_out)) ? 0 : EINVAL;
break;
case FUSE_MKNOD:
err = (blen == sizeof(struct fuse_entry_out)) ? 0 : EINVAL;
break;
case FUSE_MKDIR:
err = (blen == sizeof(struct fuse_entry_out)) ? 0 : EINVAL;
break;
case FUSE_UNLINK:
err = (blen == 0) ? 0 : EINVAL;
break;
@ -763,10 +762,6 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
err = (blen == 0) ? 0 : EINVAL;
break;
case FUSE_LINK:
err = (blen == sizeof(struct fuse_entry_out)) ? 0 : EINVAL;
break;
case FUSE_OPEN:
err = (blen == sizeof(struct fuse_open_out)) ? 0 : EINVAL;
break;
@ -864,8 +859,13 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
break;
case FUSE_CREATE:
err = (blen == sizeof(struct fuse_entry_out) +
sizeof(struct fuse_open_out)) ? 0 : EINVAL;
if (fuse_libabi_geq(ftick->tk_data, 7, 9)) {
err = (blen == sizeof(struct fuse_entry_out) +
sizeof(struct fuse_open_out)) ? 0 : EINVAL;
} else {
err = (blen == FUSE_COMPAT_ENTRY_OUT_SIZE +
sizeof(struct fuse_open_out)) ? 0 : EINVAL;
}
break;
case FUSE_DESTROY:

View File

@ -34,6 +34,22 @@
* $FreeBSD$
*/
/*
* This file defines the kernel interface of FUSE
*
* Protocol changelog:
*
* 7.9:
* - new fuse_getattr_in input argument of GETATTR
* - add lk_flags in fuse_lk_in
* - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
* - add blksize field to fuse_attr
* - add file flags field to fuse_read_in and fuse_write_in
*/
#ifndef _FUSE_FUSE_KERNEL_H
#define _FUSE_FUSE_KERNEL_H
#ifndef linux
#include <sys/types.h>
#define __u64 uint64_t
@ -48,7 +64,7 @@
#define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */
#define FUSE_KERNEL_MINOR_VERSION 8
#define FUSE_KERNEL_MINOR_VERSION 9
/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@ -77,6 +93,8 @@ struct fuse_attr {
__u32 uid;
__u32 gid;
__u32 rdev;
__u32 blksize;
__u32 padding;
};
struct fuse_kstatfs {
@ -109,6 +127,9 @@ struct fuse_file_lock {
#define FATTR_ATIME (1 << 4)
#define FATTR_MTIME (1 << 5)
#define FATTR_FH (1 << 6)
#define FATTR_ATIME_NOW (1 << 7)
#define FATTR_MTIME_NOW (1 << 8)
#define FATTR_LOCKOWNER (1 << 9)
/**
* Flags returned by the OPEN request
@ -121,15 +142,45 @@ struct fuse_file_lock {
/**
* INIT request/reply flags
*
* FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)
#define FUSE_FILE_OPS (1 << 2)
#define FUSE_ATOMIC_O_TRUNC (1 << 3)
#define FUSE_EXPORT_SUPPORT (1 << 4)
#define FUSE_BIG_WRITES (1 << 5)
/**
* Release flags
*/
#define FUSE_RELEASE_FLUSH (1 << 0)
/**
* Getattr flags
*/
#define FUSE_GETATTR_FH (1 << 0)
/**
* Lock flags
*/
#define FUSE_LK_FLOCK (1 << 0)
/**
* WRITE flags
*
* FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed
* FUSE_WRITE_LOCKOWNER: lock_owner field is valid
*/
#define FUSE_WRITE_CACHE (1 << 0)
#define FUSE_WRITE_LOCKOWNER (1 << 1)
/**
* Read flags
*/
#define FUSE_READ_LOCKOWNER (1 << 1)
enum fuse_opcode {
FUSE_LOOKUP = 1,
FUSE_FORGET = 2, /* no reply */
@ -172,6 +223,8 @@ enum fuse_opcode {
/* The read buffer is required to be at least 8k, but may be much larger */
#define FUSE_MIN_READ_BUFFER 8192
#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
struct fuse_entry_out {
__u64 nodeid; /* Inode ID */
__u64 generation; /* Inode generation: nodeid:gen must
@ -187,6 +240,14 @@ struct fuse_forget_in {
__u64 nlookup;
};
struct fuse_getattr_in {
__u32 getattr_flags;
__u32 dummy;
__u64 fh;
};
#define FUSE_COMPAT_ATTR_OUT_SIZE 96
struct fuse_attr_out {
__u64 attr_valid; /* Cache timeout for the attributes */
__u32 attr_valid_nsec;
@ -217,7 +278,7 @@ struct fuse_setattr_in {
__u32 padding;
__u64 fh;
__u64 size;
__u64 unused1;
__u64 lock_owner;
__u64 atime;
__u64 mtime;
__u64 unused2;
@ -260,14 +321,22 @@ struct fuse_read_in {
__u64 fh;
__u64 offset;
__u32 size;
__u32 read_flags;
__u64 lock_owner;
__u32 flags;
__u32 padding;
};
#define FUSE_COMPAT_WRITE_IN_SIZE 24
struct fuse_write_in {
__u64 fh;
__u64 offset;
__u32 size;
__u32 write_flags;
__u64 lock_owner;
__u32 flags;
__u32 padding;
};
struct fuse_write_out {
@ -316,6 +385,8 @@ struct fuse_lk_in {
__u64 fh;
__u64 owner;
struct fuse_file_lock lk;
__u32 lk_flags;
__u32 padding;
};
struct fuse_lk_out {
@ -386,3 +457,5 @@ struct fuse_dirent {
#define FUSE_DIRENT_ALIGN(x) (((x) + sizeof(__u64) - 1) & ~(sizeof(__u64) - 1))
#define FUSE_DIRENT_SIZE(d) \
FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
#endif /* _FUSE_FUSE_KERNEL_H */

View File

@ -56,6 +56,16 @@ void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
};
/* FUSE_CREATE operations for a protocol 7.8 server */
class Create_7_8: public Create {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
Create::SetUp();
}
};
/*
* If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the
* attribute cache
@ -338,3 +348,28 @@ TEST_F(Create, wronly_0444)
EXPECT_LE(0, fd) << strerror(errno);
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
TEST_F(Create_7_8, ok)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
mode_t mode = S_IFREG | 0755;
uint64_t ino = 42;
int fd;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_create(RELPATH, mode,
ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, create_7_8);
out->body.create.entry.attr.mode = mode;
out->body.create.entry.nodeid = ino;
out->body.create.entry.entry_valid = UINT64_MAX;
out->body.create.entry.attr_valid = UINT64_MAX;
}));
fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
EXPECT_LE(0, fd) << strerror(errno);
/* Deliberately leak fd. close(2) will be tested in release.cc */
}

View File

@ -53,6 +53,13 @@ void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
}
};
class Getattr_7_8: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
FuseTest::SetUp();
}
};
/*
* If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
@ -197,3 +204,62 @@ TEST_F(Getattr, ok)
//FUSE can't set st_blksize until protocol 7.9
}
TEST_F(Getattr_7_8, ok)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
struct stat sb;
EXPECT_LOOKUP(1, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 1;
out->body.entry.attr.size = 1;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
return (in->header.opcode == FUSE_GETATTR &&
in->header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
SET_OUT_HEADER_LEN(out, attr_7_8);
out->body.attr.attr.ino = ino; // Must match nodeid
out->body.attr.attr.mode = S_IFREG | 0644;
out->body.attr.attr.size = 1;
out->body.attr.attr.blocks = 2;
out->body.attr.attr.atime = 3;
out->body.attr.attr.mtime = 4;
out->body.attr.attr.ctime = 5;
out->body.attr.attr.atimensec = 6;
out->body.attr.attr.mtimensec = 7;
out->body.attr.attr.ctimensec = 8;
out->body.attr.attr.nlink = 9;
out->body.attr.attr.uid = 10;
out->body.attr.attr.gid = 11;
out->body.attr.attr.rdev = 12;
})));
ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
EXPECT_EQ(1, sb.st_size);
EXPECT_EQ(2, sb.st_blocks);
EXPECT_EQ(3, sb.st_atim.tv_sec);
EXPECT_EQ(6, sb.st_atim.tv_nsec);
EXPECT_EQ(4, sb.st_mtim.tv_sec);
EXPECT_EQ(7, sb.st_mtim.tv_nsec);
EXPECT_EQ(5, sb.st_ctim.tv_sec);
EXPECT_EQ(8, sb.st_ctim.tv_nsec);
EXPECT_EQ(9ull, sb.st_nlink);
EXPECT_EQ(10ul, sb.st_uid);
EXPECT_EQ(11ul, sb.st_gid);
EXPECT_EQ(12ul, sb.st_rdev);
EXPECT_EQ(ino, sb.st_ino);
EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
//st_birthtim and st_flags are not supported by protocol 7.8. They're
//only supported as OS-specific extensions to OSX.
}

View File

@ -66,6 +66,40 @@ void expect_lookup(const char *relpath, uint64_t ino)
}
};
class Link_7_8: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
FuseTest::SetUp();
}
void expect_link(uint64_t ino, const char *relpath, mode_t mode, uint32_t nlink)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in->body.bytes
+ sizeof(struct fuse_link_in);
return (in->header.opcode == FUSE_LINK &&
in->body.link.oldnodeid == ino &&
(0 == strcmp(name, relpath)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.entry.nodeid = ino;
out->body.entry.attr.mode = mode;
out->body.entry.attr.nlink = nlink;
out->body.entry.attr_valid = UINT64_MAX;
out->body.entry.entry_valid = UINT64_MAX;
})));
}
void expect_lookup(const char *relpath, uint64_t ino)
{
FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, 0, 1);
}
};
/*
* A successful link should clear the parent directory's attribute cache,
* because the fuse daemon should update its mtime and ctime
@ -164,3 +198,31 @@ TEST_F(Link, ok)
ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno);
EXPECT_EQ(2ul, sb.st_nlink);
}
TEST_F(Link_7_8, ok)
{
const char FULLPATH[] = "mountpoint/src";
const char RELPATH[] = "src";
const char FULLDST[] = "mountpoint/dst";
const char RELDST[] = "dst";
const uint64_t ino = 42;
mode_t mode = S_IFREG | 0644;
struct stat sb;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_LOOKUP(1, RELDST)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.entry.attr.mode = mode;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 1;
out->body.entry.attr_valid = UINT64_MAX;
out->body.entry.entry_valid = UINT64_MAX;
})));
expect_link(ino, RELPATH, mode, 2);
ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
// Check that the original file's nlink count has increased.
ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno);
EXPECT_EQ(2ul, sb.st_nlink);
}

View File

@ -38,6 +38,13 @@ extern "C" {
using namespace testing;
class Lookup: public FuseTest {};
class Lookup_7_8: public Lookup {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
Lookup::SetUp();
}
};
/*
* If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs
@ -355,3 +362,23 @@ TEST_F(Lookup, vtype_conflict)
ASSERT_EQ(-1, access(SECONDFULLPATH, F_OK));
ASSERT_EQ(EAGAIN, errno);
}
TEST_F(Lookup_7_8, ok)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
EXPECT_LOOKUP(1, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.entry.attr.mode = S_IFREG | 0644;
out->body.entry.nodeid = 14;
})));
/*
* access(2) is one of the few syscalls that will not (always) follow
* up a successful VOP_LOOKUP with another VOP.
*/
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
}

View File

@ -38,6 +38,13 @@ extern "C" {
using namespace testing;
class Mkdir: public FuseTest {};
class Mkdir_7_8: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
FuseTest::SetUp();
}
};
/*
* EMLINK is possible on filesystems that limit the number of hard links to a
@ -174,3 +181,32 @@ TEST_F(Mkdir, ok)
ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno);
}
TEST_F(Mkdir_7_8, ok)
{
const char FULLPATH[] = "mountpoint/some_dir";
const char RELPATH[] = "some_dir";
mode_t mode = 0755;
uint64_t ino = 42;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in->body.bytes +
sizeof(fuse_mkdir_in);
return (in->header.opcode == FUSE_MKDIR &&
in->body.mkdir.mode == (S_IFDIR | mode) &&
(0 == strcmp(RELPATH, name)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.create.entry.attr.mode = S_IFDIR | mode;
out->body.create.entry.nodeid = ino;
out->body.create.entry.entry_valid = UINT64_MAX;
out->body.create.entry.attr_valid = UINT64_MAX;
})));
ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno);
}

View File

@ -284,7 +284,8 @@ void debug_fuseop(const mockfs_buf_in *in)
}
MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags)
bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags,
uint32_t kernel_minor_version)
{
struct sigaction sa;
struct iovec *iov = NULL;
@ -293,6 +294,7 @@ MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
const bool trueval = true;
m_daemon_id = NULL;
m_kernel_minor_version = kernel_minor_version;
m_maxreadahead = max_readahead;
m_nready = -1;
m_pm = pm;
@ -399,7 +401,7 @@ void MockFS::init(uint32_t flags) {
out->header.unique = in->header.unique;
out->header.error = 0;
out->body.init.major = FUSE_KERNEL_VERSION;
out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
out->body.init.minor = m_kernel_minor_version;;
out->body.init.flags = in->body.init.flags & flags;
/*

View File

@ -76,6 +76,52 @@ struct fuse_create_out {
struct fuse_open_out open;
};
/* Protocol 7.8 version of struct fuse_attr */
struct fuse_attr_7_8
{
__u64 ino;
__u64 size;
__u64 blocks;
__u64 atime;
__u64 mtime;
__u64 ctime;
__u32 atimensec;
__u32 mtimensec;
__u32 ctimensec;
__u32 mode;
__u32 nlink;
__u32 uid;
__u32 gid;
__u32 rdev;
};
/* Protocol 7.8 version of struct fuse_attr_out */
struct fuse_attr_out_7_8
{
__u64 attr_valid;
__u32 attr_valid_nsec;
__u32 dummy;
struct fuse_attr_7_8 attr;
};
/* Protocol 7.8 version of struct fuse_entry_out */
struct fuse_entry_out_7_8 {
__u64 nodeid; /* Inode ID */
__u64 generation; /* Inode generation: nodeid:gen must
be unique for the fs's lifetime */
__u64 entry_valid; /* Cache timeout for the name */
__u64 attr_valid; /* Cache timeout for the attributes */
__u32 entry_valid_nsec;
__u32 attr_valid_nsec;
struct fuse_attr_7_8 attr;
};
/* Output struct for FUSE_CREATE for protocol 7.8 servers */
struct fuse_create_out_7_8 {
struct fuse_entry_out_7_8 entry;
struct fuse_open_out open;
};
union fuse_payloads_in {
fuse_access_in access;
/* value is from fuse_kern_chan.c in fusefs-libs */
@ -116,10 +162,13 @@ struct mockfs_buf_in {
union fuse_payloads_out {
fuse_attr_out attr;
fuse_attr_out_7_8 attr_7_8;
fuse_create_out create;
fuse_create_out_7_8 create_7_8;
/* The protocol places no limits on the size of bytes */
uint8_t bytes[0x20000];
fuse_entry_out entry;
fuse_entry_out_7_8 entry_7_8;
fuse_lk_out getlk;
fuse_getxattr_out getxattr;
fuse_init_out init;
@ -191,6 +240,9 @@ class MockFS {
/* file descriptor of /dev/fuse control device */
int m_fuse_fd;
/* The minor version of the kernel API that this mock daemon targets */
uint32_t m_kernel_minor_version;
int m_kq;
/* The max_readahead filesystem option */
@ -240,7 +292,9 @@ class MockFS {
/* Create a new mockfs and mount it to a tempdir */
MockFS(int max_readahead, bool allow_other,
bool default_permissions, bool push_symlinks_in, bool ro,
enum poll_method pm, uint32_t flags);
enum poll_method pm, uint32_t flags,
uint32_t kernel_minor_version);
virtual ~MockFS();
/* Kill the filesystem daemon without unmounting the filesystem */

View File

@ -54,6 +54,19 @@ void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
}
};
class Read_7_8: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
FuseTest::SetUp();
}
void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
{
FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
}
};
class AioRead: public Read {
public:
virtual void SetUp() {
@ -585,6 +598,29 @@ TEST_F(Read, read)
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
TEST_F(Read_7_8, read)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
char buf[bufsize];
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
expect_read(ino, 0, bufsize, bufsize, CONTENTS);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno);
ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize));
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
/* If the filesystem allows it, the kernel should try to readahead */
TEST_F(ReadCacheable, default_readahead)
{

View File

@ -97,6 +97,19 @@ void expect_readdir(uint64_t ino, uint64_t off, vector<struct dirent> &ents)
}
};
class Readdir_7_8: public Readdir {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
Readdir::SetUp();
}
void expect_lookup(const char *relpath, uint64_t ino)
{
FuseTest::expect_lookup_7_8(relpath, ino, S_IFDIR | 0755, 0, 1);
}
};
/* FUSE_READDIR returns nothing but "." and ".." */
TEST_F(Readdir, dots)
{
@ -378,3 +391,34 @@ TEST_F(Readdir, seekdir)
/* Deliberately leak dir. RELEASEDIR will be tested separately */
}
TEST_F(Readdir_7_8, nodots)
{
const char FULLPATH[] = "mountpoint/some_dir";
const char RELPATH[] = "some_dir";
uint64_t ino = 42;
DIR *dir;
expect_lookup(RELPATH, ino);
expect_opendir(ino);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in->header.opcode == FUSE_READDIR &&
in->header.nodeid == ino);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
out->header.error = 0;
out->header.len = sizeof(out->header);
})));
errno = 0;
dir = opendir(FULLPATH);
ASSERT_NE(NULL, dir) << strerror(errno);
errno = 0;
ASSERT_EQ(NULL, readdir(dir));
ASSERT_EQ(0, errno);
/* Deliberately leak dir. RELEASEDIR will be tested separately */
}

View File

@ -49,6 +49,14 @@ virtual void SetUp() {
}
};
class Setattr_7_8: public Setattr {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
Setattr::SetUp();
}
};
/*
* If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
@ -736,3 +744,37 @@ TEST_F(RofsSetattr, erofs)
ASSERT_EQ(-1, chmod(FULLPATH, newmode));
ASSERT_EQ(EROFS, errno);
}
/* Change the mode of a file */
TEST_F(Setattr_7_8, chmod)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const uint64_t ino = 42;
const mode_t oldmode = 0755;
const mode_t newmode = 0644;
EXPECT_LOOKUP(1, RELPATH)
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.entry.attr.mode = S_IFREG | oldmode;
out->body.entry.nodeid = ino;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([](auto in) {
/* In protocol 7.23, ctime will be changed too */
uint32_t valid = FATTR_MODE;
return (in->header.opcode == FUSE_SETATTR &&
in->header.nodeid == ino &&
in->body.setattr.valid == valid &&
in->body.setattr.mode == newmode);
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, attr_7_8);
out->body.attr.attr.ino = ino; // Must match nodeid
out->body.attr.attr.mode = S_IFREG | newmode;
})));
EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
}

View File

@ -62,6 +62,35 @@ void expect_symlink(uint64_t ino, const char *target, const char *relpath)
};
class Symlink_7_8: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
FuseTest::SetUp();
}
void expect_symlink(uint64_t ino, const char *target, const char *relpath)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in->body.bytes;
const char *linkname = name + strlen(name) + 1;
return (in->header.opcode == FUSE_SYMLINK &&
(0 == strcmp(linkname, target)) &&
(0 == strcmp(name, relpath)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.entry.attr.mode = S_IFLNK | 0777;
out->body.entry.nodeid = ino;
out->body.entry.entry_valid = UINT64_MAX;
out->body.entry.attr_valid = UINT64_MAX;
})));
}
};
/*
* A successful symlink should clear the parent directory's attribute cache,
* because the fuse daemon should update its mtime and ctime
@ -130,3 +159,16 @@ TEST_F(Symlink, ok)
EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno);
}
TEST_F(Symlink_7_8, ok)
{
const char FULLPATH[] = "mountpoint/src";
const char RELPATH[] = "src";
const char dst[] = "dst";
const uint64_t ino = 42;
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
expect_symlink(ino, dst, RELPATH);
EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno);
}

View File

@ -96,7 +96,7 @@ void FuseTest::SetUp() {
try {
m_mock = new MockFS(m_maxreadahead, m_allow_other,
m_default_permissions, m_push_symlinks_in, m_ro,
m_pm, m_init_flags);
m_pm, m_init_flags, m_kernel_minor_version);
/*
* FUSE_ACCESS is called almost universally. Expecting it in
* each test case would be super-annoying. Instead, set a
@ -208,6 +208,23 @@ void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
})));
}
void FuseTest::expect_lookup_7_8(const char *relpath, uint64_t ino, mode_t mode,
uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid)
{
EXPECT_LOOKUP(1, relpath)
.Times(times)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, entry_7_8);
out->body.entry.attr.mode = mode;
out->body.entry.nodeid = ino;
out->body.entry.attr.nlink = 1;
out->body.entry.attr_valid = attr_valid;
out->body.entry.attr.size = size;
out->body.entry.attr.uid = uid;
out->body.entry.attr.gid = gid;
})));
}
void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times)
{
EXPECT_CALL(*m_mock, process(
@ -334,6 +351,30 @@ void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
})));
}
void FuseTest::expect_write_7_8(uint64_t ino, uint64_t offset, uint64_t isize,
uint64_t osize, uint32_t flags, const void *contents)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *buf = (const char*)in->body.bytes +
FUSE_COMPAT_WRITE_IN_SIZE;
bool pid_ok = (pid_t)in->header.pid == getpid();
return (in->header.opcode == FUSE_WRITE &&
in->header.nodeid == ino &&
in->body.write.fh == FH &&
in->body.write.offset == offset &&
in->body.write.size == isize &&
pid_ok &&
in->body.write.write_flags == flags &&
0 == bcmp(buf, contents, isize));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
SET_OUT_HEADER_LEN(out, write);
out->body.write.size = osize;
})));
}
void
get_unprivileged_id(uid_t *uid, gid_t *gid)
{

View File

@ -52,6 +52,7 @@ class FuseTest : public ::testing::Test {
uint32_t m_init_flags;
bool m_allow_other;
bool m_default_permissions;
uint32_t m_kernel_minor_version;
enum poll_method m_pm;
bool m_push_symlinks_in;
bool m_ro;
@ -70,6 +71,7 @@ class FuseTest : public ::testing::Test {
m_init_flags(0),
m_allow_other(false),
m_default_permissions(false),
m_kernel_minor_version(FUSE_KERNEL_MINOR_VERSION),
m_pm(BLOCKING),
m_push_symlinks_in(false),
m_ro(false)
@ -119,6 +121,11 @@ class FuseTest : public ::testing::Test {
uint64_t size, int times, uint64_t attr_valid = UINT64_MAX,
uid_t uid = 0, gid_t gid = 0);
/* The protocol 7.8 version of expect_lookup */
void expect_lookup_7_8(const char *relpath, uint64_t ino, mode_t mode,
uint64_t size, int times, uint64_t attr_valid = UINT64_MAX,
uid_t uid = 0, gid_t gid = 0);
/*
* Create an expectation that FUSE_OPEN will be called for the given
* inode exactly times times. It will return with open_flags flags and
@ -136,6 +143,9 @@ class FuseTest : public ::testing::Test {
* Create an expectation that FUSE_READ will be called exactly once for
* the given inode, at offset offset and with size isize. It will
* return the first osize bytes from contents
*
* Protocol 7.8 tests can use this same expectation method because
* nothing currently validates the size of the fuse_read_in struct.
*/
void expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
uint64_t osize, const void *contents);
@ -166,6 +176,10 @@ class FuseTest : public ::testing::Test {
void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
uint64_t osize, uint32_t flags, const void *contents);
/* Protocol 7.8 version of expect_write */
void expect_write_7_8(uint64_t ino, uint64_t offset, uint64_t isize,
uint64_t osize, uint32_t flags, const void *contents);
/*
* Helper that runs code in a child process.
*

View File

@ -67,6 +67,21 @@ void expect_release(uint64_t ino, ProcessMockerT r)
};
class Write_7_8: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 8;
FuseTest::SetUp();
}
void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
{
FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
}
};
class AioWrite: public Write {
virtual void SetUp() {
const char *node = "vfs.aio.enable_unsafe";
@ -535,6 +550,26 @@ TEST_F(Write, write_nothing)
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
TEST_F(Write_7_8, write)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
uint64_t ino = 42;
int fd;
ssize_t bufsize = strlen(CONTENTS);
expect_lookup(RELPATH, ino, 0);
expect_open(ino, 0, 1);
expect_write_7_8(ino, 0, bufsize, bufsize, 0, CONTENTS);
fd = open(FULLPATH, O_WRONLY);
EXPECT_LE(0, fd) << strerror(errno);
ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
/* In writeback mode, dirty data should be written on close */
TEST_F(WriteBack, close)
{