1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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
31extern "C" {
32#include <dirent.h>
33#include <fcntl.h>
34}
35
36#include "mockfs.hh"
37#include "utils.hh"
38
39using namespace testing;
40using namespace std;
41
42class Readdir: public FuseTest {
43public:
44void expect_lookup(const char *relpath, uint64_t ino)
45{
46	FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1);
47}
48};
49
50class Readdir_7_8: public Readdir {
51public:
52virtual void SetUp() {
53	m_kernel_minor_version = 8;
54	Readdir::SetUp();
55}
56
57void 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
63const char dot[] = ".";
64const char dotdot[] = "..";
65
66/* FUSE_READDIR returns nothing but "." and ".." */
67TEST_F(Readdir, dots)
68{
69	const char FULLPATH[] = "mountpoint/some_dir";
70	const char RELPATH[] = "some_dir";
71	uint64_t ino = 42;
72	DIR *dir;
73	struct dirent *de;
74	vector<struct dirent> ents(2);
75	vector<struct dirent> empty_ents(0);
76
77	expect_lookup(RELPATH, ino);
78	expect_opendir(ino);
79	ents[0].d_fileno = 2;
80	ents[0].d_off = 2000;
81	ents[0].d_namlen = sizeof(dotdot);
82	ents[0].d_type = DT_DIR;
83	strncpy(ents[0].d_name, dotdot, ents[0].d_namlen);
84	ents[1].d_fileno = 3;
85	ents[1].d_off = 3000;
86	ents[1].d_namlen = sizeof(dot);
87	ents[1].d_type = DT_DIR;
88	strncpy(ents[1].d_name, dot, ents[1].d_namlen);
89	expect_readdir(ino, 0, ents);
90	expect_readdir(ino, 3000, empty_ents);
91
92	errno = 0;
93	dir = opendir(FULLPATH);
94	ASSERT_NE(nullptr, dir) << strerror(errno);
95
96	errno = 0;
97	de = readdir(dir);
98	ASSERT_NE(nullptr, de) << strerror(errno);
99	EXPECT_EQ(2ul, de->d_fileno);
100	EXPECT_EQ(DT_DIR, de->d_type);
101	EXPECT_EQ(sizeof(dotdot), de->d_namlen);
102	EXPECT_EQ(0, strcmp(dotdot, de->d_name));
103
104	errno = 0;
105	de = readdir(dir);
106	ASSERT_NE(nullptr, de) << strerror(errno);
107	EXPECT_EQ(3ul, de->d_fileno);
108	EXPECT_EQ(DT_DIR, de->d_type);
109	EXPECT_EQ(sizeof(dot), de->d_namlen);
110	EXPECT_EQ(0, strcmp(dot, de->d_name));
111
112	ASSERT_EQ(nullptr, readdir(dir));
113	ASSERT_EQ(0, errno);
114
115	leakdir(dir);
116}
117
118TEST_F(Readdir, eio)
119{
120	const char FULLPATH[] = "mountpoint/some_dir";
121	const char RELPATH[] = "some_dir";
122	uint64_t ino = 42;
123	DIR *dir;
124	struct dirent *de;
125
126	expect_lookup(RELPATH, ino);
127	expect_opendir(ino);
128	EXPECT_CALL(*m_mock, process(
129		ResultOf([=](auto in) {
130			return (in.header.opcode == FUSE_READDIR &&
131				in.header.nodeid == ino &&
132				in.body.readdir.offset == 0);
133		}, Eq(true)),
134		_)
135	).WillOnce(Invoke(ReturnErrno(EIO)));
136
137	errno = 0;
138	dir = opendir(FULLPATH);
139	ASSERT_NE(nullptr, dir) << strerror(errno);
140
141	errno = 0;
142	de = readdir(dir);
143	ASSERT_EQ(nullptr, de);
144	ASSERT_EQ(EIO, errno);
145
146	leakdir(dir);
147}
148
149/*
150 * getdirentries(2) can use a larger buffer size than readdir(3).  It also has
151 * some additional non-standardized fields in the returned dirent.
152 */
153TEST_F(Readdir, getdirentries_empty)
154{
155	const char FULLPATH[] = "mountpoint/some_dir";
156	const char RELPATH[] = "some_dir";
157	uint64_t ino = 42;
158	int fd;
159	char buf[8192];
160	ssize_t r;
161
162	expect_lookup(RELPATH, ino);
163	expect_opendir(ino);
164
165	EXPECT_CALL(*m_mock, process(
166		ResultOf([=](auto in) {
167			return (in.header.opcode == FUSE_READDIR &&
168				in.header.nodeid == ino &&
169				in.body.readdir.size == 8192);
170		}, Eq(true)),
171		_)
172	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
173		out.header.error = 0;
174		out.header.len = sizeof(out.header);
175	})));
176
177	fd = open(FULLPATH, O_DIRECTORY);
178	ASSERT_LE(0, fd) << strerror(errno);
179	r = getdirentries(fd, buf, sizeof(buf), 0);
180	ASSERT_EQ(0, r) << strerror(errno);
181
182	leak(fd);
183}
184
185/*
186 * The dirent.d_off field can be used with lseek to position the directory so
187 * that getdirentries will return the subsequent dirent.
188 */
189TEST_F(Readdir, getdirentries_seek)
190{
191	const char FULLPATH[] = "mountpoint/some_dir";
192	const char RELPATH[] = "some_dir";
193	vector<struct dirent> ents0(2);
194	vector<struct dirent> ents1(1);
195	uint64_t ino = 42;
196	int fd;
197	const size_t bufsize = 8192;
198	char buf[bufsize];
199	struct dirent *de0, *de1;
200	ssize_t r;
201
202	expect_lookup(RELPATH, ino);
203	expect_opendir(ino);
204
205	ents0[0].d_fileno = 2;
206	ents0[0].d_off = 2000;
207	ents0[0].d_namlen = sizeof(dotdot);
208	ents0[0].d_type = DT_DIR;
209	strncpy(ents0[0].d_name, dotdot, ents0[0].d_namlen);
210	expect_readdir(ino, 0, ents0);
211	ents0[1].d_fileno = 3;
212	ents0[1].d_off = 3000;
213	ents0[1].d_namlen = sizeof(dot);
214	ents0[1].d_type = DT_DIR;
215	ents1[0].d_fileno = 3;
216	ents1[0].d_off = 3000;
217	ents1[0].d_namlen = sizeof(dot);
218	ents1[0].d_type = DT_DIR;
219	strncpy(ents1[0].d_name, dot, ents1[0].d_namlen);
220	expect_readdir(ino, 0, ents0);
221	expect_readdir(ino, 2000, ents1);
222
223	fd = open(FULLPATH, O_DIRECTORY);
224	ASSERT_LE(0, fd) << strerror(errno);
225	r = getdirentries(fd, buf, sizeof(buf), 0);
226	ASSERT_LT(0, r) << strerror(errno);
227	de0 = (struct dirent*)&buf[0];
228	ASSERT_EQ(2000, de0->d_off);
229	ASSERT_LT(de0->d_reclen + offsetof(struct dirent, d_fileno), bufsize);
230	de1 = (struct dirent*)(&(buf[de0->d_reclen]));
231	ASSERT_EQ(3ul, de1->d_fileno);
232
233	r = lseek(fd, de0->d_off, SEEK_SET);
234	ASSERT_LE(0, r);
235	r = getdirentries(fd, buf, sizeof(buf), 0);
236	ASSERT_LT(0, r) << strerror(errno);
237	de0 = (struct dirent*)&buf[0];
238	ASSERT_EQ(3000, de0->d_off);
239}
240
241/*
242 * Nothing bad should happen if getdirentries is called on two file descriptors
243 * which were concurrently open, but one has already been closed.
244 * This is a regression test for a specific bug dating from r238402.
245 */
246TEST_F(Readdir, getdirentries_concurrent)
247{
248	const char FULLPATH[] = "mountpoint/some_dir";
249	const char RELPATH[] = "some_dir";
250	uint64_t ino = 42;
251	int fd0, fd1;
252	char buf[8192];
253	ssize_t r;
254
255	FuseTest::expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 2);
256	expect_opendir(ino);
257
258	EXPECT_CALL(*m_mock, process(
259		ResultOf([=](auto in) {
260			return (in.header.opcode == FUSE_READDIR &&
261				in.header.nodeid == ino &&
262				in.body.readdir.size == 8192);
263		}, Eq(true)),
264		_)
265	).Times(2)
266	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
267		out.header.error = 0;
268		out.header.len = sizeof(out.header);
269	})));
270
271	fd0 = open(FULLPATH, O_DIRECTORY);
272	ASSERT_LE(0, fd0) << strerror(errno);
273
274	fd1 = open(FULLPATH, O_DIRECTORY);
275	ASSERT_LE(0, fd1) << strerror(errno);
276
277	r = getdirentries(fd0, buf, sizeof(buf), 0);
278	ASSERT_EQ(0, r) << strerror(errno);
279
280	EXPECT_EQ(0, close(fd0)) << strerror(errno);
281
282	r = getdirentries(fd1, buf, sizeof(buf), 0);
283	ASSERT_EQ(0, r) << strerror(errno);
284
285	leak(fd0);
286	leak(fd1);
287}
288
289/*
290 * FUSE_READDIR returns nothing, not even "." and "..".  This is legal, though
291 * the filesystem obviously won't be fully functional.
292 */
293TEST_F(Readdir, nodots)
294{
295	const char FULLPATH[] = "mountpoint/some_dir";
296	const char RELPATH[] = "some_dir";
297	uint64_t ino = 42;
298	DIR *dir;
299
300	expect_lookup(RELPATH, ino);
301	expect_opendir(ino);
302
303	EXPECT_CALL(*m_mock, process(
304		ResultOf([=](auto in) {
305			return (in.header.opcode == FUSE_READDIR &&
306				in.header.nodeid == ino);
307		}, Eq(true)),
308		_)
309	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
310		out.header.error = 0;
311		out.header.len = sizeof(out.header);
312	})));
313
314	errno = 0;
315	dir = opendir(FULLPATH);
316	ASSERT_NE(nullptr, dir) << strerror(errno);
317	errno = 0;
318	ASSERT_EQ(nullptr, readdir(dir));
319	ASSERT_EQ(0, errno);
320
321	leakdir(dir);
322}
323
324/*
325 * FUSE_READDIR returns a path with an embedded NUL. Obviously illegal, but
326 * nothing bad should happen.
327 */
328TEST_F(Readdir, nul)
329{
330	const char FULLPATH[] = "mountpoint/some_dir";
331	const char RELPATH[] = "some_dir";
332	uint64_t ino = 42;
333	DIR *dir;
334	struct dirent *de;
335	vector<struct dirent> ents(1);
336	vector<struct dirent> empty_ents(0);
337	const char nul[] = "foo\0bar";
338
339	expect_lookup(RELPATH, ino);
340	expect_opendir(ino);
341	ents[0].d_fileno = 4;
342	ents[0].d_off = 4000;
343	ents[0].d_namlen = sizeof(nul);
344	ents[0].d_type = DT_REG;
345	strncpy(ents[0].d_name, nul, ents[0].d_namlen);
346	expect_readdir(ino, 0, ents);
347	expect_readdir(ino, 4000, empty_ents);
348
349	errno = 0;
350	dir = opendir(FULLPATH);
351	ASSERT_NE(nullptr, dir) << strerror(errno);
352
353	errno = 0;
354	de = readdir(dir);
355	ASSERT_NE(nullptr, de) << strerror(errno);
356	EXPECT_EQ(4ul, de->d_fileno);
357	EXPECT_EQ(DT_REG, de->d_type);
358	EXPECT_EQ(sizeof(nul), de->d_namlen);
359	EXPECT_EQ(0, strcmp(nul, de->d_name));
360
361	ASSERT_EQ(nullptr, readdir(dir));
362	ASSERT_EQ(0, errno);
363
364	leakdir(dir);
365}
366
367
368/* telldir(3) and seekdir(3) should work with fuse */
369TEST_F(Readdir, seekdir)
370{
371	const char FULLPATH[] = "mountpoint/some_dir";
372	const char RELPATH[] = "some_dir";
373	uint64_t ino = 42;
374	DIR *dir;
375	struct dirent *de;
376	/*
377	 * use enough entries to be > 4096 bytes, so getdirentries must be
378	 * called
379	 * multiple times.
380	 */
381	vector<struct dirent> ents0(122), ents1(102), ents2(30);
382	long bookmark;
383	int i = 0;
384
385	for (auto& it: ents0) {
386		snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
387		it.d_fileno = 2 + i;
388		it.d_off = (2 + i) * 1000;
389		it.d_namlen = strlen(it.d_name);
390		it.d_type = DT_REG;
391		i++;
392	}
393	for (auto& it: ents1) {
394		snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
395		it.d_fileno = 2 + i;
396		it.d_off = (2 + i) * 1000;
397		it.d_namlen = strlen(it.d_name);
398		it.d_type = DT_REG;
399		i++;
400	}
401	for (auto& it: ents2) {
402		snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
403		it.d_fileno = 2 + i;
404		it.d_off = (2 + i) * 1000;
405		it.d_namlen = strlen(it.d_name);
406		it.d_type = DT_REG;
407		i++;
408	}
409
410	expect_lookup(RELPATH, ino);
411	expect_opendir(ino);
412
413	expect_readdir(ino, 0, ents0);
414	expect_readdir(ino, 123000, ents1);
415	expect_readdir(ino, 225000, ents2);
416
417	errno = 0;
418	dir = opendir(FULLPATH);
419	ASSERT_NE(nullptr, dir) << strerror(errno);
420
421	for (i=0; i < 128; i++) {
422		errno = 0;
423		de = readdir(dir);
424		ASSERT_NE(nullptr, de) << strerror(errno);
425		EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
426	}
427	bookmark = telldir(dir);
428
429	for (; i < 232; i++) {
430		errno = 0;
431		de = readdir(dir);
432		ASSERT_NE(nullptr, de) << strerror(errno);
433		EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
434	}
435
436	seekdir(dir, bookmark);
437	de = readdir(dir);
438	ASSERT_NE(nullptr, de) << strerror(errno);
439	EXPECT_EQ(130ul, de->d_fileno);
440
441	leakdir(dir);
442}
443
444/*
445 * FUSE_READDIR returns a path with an embedded /. Obviously illegal, but
446 * nothing bad should happen.
447 */
448TEST_F(Readdir, slash)
449{
450	const char FULLPATH[] = "mountpoint/some_dir";
451	const char RELPATH[] = "some_dir";
452	uint64_t ino = 42;
453	DIR *dir;
454	struct dirent *de;
455	vector<struct dirent> ents(1);
456	vector<struct dirent> empty_ents(0);
457	const char foobar[] = "foo/bar";
458
459	expect_lookup(RELPATH, ino);
460	expect_opendir(ino);
461	ents[0].d_fileno = 4;
462	ents[0].d_off = 4000;
463	ents[0].d_namlen = sizeof(foobar);
464	ents[0].d_type = DT_REG;
465	strncpy(ents[0].d_name, foobar, ents[0].d_namlen);
466	expect_readdir(ino, 0, ents);
467	expect_readdir(ino, 4000, empty_ents);
468
469	errno = 0;
470	dir = opendir(FULLPATH);
471	ASSERT_NE(nullptr, dir) << strerror(errno);
472
473	errno = 0;
474	de = readdir(dir);
475	ASSERT_NE(nullptr, de) << strerror(errno);
476	EXPECT_EQ(4ul, de->d_fileno);
477	EXPECT_EQ(DT_REG, de->d_type);
478	EXPECT_EQ(sizeof(foobar), de->d_namlen);
479	EXPECT_EQ(0, strcmp(foobar, de->d_name));
480
481	ASSERT_EQ(nullptr, readdir(dir));
482	ASSERT_EQ(0, errno);
483
484	leakdir(dir);
485}
486
487TEST_F(Readdir_7_8, nodots)
488{
489	const char FULLPATH[] = "mountpoint/some_dir";
490	const char RELPATH[] = "some_dir";
491	uint64_t ino = 42;
492	DIR *dir;
493
494	expect_lookup(RELPATH, ino);
495	expect_opendir(ino);
496
497	EXPECT_CALL(*m_mock, process(
498		ResultOf([=](auto in) {
499			return (in.header.opcode == FUSE_READDIR &&
500				in.header.nodeid == ino);
501		}, Eq(true)),
502		_)
503	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
504		out.header.error = 0;
505		out.header.len = sizeof(out.header);
506	})));
507
508	errno = 0;
509	dir = opendir(FULLPATH);
510	ASSERT_NE(nullptr, dir) << strerror(errno);
511	errno = 0;
512	ASSERT_EQ(nullptr, readdir(dir));
513	ASSERT_EQ(0, errno);
514
515	leakdir(dir);
516}
517