Index: etc/mtree/BSD.tests.dist =================================================================== --- etc/mtree/BSD.tests.dist +++ etc/mtree/BSD.tests.dist @@ -713,6 +713,8 @@ file .. fs + fusefs + .. tmpfs .. .. Index: sys/fs/fuse/fuse_kernel.h =================================================================== --- sys/fs/fuse/fuse_kernel.h +++ sys/fs/fuse/fuse_kernel.h @@ -194,6 +194,11 @@ struct fuse_attr attr; }; +struct fuse_mknod_in { + __u32 mode; + __u32 rdev; +}; + struct fuse_mkdir_in { __u32 mode; __u32 padding; Index: tests/sys/fs/Makefile =================================================================== --- tests/sys/fs/Makefile +++ tests/sys/fs/Makefile @@ -7,6 +7,7 @@ TESTSRC= ${SRCTOP}/contrib/netbsd-tests/fs #TESTS_SUBDIRS+= nullfs # XXX: needs rump +TESTS_SUBDIRS+= fusefs TESTS_SUBDIRS+= tmpfs ${PACKAGE}FILES+= h_funcs.subr Index: tests/sys/fs/fusefs/Makefile =================================================================== --- tests/sys/fs/fusefs/Makefile +++ tests/sys/fs/fusefs/Makefile @@ -8,6 +8,7 @@ # Kyua treats googletest programs as plain tests, it's better to separate them # out, so we get more granular reporting. GTESTS+= access +GTESTS+= allow_other GTESTS+= create GTESTS+= default_permissions GTESTS+= destroy @@ -42,6 +43,11 @@ SRCS.access+= mockfs.cc SRCS.access+= utils.cc +SRCS.allow_other+= allow_other.cc +SRCS.allow_other+= getmntopts.c +SRCS.allow_other+= mockfs.cc +SRCS.allow_other+= utils.cc + SRCS.create+= create.cc SRCS.create+= getmntopts.c SRCS.create+= mockfs.cc @@ -194,10 +200,6 @@ CFLAGS+= -I${MOUNT} .PATH: ${MOUNT} CXXSTD= c++14 - -# XXX Setting CXXFLAGS globally seems to be necessary to get mockfs.cc and -# utils.cc to build correctly. -CXXFLAGS+= ${GTESTS_CXXFLAGS} LIBADD+= pthread LIBADD+= gmock gtest Index: tests/sys/fs/fusefs/access.cc =================================================================== --- tests/sys/fs/fusefs/access.cc +++ tests/sys/fs/fusefs/access.cc @@ -40,18 +40,6 @@ class Access: public FuseTest { public: -void expect_access(uint64_t ino, mode_t access_mode, int error) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in->header.opcode == FUSE_ACCESS && - in->header.nodeid == ino && - in->body.access.mask == access_mode); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnErrno(error))); -} - void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); Index: tests/sys/fs/fusefs/allow_other.cc =================================================================== --- /dev/null +++ tests/sys/fs/fusefs/allow_other.cc @@ -35,10 +35,11 @@ extern "C" { #include +#include #include #include #include -#include +#include } #include "mockfs.hh" @@ -101,27 +102,39 @@ uint64_t ino = 42; int fd; pid_t child; + sem_t *sem; + int mprot = PROT_READ | PROT_WRITE; + int mflags = MAP_ANON | MAP_SHARED; - signal(SIGUSR2, sighandler); + sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0); + ASSERT_NE(MAP_FAILED, sem) << strerror(errno); + ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno); if ((child = fork()) == 0) { /* In child */ - pause(); + int err = 0; + ASSERT_EQ(0, sem_wait(sem)) << strerror(errno); + /* Drop privileges before accessing */ if (0 != setreuid(-1, m_uid)) { perror("setreuid"); - _exit(1); + err = 1; + goto out; } fd = open(FULLPATH, O_RDONLY); if (fd < 0) { perror("open"); - _exit(1); + err = 1; } - _exit(0); +out: + sem_destroy(sem); + _exit(err); /* Deliberately leak fd */ } else if (child > 0) { + int child_status; + /* * In parent. Cleanup must happen here, because it's still * privileged. @@ -134,14 +147,14 @@ expect_getattr(ino, 0); m_mock->m_child_pid = child; /* Signal the child process to go */ - kill(child, SIGUSR2); - int child_status; + ASSERT_EQ(0, sem_post(sem)) << strerror(errno); wait(&child_status); ASSERT_EQ(0, WEXITSTATUS(child_status)); } else { FAIL() << strerror(errno); } + munmap(sem, sizeof(*sem)); } TEST_F(NoAllowOther, disallowed) @@ -149,29 +162,39 @@ const char FULLPATH[] = "mountpoint/some_file.txt"; int fd; pid_t child; - - signal(SIGUSR2, sighandler); + sem_t *sem; + int mprot = PROT_READ | PROT_WRITE; + int mflags = MAP_ANON | MAP_SHARED; + sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0); + ASSERT_NE(MAP_FAILED, sem) << strerror(errno); + ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno); + if ((child = fork()) == 0) { /* In child */ - pause(); + int err = 0; + ASSERT_EQ(0, sem_wait(sem)) << strerror(errno); + /* Drop privileges before accessing */ if (0 != setreuid(-1, m_uid)) { perror("setreuid"); - _exit(1); + err = 1; + goto out; } fd = open(FULLPATH, O_RDONLY); if (fd >= 0) { fprintf(stderr, "open should've failed\n"); - _exit(1); + err = 1; } else if (errno != EPERM) { fprintf(stderr, "Unexpected error: %s\n", strerror(errno)); - _exit(1); + err = 1; } - _exit(0); +out: + sem_destroy(sem); + _exit(0); /* Deliberately leak fd */ } else if (child > 0) { /* @@ -180,7 +203,7 @@ */ m_mock->m_child_pid = child; /* Signal the child process to go */ - kill(child, SIGUSR2); + ASSERT_EQ(0, sem_post(sem)) << strerror(errno); int child_status; wait(&child_status); @@ -188,4 +211,5 @@ } else { FAIL() << strerror(errno); } + munmap(sem, sizeof(*sem)); } Index: tests/sys/fs/fusefs/default_permissions.cc =================================================================== --- tests/sys/fs/fusefs/default_permissions.cc +++ tests/sys/fs/fusefs/default_permissions.cc @@ -84,7 +84,8 @@ ASSERT_EQ(EACCES, errno); } -TEST_F(Access, ok) +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236291 */ +TEST_F(Access, DISABLED_ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; @@ -92,10 +93,10 @@ mode_t access_mode = R_OK; expect_lookup(RELPATH, ino, S_IFREG | 0644); + expect_access(ino, access_mode, 0); /* * Once default_permissions is properly implemented, there might be - * another FUSE_GETATTR or something in here. But there should not be - * a FUSE_ACCESS + * another FUSE_GETATTR or something in here. */ ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); Index: tests/sys/fs/fusefs/mockfs.hh =================================================================== --- tests/sys/fs/fusefs/mockfs.hh +++ tests/sys/fs/fusefs/mockfs.hh @@ -208,12 +208,16 @@ void read_request(mockfs_buf_in*); public: + /* pid of child process, for two-process test cases */ + pid_t m_child_pid; + /* Maximum size of a FUSE_WRITE write */ uint32_t m_max_write; /* Create a new mockfs and mount it to a tempdir */ - MockFS(int max_readahead, bool push_symlinks_in, - bool default_permissions, uint32_t flags); + MockFS(int max_readahead, bool allow_other, + bool default_permissions, bool push_symlinks_in, + uint32_t flags); virtual ~MockFS(); /* Kill the filesystem daemon without unmounting the filesystem */ Index: tests/sys/fs/fusefs/mockfs.cc =================================================================== --- tests/sys/fs/fusefs/mockfs.cc +++ tests/sys/fs/fusefs/mockfs.cc @@ -243,12 +243,13 @@ printf("\n"); } -MockFS::MockFS(int max_readahead, bool push_symlinks_in, - bool default_permissions, uint32_t flags) +MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions, + bool push_symlinks_in, uint32_t flags) { struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; + const bool trueval = true; m_daemon_id = NULL; m_maxreadahead = max_readahead; @@ -262,33 +263,36 @@ * googletest doesn't allow ASSERT_ in constructors, so we must throw * instead. */ - if (mkdir("mountpoint" , 0644) && errno != EEXIST) + if (mkdir("mountpoint" , 0755) && errno != EEXIST) throw(std::system_error(errno, std::system_category(), "Couldn't make mountpoint directory")); - m_fuse_fd = open("/dev/fuse", O_RDWR); + m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR); if (m_fuse_fd < 0) throw(std::system_error(errno, std::system_category(), "Couldn't open /dev/fuse")); sprintf(fdstr, "%d", m_fuse_fd); m_pid = getpid(); + m_child_pid = -1; build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, "mountpoint"), -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); build_iovec(&iov, &iovlen, "fd", fdstr, -1); - if (push_symlinks_in) { - const bool trueval = true; - build_iovec(&iov, &iovlen, "push_symlinks_in", + if (allow_other) { + build_iovec(&iov, &iovlen, "allow_other", __DECONST(void*, &trueval), sizeof(bool)); } if (default_permissions) { - const bool trueval = true; build_iovec(&iov, &iovlen, "default_permissions", __DECONST(void*, &trueval), sizeof(bool)); } + if (push_symlinks_in) { + build_iovec(&iov, &iovlen, "push_symlinks_in", + __DECONST(void*, &trueval), sizeof(bool)); + } if (nmount(iov, iovlen, 0)) throw(std::system_error(errno, std::system_category(), "Couldn't mount filesystem")); @@ -396,6 +400,8 @@ bool MockFS::pid_ok(pid_t pid) { if (pid == m_pid) { + return (true); + } else if (pid == m_child_pid) { return (true); } else { struct kinfo_proc *ki; Index: tests/sys/fs/fusefs/utils.hh =================================================================== --- tests/sys/fs/fusefs/utils.hh +++ tests/sys/fs/fusefs/utils.hh @@ -41,6 +41,7 @@ protected: uint32_t m_maxreadahead; uint32_t m_init_flags; + bool m_allow_other; bool m_default_permissions; bool m_push_symlinks_in; MockFS *m_mock = NULL; @@ -56,6 +57,7 @@ */ m_maxreadahead(UINT_MAX), m_init_flags(0), + m_allow_other(false), m_default_permissions(false), m_push_symlinks_in(false) {} @@ -66,6 +68,12 @@ if (m_mock) delete m_mock; } + + /* + * Create an expectation that FUSE_ACCESS will be called oncde for the + * given inode with the given access_mode, returning the given errno + */ + void expect_access(uint64_t ino, mode_t access_mode, int error); /* * Create an expectation that FUSE_GETATTR will be called for the given Index: tests/sys/fs/fusefs/utils.cc =================================================================== --- tests/sys/fs/fusefs/utils.cc +++ tests/sys/fs/fusefs/utils.cc @@ -43,15 +43,10 @@ /* Check that fusefs(4) is accessible and the current user can mount(2) */ void check_environment() { - const char *mod_name = "fusefs"; const char *devnode = "/dev/fuse"; const char *usermount_node = "vfs.usermount"; int usermount_val = 0; size_t usermount_size = sizeof(usermount_val); - if (modfind(mod_name) == -1) { - GTEST_SKIP() << "Module " << mod_name << - " could not be resolved"; - } if (eaccess(devnode, R_OK | W_OK)) { if (errno == ENOENT) { GTEST_SKIP() << devnode << " does not exist"; @@ -91,11 +86,25 @@ m_maxbcachebuf = val; try { - m_mock = new MockFS(m_maxreadahead, m_push_symlinks_in, - m_default_permissions, m_init_flags); + m_mock = new MockFS(m_maxreadahead, m_allow_other, + m_default_permissions, m_push_symlinks_in, + m_init_flags); } catch (std::system_error err) { FAIL() << err.what(); } +} + +void +FuseTest::expect_access(uint64_t ino, mode_t access_mode, int error) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_ACCESS && + in->header.nodeid == ino && + in->body.access.mask == access_mode); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(error))); } void FuseTest::expect_getattr(uint64_t ino, uint64_t size)