Index: projects/fuse2/sys/fs/fuse/fuse_kernel.h =================================================================== --- projects/fuse2/sys/fs/fuse/fuse_kernel.h (revision 344785) +++ projects/fuse2/sys/fs/fuse/fuse_kernel.h (revision 344786) @@ -1,383 +1,388 @@ /*-- * This file defines the kernel interface of FUSE * Copyright (C) 2001-2007 Miklos Szeredi * * This program can be distributed under the terms of the GNU GPL. * See the file COPYING. * * This -- and only this -- header file may also be distributed under * the terms of the BSD Licence as follows: * * Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef linux #include #define __u64 uint64_t #define __u32 uint32_t #define __s32 int32_t #else #include #include #endif /** Version number of this interface */ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ #define FUSE_KERNEL_MINOR_VERSION 8 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 /** The major number of the fuse character device */ #define FUSE_MAJOR MISC_MAJOR /** The minor number of the fuse character device */ #define FUSE_MINOR 229 /* Make sure all structures are padded to 64bit boundary, so 32bit userspace works under 64bit kernels */ struct fuse_attr { __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; }; struct fuse_kstatfs { __u64 blocks; __u64 bfree; __u64 bavail; __u64 files; __u64 ffree; __u32 bsize; __u32 namelen; __u32 frsize; __u32 padding; __u32 spare[6]; }; struct fuse_file_lock { __u64 start; __u64 end; __u32 type; __u32 pid; /* tgid */ }; /** * Bitmasks for fuse_setattr_in.valid */ #define FATTR_MODE (1 << 0) #define FATTR_UID (1 << 1) #define FATTR_GID (1 << 2) #define FATTR_SIZE (1 << 3) #define FATTR_ATIME (1 << 4) #define FATTR_MTIME (1 << 5) #define FATTR_FH (1 << 6) /** * Flags returned by the OPEN request * * FOPEN_DIRECT_IO: bypass page cache for this open file * FOPEN_KEEP_CACHE: don't invalidate the data cache on open */ #define FOPEN_DIRECT_IO (1 << 0) #define FOPEN_KEEP_CACHE (1 << 1) /** * INIT request/reply flags */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) /** * Release flags */ #define FUSE_RELEASE_FLUSH (1 << 0) enum fuse_opcode { FUSE_LOOKUP = 1, FUSE_FORGET = 2, /* no reply */ FUSE_GETATTR = 3, FUSE_SETATTR = 4, FUSE_READLINK = 5, FUSE_SYMLINK = 6, FUSE_MKNOD = 8, FUSE_MKDIR = 9, FUSE_UNLINK = 10, FUSE_RMDIR = 11, FUSE_RENAME = 12, FUSE_LINK = 13, FUSE_OPEN = 14, FUSE_READ = 15, FUSE_WRITE = 16, FUSE_STATFS = 17, FUSE_RELEASE = 18, FUSE_FSYNC = 20, FUSE_SETXATTR = 21, FUSE_GETXATTR = 22, FUSE_LISTXATTR = 23, FUSE_REMOVEXATTR = 24, FUSE_FLUSH = 25, FUSE_INIT = 26, FUSE_OPENDIR = 27, FUSE_READDIR = 28, FUSE_RELEASEDIR = 29, FUSE_FSYNCDIR = 30, FUSE_GETLK = 31, FUSE_SETLK = 32, FUSE_SETLKW = 33, FUSE_ACCESS = 34, FUSE_CREATE = 35, FUSE_INTERRUPT = 36, FUSE_BMAP = 37, FUSE_DESTROY = 38, }; /* The read buffer is required to be at least 8k, but may be much larger */ #define FUSE_MIN_READ_BUFFER 8192 struct fuse_entry_out { __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 attr; }; struct fuse_forget_in { __u64 nlookup; }; struct fuse_attr_out { __u64 attr_valid; /* Cache timeout for the attributes */ __u32 attr_valid_nsec; __u32 dummy; struct fuse_attr attr; }; +struct fuse_mknod_in { + __u32 mode; + __u32 rdev; +}; + struct fuse_mkdir_in { __u32 mode; __u32 padding; }; struct fuse_rename_in { __u64 newdir; }; struct fuse_link_in { __u64 oldnodeid; }; struct fuse_setattr_in { __u32 valid; __u32 padding; __u64 fh; __u64 size; __u64 unused1; __u64 atime; __u64 mtime; __u64 unused2; __u32 atimensec; __u32 mtimensec; __u32 unused3; __u32 mode; __u32 unused4; __u32 uid; __u32 gid; __u32 unused5; }; struct fuse_open_in { __u32 flags; __u32 mode; }; struct fuse_open_out { __u64 fh; __u32 open_flags; __u32 padding; }; struct fuse_release_in { __u64 fh; __u32 flags; __u32 release_flags; __u64 lock_owner; }; struct fuse_flush_in { __u64 fh; __u32 unused; __u32 padding; __u64 lock_owner; }; struct fuse_read_in { __u64 fh; __u64 offset; __u32 size; __u32 padding; }; struct fuse_write_in { __u64 fh; __u64 offset; __u32 size; __u32 write_flags; }; struct fuse_write_out { __u32 size; __u32 padding; }; #define FUSE_COMPAT_STATFS_SIZE 48 struct fuse_statfs_out { struct fuse_kstatfs st; }; struct fuse_fsync_in { __u64 fh; __u32 fsync_flags; __u32 padding; }; struct fuse_listxattr_in { __u32 size; __u32 flags; }; struct fuse_listxattr_out { __u32 size; __u32 flags; }; struct fuse_getxattr_in { __u32 size; __u32 padding; }; struct fuse_getxattr_out { __u32 size; __u32 padding; }; struct fuse_setxattr_in { __u32 size; __u32 flags; }; struct fuse_lk_in { __u64 fh; __u64 owner; struct fuse_file_lock lk; }; struct fuse_lk_out { struct fuse_file_lock lk; }; struct fuse_access_in { __u32 mask; __u32 padding; }; struct fuse_init_in { __u32 major; __u32 minor; __u32 max_readahead; __u32 flags; }; struct fuse_init_out { __u32 major; __u32 minor; __u32 max_readahead; __u32 flags; __u32 unused; __u32 max_write; }; struct fuse_interrupt_in { __u64 unique; }; struct fuse_bmap_in { __u64 block; __u32 blocksize; __u32 padding; }; struct fuse_bmap_out { __u64 block; }; struct fuse_in_header { __u32 len; __u32 opcode; __u64 unique; __u64 nodeid; __u32 uid; __u32 gid; __u32 pid; __u32 padding; }; struct fuse_out_header { __u32 len; __s32 error; __u64 unique; }; struct fuse_dirent { __u64 ino; __u64 off; __u32 namelen; __u32 type; char name[0]; }; #define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) #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) Index: projects/fuse2/tests/sys/fs/fuse/Makefile =================================================================== --- projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344785) +++ projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344786) @@ -1,77 +1,83 @@ # $FreeBSD$ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup +ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= setattr ATF_TESTS_CXX+= symlink SRCS.create+= create.cc SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc SRCS.create+= utils.cc SRCS.getattr+= getattr.cc SRCS.getattr+= getmntopts.c SRCS.getattr+= mockfs.cc SRCS.getattr+= utils.cc SRCS.lookup+= getmntopts.c SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc + +SRCS.mknod+= getmntopts.c +SRCS.mknod+= mockfs.cc +SRCS.mknod+= mknod.cc +SRCS.mknod+= utils.cc SRCS.open+= getmntopts.c SRCS.open+= mockfs.cc SRCS.open+= open.cc SRCS.open+= utils.cc SRCS.readlink+= getmntopts.c SRCS.readlink+= mockfs.cc SRCS.readlink+= readlink.cc SRCS.readlink+= utils.cc SRCS.setattr+= getmntopts.c SRCS.setattr+= mockfs.cc SRCS.setattr+= setattr.cc SRCS.setattr+= utils.cc SRCS.symlink+= getmntopts.c SRCS.symlink+= mockfs.cc SRCS.symlink+= symlink.cc SRCS.symlink+= utils.cc # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 FUSEFS= ${.CURDIR:H:H:H:H}/sys/fs/fuse MOUNT= ${.CURDIR:H:H:H:H}/sbin/mount CFLAGS+= -I${.CURDIR:H:H:H} CFLAGS+= -I${FUSEFS} CFLAGS+= -I${MOUNT} .PATH: ${MOUNT} LIBADD+= pthread WARNS?= 6 NO_WTHREAD_SAFETY= # GoogleTest fails Clang's thread safety check # Use googlemock from ports until after the import-googletest-1.8.1 branch # merges to head. CXXFLAGS+= -I/usr/local/include CXXFLAGS+= -DGTEST_HAS_POSIX_RE=1 CXXFLAGS+= -DGTEST_HAS_PTHREAD=1 CXXFLAGS+= -DGTEST_HAS_STREAM_REDIRECTION=1 CXXFLAGS+= -frtti CXXFLAGS+= -std=c++14 LDADD+= ${LOCALBASE}/lib/libgmock.a LDADD+= ${LOCALBASE}/lib/libgtest.a # Without -lpthread, gtest fails at _runtime_ with the error pthread_key_create(&key, &DeleteThreadLocalValue)failed with error 78 LIBADD+= pthread .include Index: projects/fuse2/tests/sys/fs/fuse/create.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/create.cc (revision 344785) +++ projects/fuse2/tests/sys/fs/fuse/create.cc (revision 344786) @@ -1,383 +1,470 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Create: public FuseTest {}; /* * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the * attribute cache */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ TEST_F(Create, DISABLED_attr_cache) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_LOOKUP && strcmp(in->body.lookup, RELPATH) == 0); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = -ENOENT; out->header.len = sizeof(out->header); })); 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 && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).Times(0); 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 */ } /* * The fuse daemon fails the request with EEXIST. This usually indicates a * race condition: some other FUSE client created the file in between when the * kernel checked for it with lookup and tried to create it with create */ TEST_F(Create, eexist) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_LOOKUP && strcmp(in->body.lookup, RELPATH) == 0); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = -ENOENT; out->header.len = sizeof(out->header); })); 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 && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = -EEXIST; out->header.len = sizeof(out->header); })); EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); EXPECT_EQ(EEXIST, errno); } -// TODO: enosys: kernel should fall back to mknod/open +/* + * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback + * to FUSE_MKNOD/FUSE_OPEN + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Create, DISABLED_Enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + int fd; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + 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 && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOSYS; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + 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 == (S_IFREG | mode) && + in->body.mknod.rdev == 0 && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = S_IFREG | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + 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 */ +} /* * Creating a new file after FUSE_LOOKUP returned a negative cache entry */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Create, DISABLED_entry_cache_negative) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; /* create will first do a LOOKUP, adding a negative cache entry */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_LOOKUP && strcmp(in->body.lookup, RELPATH) == 0); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; out->header.error = 0; /* * Set entry_valid = 0 because this test isn't concerned with * whether or not we actually cache negative entries, only with * whether we interpret negative cache responses correctly. */ out->body.entry.entry_valid = 0; SET_OUT_HEADER_LEN(out, entry); })); 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 && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; })); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * Creating a new file should purge any negative namecache entries */ /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ TEST_F(Create, DISABLED_entry_cache_negative_purge) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; /* create will first do a LOOKUP, adding a negative cache entry */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_LOOKUP && strcmp(in->body.lookup, RELPATH) == 0); }, Eq(true)), _) ).Times(1) .WillOnce(Invoke([=](auto in, auto out) { /* nodeid means ENOENT and cache it */ out->body.entry.nodeid = 0; out->header.unique = in->header.unique; out->header.error = 0; out->body.entry.entry_valid = UINT64_MAX; SET_OUT_HEADER_LEN(out, entry); })).RetiresOnSaturation(); /* Then the CREATE should purge the negative cache entry */ 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 && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.attr_valid = UINT64_MAX; })); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; })); fd = open(FULLPATH, O_CREAT | O_EXCL, mode); ASSERT_LE(0, fd) << strerror(errno); /* Finally, a subsequent lookup should query the daemon */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_LOOKUP && strcmp(in->body.lookup, RELPATH) == 0); }, Eq(true)), _) ).Times(1) .WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = 0; out->body.entry.nodeid = ino; out->body.entry.attr.mode = S_IFREG | mode; SET_OUT_HEADER_LEN(out, entry); })); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* * The daemon is responsible for checking file permissions (unless the * default_permissions mount option was used) */ TEST_F(Create, eperm) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_LOOKUP && strcmp(in->body.lookup, RELPATH) == 0); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = -ENOENT; out->header.len = sizeof(out->header); })); 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 && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = -EPERM; out->header.len = sizeof(out->header); })); EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); EXPECT_EQ(EPERM, errno); } TEST_F(Create, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; int fd; EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_LOOKUP && strcmp(in->body.lookup, RELPATH) == 0); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; out->header.error = -ENOENT; out->header.len = sizeof(out->header); })); 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 && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) ).WillOnce(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, create); out->body.create.entry.attr.mode = S_IFREG | mode; out->body.create.entry.nodeid = ino; out->body.create.entry.entry_valid = UINT64_MAX; out->body.create.entry.attr_valid = UINT64_MAX; })); /* Until the attr cache is working, we may send an additional GETATTR */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillRepeatedly(Invoke([=](auto in, auto out) { out->header.unique = in->header.unique; SET_OUT_HEADER_LEN(out, attr); out->body.attr.attr.ino = ino; // Must match nodeid out->body.attr.attr.mode = S_IFREG | 0644; })); 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 */ } Index: projects/fuse2/tests/sys/fs/fuse/mknod.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mknod.cc (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/mknod.cc (revision 344786) @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Mknod: public FuseTest { + +public: + +virtual void SetUp() { + if (geteuid() != 0) { + // TODO: With GoogleTest 1.8.2, use SKIP instead + FAIL() << "Only root may use most mknod(2) variations"; + } + FuseTest::SetUp(); +} + +/* Test an OK creation of a file with the given mode and device number */ +void test_ok(mode_t mode, dev_t dev) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + 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 == dev && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + 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; + out->body.create.entry.attr.rdev = dev; + })); + EXPECT_EQ(0, mknod(FULLPATH, mode, dev)) << strerror(errno); +} + +}; + +/* + * mknod(2) should be able to create block devices on a FUSE filesystem. Even + * though FreeBSD doesn't use block devices, this is useful when copying media + * from or preparing media for other operating systems. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_blk) +{ + test_ok(S_IFBLK | 0755, 0xfe00); /* /dev/vda's device number on Linux */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_chr) +{ + test_ok(S_IFCHR | 0755, 0x64); /* /dev/fuse's device number */ +} + +/* + * The daemon is responsible for checking file permissions (unless the + * default_permissions mount option was used) + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = S_IFIFO | 0755; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + 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 && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -EPERM; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, mknod(FULLPATH, mode, 0)); + EXPECT_EQ(EPERM, errno); +} + + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_fifo) +{ + test_ok(S_IFIFO | 0755, 0); +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_whiteout) +{ + test_ok(S_IFWHT | 0755, 0); +} Property changes on: projects/fuse2/tests/sys/fs/fuse/mknod.cc ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: projects/fuse2/tests/sys/fs/fuse/mockfs.hh =================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 344785) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 344786) @@ -1,146 +1,147 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include "fuse_kernel.h" } #include #define SET_OUT_HEADER_LEN(out, variant) { \ (out)->header.len = (sizeof((out)->header) + \ sizeof((out)->body.variant)); \ } extern int verbosity; /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */ struct fuse_create_out { struct fuse_entry_out entry; struct fuse_open_out open; }; union fuse_payloads_in { /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; fuse_forget_in forget; fuse_init_in init; char lookup[0]; + fuse_mknod_in mknod; fuse_open_in open; fuse_setattr_in setattr; }; struct mockfs_buf_in { fuse_in_header header; union fuse_payloads_in body; }; union fuse_payloads_out { fuse_attr_out attr; fuse_create_out create; fuse_entry_out entry; fuse_init_out init; fuse_open_out open; /* * The protocol places no limits on the length of the string. This is * merely convenient for testing. */ char str[80]; }; struct mockfs_buf_out { fuse_out_header header; union fuse_payloads_out body; }; /* * Fake FUSE filesystem * * "Mounts" a filesystem to a temporary directory and services requests * according to the programmed expectations. * * Operates directly on the fuse(4) kernel API, not the libfuse(3) user api. */ class MockFS { public: /* thread id of the fuse daemon thread */ pthread_t m_daemon_id; private: /* file descriptor of /dev/fuse control device */ int m_fuse_fd; /* pid of the test process */ pid_t m_pid; /* * Thread that's running the mockfs daemon. * * It must run in a separate thread so it doesn't deadlock with the * client test code. */ pthread_t m_thr; /* Initialize a session after mounting */ void init(); /* Default request handler */ void process_default(const mockfs_buf_in*, mockfs_buf_out*); /* Entry point for the daemon thread */ static void* service(void*); /* Read, but do not process, a single request from the kernel */ void read_request(mockfs_buf_in*); public: /* Create a new mockfs and mount it to a tempdir */ MockFS(); virtual ~MockFS(); /* Process FUSE requests endlessly */ void loop(); /* * Request handler * * This method is expected to provide the response to each FUSE * operation. Responses must be immediate (so this method can't be used * for testing a daemon with queue depth > 1). Test cases must define * each response using Googlemock expectations */ MOCK_METHOD2(process, void(const mockfs_buf_in*, mockfs_buf_out*)); /* Gracefully unmount */ void unmount(); }; Index: projects/fuse2/tests/sys/fs/fuse/utils.hh =================================================================== --- projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 344785) +++ projects/fuse2/tests/sys/fs/fuse/utils.hh (revision 344786) @@ -1,47 +1,47 @@ /*- * Copyright (c) 2019 The FreeBSD Foundation * All rights reserved. * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ class FuseTest : public ::testing::Test { protected: MockFS *m_mock = NULL; public: - void SetUp() { + virtual void SetUp() { try { m_mock = new MockFS{}; } catch (std::system_error err) { FAIL() << err.what(); } } void TearDown() { if (m_mock) delete m_mock; } };