]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/readdir.cc
fusefs: Make fuse file systems NFS-exportable
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / readdir.cc
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 = strlen(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 = strlen(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(NULL, dir) << strerror(errno);
94
95         errno = 0;
96         de = readdir(dir);
97         ASSERT_NE(NULL, 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(2, de->d_namlen);
106         EXPECT_EQ(0, strcmp("..", de->d_name));
107
108         errno = 0;
109         de = readdir(dir);
110         ASSERT_NE(NULL, 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(1, de->d_namlen);
115         EXPECT_EQ(0, strcmp(".", de->d_name));
116
117         ASSERT_EQ(NULL, readdir(dir));
118         ASSERT_EQ(0, errno);
119
120         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
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(NULL, dir) << strerror(errno);
145
146         errno = 0;
147         de = readdir(dir);
148         ASSERT_EQ(NULL, de);
149         ASSERT_EQ(EIO, errno);
150
151         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
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         /* Deliberately leak fd.  RELEASEDIR will be tested separately */
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         /* Deliberately leak fd1. */
232 }
233
234 /*
235  * FUSE_READDIR returns nothing, not even "." and "..".  This is legal, though
236  * the filesystem obviously won't be fully functional.
237  */
238 TEST_F(Readdir, nodots)
239 {
240         const char FULLPATH[] = "mountpoint/some_dir";
241         const char RELPATH[] = "some_dir";
242         uint64_t ino = 42;
243         DIR *dir;
244
245         expect_lookup(RELPATH, ino);
246         expect_opendir(ino);
247
248         EXPECT_CALL(*m_mock, process(
249                 ResultOf([=](auto in) {
250                         return (in->header.opcode == FUSE_READDIR &&
251                                 in->header.nodeid == ino);
252                 }, Eq(true)),
253                 _)
254         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
255                 out->header.error = 0;
256                 out->header.len = sizeof(out->header);
257         })));
258
259         errno = 0;
260         dir = opendir(FULLPATH);
261         ASSERT_NE(NULL, dir) << strerror(errno);
262         errno = 0;
263         ASSERT_EQ(NULL, readdir(dir));
264         ASSERT_EQ(0, errno);
265
266         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
267 }
268
269 /* telldir(3) and seekdir(3) should work with fuse */
270 TEST_F(Readdir, seekdir)
271 {
272         const char FULLPATH[] = "mountpoint/some_dir";
273         const char RELPATH[] = "some_dir";
274         uint64_t ino = 42;
275         DIR *dir;
276         struct dirent *de;
277         /*
278          * use enough entries to be > 4096 bytes, so getdirentries must be
279          * called
280          * multiple times.
281          */
282         vector<struct dirent> ents0(122), ents1(102), ents2(30);
283         long bookmark;
284         int i = 0;
285
286         for (auto& it: ents0) {
287                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
288                 it.d_fileno = 2 + i;
289                 it.d_off = (2 + i) * 1000;
290                 it.d_namlen = strlen(it.d_name);
291                 it.d_type = DT_REG;
292                 i++;
293         }
294         for (auto& it: ents1) {
295                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
296                 it.d_fileno = 2 + i;
297                 it.d_off = (2 + i) * 1000;
298                 it.d_namlen = strlen(it.d_name);
299                 it.d_type = DT_REG;
300                 i++;
301         }
302         for (auto& it: ents2) {
303                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
304                 it.d_fileno = 2 + i;
305                 it.d_off = (2 + i) * 1000;
306                 it.d_namlen = strlen(it.d_name);
307                 it.d_type = DT_REG;
308                 i++;
309         }
310
311         expect_lookup(RELPATH, ino);
312         expect_opendir(ino);
313
314         expect_readdir(ino, 0, ents0);
315         expect_readdir(ino, 123000, ents1);
316         expect_readdir(ino, 225000, ents2);
317
318         errno = 0;
319         dir = opendir(FULLPATH);
320         ASSERT_NE(NULL, dir) << strerror(errno);
321
322         for (i=0; i < 128; i++) {
323                 errno = 0;
324                 de = readdir(dir);
325                 ASSERT_NE(NULL, de) << strerror(errno);
326                 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
327         }
328         bookmark = telldir(dir);
329
330         for (; i < 232; i++) {
331                 errno = 0;
332                 de = readdir(dir);
333                 ASSERT_NE(NULL, de) << strerror(errno);
334                 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
335         }
336
337         seekdir(dir, bookmark);
338         de = readdir(dir);
339         ASSERT_NE(NULL, de) << strerror(errno);
340         EXPECT_EQ(130ul, de->d_fileno);
341
342         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
343 }
344
345 TEST_F(Readdir_7_8, nodots)
346 {
347         const char FULLPATH[] = "mountpoint/some_dir";
348         const char RELPATH[] = "some_dir";
349         uint64_t ino = 42;
350         DIR *dir;
351
352         expect_lookup(RELPATH, ino);
353         expect_opendir(ino);
354
355         EXPECT_CALL(*m_mock, process(
356                 ResultOf([=](auto in) {
357                         return (in->header.opcode == FUSE_READDIR &&
358                                 in->header.nodeid == ino);
359                 }, Eq(true)),
360                 _)
361         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
362                 out->header.error = 0;
363                 out->header.len = sizeof(out->header);
364         })));
365
366         errno = 0;
367         dir = opendir(FULLPATH);
368         ASSERT_NE(NULL, dir) << strerror(errno);
369         errno = 0;
370         ASSERT_EQ(NULL, readdir(dir));
371         ASSERT_EQ(0, errno);
372
373         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
374 }