]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/readdir.cc
fusefs: fix a panic in VOP_READDIR
[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.fh == FH &&
56                                 in->body.readdir.offset == off);
57                 }, Eq(true)),
58                 _)
59         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
60                 struct fuse_dirent *fde = (struct fuse_dirent*)out->body.bytes;
61                 int i = 0;
62
63                 out->header.error = 0;
64                 out->header.len = 0;
65
66                 for (const auto& it: ents) {
67                         size_t entlen, entsize;
68
69                         fde->ino = it.d_fileno;
70                         fde->off = it.d_off;
71                         fde->type = it.d_type;
72                         fde->namelen = it.d_namlen;
73                         strncpy(fde->name, it.d_name, it.d_namlen);
74                         entlen = FUSE_NAME_OFFSET + fde->namelen;
75                         entsize = FUSE_DIRENT_SIZE(fde);
76                         /* 
77                          * The FUSE protocol does not require zeroing out the
78                          * unused portion of the name.  But it's a good
79                          * practice to prevent information disclosure to the
80                          * FUSE client, even though the client is usually the
81                          * kernel
82                          */
83                         memset(fde->name + fde->namelen, 0, entsize - entlen);
84                         if (out->header.len + entsize > in->body.read.size) {
85                                 printf("Overflow in readdir expectation: i=%d\n"
86                                         , i);
87                                 break;
88                         }
89                         out->header.len += entsize;
90                         fde = (struct fuse_dirent*)
91                                 ((long*)fde + entsize / sizeof(long));
92                         i++;
93                 }
94                 out->header.len += sizeof(out->header);
95         })));
96
97 }
98 };
99
100 /* FUSE_READDIR returns nothing but "." and ".." */
101 TEST_F(Readdir, dots)
102 {
103         const char FULLPATH[] = "mountpoint/some_dir";
104         const char RELPATH[] = "some_dir";
105         uint64_t ino = 42;
106         DIR *dir;
107         struct dirent *de;
108         vector<struct dirent> ents(2);
109         vector<struct dirent> empty_ents(0);
110         const char *dot = ".";
111         const char *dotdot = "..";
112
113         expect_lookup(RELPATH, ino);
114         expect_opendir(ino);
115         ents[0].d_fileno = 2;
116         ents[0].d_off = 2000;
117         ents[0].d_namlen = strlen(dotdot);
118         ents[0].d_type = DT_DIR;
119         strncpy(ents[0].d_name, dotdot, ents[0].d_namlen);
120         ents[1].d_fileno = 3;
121         ents[1].d_off = 3000;
122         ents[1].d_namlen = strlen(dot);
123         ents[1].d_type = DT_DIR;
124         strncpy(ents[1].d_name, dot, ents[1].d_namlen);
125         expect_readdir(ino, 0, ents);
126         expect_readdir(ino, 3000, empty_ents);
127
128         errno = 0;
129         dir = opendir(FULLPATH);
130         ASSERT_NE(NULL, dir) << strerror(errno);
131
132         errno = 0;
133         de = readdir(dir);
134         ASSERT_NE(NULL, de) << strerror(errno);
135         EXPECT_EQ(2ul, de->d_fileno);
136         /*
137          * fuse(4) doesn't actually set d_off, which is ok for now because
138          * nothing uses it.
139          */
140         //EXPECT_EQ(2000, de->d_off);
141         EXPECT_EQ(DT_DIR, de->d_type);
142         EXPECT_EQ(2, de->d_namlen);
143         EXPECT_EQ(0, strcmp("..", de->d_name));
144
145         errno = 0;
146         de = readdir(dir);
147         ASSERT_NE(NULL, de) << strerror(errno);
148         EXPECT_EQ(3ul, de->d_fileno);
149         //EXPECT_EQ(3000, de->d_off);
150         EXPECT_EQ(DT_DIR, de->d_type);
151         EXPECT_EQ(1, de->d_namlen);
152         EXPECT_EQ(0, strcmp(".", de->d_name));
153
154         ASSERT_EQ(NULL, readdir(dir));
155         ASSERT_EQ(0, errno);
156
157         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
158 }
159
160 TEST_F(Readdir, eio)
161 {
162         const char FULLPATH[] = "mountpoint/some_dir";
163         const char RELPATH[] = "some_dir";
164         uint64_t ino = 42;
165         DIR *dir;
166         struct dirent *de;
167
168         expect_lookup(RELPATH, ino);
169         expect_opendir(ino);
170         EXPECT_CALL(*m_mock, process(
171                 ResultOf([=](auto in) {
172                         return (in->header.opcode == FUSE_READDIR &&
173                                 in->header.nodeid == ino &&
174                                 in->body.readdir.offset == 0);
175                 }, Eq(true)),
176                 _)
177         ).WillOnce(Invoke(ReturnErrno(EIO)));
178
179         errno = 0;
180         dir = opendir(FULLPATH);
181         ASSERT_NE(NULL, dir) << strerror(errno);
182
183         errno = 0;
184         de = readdir(dir);
185         ASSERT_EQ(NULL, de);
186         ASSERT_EQ(EIO, errno);
187
188         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
189 }
190
191 /* getdirentries(2) can use a larger buffer size than readdir(3) */
192 TEST_F(Readdir, getdirentries)
193 {
194         const char FULLPATH[] = "mountpoint/some_dir";
195         const char RELPATH[] = "some_dir";
196         uint64_t ino = 42;
197         int fd;
198         char buf[8192];
199         ssize_t r;
200
201         expect_lookup(RELPATH, ino);
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         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
212                 out->header.error = 0;
213                 out->header.len = sizeof(out->header);
214         })));
215
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) << strerror(errno);
220
221         /* Deliberately leak fd.  RELEASEDIR will be tested separately */
222 }
223
224 /* 
225  * Nothing bad should happen if getdirentries is called on two file descriptors
226  * which were concurrently open, but one has already been closed.
227  * This is a regression test for a specific bug dating from r238402.
228  */
229 TEST_F(Readdir, getdirentries_concurrent)
230 {
231         const char FULLPATH[] = "mountpoint/some_dir";
232         const char RELPATH[] = "some_dir";
233         uint64_t ino = 42;
234         int fd0, fd1;
235         char buf[8192];
236         ssize_t r;
237
238         FuseTest::expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 2);
239         expect_opendir(ino);
240
241         EXPECT_CALL(*m_mock, process(
242                 ResultOf([=](auto in) {
243                         return (in->header.opcode == FUSE_READDIR &&
244                                 in->header.nodeid == ino &&
245                                 in->body.readdir.size == 8192);
246                 }, Eq(true)),
247                 _)
248         ).Times(2)
249         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
250                 out->header.error = 0;
251                 out->header.len = sizeof(out->header);
252         })));
253
254         fd0 = open(FULLPATH, O_DIRECTORY);
255         ASSERT_LE(0, fd0) << strerror(errno);
256
257         fd1 = open(FULLPATH, O_DIRECTORY);
258         ASSERT_LE(0, fd1) << strerror(errno);
259
260         r = getdirentries(fd0, buf, sizeof(buf), 0);
261         ASSERT_EQ(0, r) << strerror(errno);
262
263         EXPECT_EQ(0, close(fd0)) << strerror(errno);
264
265         r = getdirentries(fd1, buf, sizeof(buf), 0);
266         ASSERT_EQ(0, r) << strerror(errno);
267
268         /* Deliberately leak fd1. */
269 }
270
271 /*
272  * FUSE_READDIR returns nothing, not even "." and "..".  This is legal, though
273  * the filesystem obviously won't be fully functional.
274  */
275 TEST_F(Readdir, nodots)
276 {
277         const char FULLPATH[] = "mountpoint/some_dir";
278         const char RELPATH[] = "some_dir";
279         uint64_t ino = 42;
280         DIR *dir;
281
282         expect_lookup(RELPATH, ino);
283         expect_opendir(ino);
284
285         EXPECT_CALL(*m_mock, process(
286                 ResultOf([=](auto in) {
287                         return (in->header.opcode == FUSE_READDIR &&
288                                 in->header.nodeid == ino);
289                 }, Eq(true)),
290                 _)
291         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
292                 out->header.error = 0;
293                 out->header.len = sizeof(out->header);
294         })));
295
296         errno = 0;
297         dir = opendir(FULLPATH);
298         ASSERT_NE(NULL, dir) << strerror(errno);
299         errno = 0;
300         ASSERT_EQ(NULL, readdir(dir));
301         ASSERT_EQ(0, errno);
302
303         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
304 }
305
306 /* telldir(3) and seekdir(3) should work with fuse */
307 TEST_F(Readdir, seekdir)
308 {
309         const char FULLPATH[] = "mountpoint/some_dir";
310         const char RELPATH[] = "some_dir";
311         uint64_t ino = 42;
312         DIR *dir;
313         struct dirent *de;
314         /*
315          * use enough entries to be > 4096 bytes, so getdirentries must be
316          * called
317          * multiple times.
318          */
319         vector<struct dirent> ents0(122), ents1(102), ents2(30);
320         long bookmark;
321         int i = 0;
322
323         for (auto& it: ents0) {
324                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
325                 it.d_fileno = 2 + i;
326                 it.d_off = (2 + i) * 1000;
327                 it.d_namlen = strlen(it.d_name);
328                 it.d_type = DT_REG;
329                 i++;
330         }
331         for (auto& it: ents1) {
332                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
333                 it.d_fileno = 2 + i;
334                 it.d_off = (2 + i) * 1000;
335                 it.d_namlen = strlen(it.d_name);
336                 it.d_type = DT_REG;
337                 i++;
338         }
339         for (auto& it: ents2) {
340                 snprintf(it.d_name, MAXNAMLEN, "file.%d", i);
341                 it.d_fileno = 2 + i;
342                 it.d_off = (2 + i) * 1000;
343                 it.d_namlen = strlen(it.d_name);
344                 it.d_type = DT_REG;
345                 i++;
346         }
347
348         expect_lookup(RELPATH, ino);
349         expect_opendir(ino);
350
351         expect_readdir(ino, 0, ents0);
352         expect_readdir(ino, 123000, ents1);
353         expect_readdir(ino, 225000, ents2);
354
355         errno = 0;
356         dir = opendir(FULLPATH);
357         ASSERT_NE(NULL, dir) << strerror(errno);
358
359         for (i=0; i < 128; i++) {
360                 errno = 0;
361                 de = readdir(dir);
362                 ASSERT_NE(NULL, de) << strerror(errno);
363                 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
364         }
365         bookmark = telldir(dir);
366
367         for (; i < 232; i++) {
368                 errno = 0;
369                 de = readdir(dir);
370                 ASSERT_NE(NULL, de) << strerror(errno);
371                 EXPECT_EQ(2 + (ino_t)i, de->d_fileno);
372         }
373
374         seekdir(dir, bookmark);
375         de = readdir(dir);
376         ASSERT_NE(NULL, de) << strerror(errno);
377         EXPECT_EQ(130ul, de->d_fileno);
378
379         /* Deliberately leak dir.  RELEASEDIR will be tested separately */
380 }