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