xref: /freebsd/tests/sys/fs/fusefs/readdir.cc (revision 2ed3236082a4473c1da8f72c1ebc071a7b54321f)
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