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:
expect_lookup(const char * relpath,uint64_t ino)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:
SetUp()54 virtual void SetUp() {
55 m_kernel_minor_version = 8;
56 Readdir::SetUp();
57 }
58
expect_lookup(const char * relpath,uint64_t ino)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 ".." */
TEST_F(Readdir,dots)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
TEST_F(Readdir,eio)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) */
TEST_F(Readdir,getdirentries)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 */
TEST_F(Readdir,getdirentries_concurrent)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 */
TEST_F(Readdir,nodots)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 */
TEST_F(Readdir,seekdir)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
TEST_F(Readdir_7_8,nodots)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 }
378