]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/readdir.cc
fuse(4): add tests for opendir and readdir
[FreeBSD/FreeBSD.git] / tests / sys / fs / fuse / 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 const static uint64_t FH = 0xdeadbeef1a7ebabe;
44 public:
45 void expect_lookup(const char *relpath, uint64_t ino)
46 {
47         EXPECT_LOOKUP(1, relpath).WillRepeatedly(Invoke([=](auto in, auto out) {
48                 out->header.unique = in->header.unique;
49                 SET_OUT_HEADER_LEN(out, entry);
50                 out->body.entry.attr.mode = S_IFDIR | 0755;
51                 out->body.entry.nodeid = ino;
52                 out->body.entry.attr_valid = UINT64_MAX;
53         }));
54 }
55
56 void expect_opendir(uint64_t ino)
57 {
58         EXPECT_CALL(*m_mock, process(
59                 ResultOf([](auto in) {
60                         return (in->header.opcode == FUSE_STATFS);
61                 }, Eq(true)),
62                 _)
63         ).WillOnce(Invoke([=](auto in, auto out) {
64                 out->header.unique = in->header.unique;
65                 SET_OUT_HEADER_LEN(out, statfs);
66         }));
67
68         EXPECT_CALL(*m_mock, process(
69                 ResultOf([=](auto in) {
70                         return (in->header.opcode == FUSE_OPENDIR &&
71                                 in->header.nodeid == ino);
72                 }, Eq(true)),
73                 _)
74         ).WillOnce(Invoke([=](auto in, auto out) {
75                 out->header.unique = in->header.unique;
76                 out->header.len = sizeof(out->header);
77                 SET_OUT_HEADER_LEN(out, open);
78                 out->body.open.fh = FH;
79         }));
80 }
81
82 void expect_readdir(uint64_t ino, uint64_t off, vector<struct dirent> &ents)
83 {
84         EXPECT_CALL(*m_mock, process(
85                 ResultOf([=](auto in) {
86                         return (in->header.opcode == FUSE_READDIR &&
87                                 in->header.nodeid == ino &&
88                                 in->body.readdir.offset == off);
89                 }, Eq(true)),
90                 _)
91         ).WillRepeatedly(Invoke([=](auto in, auto out) {
92                 struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes;
93                 int i = 0;
94
95                 out->header.unique = in->header.unique;
96                 out->header.error = 0;
97                 out->header.len = 0;
98
99                 for (const auto& it: ents) {
100                         size_t entlen, entsize;
101
102                         fde->ino = it.d_fileno;
103                         fde->off = it.d_off;
104                         fde->type = it.d_type;
105                         fde->namelen = it.d_namlen;
106                         strncpy(fde->name, it.d_name, it.d_namlen);
107                         entlen = FUSE_NAME_OFFSET + fde->namelen;
108                         entsize = FUSE_DIRENT_SIZE(fde);
109                         /* 
110                          * The FUSE protocol does not require zeroing out the
111                          * unused portion of the name.  But it's a good
112                          * practice to prevent information disclosure to the
113                          * FUSE client, even though the client is usually the
114                          * kernel
115                          */
116                         memset(fde->name + fde->namelen, 0, entsize - entlen);
117                         if (out->header.len + entsize > in->body.read.size) {
118                                 printf("Overflow in readdir expectation: i=%d\n"
119                                         , i);
120                                 break;
121                         }
122                         out->header.len += entsize;
123                         fde = (struct fuse_dirent*)
124                                 ((long*)fde + entsize / sizeof(long));
125                         i++;
126                 }
127                 out->header.len += sizeof(out->header);
128         }));
129
130 }
131 };
132
133 /* FUSE_READDIR returns nothing but "." and ".." */
134 TEST_F(Readdir, dots)
135 {
136         const char FULLPATH[] = "mountpoint/some_dir";
137         const char RELPATH[] = "some_dir";
138         uint64_t ino = 42;
139         DIR *dir;
140         struct dirent *de;
141         vector<struct dirent> ents(2);
142         vector<struct dirent> empty_ents(0);
143         const char *dot = ".";
144         const char *dotdot = "..";
145
146         expect_lookup(RELPATH, ino);
147         expect_opendir(ino);
148         ents[0].d_fileno = 2;
149         ents[0].d_off = 2000;
150         ents[0].d_namlen = strlen(dotdot);
151         ents[0].d_type = DT_DIR;
152         strncpy(ents[0].d_name, dotdot, ents[0].d_namlen);
153         ents[1].d_fileno = 3;
154         ents[1].d_off = 3000;
155         ents[1].d_namlen = strlen(dot);
156         ents[1].d_type = DT_DIR;
157         strncpy(ents[1].d_name, dot, ents[1].d_namlen);
158         expect_readdir(ino, 0, ents);
159         expect_readdir(ino, 3000, empty_ents);
160
161         errno = 0;
162         dir = opendir(FULLPATH);
163         ASSERT_NE(NULL, dir) << strerror(errno);
164
165         errno = 0;
166         de = readdir(dir);
167         ASSERT_NE(NULL, de) << strerror(errno);
168         EXPECT_EQ(2ul, de->d_fileno);
169         /*
170          * fuse(4) doesn't actually set d_off, which is ok for now because
171          * nothing uses it.
172          */
173         //EXPECT_EQ(2000, de->d_off);
174         EXPECT_EQ(DT_DIR, de->d_type);
175         EXPECT_EQ(2, de->d_namlen);
176         EXPECT_EQ(0, strcmp("..", de->d_name));
177
178         errno = 0;
179         de = readdir(dir);
180         ASSERT_NE(NULL, de) << strerror(errno);
181         EXPECT_EQ(3ul, de->d_fileno);
182         //EXPECT_EQ(3000, de->d_off);
183         EXPECT_EQ(DT_DIR, de->d_type);
184         EXPECT_EQ(1, de->d_namlen);
185         EXPECT_EQ(0, strcmp(".", de->d_name));
186
187         ASSERT_EQ(NULL, readdir(dir));
188         ASSERT_EQ(0, errno);
189
190         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
191 }
192
193 TEST_F(Readdir, eio)
194 {
195         const char FULLPATH[] = "mountpoint/some_dir";
196         const char RELPATH[] = "some_dir";
197         uint64_t ino = 42;
198         DIR *dir;
199         struct dirent *de;
200
201         expect_lookup(RELPATH, ino);
202         expect_opendir(ino);
203         EXPECT_CALL(*m_mock, process(
204                 ResultOf([=](auto in) {
205                         return (in->header.opcode == FUSE_READDIR &&
206                                 in->header.nodeid == ino &&
207                                 in->body.readdir.offset == 0);
208                 }, Eq(true)),
209                 _)
210         ).WillOnce(Invoke(ReturnErrno(EIO)));
211
212         errno = 0;
213         dir = opendir(FULLPATH);
214         ASSERT_NE(NULL, dir) << strerror(errno);
215
216         errno = 0;
217         de = readdir(dir);
218         ASSERT_EQ(NULL, de);
219         ASSERT_EQ(EIO, errno);
220
221         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
222 }
223
224 /*
225  * FUSE_READDIR returns nothing, not even "." and "..".  This is legal, though
226  * the filesystem obviously won't be fully functional.
227  */
228 TEST_F(Readdir, nodots)
229 {
230         const char FULLPATH[] = "mountpoint/some_dir";
231         const char RELPATH[] = "some_dir";
232         uint64_t ino = 42;
233         DIR *dir;
234
235         expect_lookup(RELPATH, ino);
236         expect_opendir(ino);
237
238         EXPECT_CALL(*m_mock, process(
239                 ResultOf([=](auto in) {
240                         return (in->header.opcode == FUSE_READDIR &&
241                                 in->header.nodeid == ino);
242                 }, Eq(true)),
243                 _)
244         ).WillOnce(Invoke([=](auto in, auto out) {
245                 out->header.unique = in->header.unique;
246                 out->header.error = 0;
247                 out->header.len = sizeof(out->header);
248         }));
249
250         errno = 0;
251         dir = opendir(FULLPATH);
252         ASSERT_NE(NULL, dir) << strerror(errno);
253         errno = 0;
254         ASSERT_EQ(NULL, readdir(dir));
255         ASSERT_EQ(0, errno);
256
257         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
258 }
259
260 /* telldir(3) and seekdir(3) should work with fuse */
261 TEST_F(Readdir, seekdir)
262 {
263         const char FULLPATH[] = "mountpoint/some_dir";
264         const char RELPATH[] = "some_dir";
265         uint64_t ino = 42;
266         DIR *dir;
267         struct dirent *de;
268         /*
269          * use enough entries to be > 4096 bytes, so getdirentries must be
270          * called
271          * multiple times.
272          */
273         vector<struct dirent> ents0(122), ents1(102), ents2(30);
274         long bookmark;
275         int i = 0;
276
277         for (auto& it: ents0) {
278                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
279                 it.d_fileno = 2 + i;
280                 it.d_off = (2 + i) * 1000;
281                 it.d_namlen = strlen(it.d_name);
282                 it.d_type = DT_REG;
283                 i++;
284         }
285         for (auto& it: ents1) {
286                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
287                 it.d_fileno = 2 + i;
288                 it.d_off = (2 + i) * 1000;
289                 it.d_namlen = strlen(it.d_name);
290                 it.d_type = DT_REG;
291                 i++;
292         }
293         for (auto& it: ents2) {
294                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
295                 it.d_fileno = 2 + i;
296                 it.d_off = (2 + i) * 1000;
297                 it.d_namlen = strlen(it.d_name);
298                 it.d_type = DT_REG;
299                 i++;
300         }
301
302         expect_lookup(RELPATH, ino);
303         expect_opendir(ino);
304
305         expect_readdir(ino, 0, ents0);
306         expect_readdir(ino, 123000, ents1);
307         expect_readdir(ino, 225000, ents2);
308
309         errno = 0;
310         dir = opendir(FULLPATH);
311         ASSERT_NE(NULL, dir) << strerror(errno);
312
313         for (i=0; i < 128; i++) {
314                 errno = 0;
315                 de = readdir(dir);
316                 ASSERT_NE(NULL, de) << strerror(errno);
317                 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
318         }
319         bookmark = telldir(dir);
320
321         for (; i < 232; i++) {
322                 errno = 0;
323                 de = readdir(dir);
324                 ASSERT_NE(NULL, de) << strerror(errno);
325                 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
326         }
327
328         seekdir(dir, bookmark);
329         de = readdir(dir);
330         ASSERT_NE(NULL, de) << strerror(errno);
331         EXPECT_EQ(130ul, de->d_fileno);
332
333         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
334 }