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, fuse_internal_mknod(struct vnode *dvp, struct vnode **vpp,
struct componentname *cnp, struct vattr *vap) struct componentname *cnp, struct vattr *vap)
{ {
struct fuse_data *data;
struct fuse_mknod_in fmni; 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.mode = MAKEIMODE(vap->va_type, vap->va_mode);
fmni.rdev = vap->va_rdev; 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, return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKNOD, &fmni,
sizeof(fmni), vap->va_type)); insize, vap->va_type));
} }
/* readdir */ /* readdir */
@ -824,6 +834,8 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
* Unsupported features: * Unsupported features:
* FUSE_FILE_OPS: No known FUSE server or client supports it * FUSE_FILE_OPS: No known FUSE server or client supports it
* FUSE_ATOMIC_O_TRUNC: our VFS cannot support 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 fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT
| FUSE_BIG_WRITES; | FUSE_BIG_WRITES;

View File

@ -52,6 +52,11 @@
* 7.11 * 7.11
* - add IOCTL message * - add IOCTL message
* - add unsolicited notification support * - 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 #ifndef _FUSE_FUSE_KERNEL_H
@ -60,6 +65,7 @@
#ifndef linux #ifndef linux
#include <sys/types.h> #include <sys/types.h>
#define __u64 uint64_t #define __u64 uint64_t
#define __s64 int64_t
#define __u32 uint32_t #define __u32 uint32_t
#define __s32 int32_t #define __s32 int32_t
#else #else
@ -70,7 +76,7 @@
#define FUSE_KERNEL_VERSION 7 #define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */ /** 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 */ /** The node ID of the root inode */
#define FUSE_ROOT_ID 1 #define FUSE_ROOT_ID 1
@ -146,6 +152,7 @@ struct fuse_file_lock {
* INIT request/reply flags * INIT request/reply flags
* *
* FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." * 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_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1) #define FUSE_POSIX_LOCKS (1 << 1)
@ -153,6 +160,7 @@ struct fuse_file_lock {
#define FUSE_ATOMIC_O_TRUNC (1 << 3) #define FUSE_ATOMIC_O_TRUNC (1 << 3)
#define FUSE_EXPORT_SUPPORT (1 << 4) #define FUSE_EXPORT_SUPPORT (1 << 4)
#define FUSE_BIG_WRITES (1 << 5) #define FUSE_BIG_WRITES (1 << 5)
#define FUSE_DONT_MASK (1 << 6)
#ifdef linux #ifdef linux
/** /**
@ -262,6 +270,8 @@ enum fuse_opcode {
enum fuse_notify_code { enum fuse_notify_code {
FUSE_NOTIFY_POLL = 1, FUSE_NOTIFY_POLL = 1,
FUSE_NOTIFY_INVAL_INODE = 2,
FUSE_NOTIFY_INVAL_ENTRY = 3,
FUSE_NOTIFY_CODE_MAX, FUSE_NOTIFY_CODE_MAX,
}; };
@ -300,14 +310,18 @@ struct fuse_attr_out {
struct fuse_attr attr; struct fuse_attr attr;
}; };
#define FUSE_COMPAT_MKNOD_IN_SIZE 8
struct fuse_mknod_in { struct fuse_mknod_in {
__u32 mode; __u32 mode;
__u32 rdev; __u32 rdev;
__u32 umask;
__u32 padding;
}; };
struct fuse_mkdir_in { struct fuse_mkdir_in {
__u32 mode; __u32 mode;
__u32 padding; __u32 umask;
}; };
struct fuse_rename_in { struct fuse_rename_in {
@ -338,8 +352,15 @@ struct fuse_setattr_in {
}; };
struct fuse_open_in { struct fuse_open_in {
__u32 flags;
__u32 unused;
};
struct fuse_create_in {
__u32 flags; __u32 flags;
__u32 mode; __u32 mode;
__u32 umask;
__u32 padding;
}; };
struct fuse_open_out { struct fuse_open_out {
@ -558,4 +579,16 @@ struct fuse_dirent {
#define FUSE_DIRENT_SIZE(d) \ #define FUSE_DIRENT_SIZE(d) \
FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) 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 */ #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 thread *td = cnp->cn_thread;
struct ucred *cred = cnp->cn_cred; 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_entry_out *feo;
struct fuse_open_out *foo; struct fuse_open_out *foo;
struct fuse_dispatcher fdi, fdi2; struct fuse_dispatcher fdi, fdi2;
@ -550,6 +551,7 @@ fuse_vnop_create(struct vop_create_args *ap)
int err; int err;
struct mount *mp = vnode_mount(dvp); struct mount *mp = vnode_mount(dvp);
data = fuse_get_mpdata(mp);
uint64_t parentnid = VTOFUD(dvp)->nid; uint64_t parentnid = VTOFUD(dvp)->nid;
mode_t mode = MAKEIMODE(vap->va_type, vap->va_mode); mode_t mode = MAKEIMODE(vap->va_type, vap->va_mode);
enum fuse_opcode op; enum fuse_opcode op;
@ -580,15 +582,24 @@ fuse_vnop_create(struct vop_create_args *ap)
cred, mode, &op); cred, mode, &op);
} else { } else {
/* Use FUSE_CREATE */ /* Use FUSE_CREATE */
size_t insize;
op = FUSE_CREATE; 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); fdisp_make(fdip, op, vnode_mount(dvp), parentnid, td, cred);
foi = fdip->indata; fci = fdip->indata;
foi->mode = mode; fci->mode = mode;
foi->flags = O_CREAT | flags; fci->flags = O_CREAT | flags;
memcpy((char *)fdip->indata + sizeof(*foi), cnp->cn_nameptr, 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); 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); err = fdisp_wait_answ(fdip);
@ -614,12 +625,13 @@ fuse_vnop_create(struct vop_create_args *ap)
foo = (struct fuse_open_out*)(feo + 1); foo = (struct fuse_open_out*)(feo + 1);
} else { } else {
/* Issue a separate FUSE_OPEN */ /* Issue a separate FUSE_OPEN */
struct fuse_open_in *foi;
fdip2 = &fdi2; fdip2 = &fdi2;
fdisp_init(fdip2, sizeof(*foi)); fdisp_init(fdip2, sizeof(*foi));
fdisp_make(fdip2, FUSE_OPEN, vnode_mount(dvp), feo->nodeid, td, fdisp_make(fdip2, FUSE_OPEN, vnode_mount(dvp), feo->nodeid, td,
cred); cred);
foi = fdip2->indata; foi = fdip2->indata;
foi->mode = mode;
foi->flags = flags; foi->flags = flags;
err = fdisp_wait_answ(fdip2); err = fdisp_wait_answ(fdip2);
if (err) if (err)
@ -1162,6 +1174,7 @@ fuse_vnop_mkdir(struct vop_mkdir_args *ap)
return ENXIO; return ENXIO;
} }
fmdi.mode = MAKEIMODE(vap->va_type, vap->va_mode); 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, return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKDIR, &fmdi,
sizeof(fmdi), VDIR)); sizeof(fmdi), VDIR));

View File

@ -42,12 +42,16 @@ public:
void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
{ {
mode_t mask = umask(0);
(void)umask(mask);
EXPECT_CALL(*m_mock, process( EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) { ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes + const char *name = (const char*)in.body.bytes +
sizeof(fuse_open_in); sizeof(fuse_create_in);
return (in.header.opcode == FUSE_CREATE && 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))); (0 == strcmp(relpath, name)));
}, Eq(true)), }, Eq(true)),
_) _)
@ -63,6 +67,45 @@ virtual void SetUp() {
m_kernel_minor_version = 8; m_kernel_minor_version = 8;
Create::SetUp(); 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 */ /* 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( EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) { ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes + const char *name = (const char*)in.body.bytes +
sizeof(fuse_open_in); sizeof(fuse_create_in);
return (in.header.opcode == FUSE_CREATE && return (in.header.opcode == FUSE_CREATE &&
(0 == strcmp(relpath, name))); (0 == strcmp(relpath, name)));
}, Eq(true)), }, Eq(true)),

View File

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

View File

@ -48,14 +48,23 @@ const char RELPATH[] = "some_file.txt";
class Mknod: public FuseTest { class Mknod: public FuseTest {
mode_t m_oldmask;
const static mode_t c_umask = 022;
public: public:
virtual void SetUp() { virtual void SetUp() {
FuseTest::SetUp(); m_oldmask = umask(c_umask);
printf("m_oldmask=%#o\n", m_oldmask);
if (geteuid() != 0) { if (geteuid() != 0) {
GTEST_SKIP() << "Only root may use most mknod(2) variations"; 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 */ /* 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) { ResultOf([=](auto in) {
const char *name = (const char*)in.body.bytes + const char *name = (const char*)in.body.bytes +
sizeof(fuse_mknod_in); 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 && return (in.header.opcode == FUSE_MKNOD &&
in.body.mknod.mode == mode && in.body.mknod.mode == mode &&
in.body.mknod.rdev == (uint32_t)dev && in.body.mknod.rdev == (uint32_t)dev &&
@ -172,3 +225,12 @@ TEST_F(Mknod, DISABLED_whiteout)
expect_mknod(mode, rdev); expect_mknod(mode, rdev);
EXPECT_EQ(0, mknod(FULLPATH, mode, 0)) << strerror(errno); 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 // 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), printf("%-11s ino=%2" PRIu64, opcode2opname(in.header.opcode),
in.header.nodeid); in.header.nodeid);
@ -169,8 +169,12 @@ void debug_fuseop(const mockfs_buf_in &in)
printf(" mask=%#x", in.body.access.mask); printf(" mask=%#x", in.body.access.mask);
break; break;
case FUSE_CREATE: case FUSE_CREATE:
name = (const char*)in.body.bytes + if (m_kernel_minor_version >= 12)
sizeof(fuse_open_in); 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", printf(" flags=%#x name=%s",
in.body.open.flags, name); in.body.open.flags, name);
break; break;
@ -200,19 +204,25 @@ void debug_fuseop(const mockfs_buf_in &in)
case FUSE_MKDIR: case FUSE_MKDIR:
name = (const char*)in.body.bytes + name = (const char*)in.body.bytes +
sizeof(fuse_mkdir_in); 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; break;
case FUSE_MKNOD: case FUSE_MKNOD:
printf(" mode=%#o rdev=%x", in.body.mknod.mode, if (m_kernel_minor_version >= 12)
in.body.mknod.rdev); 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; break;
case FUSE_OPEN: case FUSE_OPEN:
printf(" flags=%#x mode=%#o", printf(" flags=%#x", in.body.open.flags);
in.body.open.flags, in.body.open.mode);
break; break;
case FUSE_OPENDIR: case FUSE_OPENDIR:
printf(" flags=%#x mode=%#o", printf(" flags=%#x", in.body.opendir.flags);
in.body.opendir.flags, in.body.opendir.mode);
break; break;
case FUSE_READ: case FUSE_READ:
printf(" offset=%" PRIu64 " size=%u", printf(" offset=%" PRIu64 " size=%u",

View File

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