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 extern "C" { 32 #include <dirent.h> 33 #include <fcntl.h> 34 } 35 36 #include "mockfs.hh" 37 #include "utils.hh" 38 39 using namespace testing; 40 using namespace std; 41 42 class Readdir: public FuseTest { 43 public: 44 void expect_lookup(const char *relpath, uint64_t ino) 45 { 46 FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1); 47 } 48 }; 49 50 class Readdir_7_8: public Readdir { 51 public: 52 virtual void SetUp() { 53 m_kernel_minor_version = 8; 54 Readdir::SetUp(); 55 } 56 57 void expect_lookup(const char *relpath, uint64_t ino) 58 { 59 FuseTest::expect_lookup_7_8(relpath, ino, S_IFDIR | 0755, 0, 1); 60 } 61 }; 62 63 /* FUSE_READDIR returns nothing but "." and ".." */ 64 TEST_F(Readdir, dots) 65 { 66 const char FULLPATH[] = "mountpoint/some_dir"; 67 const char RELPATH[] = "some_dir"; 68 uint64_t ino = 42; 69 DIR *dir; 70 struct dirent *de; 71 vector<struct dirent> ents(2); 72 vector<struct dirent> empty_ents(0); 73 const char dot[] = "."; 74 const char dotdot[] = ".."; 75 76 expect_lookup(RELPATH, ino); 77 expect_opendir(ino); 78 ents[0].d_fileno = 2; 79 ents[0].d_off = 2000; 80 ents[0].d_namlen = sizeof(dotdot); 81 ents[0].d_type = DT_DIR; 82 strncpy(ents[0].d_name, dotdot, ents[0].d_namlen); 83 ents[1].d_fileno = 3; 84 ents[1].d_off = 3000; 85 ents[1].d_namlen = sizeof(dot); 86 ents[1].d_type = DT_DIR; 87 strncpy(ents[1].d_name, dot, ents[1].d_namlen); 88 expect_readdir(ino, 0, ents); 89 expect_readdir(ino, 3000, empty_ents); 90 91 errno = 0; 92 dir = opendir(FULLPATH); 93 ASSERT_NE(nullptr, dir) << strerror(errno); 94 95 errno = 0; 96 de = readdir(dir); 97 ASSERT_NE(nullptr, de) << strerror(errno); 98 EXPECT_EQ(2ul, de->d_fileno); 99 /* 100 * fuse(4) doesn't actually set d_off, which is ok for now because 101 * nothing uses it. 102 */ 103 //EXPECT_EQ(2000, de->d_off); 104 EXPECT_EQ(DT_DIR, de->d_type); 105 EXPECT_EQ(sizeof(dotdot), de->d_namlen); 106 EXPECT_EQ(0, strcmp(dotdot, de->d_name)); 107 108 errno = 0; 109 de = readdir(dir); 110 ASSERT_NE(nullptr, de) << strerror(errno); 111 EXPECT_EQ(3ul, de->d_fileno); 112 //EXPECT_EQ(3000, de->d_off); 113 EXPECT_EQ(DT_DIR, de->d_type); 114 EXPECT_EQ(sizeof(dot), de->d_namlen); 115 EXPECT_EQ(0, strcmp(dot, de->d_name)); 116 117 ASSERT_EQ(nullptr, readdir(dir)); 118 ASSERT_EQ(0, errno); 119 120 leakdir(dir); 121 } 122 123 TEST_F(Readdir, eio) 124 { 125 const char FULLPATH[] = "mountpoint/some_dir"; 126 const char RELPATH[] = "some_dir"; 127 uint64_t ino = 42; 128 DIR *dir; 129 struct dirent *de; 130 131 expect_lookup(RELPATH, ino); 132 expect_opendir(ino); 133 EXPECT_CALL(*m_mock, process( 134 ResultOf([=](auto in) { 135 return (in.header.opcode == FUSE_READDIR && 136 in.header.nodeid == ino && 137 in.body.readdir.offset == 0); 138 }, Eq(true)), 139 _) 140 ).WillOnce(Invoke(ReturnErrno(EIO))); 141 142 errno = 0; 143 dir = opendir(FULLPATH); 144 ASSERT_NE(nullptr, dir) << strerror(errno); 145 146 errno = 0; 147 de = readdir(dir); 148 ASSERT_EQ(nullptr, de); 149 ASSERT_EQ(EIO, errno); 150 151 leakdir(dir); 152 } 153 154 /* getdirentries(2) can use a larger buffer size than readdir(3) */ 155 TEST_F(Readdir, getdirentries) 156 { 157 const char FULLPATH[] = "mountpoint/some_dir"; 158 const char RELPATH[] = "some_dir"; 159 uint64_t ino = 42; 160 int fd; 161 char buf[8192]; 162 ssize_t r; 163 164 expect_lookup(RELPATH, ino); 165 expect_opendir(ino); 166 167 EXPECT_CALL(*m_mock, process( 168 ResultOf([=](auto in) { 169 return (in.header.opcode == FUSE_READDIR && 170 in.header.nodeid == ino && 171 in.body.readdir.size == 8192); 172 }, Eq(true)), 173 _) 174 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 175 out.header.error = 0; 176 out.header.len = sizeof(out.header); 177 }))); 178 179 fd = open(FULLPATH, O_DIRECTORY); 180 ASSERT_LE(0, fd) << strerror(errno); 181 r = getdirentries(fd, buf, sizeof(buf), 0); 182 ASSERT_EQ(0, r) << strerror(errno); 183 184 leak(fd); 185 } 186 187 /* 188 * Nothing bad should happen if getdirentries is called on two file descriptors 189 * which were concurrently open, but one has already been closed. 190 * This is a regression test for a specific bug dating from r238402. 191 */ 192 TEST_F(Readdir, getdirentries_concurrent) 193 { 194 const char FULLPATH[] = "mountpoint/some_dir"; 195 const char RELPATH[] = "some_dir"; 196 uint64_t ino = 42; 197 int fd0, fd1; 198 char buf[8192]; 199 ssize_t r; 200 201 FuseTest::expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 2); 202 expect_opendir(ino); 203 204 EXPECT_CALL(*m_mock, process( 205 ResultOf([=](auto in) { 206 return (in.header.opcode == FUSE_READDIR && 207 in.header.nodeid == ino && 208 in.body.readdir.size == 8192); 209 }, Eq(true)), 210 _) 211 ).Times(2) 212 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 213 out.header.error = 0; 214 out.header.len = sizeof(out.header); 215 }))); 216 217 fd0 = open(FULLPATH, O_DIRECTORY); 218 ASSERT_LE(0, fd0) << strerror(errno); 219 220 fd1 = open(FULLPATH, O_DIRECTORY); 221 ASSERT_LE(0, fd1) << strerror(errno); 222 223 r = getdirentries(fd0, buf, sizeof(buf), 0); 224 ASSERT_EQ(0, r) << strerror(errno); 225 226 EXPECT_EQ(0, close(fd0)) << strerror(errno); 227 228 r = getdirentries(fd1, buf, sizeof(buf), 0); 229 ASSERT_EQ(0, r) << strerror(errno); 230 231 leak(fd0); 232 leak(fd1); 233 } 234 235 /* 236 * FUSE_READDIR returns nothing, not even "." and "..". This is legal, though 237 * the filesystem obviously won't be fully functional. 238 */ 239 TEST_F(Readdir, nodots) 240 { 241 const char FULLPATH[] = "mountpoint/some_dir"; 242 const char RELPATH[] = "some_dir"; 243 uint64_t ino = 42; 244 DIR *dir; 245 246 expect_lookup(RELPATH, ino); 247 expect_opendir(ino); 248 249 EXPECT_CALL(*m_mock, process( 250 ResultOf([=](auto in) { 251 return (in.header.opcode == FUSE_READDIR && 252 in.header.nodeid == ino); 253 }, Eq(true)), 254 _) 255 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 256 out.header.error = 0; 257 out.header.len = sizeof(out.header); 258 }))); 259 260 errno = 0; 261 dir = opendir(FULLPATH); 262 ASSERT_NE(nullptr, dir) << strerror(errno); 263 errno = 0; 264 ASSERT_EQ(nullptr, readdir(dir)); 265 ASSERT_EQ(0, errno); 266 267 leakdir(dir); 268 } 269 270 /* telldir(3) and seekdir(3) should work with fuse */ 271 TEST_F(Readdir, seekdir) 272 { 273 const char FULLPATH[] = "mountpoint/some_dir"; 274 const char RELPATH[] = "some_dir"; 275 uint64_t ino = 42; 276 DIR *dir; 277 struct dirent *de; 278 /* 279 * use enough entries to be > 4096 bytes, so getdirentries must be 280 * called 281 * multiple times. 282 */ 283 vector<struct dirent> ents0(122), ents1(102), ents2(30); 284 long bookmark; 285 int i = 0; 286 287 for (auto& it: ents0) { 288 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 289 it.d_fileno = 2 + i; 290 it.d_off = (2 + i) * 1000; 291 it.d_namlen = strlen(it.d_name); 292 it.d_type = DT_REG; 293 i++; 294 } 295 for (auto& it: ents1) { 296 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 297 it.d_fileno = 2 + i; 298 it.d_off = (2 + i) * 1000; 299 it.d_namlen = strlen(it.d_name); 300 it.d_type = DT_REG; 301 i++; 302 } 303 for (auto& it: ents2) { 304 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 305 it.d_fileno = 2 + i; 306 it.d_off = (2 + i) * 1000; 307 it.d_namlen = strlen(it.d_name); 308 it.d_type = DT_REG; 309 i++; 310 } 311 312 expect_lookup(RELPATH, ino); 313 expect_opendir(ino); 314 315 expect_readdir(ino, 0, ents0); 316 expect_readdir(ino, 123000, ents1); 317 expect_readdir(ino, 225000, ents2); 318 319 errno = 0; 320 dir = opendir(FULLPATH); 321 ASSERT_NE(nullptr, dir) << strerror(errno); 322 323 for (i=0; i < 128; i++) { 324 errno = 0; 325 de = readdir(dir); 326 ASSERT_NE(nullptr, de) << strerror(errno); 327 EXPECT_EQ(2 + (ino_t)i, de->d_fileno); 328 } 329 bookmark = telldir(dir); 330 331 for (; i < 232; i++) { 332 errno = 0; 333 de = readdir(dir); 334 ASSERT_NE(nullptr, de) << strerror(errno); 335 EXPECT_EQ(2 + (ino_t)i, de->d_fileno); 336 } 337 338 seekdir(dir, bookmark); 339 de = readdir(dir); 340 ASSERT_NE(nullptr, de) << strerror(errno); 341 EXPECT_EQ(130ul, de->d_fileno); 342 343 leakdir(dir); 344 } 345 346 TEST_F(Readdir_7_8, nodots) 347 { 348 const char FULLPATH[] = "mountpoint/some_dir"; 349 const char RELPATH[] = "some_dir"; 350 uint64_t ino = 42; 351 DIR *dir; 352 353 expect_lookup(RELPATH, ino); 354 expect_opendir(ino); 355 356 EXPECT_CALL(*m_mock, process( 357 ResultOf([=](auto in) { 358 return (in.header.opcode == FUSE_READDIR && 359 in.header.nodeid == ino); 360 }, Eq(true)), 361 _) 362 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 363 out.header.error = 0; 364 out.header.len = sizeof(out.header); 365 }))); 366 367 errno = 0; 368 dir = opendir(FULLPATH); 369 ASSERT_NE(nullptr, dir) << strerror(errno); 370 errno = 0; 371 ASSERT_EQ(nullptr, readdir(dir)); 372 ASSERT_EQ(0, errno); 373 374 leakdir(dir); 375 } 376