fusefs: raise protocol level to 7.12

This commit raises the protocol level and adds backwards-compatibility code
to handle structure size changes.  It doesn't implement any new features.
The new features added in protocol 7.12 are:

* server-side umask processing (which FreeBSD won't do)
* asynchronous inode and directory entry invalidation (which I'll do next)

Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Alan Somers 2019-05-29 16:39:52 +00:00
parent e039bafa87
commit a4856c96d0
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=348365
9 changed files with 228 additions and 26 deletions

View File

@ -331,12 +331,22 @@ int
fuse_internal_mknod(struct vnode *dvp, struct vnode **vpp,
struct componentname *cnp, struct vattr *vap)
{
struct fuse_data *data;
struct fuse_mknod_in fmni;
size_t insize;
data = fuse_get_mpdata(dvp->v_mount);
fmni.mode = MAKEIMODE(vap->va_type, vap->va_mode);
fmni.rdev = vap->va_rdev;
if (fuse_libabi_geq(data, 7, 12)) {
insize = sizeof(fmni);
fmni.umask = curthread->td_proc->p_fd->fd_cmask;
} else {
insize = FUSE_COMPAT_MKNOD_IN_SIZE;
}
return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKNOD, &fmni,
sizeof(fmni), vap->va_type));
insize, vap->va_type));
}
/* readdir */
@ -824,6 +834,8 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
* Unsupported features:
* FUSE_FILE_OPS: No known FUSE server or client supports it
* FUSE_ATOMIC_O_TRUNC: our VFS cannot support it
* FUSE_DONT_MASK: unlike Linux, FreeBSD always applies the umask, even
* when default ACLs are in use.
*/
fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT
| FUSE_BIG_WRITES;

View File

@ -52,6 +52,11 @@
* 7.11
* - add IOCTL message
* - add unsolicited notification support
*
* 7.12
* - add umask flag to input argument of open, mknod and mkdir
* - add notification messages for invalidation of inodes and
* directory entries
*/
#ifndef _FUSE_FUSE_KERNEL_H
@ -60,6 +65,7 @@
#ifndef linux
#include <sys/types.h>
#define __u64 uint64_t
#define __s64 int64_t
#define __u32 uint32_t
#define __s32 int32_t
#else
@ -70,7 +76,7 @@
#define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */
#define FUSE_KERNEL_MINOR_VERSION 11
#define FUSE_KERNEL_MINOR_VERSION 12
/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@ -146,6 +152,7 @@ struct fuse_file_lock {
* INIT request/reply flags
*
* FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
* FUSE_DONT_MASK: don't apply umask to file mode on create operations
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)
@ -153,6 +160,7 @@ struct fuse_file_lock {
#define FUSE_ATOMIC_O_TRUNC (1 << 3)
#define FUSE_EXPORT_SUPPORT (1 << 4)
#define FUSE_BIG_WRITES (1 << 5)
#define FUSE_DONT_MASK (1 << 6)
#ifdef linux
/**
@ -262,6 +270,8 @@ enum fuse_opcode {
enum fuse_notify_code {
FUSE_NOTIFY_POLL = 1,
FUSE_NOTIFY_INVAL_INODE = 2,
FUSE_NOTIFY_INVAL_ENTRY = 3,
FUSE_NOTIFY_CODE_MAX,
};
@ -300,14 +310,18 @@ struct fuse_attr_out {
struct fuse_attr attr;
};
#define FUSE_COMPAT_MKNOD_IN_SIZE 8
struct fuse_mknod_in {
__u32 mode;
__u32 rdev;
__u32 umask;
__u32 padding;
};
struct fuse_mkdir_in {
__u32 mode;
__u32 padding;
__u32 umask;
};
struct fuse_rename_in {
@ -338,8 +352,15 @@ struct fuse_setattr_in {
};
struct fuse_open_in {
__u32 flags;
__u32 unused;
};
struct fuse_create_in {
__u32 flags;
__u32 mode;
__u32 umask;
__u32 padding;
};
struct fuse_open_out {
@ -558,4 +579,16 @@ struct fuse_dirent {
#define FUSE_DIRENT_SIZE(d) \
FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
struct fuse_notify_inval_inode_out {
__u64 ino;
__s64 off;
__s64 len;
};
struct fuse_notify_inval_entry_out {
__u64 parent;
__u32 namelen;
__u32 padding;
};
#endif /* _FUSE_FUSE_KERNEL_H */

View File

@ -540,7 +540,8 @@ fuse_vnop_create(struct vop_create_args *ap)
struct thread *td = cnp->cn_thread;
struct ucred *cred = cnp->cn_cred;
struct fuse_open_in *foi;
struct fuse_data *data;
struct fuse_create_in *fci;
struct fuse_entry_out *feo;
struct fuse_open_out *foo;
struct fuse_dispatcher fdi, fdi2;
@ -550,6 +551,7 @@ fuse_vnop_create(struct vop_create_args *ap)
int err;
struct mount *mp = vnode_mount(dvp);
data = fuse_get_mpdata(mp);
uint64_t parentnid = VTOFUD(dvp)->nid;
mode_t mode = MAKEIMODE(vap->va_type, vap->va_mode);
enum fuse_opcode op;
@ -580,15 +582,24 @@ fuse_vnop_create(struct vop_create_args *ap)
cred, mode, &op);
} else {
/* Use FUSE_CREATE */
size_t insize;
op = FUSE_CREATE;
fdisp_init(fdip, sizeof(*foi) + cnp->cn_namelen + 1);
fdisp_init(fdip, sizeof(*fci) + cnp->cn_namelen + 1);
fdisp_make(fdip, op, vnode_mount(dvp), parentnid, td, cred);
foi = fdip->indata;
foi->mode = mode;
foi->flags = O_CREAT | flags;
memcpy((char *)fdip->indata + sizeof(*foi), cnp->cn_nameptr,
fci = fdip->indata;
fci->mode = mode;
fci->flags = O_CREAT | flags;
if (fuse_libabi_geq(data, 7, 12)) {
insize = sizeof(*fci);
fci->umask = td->td_proc->p_fd->fd_cmask;
} else {
insize = sizeof(struct fuse_open_in);
}
memcpy((char *)fdip->indata + insize, cnp->cn_nameptr,
cnp->cn_namelen);
((char *)fdip->indata)[sizeof(*foi) + cnp->cn_namelen] = '\0';
((char *)fdip->indata)[insize + cnp->cn_namelen] = '\0';
}
err = fdisp_wait_answ(fdip);
@ -614,12 +625,13 @@ fuse_vnop_create(struct vop_create_args *ap)
foo = (struct fuse_open_out*)(feo + 1);
} else {
/* Issue a separate FUSE_OPEN */
struct fuse_open_in *foi;
fdip2 = &fdi2;
fdisp_init(fdip2, sizeof(*foi));
fdisp_make(fdip2, FUSE_OPEN, vnode_mount(dvp), feo->nodeid, td,
cred);
foi = fdip2->indata;
foi->mode = mode;
foi->flags = flags;
err = fdisp_wait_answ(fdip2);
if (err)
@ -1162,6 +1174,7 @@ fuse_vnop_mkdir(struct vop_mkdir_args *ap)
return ENXIO;
}
fmdi.mode = MAKEIMODE(vap->va_type, vap->va_mode);
fmdi.umask = curthread->td_proc->p_fd->fd_cmask;
return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKDIR, &fmdi,
sizeof(fmdi), VDIR));

View File

@ -42,12 +42,16 @@ public:
void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
{
mode_t mask = umask(0);
(void)umask(mask);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
sizeof(fuse_open_in);
sizeof(fuse_create_in);
return (in.header.opcode == FUSE_CREATE &&
in.body.open.mode == mode &&
in.body.create.mode == mode &&
in.body.create.umask == mask &&
(0 == strcmp(relpath, name)));
}, Eq(true)),
_)
@ -63,6 +67,45 @@ virtual void SetUp() {
m_kernel_minor_version = 8;
Create::SetUp();
}
void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
sizeof(fuse_open_in);
return (in.header.opcode == FUSE_CREATE &&
in.body.create.mode == mode &&
(0 == strcmp(relpath, name)));
}, Eq(true)),
_)
).WillOnce(Invoke(r));
}
};
/* FUSE_CREATE operations for a server built at protocol <= 7.11 */
class Create_7_11: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 11;
FuseTest::SetUp();
}
void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
{
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
sizeof(fuse_open_in);
return (in.header.opcode == FUSE_CREATE &&
in.body.create.mode == mode &&
(0 == strcmp(relpath, name)));
}, Eq(true)),
_)
).WillOnce(Invoke(r));
}
};
@ -372,4 +415,25 @@ TEST_F(Create_7_8, ok)
/* Deliberately leak fd. close(2) will be tested in release.cc */
}
TEST_F(Create_7_11, 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);
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

@ -92,7 +92,7 @@ void expect_create(const char *relpath, uint64_t ino)
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
sizeof(fuse_open_in);
sizeof(fuse_create_in);
return (in.header.opcode == FUSE_CREATE &&
(0 == strcmp(relpath, name)));
}, Eq(true)),

View File

@ -159,6 +159,10 @@ TEST_F(Mkdir, ok)
const char RELPATH[] = "some_dir";
mode_t mode = 0755;
uint64_t ino = 42;
mode_t mask;
mask = umask(0);
(void)umask(mask);
EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
@ -168,6 +172,7 @@ TEST_F(Mkdir, ok)
sizeof(fuse_mkdir_in);
return (in.header.opcode == FUSE_MKDIR &&
in.body.mkdir.mode == (S_IFDIR | mode) &&
in.body.mkdir.umask == mask &&
(0 == strcmp(RELPATH, name)));
}, Eq(true)),
_)

View File

@ -48,14 +48,23 @@ const char RELPATH[] = "some_file.txt";
class Mknod: public FuseTest {
mode_t m_oldmask;
const static mode_t c_umask = 022;
public:
virtual void SetUp() {
FuseTest::SetUp();
m_oldmask = umask(c_umask);
printf("m_oldmask=%#o\n", m_oldmask);
if (geteuid() != 0) {
GTEST_SKIP() << "Only root may use most mknod(2) variations";
}
FuseTest::SetUp();
}
virtual void TearDown() {
FuseTest::TearDown();
(void)umask(m_oldmask);
}
/* Test an OK creation of a file with the given mode and device number */
@ -68,6 +77,50 @@ void expect_mknod(mode_t mode, dev_t dev) {
ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes +
sizeof(fuse_mknod_in);
return (in.header.opcode == FUSE_MKNOD &&
in.body.mknod.mode == mode &&
in.body.mknod.rdev == (uint32_t)dev &&
in.body.mknod.umask == c_umask &&
(0 == strcmp(RELPATH, name)));
}, Eq(true)),
_)
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
SET_OUT_HEADER_LEN(out, entry);
out.body.entry.attr.mode = mode;
out.body.entry.nodeid = ino;
out.body.entry.entry_valid = UINT64_MAX;
out.body.entry.attr_valid = UINT64_MAX;
out.body.entry.attr.rdev = dev;
})));
}
};
class Mknod_7_11: public FuseTest {
public:
virtual void SetUp() {
m_kernel_minor_version = 11;
if (geteuid() != 0) {
GTEST_SKIP() << "Only root may use most mknod(2) variations";
}
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);
}
/* Test an OK creation of a file with the given mode and device number */
void expect_mknod(mode_t mode, dev_t dev) {
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 +
FUSE_COMPAT_MKNOD_IN_SIZE;
return (in.header.opcode == FUSE_MKNOD &&
in.body.mknod.mode == mode &&
in.body.mknod.rdev == (uint32_t)dev &&
@ -172,3 +225,12 @@ TEST_F(Mknod, DISABLED_whiteout)
expect_mknod(mode, rdev);
EXPECT_EQ(0, mknod(FULLPATH, mode, 0)) << strerror(errno);
}
/* A server built at protocol version 7.11 or earlier can still use mknod */
TEST_F(Mknod_7_11, fifo)
{
mode_t mode = S_IFIFO | 0755;
dev_t rdev = VNOVAL;
expect_mknod(mode, rdev);
EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno);
}

View File

@ -153,7 +153,7 @@ void sigint_handler(int __unused sig) {
// Don't do anything except interrupt the daemon's read(2) call
}
void debug_fuseop(const mockfs_buf_in &in)
void MockFS::debug_fuseop(const mockfs_buf_in &in)
{
printf("%-11s ino=%2" PRIu64, opcode2opname(in.header.opcode),
in.header.nodeid);
@ -169,8 +169,12 @@ void debug_fuseop(const mockfs_buf_in &in)
printf(" mask=%#x", in.body.access.mask);
break;
case FUSE_CREATE:
name = (const char*)in.body.bytes +
sizeof(fuse_open_in);
if (m_kernel_minor_version >= 12)
name = (const char*)in.body.bytes +
sizeof(fuse_create_in);
else
name = (const char*)in.body.bytes +
sizeof(fuse_open_in);
printf(" flags=%#x name=%s",
in.body.open.flags, name);
break;
@ -200,19 +204,25 @@ void debug_fuseop(const mockfs_buf_in &in)
case FUSE_MKDIR:
name = (const char*)in.body.bytes +
sizeof(fuse_mkdir_in);
printf(" name=%s mode=%#o", name, in.body.mkdir.mode);
printf(" name=%s mode=%#o umask=%#o", name,
in.body.mkdir.mode, in.body.mkdir.umask);
break;
case FUSE_MKNOD:
printf(" mode=%#o rdev=%x", in.body.mknod.mode,
in.body.mknod.rdev);
if (m_kernel_minor_version >= 12)
name = (const char*)in.body.bytes +
sizeof(fuse_mknod_in);
else
name = (const char*)in.body.bytes +
FUSE_COMPAT_MKNOD_IN_SIZE;
printf(" mode=%#o rdev=%x umask=%#o name=%s",
in.body.mknod.mode, in.body.mknod.rdev,
in.body.mknod.umask, name);
break;
case FUSE_OPEN:
printf(" flags=%#x mode=%#o",
in.body.open.flags, in.body.open.mode);
printf(" flags=%#x", in.body.open.flags);
break;
case FUSE_OPENDIR:
printf(" flags=%#x mode=%#o",
in.body.opendir.flags, in.body.opendir.mode);
printf(" flags=%#x", in.body.opendir.flags);
break;
case FUSE_READ:
printf(" offset=%" PRIu64 " size=%u",

View File

@ -126,6 +126,7 @@ union fuse_payloads_in {
fuse_access_in access;
/* value is from fuse_kern_chan.c in fusefs-libs */
uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)];
fuse_create_in create;
fuse_flush_in flush;
fuse_fsync_in fsync;
fuse_fsync_in fsyncdir;
@ -257,6 +258,8 @@ class MockFS {
/* Method the daemon should use for I/O to and from /dev/fuse */
enum poll_method m_pm;
void debug_fuseop(const mockfs_buf_in&);
/* Initialize a session after mounting */
void init(uint32_t flags);