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