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