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