1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 The FreeBSD Foundation 5 * 6 * This software was developed by BFF Storage Systems, LLC under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 /* 32 * This file tests different polling methods for the /dev/fuse device 33 */ 34 35 extern "C" { 36 #include <fcntl.h> 37 #include <semaphore.h> 38 #include <unistd.h> 39 } 40 41 #include "mockfs.hh" 42 #include "utils.hh" 43 44 using namespace testing; 45 46 const char FULLPATH[] = "mountpoint/some_file.txt"; 47 const char RELPATH[] = "some_file.txt"; 48 const uint64_t ino = 42; 49 const mode_t access_mode = R_OK; 50 51 /* 52 * Translate a poll method's string representation to the enum value. 53 * Using strings with ::testing::Values gives better output with 54 * --gtest_list_tests 55 */ 56 enum poll_method poll_method_from_string(const char *s) 57 { 58 if (0 == strcmp("BLOCKING", s)) 59 return BLOCKING; 60 else if (0 == strcmp("KQ", s)) 61 return KQ; 62 else if (0 == strcmp("POLL", s)) 63 return POLL; 64 else 65 return SELECT; 66 } 67 68 class DevFusePoll: public FuseTest, public WithParamInterface<const char *> { 69 virtual void SetUp() { 70 m_pm = poll_method_from_string(GetParam()); 71 FuseTest::SetUp(); 72 } 73 }; 74 75 class Kqueue: public FuseTest { 76 virtual void SetUp() { 77 m_pm = KQ; 78 FuseTest::SetUp(); 79 } 80 }; 81 82 TEST_P(DevFusePoll, access) 83 { 84 expect_access(FUSE_ROOT_ID, X_OK, 0); 85 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 86 expect_access(ino, access_mode, 0); 87 88 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 89 } 90 91 /* Ensure that we wake up pollers during unmount */ 92 TEST_P(DevFusePoll, destroy) 93 { 94 expect_forget(FUSE_ROOT_ID, 1); 95 expect_destroy(0); 96 97 m_mock->unmount(); 98 } 99 100 INSTANTIATE_TEST_CASE_P(PM, DevFusePoll, 101 ::testing::Values("BLOCKING", "KQ", "POLL", "SELECT")); 102 103 static void* statter(void* arg) { 104 const char *name; 105 struct stat sb; 106 107 name = (const char*)arg; 108 stat(name, &sb); 109 return 0; 110 } 111 112 /* 113 * A kevent's data field should contain the number of operations available to 114 * be immediately rea. 115 */ 116 TEST_F(Kqueue, data) 117 { 118 pthread_t th0, th1, th2; 119 sem_t sem0, sem1; 120 int nready0, nready1, nready2; 121 uint64_t foo_ino = 42; 122 uint64_t bar_ino = 43; 123 uint64_t baz_ino = 44; 124 Sequence seq; 125 126 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 127 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 128 129 EXPECT_LOOKUP(FUSE_ROOT_ID, "foo") 130 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 131 SET_OUT_HEADER_LEN(out, entry); 132 out.body.entry.entry_valid = UINT64_MAX; 133 out.body.entry.attr.mode = S_IFREG | 0644; 134 out.body.entry.nodeid = foo_ino; 135 }))); 136 EXPECT_LOOKUP(FUSE_ROOT_ID, "bar") 137 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 138 SET_OUT_HEADER_LEN(out, entry); 139 out.body.entry.entry_valid = UINT64_MAX; 140 out.body.entry.attr.mode = S_IFREG | 0644; 141 out.body.entry.nodeid = bar_ino; 142 }))); 143 EXPECT_LOOKUP(FUSE_ROOT_ID, "baz") 144 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 145 SET_OUT_HEADER_LEN(out, entry); 146 out.body.entry.entry_valid = UINT64_MAX; 147 out.body.entry.attr.mode = S_IFREG | 0644; 148 out.body.entry.nodeid = baz_ino; 149 }))); 150 151 EXPECT_CALL(*m_mock, process( 152 ResultOf([=](auto in) { 153 return (in.header.opcode == FUSE_GETATTR && 154 in.header.nodeid == foo_ino); 155 }, Eq(true)), 156 _) 157 ) 158 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 159 nready0 = m_mock->m_nready; 160 161 sem_post(&sem0); 162 // Block the daemon so we can accumulate a few more ops 163 sem_wait(&sem1); 164 165 out.header.unique = in.header.unique; 166 out.header.error = -EIO; 167 out.header.len = sizeof(out.header); 168 }))); 169 170 EXPECT_CALL(*m_mock, process( 171 ResultOf([=](auto in) { 172 return (in.header.opcode == FUSE_GETATTR && 173 (in.header.nodeid == bar_ino || 174 in.header.nodeid == baz_ino)); 175 }, Eq(true)), 176 _) 177 ).InSequence(seq) 178 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 179 nready1 = m_mock->m_nready; 180 out.header.unique = in.header.unique; 181 out.header.error = -EIO; 182 out.header.len = sizeof(out.header); 183 }))); 184 EXPECT_CALL(*m_mock, process( 185 ResultOf([=](auto in) { 186 return (in.header.opcode == FUSE_GETATTR && 187 (in.header.nodeid == bar_ino || 188 in.header.nodeid == baz_ino)); 189 }, Eq(true)), 190 _) 191 ).InSequence(seq) 192 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 193 nready2 = m_mock->m_nready; 194 out.header.unique = in.header.unique; 195 out.header.error = -EIO; 196 out.header.len = sizeof(out.header); 197 }))); 198 199 /* 200 * Create cached lookup entries for these files. It seems that only 201 * one thread at a time can be in VOP_LOOKUP for a given directory 202 */ 203 access("mountpoint/foo", F_OK); 204 access("mountpoint/bar", F_OK); 205 access("mountpoint/baz", F_OK); 206 ASSERT_EQ(0, pthread_create(&th0, NULL, statter, 207 __DECONST(void*, "mountpoint/foo"))) << strerror(errno); 208 EXPECT_EQ(0, sem_wait(&sem0)) << strerror(errno); 209 ASSERT_EQ(0, pthread_create(&th1, NULL, statter, 210 __DECONST(void*, "mountpoint/bar"))) << strerror(errno); 211 ASSERT_EQ(0, pthread_create(&th2, NULL, statter, 212 __DECONST(void*, "mountpoint/baz"))) << strerror(errno); 213 214 nap(); // Allow th1 and th2 to send their ops to the daemon 215 EXPECT_EQ(0, sem_post(&sem1)) << strerror(errno); 216 217 pthread_join(th0, NULL); 218 pthread_join(th1, NULL); 219 pthread_join(th2, NULL); 220 221 EXPECT_EQ(1, nready0); 222 EXPECT_EQ(2, nready1); 223 EXPECT_EQ(1, nready2); 224 225 sem_destroy(&sem0); 226 sem_destroy(&sem1); 227 } 228