From owner-svn-src-projects@freebsd.org Wed Mar 6 00:38:13 2019 Return-Path: Delivered-To: svn-src-projects@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 4A665151D544 for ; Wed, 6 Mar 2019 00:38:13 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id EA97868672; Wed, 6 Mar 2019 00:38:12 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id DF80423D5D; Wed, 6 Mar 2019 00:38:12 +0000 (UTC) (envelope-from asomers@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x260cCF5040664; Wed, 6 Mar 2019 00:38:12 GMT (envelope-from asomers@FreeBSD.org) Received: (from asomers@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x260cAUU040654; Wed, 6 Mar 2019 00:38:10 GMT (envelope-from asomers@FreeBSD.org) Message-Id: <201903060038.x260cAUU040654@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: asomers set sender to asomers@FreeBSD.org using -f From: Alan Somers Date: Wed, 6 Mar 2019 00:38:10 +0000 (UTC) To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r344831 - projects/fuse2/tests/sys/fs/fuse X-SVN-Group: projects X-SVN-Commit-Author: asomers X-SVN-Commit-Paths: projects/fuse2/tests/sys/fs/fuse X-SVN-Commit-Revision: 344831 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: EA97868672 X-Spamd-Bar: -- Authentication-Results: mx1.freebsd.org X-Spamd-Result: default: False [-2.98 / 15.00]; local_wl_from(0.00)[FreeBSD.org]; NEURAL_HAM_MEDIUM(-1.00)[-1.000,0]; NEURAL_HAM_LONG(-1.00)[-1.000,0]; NEURAL_HAM_SHORT(-0.98)[-0.981,0]; ASN(0.00)[asn:11403, ipnet:2610:1c1:1::/48, country:US] X-BeenThere: svn-src-projects@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "SVN commit messages for the src " projects" tree" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 06 Mar 2019 00:38:13 -0000 Author: asomers Date: Wed Mar 6 00:38:10 2019 New Revision: 344831 URL: https://svnweb.freebsd.org/changeset/base/344831 Log: fuse(4): add tests for unlink, rmdir, and statfs Also, combine some common code for sending cacheable negative lookup responses. Sponsored by: The FreeBSD Foundation Added: projects/fuse2/tests/sys/fs/fuse/rmdir.cc (contents, props changed) projects/fuse2/tests/sys/fs/fuse/statfs.cc (contents, props changed) projects/fuse2/tests/sys/fs/fuse/unlink.cc (contents, props changed) Modified: projects/fuse2/tests/sys/fs/fuse/Makefile projects/fuse2/tests/sys/fs/fuse/create.cc projects/fuse2/tests/sys/fs/fuse/lookup.cc projects/fuse2/tests/sys/fs/fuse/mkdir.cc projects/fuse2/tests/sys/fs/fuse/mockfs.cc projects/fuse2/tests/sys/fs/fuse/mockfs.hh projects/fuse2/tests/sys/fs/fuse/rename.cc Modified: projects/fuse2/tests/sys/fs/fuse/Makefile ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/Makefile Wed Mar 6 00:01:06 2019 (r344830) +++ projects/fuse2/tests/sys/fs/fuse/Makefile Wed Mar 6 00:38:10 2019 (r344831) @@ -14,8 +14,11 @@ ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= rename +ATF_TESTS_CXX+= rmdir ATF_TESTS_CXX+= setattr +ATF_TESTS_CXX+= statfs ATF_TESTS_CXX+= symlink +ATF_TESTS_CXX+= unlink SRCS.access+= access.cc SRCS.access+= getmntopts.c @@ -67,15 +70,30 @@ SRCS.rename+= mockfs.cc SRCS.rename+= rename.cc SRCS.rename+= utils.cc +SRCS.rmdir+= getmntopts.c +SRCS.rmdir+= mockfs.cc +SRCS.rmdir+= rmdir.cc +SRCS.rmdir+= utils.cc + SRCS.setattr+= getmntopts.c SRCS.setattr+= mockfs.cc SRCS.setattr+= setattr.cc SRCS.setattr+= utils.cc +SRCS.statfs+= getmntopts.c +SRCS.statfs+= mockfs.cc +SRCS.statfs+= statfs.cc +SRCS.statfs+= utils.cc + SRCS.symlink+= getmntopts.c SRCS.symlink+= mockfs.cc SRCS.symlink+= symlink.cc SRCS.symlink+= utils.cc + +SRCS.unlink+= getmntopts.c +SRCS.unlink+= mockfs.cc +SRCS.unlink+= unlink.cc +SRCS.unlink+= utils.cc # TODO: drastically increase timeout after test development is mostly complete TEST_METADATA+= timeout=10 Modified: projects/fuse2/tests/sys/fs/fuse/create.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/create.cc Wed Mar 6 00:01:06 2019 (r344830) +++ projects/fuse2/tests/sys/fs/fuse/create.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -195,21 +195,15 @@ TEST_F(Create, DISABLED_entry_cache_negative) mode_t mode = 0755; uint64_t ino = 42; int fd; + /* + * 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. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; /* create will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(1, RELPATH).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_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -258,17 +252,12 @@ TEST_F(Create, DISABLED_entry_cache_negative_purge) mode_t mode = 0755; uint64_t ino = 42; int fd; + struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; /* create will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).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(); + .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) + .RetiresOnSaturation(); /* Then the CREATE should purge the negative cache entry */ EXPECT_CALL(*m_mock, process( Modified: projects/fuse2/tests/sys/fs/fuse/lookup.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/lookup.cc Wed Mar 6 00:01:06 2019 (r344830) +++ projects/fuse2/tests/sys/fs/fuse/lookup.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -181,14 +181,11 @@ TEST_F(Lookup, entry_cache) /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */ TEST_F(Lookup, DISABLED_entry_cache_negative) { + struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; + EXPECT_LOOKUP(1, "does_not_exist").Times(1) - .WillOnce(Invoke([](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = 0; - out->body.entry.entry_valid = UINT64_MAX; - SET_OUT_HEADER_LEN(out, entry); - })); + .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))); + EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); EXPECT_EQ(ENOENT, errno); EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); @@ -204,20 +201,15 @@ TEST_F(Lookup, entry_cache_negative_timeout) * The timeout should be longer than the longest plausible time the * daemon would take to complete a write(2) to /dev/fuse, but no longer. */ - long timeout_ns = 250'000'000; + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 250'000'000}; EXPECT_LOOKUP(1, RELPATH).Times(2) - .WillRepeatedly(Invoke([=](auto in, auto out) { - out->header.unique = in->header.unique; - out->header.error = 0; - out->body.entry.nodeid = 0; - out->body.entry.entry_valid_nsec = timeout_ns; - SET_OUT_HEADER_LEN(out, entry); - })); + .WillRepeatedly(Invoke(ReturnNegativeCache(&entry_valid))); + EXPECT_NE(0, access(FULLPATH, F_OK)); EXPECT_EQ(ENOENT, errno); - usleep(2 * timeout_ns / 1000); + usleep(2 * entry_valid.tv_nsec / 1000); /* The cache has timed out; VOP_LOOKUP should requery the daemon*/ EXPECT_NE(0, access(FULLPATH, F_OK)); Modified: projects/fuse2/tests/sys/fs/fuse/mkdir.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mkdir.cc Wed Mar 6 00:01:06 2019 (r344830) +++ projects/fuse2/tests/sys/fs/fuse/mkdir.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -75,21 +75,15 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; + /* + * 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. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; /* mkdir will first do a LOOKUP, adding a negative cache entry */ - EXPECT_LOOKUP(1, RELPATH).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_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { @@ -122,17 +116,12 @@ TEST_F(Mkdir, DISABLED_entry_cache_negative_purge) const char RELPATH[] = "some_file.txt"; mode_t mode = 0755; uint64_t ino = 42; + struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; /* mkdir will first do a LOOKUP, adding a negative cache entry */ EXPECT_LOOKUP(1, RELPATH).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(); + .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) + .RetiresOnSaturation(); /* Then the MKDIR should purge the negative cache entry */ EXPECT_CALL(*m_mock, process( Modified: projects/fuse2/tests/sys/fs/fuse/mockfs.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.cc Wed Mar 6 00:01:06 2019 (r344830) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -112,6 +112,21 @@ ReturnErrno(int error) }); } +/* Helper function used for returning negative cache entries for LOOKUP */ +std::function +ReturnNegativeCache(const struct timespec *entry_valid) +{ + return([=](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 = entry_valid->tv_sec; + out->body.entry.entry_valid_nsec = entry_valid->tv_nsec; + SET_OUT_HEADER_LEN(out, entry); + }); +} + void sigint_handler(int __unused sig) { quit = 1; } @@ -135,6 +150,9 @@ MockFS::MockFS() { int iovlen = 0; char fdstr[15]; + m_daemon_id = NULL; + quit = 0; + /* * Kyua sets pwd to a testcase-unique tempdir; no need to use * mkdtemp @@ -169,18 +187,14 @@ MockFS::MockFS() { .WillByDefault(Invoke(this, &MockFS::process_default)); init(); - if (pthread_create(&m_thr, NULL, service, (void*)this)) + signal(SIGUSR1, sigint_handler); + if (pthread_create(&m_daemon_id, NULL, service, (void*)this)) throw(std::system_error(errno, std::system_category(), "Couldn't Couldn't start fuse thread")); } MockFS::~MockFS() { - pthread_kill(m_daemon_id, SIGUSR1); - // Closing the /dev/fuse file descriptor first allows unmount to - // succeed even if the daemon doesn't correctly respond to commands - // during the unmount sequence. - close(m_fuse_fd); - pthread_join(m_daemon_id, NULL); + kill_daemon(); ::unmount("mountpoint", MNT_FORCE); rmdir("mountpoint"); } @@ -206,6 +220,18 @@ void MockFS::init() { free(in); } +void MockFS::kill_daemon() { + if (m_daemon_id != NULL) { + pthread_kill(m_daemon_id, SIGUSR1); + // Closing the /dev/fuse file descriptor first allows unmount + // to succeed even if the daemon doesn't correctly respond to + // commands during the unmount sequence. + close(m_fuse_fd); + pthread_join(m_daemon_id, NULL); + m_daemon_id = NULL; + } +} + void MockFS::loop() { mockfs_buf_in *in; mockfs_buf_out out; @@ -258,10 +284,6 @@ void MockFS::read_request(mockfs_buf_in *in) { void* MockFS::service(void *pthr_data) { MockFS *mock_fs = (MockFS*)pthr_data; - mock_fs->m_daemon_id = pthread_self(); - - quit = 0; - signal(SIGUSR1, sigint_handler); mock_fs->loop(); Modified: projects/fuse2/tests/sys/fs/fuse/mockfs.hh ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/mockfs.hh Wed Mar 6 00:01:06 2019 (r344830) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh Wed Mar 6 00:38:10 2019 (r344831) @@ -37,6 +37,8 @@ extern "C" { #include +#define TIME_T_MAX (std::numeric_limits::max()) + #define SET_OUT_HEADER_LEN(out, variant) { \ (out)->header.len = (sizeof((out)->header) + \ sizeof((out)->body.variant)); \ @@ -79,7 +81,9 @@ union fuse_payloads_in { fuse_mknod_in mknod; fuse_open_in open; fuse_rename_in rename; + char rmdir[0]; fuse_setattr_in setattr; + char unlink[0]; }; struct mockfs_buf_in { @@ -93,6 +97,7 @@ union fuse_payloads_out { fuse_entry_out entry; fuse_init_out init; fuse_open_out open; + fuse_statfs_out statfs; /* * The protocol places no limits on the length of the string. This is * merely convenient for testing. @@ -112,6 +117,10 @@ struct mockfs_buf_out { std::function ReturnErrno(int error); +/* Helper function used for returning negative cache entries for LOOKUP */ +std::function +ReturnNegativeCache(const struct timespec *entry_valid); + /* * Fake FUSE filesystem * @@ -122,7 +131,12 @@ ReturnErrno(int error); */ class MockFS { public: - /* thread id of the fuse daemon thread */ + /* + * thread id of the fuse daemon thread + * + * It must run in a separate thread so it doesn't deadlock with the + * client test code. + */ pthread_t m_daemon_id; private: @@ -132,14 +146,6 @@ class MockFS { /* 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(); @@ -156,6 +162,9 @@ class MockFS { /* Create a new mockfs and mount it to a tempdir */ MockFS(); virtual ~MockFS(); + + /* Kill the filesystem daemon without unmounting the filesystem */ + void kill_daemon(); /* Process FUSE requests endlessly */ void loop(); Modified: projects/fuse2/tests/sys/fs/fuse/rename.cc ============================================================================== --- projects/fuse2/tests/sys/fs/fuse/rename.cc Wed Mar 6 00:01:06 2019 (r344830) +++ projects/fuse2/tests/sys/fs/fuse/rename.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -87,6 +87,111 @@ TEST_F(Rename, enoent) ASSERT_EQ(ENOENT, errno); } +/* + * Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Rename, DISABLED_entry_cache_negative) +{ + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + // FUSE hardcodes the mountpoint to inocde 1 + uint64_t dst_dir_ino = 1; + uint64_t ino = 42; + /* + * 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. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + /* LOOKUP returns a negative cache entry for dst */ + EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *src = (const char*)in->body.bytes + + sizeof(fuse_rename_in); + const char *dst = src + strlen(src) + 1; + return (in->header.opcode == FUSE_RENAME && + in->body.rename.newdir == dst_dir_ino && + (0 == strcmp(RELDST, dst)) && + (0 == strcmp(RELSRC, src))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); +} + +/* + * Renaming a file should purge any negative namecache entries for the dst + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */ +TEST_F(Rename, DISABLED_entry_cache_negative_purge) +{ + const char FULLDST[] = "mountpoint/dst"; + const char RELDST[] = "dst"; + const char FULLSRC[] = "mountpoint/src"; + const char RELSRC[] = "src"; + // FUSE hardcodes the mountpoint to inocde 1 + uint64_t dst_dir_ino = 1; + uint64_t ino = 42; + /* + * 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. + */ + struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; + + EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + SET_OUT_HEADER_LEN(out, entry); + })); + + /* LOOKUP returns a negative cache entry for dst */ + EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid)) + .RetiresOnSaturation(); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *src = (const char*)in->body.bytes + + sizeof(fuse_rename_in); + const char *dst = src + strlen(src) + 1; + return (in->header.opcode == FUSE_RENAME && + in->body.rename.newdir == dst_dir_ino && + (0 == strcmp(RELDST, dst)) && + (0 == strcmp(RELSRC, src))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); + + /* Finally, a subsequent lookup should query the daemon */ + EXPECT_LOOKUP(1, RELDST).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 | 0644; + SET_OUT_HEADER_LEN(out, entry); + })); + + ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno); +} + TEST_F(Rename, exdev) { const char FULLB[] = "mountpoint/src"; Added: projects/fuse2/tests/sys/fs/fuse/rmdir.cc ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ projects/fuse2/tests/sys/fs/fuse/rmdir.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -0,0 +1,90 @@ +/*- + * 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 Rmdir: public FuseTest {}; + +TEST_F(Rmdir, enotempty) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 2; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RMDIR && + 0 == strcmp(RELPATH, in->body.rmdir) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(ENOTEMPTY))); + + ASSERT_NE(0, rmdir(FULLPATH)); + ASSERT_EQ(ENOTEMPTY, errno); +} + +TEST_F(Rmdir, ok) +{ + const char FULLPATH[] = "mountpoint/some_dir"; + const char RELPATH[] = "some_dir"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFDIR | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 2; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_RMDIR && + 0 == strcmp(RELPATH, in->body.rmdir) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno); +} Added: projects/fuse2/tests/sys/fs/fuse/statfs.cc ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ projects/fuse2/tests/sys/fs/fuse/statfs.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -0,0 +1,118 @@ +/*- + * 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 "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Statfs: public FuseTest {}; + +TEST_F(Statfs, eio) +{ + struct statfs statbuf; + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EIO))); + + ASSERT_NE(0, statfs("mountpoint", &statbuf)); + ASSERT_EQ(EIO, errno); +} + +/* + * When the daemon is dead but the filesystem is still mounted, fuse(4) fakes + * the statfs(2) response, which is necessary for unmounting. + */ +TEST_F(Statfs, enotconn) +{ + struct statfs statbuf; + char mp[PATH_MAX]; + + m_mock->kill_daemon(); + + ASSERT_NE(NULL, getcwd(mp, PATH_MAX)) << strerror(errno); + strlcat(mp, "/mountpoint", PATH_MAX); + ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); + + EXPECT_EQ(getuid(), statbuf.f_owner); + EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename)); + EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname)); + EXPECT_EQ(0, strcmp(mp, statbuf.f_mntonname)); +} + +TEST_F(Statfs, ok) +{ + struct statfs statbuf; + char mp[PATH_MAX]; + + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in->header.opcode == FUSE_STATFS); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, statfs); + out->body.statfs.st.blocks = 1000; + out->body.statfs.st.bfree = 100; + out->body.statfs.st.bavail = 200; + out->body.statfs.st.files = 5; + out->body.statfs.st.ffree = 6; + out->body.statfs.st.namelen = 128; + out->body.statfs.st.frsize = 1024; + })); + + ASSERT_NE(NULL, getcwd(mp, PATH_MAX)) << strerror(errno); + strlcat(mp, "/mountpoint", PATH_MAX); + ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); + EXPECT_EQ(1024ul, statbuf.f_bsize); + /* + * fuse(4) ignores the filesystem's reported optimal transfer size, and + * chooses a size that works well with the rest of the system instead + */ + EXPECT_EQ(1000ul, statbuf.f_blocks); + EXPECT_EQ(100ul, statbuf.f_bfree); + EXPECT_EQ(200l, statbuf.f_bavail); + EXPECT_EQ(5ul, statbuf.f_files); + EXPECT_EQ(6l, statbuf.f_ffree); + EXPECT_EQ(128u, statbuf.f_namemax); + EXPECT_EQ(getuid(), statbuf.f_owner); + EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename)); + EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname)); + EXPECT_EQ(0, strcmp(mp, statbuf.f_mntonname)); +} Added: projects/fuse2/tests/sys/fs/fuse/unlink.cc ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ projects/fuse2/tests/sys/fs/fuse/unlink.cc Wed Mar 6 00:38:10 2019 (r344831) @@ -0,0 +1,90 @@ +/*- + * 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 Unlink: public FuseTest {}; + +TEST_F(Unlink, eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 1; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_UNLINK && + 0 == strcmp(RELPATH, in->body.unlink) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(EPERM))); + + ASSERT_NE(0, unlink(FULLPATH)); + ASSERT_EQ(EPERM, errno); +} + +TEST_F(Unlink, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.entry.attr.nlink = 1; + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_UNLINK && + 0 == strcmp(RELPATH, in->body.unlink) && + in->header.nodeid == 1); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnErrno(0))); + + ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); +}