xref: /freebsd/tests/sys/fs/fusefs/readdir.cc (revision c7a063741720ef81d4caa4613242579d12f1d605)
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 const char dot[] = ".";
66 const char dotdot[] = "..";
67 
68 /* FUSE_READDIR returns nothing but "." and ".." */
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 
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  */
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  */
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  */
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  */
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 */
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 
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