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 /* telldir(3) and seekdir(3) should work with fuse */ 325 TEST_F(Readdir, seekdir) 326 { 327 const char FULLPATH[] = "mountpoint/some_dir"; 328 const char RELPATH[] = "some_dir"; 329 uint64_t ino = 42; 330 DIR *dir; 331 struct dirent *de; 332 /* 333 * use enough entries to be > 4096 bytes, so getdirentries must be 334 * called 335 * multiple times. 336 */ 337 vector<struct dirent> ents0(122), ents1(102), ents2(30); 338 long bookmark; 339 int i = 0; 340 341 for (auto& it: ents0) { 342 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 343 it.d_fileno = 2 + i; 344 it.d_off = (2 + i) * 1000; 345 it.d_namlen = strlen(it.d_name); 346 it.d_type = DT_REG; 347 i++; 348 } 349 for (auto& it: ents1) { 350 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 351 it.d_fileno = 2 + i; 352 it.d_off = (2 + i) * 1000; 353 it.d_namlen = strlen(it.d_name); 354 it.d_type = DT_REG; 355 i++; 356 } 357 for (auto& it: ents2) { 358 snprintf(it.d_name, MAXNAMLEN, "file.%d", i); 359 it.d_fileno = 2 + i; 360 it.d_off = (2 + i) * 1000; 361 it.d_namlen = strlen(it.d_name); 362 it.d_type = DT_REG; 363 i++; 364 } 365 366 expect_lookup(RELPATH, ino); 367 expect_opendir(ino); 368 369 expect_readdir(ino, 0, ents0); 370 expect_readdir(ino, 123000, ents1); 371 expect_readdir(ino, 225000, ents2); 372 373 errno = 0; 374 dir = opendir(FULLPATH); 375 ASSERT_NE(nullptr, dir) << strerror(errno); 376 377 for (i=0; i < 128; i++) { 378 errno = 0; 379 de = readdir(dir); 380 ASSERT_NE(nullptr, de) << strerror(errno); 381 EXPECT_EQ(2 + (ino_t)i, de->d_fileno); 382 } 383 bookmark = telldir(dir); 384 385 for (; i < 232; i++) { 386 errno = 0; 387 de = readdir(dir); 388 ASSERT_NE(nullptr, de) << strerror(errno); 389 EXPECT_EQ(2 + (ino_t)i, de->d_fileno); 390 } 391 392 seekdir(dir, bookmark); 393 de = readdir(dir); 394 ASSERT_NE(nullptr, de) << strerror(errno); 395 EXPECT_EQ(130ul, de->d_fileno); 396 397 leakdir(dir); 398 } 399 400 TEST_F(Readdir_7_8, nodots) 401 { 402 const char FULLPATH[] = "mountpoint/some_dir"; 403 const char RELPATH[] = "some_dir"; 404 uint64_t ino = 42; 405 DIR *dir; 406 407 expect_lookup(RELPATH, ino); 408 expect_opendir(ino); 409 410 EXPECT_CALL(*m_mock, process( 411 ResultOf([=](auto in) { 412 return (in.header.opcode == FUSE_READDIR && 413 in.header.nodeid == ino); 414 }, Eq(true)), 415 _) 416 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 417 out.header.error = 0; 418 out.header.len = sizeof(out.header); 419 }))); 420 421 errno = 0; 422 dir = opendir(FULLPATH); 423 ASSERT_NE(nullptr, dir) << strerror(errno); 424 errno = 0; 425 ASSERT_EQ(nullptr, readdir(dir)); 426 ASSERT_EQ(0, errno); 427 428 leakdir(dir); 429 } 430