]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/readdir.cc
THIS BRANCH IS OBSOLETE, PLEASE READ:
[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  * $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 }