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