Index: projects/fuse2/tests/sys/fs/fuse/Makefile =================================================================== --- projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344784) +++ projects/fuse2/tests/sys/fs/fuse/Makefile (revision 344785) @@ -1,53 +1,77 @@ # $FreeBSD$ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/fs/fuse +ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup +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+= lookup.cc SRCS.lookup+= getmntopts.c +SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc -SRCS.setattr+= setattr.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 (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/create.cc (revision 344785) @@ -0,0 +1,383 @@ +/*- + * 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 + +/* + * 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 */ +} Property changes on: projects/fuse2/tests/sys/fs/fuse/create.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 344784) +++ projects/fuse2/tests/sys/fs/fuse/mockfs.hh (revision 344785) @@ -1,132 +1,146 @@ /*- * 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_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_init_out init; - fuse_entry_out entry; 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/open.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/open.cc (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/open.cc (revision 344785) @@ -0,0 +1,168 @@ +/*- + * 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 Open: public FuseTest {}; + +/* + * The fuse daemon fails the request with enoent. This usually indicates a + * race condition: some other FUSE client removed the file in between when the + * kernel checked for it with lookup and tried to open it + */ +TEST_F(Open, enoent) +{ + 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; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + })); + + 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.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, open(FULLPATH, O_RDONLY)); + EXPECT_EQ(ENOENT, errno); +} + +/* + * The daemon is responsible for checking file permissions (unless the + * default_permissions mount option was used) + */ +TEST_F(Open, eperm) +{ + 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; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + })); + + 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.error = -EPERM; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, open(FULLPATH, O_RDONLY)); + EXPECT_EQ(EPERM, errno); +} + +TEST_F(Open, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + 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; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0644; + out->body.entry.nodeid = ino; + out->body.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_RDONLY); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} Property changes on: projects/fuse2/tests/sys/fs/fuse/open.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/readlink.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/readlink.cc (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/readlink.cc (revision 344785) @@ -0,0 +1,115 @@ +/*- + * 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 Readlink: public FuseTest {}; + +TEST_F(Readlink, eloop) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const uint64_t ino = 42; + char buf[80]; + + 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; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFLNK | 0777; + out->body.entry.nodeid = ino; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READLINK && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ELOOP; + out->header.len = sizeof(out->header); + })); + + + EXPECT_EQ(-1, readlink(FULLPATH, buf, sizeof(buf))); + EXPECT_EQ(ELOOP, errno); +} + +TEST_F(Readlink, ok) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const char dst[] = "dst"; + const uint64_t ino = 42; + char buf[80]; + + 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; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFLNK | 0777; + out->body.entry.nodeid = ino; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READLINK && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + strlcpy(out->body.str, dst, sizeof(out->body.str)); + out->header.len = sizeof(out->header) + strlen(dst) + 1; + })); + + + EXPECT_EQ((ssize_t)strlen(dst) + 1, + readlink(FULLPATH, buf, sizeof(buf))); + EXPECT_STREQ(dst, buf); +} Property changes on: projects/fuse2/tests/sys/fs/fuse/readlink.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/setattr.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/setattr.cc (revision 344784) +++ projects/fuse2/tests/sys/fs/fuse/setattr.cc (revision 344785) @@ -1,386 +1,535 @@ /*- * 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 Setattr : public FuseTest {}; /* Change the mode of a file */ TEST_F(Setattr, 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_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; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | oldmode; out->body.entry.nodeid = ino; out->body.entry.attr.mode = S_IFREG | oldmode; })); 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([](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 | newmode; })); EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); } /* Change the owner and group of a file */ TEST_F(Setattr, chown) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const gid_t oldgroup = 66; const gid_t newgroup = 99; const uid_t olduser = 33; const uid_t newuser = 44; 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; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr.gid = oldgroup; out->body.entry.attr.uid = olduser; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_GID | FATTR_UID; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.uid == newuser && in->body.setattr.gid == newgroup); }, Eq(true)), _) ).WillOnce(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; out->body.attr.attr.uid = newuser; out->body.attr.attr.gid = newgroup; })); EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno); } -/* Change the mode of an open file, by its file descriptor */ -//TODO TEST_F(Setattr, fchmod) {} /* * FUSE daemons are allowed to check permissions however they like. If the * daemon returns EPERM, even if the file permissions "should" grant access, * then fuse(4) should return EPERM too. */ TEST_F(Setattr, eperm) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const 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; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0777; out->body.entry.nodeid = ino; out->body.entry.attr.uid = in->header.uid; out->body.entry.attr.gid = in->header.gid; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino); }, 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, truncate(FULLPATH, 10)); EXPECT_EQ(EPERM, errno); +} + +/* Change the mode of an open file, by its file descriptor */ +TEST_F(Setattr, fchmod) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + const mode_t oldmode = 0755; + const mode_t newmode = 0644; + + 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; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | oldmode; + out->body.entry.nodeid = ino; + out->body.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 | oldmode; + })); + + 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([=](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 | newmode; + })); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* Change the size of an open file, by its file descriptor */ +TEST_F(Setattr, ftruncate) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + uint64_t fh = 0xdeadbeef1a7ebabe; + const off_t oldsize = 99; + const off_t newsize = 12345; + + 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; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFREG | 0755; + out->body.entry.nodeid = ino; + out->body.entry.attr_valid = UINT64_MAX; + out->body.entry.attr.size = oldsize; + })); + + 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); + out->body.open.fh = fh; + })); + + /* 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 | 0755; + out->body.attr.attr.size = oldsize; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + /* In protocol 7.23, ctime will be changed too */ + uint32_t valid = FATTR_SIZE | FATTR_FH; + return (in->header.opcode == FUSE_SETATTR && + in->header.nodeid == ino && + in->body.setattr.valid == valid && + in->body.setattr.fh == fh); + }, Eq(true)), + _) + ).WillOnce(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 | 0755; + out->body.attr.attr.size = newsize; + })); + + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ } /* Change the size of the file */ TEST_F(Setattr, truncate) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const uint64_t oldsize = 100'000'000; const uint64_t newsize = 20'000'000; 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; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr.size = oldsize; })); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_SIZE; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.size == newsize); }, Eq(true)), _) ).WillOnce(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; out->body.attr.attr.size = newsize; })); EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno); } /* Change a file's timestamps */ TEST_F(Setattr, utimensat) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const timespec oldtimes[2] = { {.tv_sec = 1, .tv_nsec = 2}, {.tv_sec = 3, .tv_nsec = 4}, }; const timespec newtimes[2] = { {.tv_sec = 5, .tv_nsec = 6}, {.tv_sec = 7, .tv_nsec = 8}, }; 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; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; out->body.entry.attr.atime = oldtimes[0].tv_sec; out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; out->body.entry.attr.mtime = oldtimes[1].tv_sec; out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; })); /* * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR * call */ EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(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; out->body.attr.attr.atime = oldtimes[0].tv_sec; out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = oldtimes[1].tv_sec; out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_ATIME | FATTR_MTIME; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.atime == newtimes[0].tv_sec && in->body.setattr.atimensec == newtimes[0].tv_nsec && in->body.setattr.mtime == newtimes[1].tv_sec && in->body.setattr.mtimensec == newtimes[1].tv_nsec); }, Eq(true)), _) ).WillOnce(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; out->body.attr.attr.atime = newtimes[0].tv_sec; out->body.attr.attr.atimensec = newtimes[0].tv_nsec; out->body.attr.attr.mtime = newtimes[1].tv_sec; out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; })); EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } /* Change a file mtime but not its atime */ TEST_F(Setattr, utimensat_mtime_only) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const uint64_t ino = 42; const timespec oldtimes[2] = { {.tv_sec = 1, .tv_nsec = 2}, {.tv_sec = 3, .tv_nsec = 4}, }; const timespec newtimes[2] = { {.tv_sec = 5, .tv_nsec = UTIME_OMIT}, {.tv_sec = 7, .tv_nsec = 8}, }; 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; SET_OUT_HEADER_LEN(out, entry); out->body.entry.attr.mode = S_IFREG | 0644; out->body.entry.nodeid = ino; out->body.entry.attr_valid = UINT64_MAX; out->body.entry.attr.atime = oldtimes[0].tv_sec; out->body.entry.attr.atimensec = oldtimes[0].tv_nsec; out->body.entry.attr.mtime = oldtimes[1].tv_sec; out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec; })); /* * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR * call */ EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in->header.opcode == FUSE_GETATTR && in->header.nodeid == ino); }, Eq(true)), _) ).WillOnce(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; out->body.attr.attr.atime = oldtimes[0].tv_sec; out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = oldtimes[1].tv_sec; out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec; })); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { /* In protocol 7.23, ctime will be changed too */ uint32_t valid = FATTR_MTIME; return (in->header.opcode == FUSE_SETATTR && in->header.nodeid == ino && in->body.setattr.valid == valid && in->body.setattr.mtime == newtimes[1].tv_sec && in->body.setattr.mtimensec == newtimes[1].tv_nsec); }, Eq(true)), _) ).WillOnce(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; out->body.attr.attr.atime = oldtimes[0].tv_sec; out->body.attr.attr.atimensec = oldtimes[0].tv_nsec; out->body.attr.attr.mtime = newtimes[1].tv_sec; out->body.attr.attr.mtimensec = newtimes[1].tv_nsec; })); EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) << strerror(errno); } /* * Writethrough cache: newly changed attributes should be automatically cached, * if the filesystem allows it */ //TODO TEST_F(Setattr, writethrough_cache){} Index: projects/fuse2/tests/sys/fs/fuse/symlink.cc =================================================================== --- projects/fuse2/tests/sys/fs/fuse/symlink.cc (nonexistent) +++ projects/fuse2/tests/sys/fs/fuse/symlink.cc (revision 344785) @@ -0,0 +1,116 @@ +/*- + * 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 Symlink: public FuseTest {}; + +TEST_F(Symlink, enospc) +{ + const char FULLPATH[] = "mountpoint/lnk"; + const char RELPATH[] = "lnk"; + const char dst[] = "dst"; + //const 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; + const char *linkname = name + strlen(name) + 1; + return (in->header.opcode == FUSE_SYMLINK && + (0 == strcmp(linkname, dst)) && + (0 == strcmp(name, RELPATH))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOSPC; + out->header.len = sizeof(out->header); + })); + + + EXPECT_EQ(-1, symlink(dst, FULLPATH)); + EXPECT_EQ(ENOSPC, errno); +} + +TEST_F(Symlink, ok) +{ + const char FULLPATH[] = "mountpoint/src"; + const char RELPATH[] = "src"; + const char dst[] = "dst"; + const 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; + const char *linkname = name + strlen(name) + 1; + return (in->header.opcode == FUSE_SYMLINK && + (0 == strcmp(linkname, dst)) && + (0 == strcmp(name, RELPATH))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, entry); + out->body.entry.attr.mode = S_IFLNK | 0777; + out->body.entry.nodeid = ino; + })); + + EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno); +} Property changes on: projects/fuse2/tests/sys/fs/fuse/symlink.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