1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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_destroy(0); 95 96 m_mock->unmount(); 97 } 98 99 INSTANTIATE_TEST_SUITE_P(PM, DevFusePoll, 100 ::testing::Values("BLOCKING", "KQ", "POLL", "SELECT")); 101 102 static void* statter(void* arg) { 103 const char *name; 104 struct stat sb; 105 106 name = (const char*)arg; 107 return ((void*)(intptr_t)stat(name, &sb)); 108 } 109 110 /* 111 * A kevent's data field should contain the number of operations available to 112 * be immediately read. 113 */ 114 TEST_F(Kqueue, data) 115 { 116 pthread_t th0, th1, th2; 117 sem_t sem0, sem1; 118 int nready0, nready1, nready2; 119 uint64_t foo_ino = 42; 120 uint64_t bar_ino = 43; 121 uint64_t baz_ino = 44; 122 Sequence seq; 123 void *th_ret; 124 125 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 126 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 127 128 EXPECT_LOOKUP(FUSE_ROOT_ID, "foo") 129 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 130 SET_OUT_HEADER_LEN(out, entry); 131 out.body.entry.entry_valid = UINT64_MAX; 132 out.body.entry.attr.mode = S_IFREG | 0644; 133 out.body.entry.nodeid = foo_ino; 134 }))); 135 EXPECT_LOOKUP(FUSE_ROOT_ID, "bar") 136 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 137 SET_OUT_HEADER_LEN(out, entry); 138 out.body.entry.entry_valid = UINT64_MAX; 139 out.body.entry.attr.mode = S_IFREG | 0644; 140 out.body.entry.nodeid = bar_ino; 141 }))); 142 EXPECT_LOOKUP(FUSE_ROOT_ID, "baz") 143 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 144 SET_OUT_HEADER_LEN(out, entry); 145 out.body.entry.entry_valid = UINT64_MAX; 146 out.body.entry.attr.mode = S_IFREG | 0644; 147 out.body.entry.nodeid = baz_ino; 148 }))); 149 150 EXPECT_CALL(*m_mock, process( 151 ResultOf([=](auto in) { 152 return (in.header.opcode == FUSE_GETATTR && 153 in.header.nodeid == foo_ino); 154 }, Eq(true)), 155 _) 156 ) 157 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 158 nready0 = m_mock->m_nready; 159 160 sem_post(&sem0); 161 // Block the daemon so we can accumulate a few more ops 162 sem_wait(&sem1); 163 164 out.header.unique = in.header.unique; 165 out.header.error = -EIO; 166 out.header.len = sizeof(out.header); 167 }))); 168 169 EXPECT_CALL(*m_mock, process( 170 ResultOf([=](auto in) { 171 return (in.header.opcode == FUSE_GETATTR && 172 (in.header.nodeid == bar_ino || 173 in.header.nodeid == baz_ino)); 174 }, Eq(true)), 175 _) 176 ).InSequence(seq) 177 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 178 nready1 = m_mock->m_nready; 179 out.header.unique = in.header.unique; 180 out.header.error = -EIO; 181 out.header.len = sizeof(out.header); 182 }))); 183 EXPECT_CALL(*m_mock, process( 184 ResultOf([=](auto in) { 185 return (in.header.opcode == FUSE_GETATTR && 186 (in.header.nodeid == bar_ino || 187 in.header.nodeid == baz_ino)); 188 }, Eq(true)), 189 _) 190 ).InSequence(seq) 191 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 192 nready2 = m_mock->m_nready; 193 out.header.unique = in.header.unique; 194 out.header.error = -EIO; 195 out.header.len = sizeof(out.header); 196 }))); 197 198 /* 199 * Create cached lookup entries for these files. It seems that only 200 * one thread at a time can be in VOP_LOOKUP for a given directory 201 */ 202 access("mountpoint/foo", F_OK); 203 access("mountpoint/bar", F_OK); 204 access("mountpoint/baz", F_OK); 205 ASSERT_EQ(0, pthread_create(&th0, NULL, statter, 206 __DECONST(void*, "mountpoint/foo"))) << strerror(errno); 207 EXPECT_EQ(0, sem_wait(&sem0)) << strerror(errno); 208 ASSERT_EQ(0, pthread_create(&th1, NULL, statter, 209 __DECONST(void*, "mountpoint/bar"))) << strerror(errno); 210 ASSERT_EQ(0, pthread_create(&th2, NULL, statter, 211 __DECONST(void*, "mountpoint/baz"))) << strerror(errno); 212 213 nap(); // Allow th1 and th2 to send their ops to the daemon 214 EXPECT_EQ(0, sem_post(&sem1)) << strerror(errno); 215 216 pthread_join(th0, &th_ret); 217 ASSERT_EQ(-1, (intptr_t)th_ret); 218 pthread_join(th1, &th_ret); 219 ASSERT_EQ(-1, (intptr_t)th_ret); 220 pthread_join(th2, &th_ret); 221 ASSERT_EQ(-1, (intptr_t)th_ret); 222 223 EXPECT_EQ(1, nready0); 224 EXPECT_EQ(2, nready1); 225 EXPECT_EQ(1, nready2); 226 227 sem_destroy(&sem0); 228 sem_destroy(&sem1); 229 } 230