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:
expect_lookup(const char * relpath,uint64_t ino)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:
SetUp()54 virtual void SetUp() {
55 m_kernel_minor_version = 8;
56 Readdir::SetUp();
57 }
58
expect_lookup(const char * relpath,uint64_t ino)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 const char dot[] = ".";
66 const char dotdot[] = "..";
67
68 /* FUSE_READDIR returns nothing but "." and ".." */
TEST_F(Readdir,dots)69 TEST_F(Readdir, dots)
70 {
71 const char FULLPATH[] = "mountpoint/some_dir";
72 const char RELPATH[] = "some_dir";
73 uint64_t ino = 42;
74 DIR *dir;
75 struct dirent *de;
76 vector<struct dirent> ents(2);
77 vector<struct dirent> empty_ents(0);
78
79 expect_lookup(RELPATH, ino);
80 expect_opendir(ino);
81 ents[0].d_fileno = 2;
82 ents[0].d_off = 2000;
83 ents[0].d_namlen = sizeof(dotdot);
84 ents[0].d_type = DT_DIR;
85 strncpy(ents[0].d_name, dotdot, ents[0].d_namlen);
86 ents[1].d_fileno = 3;
87 ents[1].d_off = 3000;
88 ents[1].d_namlen = sizeof(dot);
89 ents[1].d_type = DT_DIR;
90 strncpy(ents[1].d_name, dot, ents[1].d_namlen);
91 expect_readdir(ino, 0, ents);
92 expect_readdir(ino, 3000, empty_ents);
93
94 errno = 0;
95 dir = opendir(FULLPATH);
96 ASSERT_NE(nullptr, dir) << strerror(errno);
97
98 errno = 0;
99 de = readdir(dir);
100 ASSERT_NE(nullptr, de) << strerror(errno);
101 EXPECT_EQ(2ul, de->d_fileno);
102 EXPECT_EQ(DT_DIR, de->d_type);
103 EXPECT_EQ(sizeof(dotdot), de->d_namlen);
104 EXPECT_EQ(0, strcmp(dotdot, de->d_name));
105
106 errno = 0;
107 de = readdir(dir);
108 ASSERT_NE(nullptr, de) << strerror(errno);
109 EXPECT_EQ(3ul, de->d_fileno);
110 EXPECT_EQ(DT_DIR, de->d_type);
111 EXPECT_EQ(sizeof(dot), de->d_namlen);
112 EXPECT_EQ(0, strcmp(dot, de->d_name));
113
114 ASSERT_EQ(nullptr, readdir(dir));
115 ASSERT_EQ(0, errno);
116
117 leakdir(dir);
118 }
119
TEST_F(Readdir,eio)120 TEST_F(Readdir, eio)
121 {
122 const char FULLPATH[] = "mountpoint/some_dir";
123 const char RELPATH[] = "some_dir";
124 uint64_t ino = 42;
125 DIR *dir;
126 struct dirent *de;
127
128 expect_lookup(RELPATH, ino);
129 expect_opendir(ino);
130 EXPECT_CALL(*m_mock, process(
131 ResultOf([=](auto in) {
132 return (in.header.opcode == FUSE_READDIR &&
133 in.header.nodeid == ino &&
134 in.body.readdir.offset == 0);
135 }, Eq(true)),
136 _)
137 ).WillOnce(Invoke(ReturnErrno(EIO)));
138
139 errno = 0;
140 dir = opendir(FULLPATH);
141 ASSERT_NE(nullptr, dir) << strerror(errno);
142
143 errno = 0;
144 de = readdir(dir);
145 ASSERT_EQ(nullptr, de);
146 ASSERT_EQ(EIO, errno);
147
148 leakdir(dir);
149 }
150
151 /*
152 * getdirentries(2) can use a larger buffer size than readdir(3). It also has
153 * some additional non-standardized fields in the returned dirent.
154 */
TEST_F(Readdir,getdirentries_empty)155 TEST_F(Readdir, getdirentries_empty)
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 * The dirent.d_off field can be used with lseek to position the directory so
189 * that getdirentries will return the subsequent dirent.
190 */
TEST_F(Readdir,getdirentries_seek)191 TEST_F(Readdir, getdirentries_seek)
192 {
193 const char FULLPATH[] = "mountpoint/some_dir";
194 const char RELPATH[] = "some_dir";
195 vector<struct dirent> ents0(2);
196 vector<struct dirent> ents1(1);
197 uint64_t ino = 42;
198 int fd;
199 const size_t bufsize = 8192;
200 char buf[bufsize];
201 struct dirent *de0, *de1;
202 ssize_t r;
203
204 expect_lookup(RELPATH, ino);
205 expect_opendir(ino);
206
207 ents0[0].d_fileno = 2;
208 ents0[0].d_off = 2000;
209 ents0[0].d_namlen = sizeof(dotdot);
210 ents0[0].d_type = DT_DIR;
211 strncpy(ents0[0].d_name, dotdot, ents0[0].d_namlen);
212 expect_readdir(ino, 0, ents0);
213 ents0[1].d_fileno = 3;
214 ents0[1].d_off = 3000;
215 ents0[1].d_namlen = sizeof(dot);
216 ents0[1].d_type = DT_DIR;
217 ents1[0].d_fileno = 3;
218 ents1[0].d_off = 3000;
219 ents1[0].d_namlen = sizeof(dot);
220 ents1[0].d_type = DT_DIR;
221 strncpy(ents1[0].d_name, dot, ents1[0].d_namlen);
222 expect_readdir(ino, 0, ents0);
223 expect_readdir(ino, 2000, ents1);
224
225 fd = open(FULLPATH, O_DIRECTORY);
226 ASSERT_LE(0, fd) << strerror(errno);
227 r = getdirentries(fd, buf, sizeof(buf), 0);
228 ASSERT_LT(0, r) << strerror(errno);
229 de0 = (struct dirent*)&buf[0];
230 ASSERT_EQ(2000, de0->d_off);
231 ASSERT_LT(de0->d_reclen + offsetof(struct dirent, d_fileno), bufsize);
232 de1 = (struct dirent*)(&(buf[de0->d_reclen]));
233 ASSERT_EQ(3ul, de1->d_fileno);
234
235 r = lseek(fd, de0->d_off, SEEK_SET);
236 ASSERT_LE(0, r);
237 r = getdirentries(fd, buf, sizeof(buf), 0);
238 ASSERT_LT(0, r) << strerror(errno);
239 de0 = (struct dirent*)&buf[0];
240 ASSERT_EQ(3000, de0->d_off);
241 }
242
243 /*
244 * Nothing bad should happen if getdirentries is called on two file descriptors
245 * which were concurrently open, but one has already been closed.
246 * This is a regression test for a specific bug dating from r238402.
247 */
TEST_F(Readdir,getdirentries_concurrent)248 TEST_F(Readdir, getdirentries_concurrent)
249 {
250 const char FULLPATH[] = "mountpoint/some_dir";
251 const char RELPATH[] = "some_dir";
252 uint64_t ino = 42;
253 int fd0, fd1;
254 char buf[8192];
255 ssize_t r;
256
257 FuseTest::expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 2);
258 expect_opendir(ino);
259
260 EXPECT_CALL(*m_mock, process(
261 ResultOf([=](auto in) {
262 return (in.header.opcode == FUSE_READDIR &&
263 in.header.nodeid == ino &&
264 in.body.readdir.size == 8192);
265 }, Eq(true)),
266 _)
267 ).Times(2)
268 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
269 out.header.error = 0;
270 out.header.len = sizeof(out.header);
271 })));
272
273 fd0 = open(FULLPATH, O_DIRECTORY);
274 ASSERT_LE(0, fd0) << strerror(errno);
275
276 fd1 = open(FULLPATH, O_DIRECTORY);
277 ASSERT_LE(0, fd1) << strerror(errno);
278
279 r = getdirentries(fd0, buf, sizeof(buf), 0);
280 ASSERT_EQ(0, r) << strerror(errno);
281
282 EXPECT_EQ(0, close(fd0)) << strerror(errno);
283
284 r = getdirentries(fd1, buf, sizeof(buf), 0);
285 ASSERT_EQ(0, r) << strerror(errno);
286
287 leak(fd0);
288 leak(fd1);
289 }
290
291 /*
292 * FUSE_READDIR returns nothing, not even "." and "..". This is legal, though
293 * the filesystem obviously won't be fully functional.
294 */
TEST_F(Readdir,nodots)295 TEST_F(Readdir, nodots)
296 {
297 const char FULLPATH[] = "mountpoint/some_dir";
298 const char RELPATH[] = "some_dir";
299 uint64_t ino = 42;
300 DIR *dir;
301
302 expect_lookup(RELPATH, ino);
303 expect_opendir(ino);
304
305 EXPECT_CALL(*m_mock, process(
306 ResultOf([=](auto in) {
307 return (in.header.opcode == FUSE_READDIR &&
308 in.header.nodeid == ino);
309 }, Eq(true)),
310 _)
311 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
312 out.header.error = 0;
313 out.header.len = sizeof(out.header);
314 })));
315
316 errno = 0;
317 dir = opendir(FULLPATH);
318 ASSERT_NE(nullptr, dir) << strerror(errno);
319 errno = 0;
320 ASSERT_EQ(nullptr, readdir(dir));
321 ASSERT_EQ(0, errno);
322
323 leakdir(dir);
324 }
325
326 /* telldir(3) and seekdir(3) should work with fuse */
TEST_F(Readdir,seekdir)327 TEST_F(Readdir, seekdir)
328 {
329 const char FULLPATH[] = "mountpoint/some_dir";
330 const char RELPATH[] = "some_dir";
331 uint64_t ino = 42;
332 DIR *dir;
333 struct dirent *de;
334 /*
335 * use enough entries to be > 4096 bytes, so getdirentries must be
336 * called
337 * multiple times.
338 */
339 vector<struct dirent> ents0(122), ents1(102), ents2(30);
340 long bookmark;
341 int i = 0;
342
343 for (auto& it: ents0) {
344 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
345 it.d_fileno = 2 + i;
346 it.d_off = (2 + i) * 1000;
347 it.d_namlen = strlen(it.d_name);
348 it.d_type = DT_REG;
349 i++;
350 }
351 for (auto& it: ents1) {
352 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
353 it.d_fileno = 2 + i;
354 it.d_off = (2 + i) * 1000;
355 it.d_namlen = strlen(it.d_name);
356 it.d_type = DT_REG;
357 i++;
358 }
359 for (auto& it: ents2) {
360 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
361 it.d_fileno = 2 + i;
362 it.d_off = (2 + i) * 1000;
363 it.d_namlen = strlen(it.d_name);
364 it.d_type = DT_REG;
365 i++;
366 }
367
368 expect_lookup(RELPATH, ino);
369 expect_opendir(ino);
370
371 expect_readdir(ino, 0, ents0);
372 expect_readdir(ino, 123000, ents1);
373 expect_readdir(ino, 225000, ents2);
374
375 errno = 0;
376 dir = opendir(FULLPATH);
377 ASSERT_NE(nullptr, dir) << strerror(errno);
378
379 for (i=0; i < 128; i++) {
380 errno = 0;
381 de = readdir(dir);
382 ASSERT_NE(nullptr, de) << strerror(errno);
383 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
384 }
385 bookmark = telldir(dir);
386
387 for (; i < 232; i++) {
388 errno = 0;
389 de = readdir(dir);
390 ASSERT_NE(nullptr, de) << strerror(errno);
391 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
392 }
393
394 seekdir(dir, bookmark);
395 de = readdir(dir);
396 ASSERT_NE(nullptr, de) << strerror(errno);
397 EXPECT_EQ(130ul, de->d_fileno);
398
399 leakdir(dir);
400 }
401
TEST_F(Readdir_7_8,nodots)402 TEST_F(Readdir_7_8, nodots)
403 {
404 const char FULLPATH[] = "mountpoint/some_dir";
405 const char RELPATH[] = "some_dir";
406 uint64_t ino = 42;
407 DIR *dir;
408
409 expect_lookup(RELPATH, ino);
410 expect_opendir(ino);
411
412 EXPECT_CALL(*m_mock, process(
413 ResultOf([=](auto in) {
414 return (in.header.opcode == FUSE_READDIR &&
415 in.header.nodeid == ino);
416 }, Eq(true)),
417 _)
418 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
419 out.header.error = 0;
420 out.header.len = sizeof(out.header);
421 })));
422
423 errno = 0;
424 dir = opendir(FULLPATH);
425 ASSERT_NE(nullptr, dir) << strerror(errno);
426 errno = 0;
427 ASSERT_EQ(nullptr, readdir(dir));
428 ASSERT_EQ(0, errno);
429
430 leakdir(dir);
431 }
432