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 * $FreeBSD$ 31 */ 32 33 /* 34 * This file tests different polling methods for the /dev/fuse device 35 */ 36 37 extern "C" { 38 #include <fcntl.h> 39 #include <semaphore.h> 40 #include <unistd.h> 41 } 42 43 #include "mockfs.hh" 44 #include "utils.hh" 45 46 using namespace testing; 47 48 const char FULLPATH[] = "mountpoint/some_file.txt"; 49 const char RELPATH[] = "some_file.txt"; 50 const uint64_t ino = 42; 51 const mode_t access_mode = R_OK; 52 53 /* 54 * Translate a poll method's string representation to the enum value. 55 * Using strings with ::testing::Values gives better output with 56 * --gtest_list_tests 57 */ 58 enum poll_method poll_method_from_string(const char *s) 59 { 60 if (0 == strcmp("BLOCKING", s)) 61 return BLOCKING; 62 else if (0 == strcmp("KQ", s)) 63 return KQ; 64 else if (0 == strcmp("POLL", s)) 65 return POLL; 66 else 67 return SELECT; 68 } 69 70 class DevFusePoll: public FuseTest, public WithParamInterface<const char *> { 71 virtual void SetUp() { 72 m_pm = poll_method_from_string(GetParam()); 73 FuseTest::SetUp(); 74 } 75 }; 76 77 class Kqueue: public FuseTest { 78 virtual void SetUp() { 79 m_pm = KQ; 80 FuseTest::SetUp(); 81 } 82 }; 83 84 TEST_P(DevFusePoll, access) 85 { 86 expect_access(FUSE_ROOT_ID, X_OK, 0); 87 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 88 expect_access(ino, access_mode, 0); 89 90 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 91 } 92 93 /* Ensure that we wake up pollers during unmount */ 94 TEST_P(DevFusePoll, destroy) 95 { 96 expect_forget(FUSE_ROOT_ID, 1); 97 expect_destroy(0); 98 99 m_mock->unmount(); 100 } 101 102 INSTANTIATE_TEST_CASE_P(PM, DevFusePoll, 103 ::testing::Values("BLOCKING", "KQ", "POLL", "SELECT")); 104 105 static void* statter(void* arg) { 106 const char *name; 107 struct stat sb; 108 109 name = (const char*)arg; 110 return ((void*)(intptr_t)stat(name, &sb)); 111 } 112 113 /* 114 * A kevent's data field should contain the number of operations available to 115 * be immediately read. 116 */ 117 TEST_F(Kqueue, data) 118 { 119 pthread_t th0, th1, th2; 120 sem_t sem0, sem1; 121 int nready0, nready1, nready2; 122 uint64_t foo_ino = 42; 123 uint64_t bar_ino = 43; 124 uint64_t baz_ino = 44; 125 Sequence seq; 126 void *th_ret; 127 128 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 129 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 130 131 EXPECT_LOOKUP(FUSE_ROOT_ID, "foo") 132 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 133 SET_OUT_HEADER_LEN(out, entry); 134 out.body.entry.entry_valid = UINT64_MAX; 135 out.body.entry.attr.mode = S_IFREG | 0644; 136 out.body.entry.nodeid = foo_ino; 137 }))); 138 EXPECT_LOOKUP(FUSE_ROOT_ID, "bar") 139 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 140 SET_OUT_HEADER_LEN(out, entry); 141 out.body.entry.entry_valid = UINT64_MAX; 142 out.body.entry.attr.mode = S_IFREG | 0644; 143 out.body.entry.nodeid = bar_ino; 144 }))); 145 EXPECT_LOOKUP(FUSE_ROOT_ID, "baz") 146 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 147 SET_OUT_HEADER_LEN(out, entry); 148 out.body.entry.entry_valid = UINT64_MAX; 149 out.body.entry.attr.mode = S_IFREG | 0644; 150 out.body.entry.nodeid = baz_ino; 151 }))); 152 153 EXPECT_CALL(*m_mock, process( 154 ResultOf([=](auto in) { 155 return (in.header.opcode == FUSE_GETATTR && 156 in.header.nodeid == foo_ino); 157 }, Eq(true)), 158 _) 159 ) 160 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 161 nready0 = m_mock->m_nready; 162 163 sem_post(&sem0); 164 // Block the daemon so we can accumulate a few more ops 165 sem_wait(&sem1); 166 167 out.header.unique = in.header.unique; 168 out.header.error = -EIO; 169 out.header.len = sizeof(out.header); 170 }))); 171 172 EXPECT_CALL(*m_mock, process( 173 ResultOf([=](auto in) { 174 return (in.header.opcode == FUSE_GETATTR && 175 (in.header.nodeid == bar_ino || 176 in.header.nodeid == baz_ino)); 177 }, Eq(true)), 178 _) 179 ).InSequence(seq) 180 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 181 nready1 = m_mock->m_nready; 182 out.header.unique = in.header.unique; 183 out.header.error = -EIO; 184 out.header.len = sizeof(out.header); 185 }))); 186 EXPECT_CALL(*m_mock, process( 187 ResultOf([=](auto in) { 188 return (in.header.opcode == FUSE_GETATTR && 189 (in.header.nodeid == bar_ino || 190 in.header.nodeid == baz_ino)); 191 }, Eq(true)), 192 _) 193 ).InSequence(seq) 194 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 195 nready2 = m_mock->m_nready; 196 out.header.unique = in.header.unique; 197 out.header.error = -EIO; 198 out.header.len = sizeof(out.header); 199 }))); 200 201 /* 202 * Create cached lookup entries for these files. It seems that only 203 * one thread at a time can be in VOP_LOOKUP for a given directory 204 */ 205 access("mountpoint/foo", F_OK); 206 access("mountpoint/bar", F_OK); 207 access("mountpoint/baz", F_OK); 208 ASSERT_EQ(0, pthread_create(&th0, NULL, statter, 209 __DECONST(void*, "mountpoint/foo"))) << strerror(errno); 210 EXPECT_EQ(0, sem_wait(&sem0)) << strerror(errno); 211 ASSERT_EQ(0, pthread_create(&th1, NULL, statter, 212 __DECONST(void*, "mountpoint/bar"))) << strerror(errno); 213 ASSERT_EQ(0, pthread_create(&th2, NULL, statter, 214 __DECONST(void*, "mountpoint/baz"))) << strerror(errno); 215 216 nap(); // Allow th1 and th2 to send their ops to the daemon 217 EXPECT_EQ(0, sem_post(&sem1)) << strerror(errno); 218 219 pthread_join(th0, &th_ret); 220 ASSERT_EQ(-1, (intptr_t)th_ret); 221 pthread_join(th1, &th_ret); 222 ASSERT_EQ(-1, (intptr_t)th_ret); 223 pthread_join(th2, &th_ret); 224 ASSERT_EQ(-1, (intptr_t)th_ret); 225 226 EXPECT_EQ(1, nready0); 227 EXPECT_EQ(2, nready1); 228 EXPECT_EQ(1, nready2); 229 230 sem_destroy(&sem0); 231 sem_destroy(&sem1); 232 } 233