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