Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 16 May 2019 17:24:12 +0000 (UTC)
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r347814 - in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs
Message-ID:  <201905161724.x4GHOCfP004763@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: asomers
Date: Thu May 16 17:24:11 2019
New Revision: 347814
URL: https://svnweb.freebsd.org/changeset/base/347814

Log:
  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

Modified:
  projects/fuse2/sys/fs/fuse/fuse_io.c
  projects/fuse2/sys/fs/fuse/fuse_ipc.c
  projects/fuse2/sys/fs/fuse/fuse_kernel.h
  projects/fuse2/tests/sys/fs/fusefs/create.cc
  projects/fuse2/tests/sys/fs/fusefs/getattr.cc
  projects/fuse2/tests/sys/fs/fusefs/link.cc
  projects/fuse2/tests/sys/fs/fusefs/lookup.cc
  projects/fuse2/tests/sys/fs/fusefs/mkdir.cc
  projects/fuse2/tests/sys/fs/fusefs/mockfs.cc
  projects/fuse2/tests/sys/fs/fusefs/mockfs.hh
  projects/fuse2/tests/sys/fs/fusefs/read.cc
  projects/fuse2/tests/sys/fs/fusefs/readdir.cc
  projects/fuse2/tests/sys/fs/fusefs/setattr.cc
  projects/fuse2/tests/sys/fs/fusefs/symlink.cc
  projects/fuse2/tests/sys/fs/fusefs/utils.cc
  projects/fuse2/tests/sys/fs/fusefs/utils.hh
  projects/fuse2/tests/sys/fs/fusefs/write.cc

Modified: projects/fuse2/sys/fs/fuse/fuse_io.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_io.c	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/sys/fs/fuse/fuse_io.c	Thu May 16 17:24:11 2019	(r347814)
@@ -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 *
 		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 
     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 
 	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 
 	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 
 		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;

Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_ipc.c	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/sys/fs/fuse/fuse_ipc.c	Thu May 16 17:24:11 2019	(r347814)
@@ -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:

Modified: projects/fuse2/sys/fs/fuse/fuse_kernel.h
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_kernel.h	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/sys/fs/fuse/fuse_kernel.h	Thu May 16 17:24:11 2019	(r347814)
@@ -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 */

Modified: projects/fuse2/tests/sys/fs/fusefs/create.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/create.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/create.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -56,6 +56,16 @@ void expect_create(const char *relpath, mode_t mode, P
 
 };
 
+/* 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 */
+}
+
+

Modified: projects/fuse2/tests/sys/fs/fusefs/getattr.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/getattr.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/getattr.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -53,6 +53,13 @@ void expect_lookup(const char *relpath, uint64_t ino, 
 }
 };
 
+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
@@ -196,4 +203,63 @@ TEST_F(Getattr, ok)
 	//EXPECT_EQ(, sb.st_flags);
 	
 	//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.
 }

Modified: projects/fuse2/tests/sys/fs/fusefs/link.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/link.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/link.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -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
@@ -151,6 +185,34 @@ TEST_F(Link, ok)
 	EXPECT_LOOKUP(1, RELDST)
 	.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.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);
+}
+
+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;

Modified: projects/fuse2/tests/sys/fs/fusefs/lookup.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/lookup.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/lookup.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -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);
+}
+
+

Modified: projects/fuse2/tests/sys/fs/fusefs/mkdir.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/mkdir.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/mkdir.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -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
@@ -166,6 +173,35 @@ TEST_F(Mkdir, ok)
 		_)
 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
 		SET_OUT_HEADER_LEN(out, entry);
+		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);
+}
+
+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;

Modified: projects/fuse2/tests/sys/fs/fusefs/mockfs.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/mockfs.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/mockfs.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -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, bo
 	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;
 
 	/*

Modified: projects/fuse2/tests/sys/fs/fusefs/mockfs.hh
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/mockfs.hh	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/mockfs.hh	Thu May 16 17:24:11 2019	(r347814)
@@ -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 */

Modified: projects/fuse2/tests/sys/fs/fusefs/read.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/read.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/read.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -54,6 +54,19 @@ void expect_lookup(const char *relpath, uint64_t ino, 
 }
 };
 
+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() {
@@ -563,6 +576,29 @@ TEST_F(Read, pread)
 }
 
 TEST_F(Read, 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 */
+}
+
+TEST_F(Read_7_8, read)
 {
 	const char FULLPATH[] = "mountpoint/some_file.txt";
 	const char RELPATH[] = "some_file.txt";

Modified: projects/fuse2/tests/sys/fs/fusefs/readdir.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/readdir.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/readdir.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -97,6 +97,19 @@ void expect_readdir(uint64_t ino, uint64_t off, vector
 }
 };
 
+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)
 {
@@ -375,6 +388,37 @@ TEST_F(Readdir, seekdir)
 	de = readdir(dir);
 	ASSERT_NE(NULL, de) << strerror(errno);
 	EXPECT_EQ(130ul, de->d_fileno);
+
+	/* 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 */
 }

Modified: projects/fuse2/tests/sys/fs/fusefs/setattr.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/setattr.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/setattr.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -49,7 +49,15 @@ 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
  * should use the cached attributes, rather than query the daemon
@@ -735,4 +743,38 @@ 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);
 }

Modified: projects/fuse2/tests/sys/fs/fusefs/symlink.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/symlink.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/symlink.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -62,6 +62,35 @@ void expect_symlink(uint64_t ino, const char *target, 
 
 };
 
+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
@@ -119,6 +148,19 @@ TEST_F(Symlink, enospc)
 }
 
 TEST_F(Symlink, 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);
+}
+
+TEST_F(Symlink_7_8, ok)
 {
 	const char FULLPATH[] = "mountpoint/src";
 	const char RELPATH[] = "src";

Modified: projects/fuse2/tests/sys/fs/fusefs/utils.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/utils.cc	Thu May 16 17:23:36 2019	(r347813)
+++ projects/fuse2/tests/sys/fs/fusefs/utils.cc	Thu May 16 17:24:11 2019	(r347814)
@@ -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, uint
 	})));
 }
 
+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);

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201905161724.x4GHOCfP004763>