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 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 const char dot[] = "."; 64 const char dotdot[] = ".."; 65 66 /* FUSE_READDIR returns nothing but "." and ".." */ 67 TEST_F(Readdir, dots) 68 { 69 const char FULLPATH[] = "mountpoint/some_dir"; 70 const char RELPATH[] = "some_dir"; 71 uint64_t ino = 42; 72 DIR *dir; 73 struct dirent *de; 74 vector<struct dirent> ents(2); 75 vector<struct dirent> empty_ents(0); 76 77 expect_lookup(RELPATH, ino); 78 expect_opendir(ino); 79 ents[0].d_fileno = 2; 80 ents[0].d_off = 2000; 81 ents[0].d_namlen = sizeof(dotdot); 82 ents[0].d_type = DT_DIR; 83 strncpy(ents[0].d_name, dotdot, ents[0].d_namlen); 84 ents[1].d_fileno = 3; 85 ents[1].d_off = 3000; 86 ents[1].d_namlen = sizeof(dot); 87 ents[1].d_type = DT_DIR; 88 strncpy(ents[1].d_name, dot, ents[1].d_namlen); 89 expect_readdir(ino, 0, ents); 90 expect_readdir(ino, 3000, empty_ents); 91 92 errno = 0; 93 dir = opendir(FULLPATH); 94 ASSERT_NE(nullptr, dir) << strerror(errno); 95 96 errno = 0; 97 de = readdir(dir); 98 ASSERT_NE(nullptr, de) << strerror(errno); 99 EXPECT_EQ(2ul, de->d_fileno); 100 EXPECT_EQ(DT_DIR, de->d_type); 101 EXPECT_EQ(sizeof(dotdot), de->d_namlen); 102 EXPECT_EQ(0, strcmp(dotdot, de->d_name)); 103 104 errno = 0; 105 de = readdir(dir); 106 ASSERT_NE(nullptr, de) << strerror(errno); 107 EXPECT_EQ(3ul, de->d_fileno); 108 EXPECT_EQ(DT_DIR, de->d_type); 109 EXPECT_EQ(sizeof(dot), de->d_namlen); 110 EXPECT_EQ(0, strcmp(dot, de->d_name)); 111 112 ASSERT_EQ(nullptr, readdir(dir)); 113 ASSERT_EQ(0, errno); 114 115 leakdir(dir); 116 } 117 118 TEST_F(Readdir, eio) 119 { 120 const char FULLPATH[] = "mountpoint/some_dir"; 121 const char RELPATH[] = "some_dir"; 122 uint64_t ino = 42; 123 DIR *dir; 124 struct dirent *de; 125 126 expect_lookup(RELPATH, ino); 127 expect_opendir(ino); 128 EXPECT_CALL(*m_mock, process( 129 ResultOf([=](auto in) { 130 return (in.header.opcode == FUSE_READDIR && 131 in.header.nodeid == ino && 132 in.body.readdir.offset == 0); 133 }, Eq(true)), 134 _) 135 ).WillOnce(Invoke(ReturnErrno(EIO))); 136 137 errno = 0; 138 dir = opendir(FULLPATH); 139 ASSERT_NE(nullptr, dir) << strerror(errno); 140 141 errno = 0; 142 de = readdir(dir); 143 ASSERT_EQ(nullptr, de); 144 ASSERT_EQ(EIO, errno); 145 146 leakdir(dir); 147 } 148 149 /* 150 * getdirentries(2) can use a larger buffer size than readdir(3). It also has 151 * some additional non-standardized fields in the returned dirent. 152 */ 153 TEST_F(Readdir, getdirentries_empty) 154 { 155 const char FULLPATH[] = "mountpoint/some_dir"; 156 const char RELPATH[] = "some_dir"; 157 uint64_t ino = 42; 158 int fd; 159 char buf[8192]; 160 ssize_t r; 161 162 expect_lookup(RELPATH, ino); 163 expect_opendir(ino); 164 165 EXPECT_CALL(*m_mock, process( 166 ResultOf([=](auto in) { 167 return (in.header.opcode == FUSE_READDIR && 168 in.header.nodeid == ino && 169 in.body.readdir.size == 8192); 170 }, Eq(true)), 171 _) 172 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 173 out.header.error = 0; 174 out.header.len = sizeof(out.header); 175 }))); 176 177 fd = open(FULLPATH, O_DIRECTORY); 178 ASSERT_LE(0, fd) << strerror(errno); 179 r = getdirentries(fd, buf, sizeof(buf), 0); 180 ASSERT_EQ(0, r) << strerror(errno); 181 182 leak(fd); 183 } 184 185 /* 186 * The dirent.d_off field can be used with lseek to position the directory so 187 * that getdirentries will return the subsequent dirent. 188 */ 189 TEST_F(Readdir, getdirentries_seek) 190 { 191 const char FULLPATH[] = "mountpoint/some_dir"; 192 const char RELPATH[] = "some_dir"; 193 vector<struct dirent> ents0(2); 194 vector<struct dirent> ents1(1); 195 uint64_t ino = 42; 196 int fd; 197 const size_t bufsize = 8192; 198 char buf[bufsize]; 199 struct dirent *de0, *de1; 200 ssize_t r; 201 202 expect_lookup(RELPATH, ino); 203 expect_opendir(ino); 204 205 ents0[0].d_fileno = 2; 206 ents0[0].d_off = 2000; 207 ents0[0].d_namlen = sizeof(dotdot); 208 ents0[0].d_type = DT_DIR; 209 strncpy(ents0[0].d_name, dotdot, ents0[0].d_namlen); 210 expect_readdir(ino, 0, ents0); 211 ents0[1].d_fileno = 3; 212 ents0[1].d_off = 3000; 213 ents0[1].d_namlen = sizeof(dot); 214 ents0[1].d_type = DT_DIR; 215 ents1[0].d_fileno = 3; 216 ents1[0].d_off = 3000; 217 ents1[0].d_namlen = sizeof(dot); 218 ents1[0].d_type = DT_DIR; 219 strncpy(ents1[0].d_name, dot, ents1[0].d_namlen); 220 expect_readdir(ino, 0, ents0); 221 expect_readdir(ino, 2000, ents1); 222 223 fd = open(FULLPATH, O_DIRECTORY); 224 ASSERT_LE(0, fd) << strerror(errno); 225 r = getdirentries(fd, buf, sizeof(buf), 0); 226 ASSERT_LT(0, r) << strerror(errno); 227 de0 = (struct dirent*)&buf[0]; 228 ASSERT_EQ(2000, de0->d_off); 229 ASSERT_LT(de0->d_reclen + offsetof(struct dirent, d_fileno), bufsize); 230 de1 = (struct dirent*)(&(buf[de0->d_reclen])); 231 ASSERT_EQ(3ul, de1->d_fileno); 232 233 r = lseek(fd, de0->d_off, SEEK_SET); 234 ASSERT_LE(0, r); 235 r = getdirentries(fd, buf, sizeof(buf), 0); 236 ASSERT_LT(0, r) << strerror(errno); 237 de0 = (struct dirent*)&buf[0]; 238 ASSERT_EQ(3000, de0->d_off); 239 } 240 241 /* 242 * Nothing bad should happen if getdirentries is called on two file descriptors 243 * which were concurrently open, but one has already been closed. 244 * This is a regression test for a specific bug dating from r238402. 245 */ 246 TEST_F(Readdir, getdirentries_concurrent) 247 { 248 const char FULLPATH[] = "mountpoint/some_dir"; 249 const char RELPATH[] = "some_dir"; 250 uint64_t ino = 42; 251 int fd0, fd1; 252 char buf[8192]; 253 ssize_t r; 254 255 FuseTest::expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 2); 256 expect_opendir(ino); 257 258 EXPECT_CALL(*m_mock, process( 259 ResultOf([=](auto in) { 260 return (in.header.opcode == FUSE_READDIR && 261 in.header.nodeid == ino && 262 in.body.readdir.size == 8192); 263 }, Eq(true)), 264 _) 265 ).Times(2) 266 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 267 out.header.error = 0; 268 out.header.len = sizeof(out.header); 269 }))); 270 271 fd0 = open(FULLPATH, O_DIRECTORY); 272 ASSERT_LE(0, fd0) << strerror(errno); 273 274 fd1 = open(FULLPATH, O_DIRECTORY); 275 ASSERT_LE(0, fd1) << strerror(errno); 276 277 r = getdirentries(fd0, buf, sizeof(buf), 0); 278 ASSERT_EQ(0, r) << strerror(errno); 279 280 EXPECT_EQ(0, close(fd0)) << strerror(errno); 281 282 r = getdirentries(fd1, buf, sizeof(buf), 0); 283 ASSERT_EQ(0, r) << strerror(errno); 284 285 leak(fd0); 286 leak(fd1); 287 } 288 289 /* 290 * FUSE_READDIR returns nothing, not even "." and "..". This is legal, though 291 * the filesystem obviously won't be fully functional. 292 */ 293 TEST_F(Readdir, nodots) 294 { 295 const char FULLPATH[] = "mountpoint/some_dir"; 296 const char RELPATH[] = "some_dir"; 297 uint64_t ino = 42; 298 DIR *dir; 299 300 expect_lookup(RELPATH, ino); 301 expect_opendir(ino); 302 303 EXPECT_CALL(*m_mock, process( 304 ResultOf([=](auto in) { 305 return (in.header.opcode == FUSE_READDIR && 306 in.header.nodeid == ino); 307 }, Eq(true)), 308 _) 309 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 310 out.header.error = 0; 311 out.header.len = sizeof(out.header); 312 }))); 313 314 errno = 0; 315 dir = opendir(FULLPATH); 316 ASSERT_NE(nullptr, dir) << strerror(errno); 317 errno = 0; 318 ASSERT_EQ(nullptr, readdir(dir)); 319 ASSERT_EQ(0, errno); 320 321 leakdir(dir); 322 } 323 324 /* 325 * FUSE_READDIR returns a path with an embedded NUL. Obviously illegal, but 326 * nothing bad should happen. 327 */ 328 TEST_F(Readdir, nul) 329 { 330 const char FULLPATH[] = "mountpoint/some_dir"; 331 const char RELPATH[] = "some_dir"; 332 uint64_t ino = 42; 333 DIR *dir; 334 struct dirent *de; 335 vector<struct dirent> ents(1); 336 vector<struct dirent> empty_ents(0); 337 const char nul[] = "foo\0bar"; 338 339 expect_lookup(RELPATH, ino); 340 expect_opendir(ino); 341 ents[0].d_fileno = 4; 342 ents[0].d_off = 4000; 343 ents[0].d_namlen = sizeof(nul); 344 ents[0].d_type = DT_REG; 345 strncpy(ents[0].d_name, nul, ents[0].d_namlen); 346 expect_readdir(ino, 0, ents); 347 expect_readdir(ino, 4000, empty_ents); 348 349 errno = 0; 350 dir = opendir(FULLPATH); 351 ASSERT_NE(nullptr, dir) << strerror(errno); 352 353 errno = 0; 354 de = readdir(dir); 355 ASSERT_NE(nullptr, de) << strerror(errno); 356 EXPECT_EQ(4ul, de->d_fileno); 357 EXPECT_EQ(DT_REG, de->d_type); 358 EXPECT_EQ(sizeof(nul), de->d_namlen); 359 EXPECT_EQ(0, strcmp(nul, de->d_name)); 360 361 ASSERT_EQ(nullptr, readdir(dir)); 362 ASSERT_EQ(0, errno); 363 364 leakdir(dir); 365 } 366 367 368 /* telldir(3) and seekdir(3) should work with fuse */ 369 TEST_F(Readdir, seekdir) 370 { 371 const char FULLPATH[] = "mountpoint/some_dir"; 372 const char RELPATH[] = "some_dir"; 373 uint64_t ino = 42; 374 DIR *dir; 375 struct dirent *de; 376 /* 377 * use enough entries to be > 4096 bytes, so getdirentries must be 378 * called 379 * multiple times. 380 */ 381 vector<struct dirent> ents0(122), ents1(102), ents2(30); 382 long bookmark; 383 int i = 0; 384 385 for (auto& it: ents0) { 386 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 387 it.d_fileno = 2 + i; 388 it.d_off = (2 + i) * 1000; 389 it.d_namlen = strlen(it.d_name); 390 it.d_type = DT_REG; 391 i++; 392 } 393 for (auto& it: ents1) { 394 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 395 it.d_fileno = 2 + i; 396 it.d_off = (2 + i) * 1000; 397 it.d_namlen = strlen(it.d_name); 398 it.d_type = DT_REG; 399 i++; 400 } 401 for (auto& it: ents2) { 402 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 403 it.d_fileno = 2 + i; 404 it.d_off = (2 + i) * 1000; 405 it.d_namlen = strlen(it.d_name); 406 it.d_type = DT_REG; 407 i++; 408 } 409 410 expect_lookup(RELPATH, ino); 411 expect_opendir(ino); 412 413 expect_readdir(ino, 0, ents0); 414 expect_readdir(ino, 123000, ents1); 415 expect_readdir(ino, 225000, ents2); 416 417 errno = 0; 418 dir = opendir(FULLPATH); 419 ASSERT_NE(nullptr, dir) << strerror(errno); 420 421 for (i=0; i < 128; i++) { 422 errno = 0; 423 de = readdir(dir); 424 ASSERT_NE(nullptr, de) << strerror(errno); 425 EXPECT_EQ(2 + (ino_t)i, de->d_fileno); 426 } 427 bookmark = telldir(dir); 428 429 for (; i < 232; i++) { 430 errno = 0; 431 de = readdir(dir); 432 ASSERT_NE(nullptr, de) << strerror(errno); 433 EXPECT_EQ(2 + (ino_t)i, de->d_fileno); 434 } 435 436 seekdir(dir, bookmark); 437 de = readdir(dir); 438 ASSERT_NE(nullptr, de) << strerror(errno); 439 EXPECT_EQ(130ul, de->d_fileno); 440 441 leakdir(dir); 442 } 443 444 /* 445 * FUSE_READDIR returns a path with an embedded /. Obviously illegal, but 446 * nothing bad should happen. 447 */ 448 TEST_F(Readdir, slash) 449 { 450 const char FULLPATH[] = "mountpoint/some_dir"; 451 const char RELPATH[] = "some_dir"; 452 uint64_t ino = 42; 453 DIR *dir; 454 struct dirent *de; 455 vector<struct dirent> ents(1); 456 vector<struct dirent> empty_ents(0); 457 const char foobar[] = "foo/bar"; 458 459 expect_lookup(RELPATH, ino); 460 expect_opendir(ino); 461 ents[0].d_fileno = 4; 462 ents[0].d_off = 4000; 463 ents[0].d_namlen = sizeof(foobar); 464 ents[0].d_type = DT_REG; 465 strncpy(ents[0].d_name, foobar, ents[0].d_namlen); 466 expect_readdir(ino, 0, ents); 467 expect_readdir(ino, 4000, empty_ents); 468 469 errno = 0; 470 dir = opendir(FULLPATH); 471 ASSERT_NE(nullptr, dir) << strerror(errno); 472 473 errno = 0; 474 de = readdir(dir); 475 ASSERT_NE(nullptr, de) << strerror(errno); 476 EXPECT_EQ(4ul, de->d_fileno); 477 EXPECT_EQ(DT_REG, de->d_type); 478 EXPECT_EQ(sizeof(foobar), de->d_namlen); 479 EXPECT_EQ(0, strcmp(foobar, de->d_name)); 480 481 ASSERT_EQ(nullptr, readdir(dir)); 482 ASSERT_EQ(0, errno); 483 484 leakdir(dir); 485 } 486 487 TEST_F(Readdir_7_8, nodots) 488 { 489 const char FULLPATH[] = "mountpoint/some_dir"; 490 const char RELPATH[] = "some_dir"; 491 uint64_t ino = 42; 492 DIR *dir; 493 494 expect_lookup(RELPATH, ino); 495 expect_opendir(ino); 496 497 EXPECT_CALL(*m_mock, process( 498 ResultOf([=](auto in) { 499 return (in.header.opcode == FUSE_READDIR && 500 in.header.nodeid == ino); 501 }, Eq(true)), 502 _) 503 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 504 out.header.error = 0; 505 out.header.len = sizeof(out.header); 506 }))); 507 508 errno = 0; 509 dir = opendir(FULLPATH); 510 ASSERT_NE(nullptr, dir) << strerror(errno); 511 errno = 0; 512 ASSERT_EQ(nullptr, readdir(dir)); 513 ASSERT_EQ(0, errno); 514 515 leakdir(dir); 516 } 517