]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/getattr.cc
fusefs: move common code from forget.cc to utils.cc
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / getattr.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 <sys/param.h>
35
36 #include <semaphore.h>
37 }
38
39 #include "mockfs.hh"
40 #include "utils.hh"
41
42 using namespace testing;
43
44 class Getattr : public FuseTest {
45 public:
46 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
47         uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec)
48 {
49         EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
50         .Times(times)
51         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
52                 SET_OUT_HEADER_LEN(out, entry);
53                 out.body.entry.attr.mode = mode;
54                 out.body.entry.nodeid = ino;
55                 out.body.entry.attr.nlink = 1;
56                 out.body.entry.attr_valid = attr_valid;
57                 out.body.entry.attr_valid_nsec = attr_valid_nsec;
58                 out.body.entry.attr.size = size;
59                 out.body.entry.entry_valid = UINT64_MAX;
60         })));
61 }
62 };
63
64 class Getattr_7_8: public FuseTest {
65 public:
66 virtual void SetUp() {
67         m_kernel_minor_version = 8;
68         FuseTest::SetUp();
69 }
70 };
71
72 /*
73  * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
74  * should use the cached attributes, rather than query the daemon
75  */
76 TEST_F(Getattr, attr_cache)
77 {
78         const char FULLPATH[] = "mountpoint/some_file.txt";
79         const char RELPATH[] = "some_file.txt";
80         const uint64_t ino = 42;
81         struct stat sb;
82
83         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
84         .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
85                 SET_OUT_HEADER_LEN(out, entry);
86                 out.body.entry.attr.mode = S_IFREG | 0644;
87                 out.body.entry.nodeid = ino;
88                 out.body.entry.entry_valid = UINT64_MAX;
89         })));
90         EXPECT_CALL(*m_mock, process(
91                 ResultOf([](auto in) {
92                         return (in.header.opcode == FUSE_GETATTR &&
93                                 in.header.nodeid == ino);
94                 }, Eq(true)),
95                 _)
96         ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
97                 SET_OUT_HEADER_LEN(out, attr);
98                 out.body.attr.attr_valid = UINT64_MAX;
99                 out.body.attr.attr.ino = ino;   // Must match nodeid
100                 out.body.attr.attr.mode = S_IFREG | 0644;
101         })));
102         EXPECT_EQ(0, stat(FULLPATH, &sb));
103         /* The second stat(2) should use cached attributes */
104         EXPECT_EQ(0, stat(FULLPATH, &sb));
105 }
106
107 /*
108  * If getattr returns a finite but non-zero cache timeout, then we should
109  * discard the cached attributes and requery the daemon after the timeout
110  * period passes.
111  */
112 TEST_F(Getattr, attr_cache_timeout)
113 {
114         const char FULLPATH[] = "mountpoint/some_file.txt";
115         const char RELPATH[] = "some_file.txt";
116         const uint64_t ino = 42;
117         struct stat sb;
118
119         expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
120         EXPECT_CALL(*m_mock, process(
121                 ResultOf([](auto in) {
122                         return (in.header.opcode == FUSE_GETATTR &&
123                                 in.header.nodeid == ino);
124                 }, Eq(true)),
125                 _)
126         ).Times(2)
127         .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
128                 SET_OUT_HEADER_LEN(out, attr);
129                 out.body.attr.attr_valid_nsec = NAP_NS / 2;
130                 out.body.attr.attr_valid = 0;
131                 out.body.attr.attr.ino = ino;   // Must match nodeid
132                 out.body.attr.attr.mode = S_IFREG | 0644;
133         })));
134
135         EXPECT_EQ(0, stat(FULLPATH, &sb));
136         nap();
137         /* Timeout has expired. stat(2) should requery the daemon */
138         EXPECT_EQ(0, stat(FULLPATH, &sb));
139 }
140
141 /* 
142  * If attr.blksize is zero, then the kernel should use a default value for
143  * st_blksize
144  */
145 TEST_F(Getattr, blksize_zero)
146 {
147         const char FULLPATH[] = "mountpoint/some_file.txt";
148         const char RELPATH[] = "some_file.txt";
149         const uint64_t ino = 42;
150         struct stat sb;
151
152         expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
153         EXPECT_CALL(*m_mock, process(
154                 ResultOf([](auto in) {
155                         return (in.header.opcode == FUSE_GETATTR &&
156                                 in.header.nodeid == ino);
157                 }, Eq(true)),
158                 _)
159         ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
160                 SET_OUT_HEADER_LEN(out, attr);
161                 out.body.attr.attr.mode = S_IFREG | 0644;
162                 out.body.attr.attr.ino = ino;   // Must match nodeid
163                 out.body.attr.attr.blksize = 0;
164                 out.body.attr.attr.size = 1;
165         })));
166
167         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
168         EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
169 }
170
171 TEST_F(Getattr, enoent)
172 {
173         const char FULLPATH[] = "mountpoint/some_file.txt";
174         const char RELPATH[] = "some_file.txt";
175         struct stat sb;
176         const uint64_t ino = 42;
177         sem_t sem;
178
179         ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
180
181         expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
182         EXPECT_CALL(*m_mock, process(
183                 ResultOf([](auto in) {
184                         return (in.header.opcode == FUSE_GETATTR &&
185                                 in.header.nodeid == ino);
186                 }, Eq(true)),
187                 _)
188         ).WillOnce(Invoke(ReturnErrno(ENOENT)));
189         // Since FUSE_GETATTR returns ENOENT, the kernel will reclaim the vnode
190         // and send a FUSE_FORGET
191         expect_forget(ino, 1, &sem);
192
193         EXPECT_NE(0, stat(FULLPATH, &sb));
194         EXPECT_EQ(ENOENT, errno);
195
196         sem_wait(&sem);
197         sem_destroy(&sem);
198 }
199
200 TEST_F(Getattr, ok)
201 {
202         const char FULLPATH[] = "mountpoint/some_file.txt";
203         const char RELPATH[] = "some_file.txt";
204         const uint64_t ino = 42;
205         struct stat sb;
206
207         expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
208         EXPECT_CALL(*m_mock, process(
209                 ResultOf([](auto in) {
210                         return (in.header.opcode == FUSE_GETATTR &&
211                                 in.body.getattr.getattr_flags == 0 &&
212                                 in.header.nodeid == ino);
213                 }, Eq(true)),
214                 _)
215         ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
216                 SET_OUT_HEADER_LEN(out, attr);
217                 out.body.attr.attr.ino = ino;   // Must match nodeid
218                 out.body.attr.attr.mode = S_IFREG | 0644;
219                 out.body.attr.attr.size = 1;
220                 out.body.attr.attr.blocks = 2;
221                 out.body.attr.attr.atime = 3;
222                 out.body.attr.attr.mtime = 4;
223                 out.body.attr.attr.ctime = 5;
224                 out.body.attr.attr.atimensec = 6;
225                 out.body.attr.attr.mtimensec = 7;
226                 out.body.attr.attr.ctimensec = 8;
227                 out.body.attr.attr.nlink = 9;
228                 out.body.attr.attr.uid = 10;
229                 out.body.attr.attr.gid = 11;
230                 out.body.attr.attr.rdev = 12;
231                 out.body.attr.attr.blksize = 12345;
232         })));
233
234         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
235         EXPECT_EQ(1, sb.st_size);
236         EXPECT_EQ(2, sb.st_blocks);
237         EXPECT_EQ(3, sb.st_atim.tv_sec);
238         EXPECT_EQ(6, sb.st_atim.tv_nsec);
239         EXPECT_EQ(4, sb.st_mtim.tv_sec);
240         EXPECT_EQ(7, sb.st_mtim.tv_nsec);
241         EXPECT_EQ(5, sb.st_ctim.tv_sec);
242         EXPECT_EQ(8, sb.st_ctim.tv_nsec);
243         EXPECT_EQ(9ull, sb.st_nlink);
244         EXPECT_EQ(10ul, sb.st_uid);
245         EXPECT_EQ(11ul, sb.st_gid);
246         EXPECT_EQ(12ul, sb.st_rdev);
247         EXPECT_EQ((blksize_t)12345, sb.st_blksize);
248         EXPECT_EQ(ino, sb.st_ino);
249         EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
250
251         //st_birthtim and st_flags are not supported by protocol 7.8.  They're
252         //only supported as OS-specific extensions to OSX.
253         //EXPECT_EQ(, sb.st_birthtim);
254         //EXPECT_EQ(, sb.st_flags);
255         
256         //FUSE can't set st_blksize until protocol 7.9
257 }
258
259 /*
260  * FUSE_GETATTR returns a different file type, even though the entry cache
261  * hasn't expired.  This is a server bug!  It probably means that the server
262  * removed the file and recreated it with the same inode but a different vtyp.
263  * The best thing fusefs can do is return ENOENT to the caller.  After all, the
264  * entry must not have existed recently.
265  */
266 TEST_F(Getattr, vtyp_conflict)
267 {
268         const char FULLPATH[] = "mountpoint/some_file.txt";
269         const char RELPATH[] = "some_file.txt";
270         const uint64_t ino = 42;
271         struct stat sb;
272         sem_t sem;
273
274         ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
275
276         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
277         .WillOnce(Invoke(
278                 ReturnImmediate([=](auto in __unused, auto& out) {
279                 SET_OUT_HEADER_LEN(out, entry);
280                 out.body.entry.attr.mode = S_IFREG | 0644;
281                 out.body.entry.nodeid = ino;
282                 out.body.entry.attr.nlink = 1;
283                 out.body.entry.attr_valid = 0;
284                 out.body.entry.entry_valid = UINT64_MAX;
285         })));
286         EXPECT_CALL(*m_mock, process(
287                 ResultOf([](auto in) {
288                         return (in.header.opcode == FUSE_GETATTR &&
289                                 in.body.getattr.getattr_flags == 0 &&
290                                 in.header.nodeid == ino);
291                 }, Eq(true)),
292                 _)
293         ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
294                 SET_OUT_HEADER_LEN(out, attr);
295                 out.body.attr.attr.ino = ino;   // Must match nodeid
296                 out.body.attr.attr.mode = S_IFDIR | 0755;       // Changed!
297                 out.body.attr.attr.nlink = 2;
298         })));
299         // We should reclaim stale vnodes
300         expect_forget(ino, 1, &sem);
301
302         ASSERT_NE(0, stat(FULLPATH, &sb));
303         EXPECT_EQ(errno, ENOENT);
304
305         sem_wait(&sem);
306         sem_destroy(&sem);
307 }
308
309 TEST_F(Getattr_7_8, ok)
310 {
311         const char FULLPATH[] = "mountpoint/some_file.txt";
312         const char RELPATH[] = "some_file.txt";
313         const uint64_t ino = 42;
314         struct stat sb;
315
316         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
317         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
318                 SET_OUT_HEADER_LEN(out, entry_7_8);
319                 out.body.entry.attr.mode = S_IFREG | 0644;
320                 out.body.entry.nodeid = ino;
321                 out.body.entry.attr.nlink = 1;
322                 out.body.entry.attr.size = 1;
323         })));
324         EXPECT_CALL(*m_mock, process(
325                 ResultOf([](auto in) {
326                         return (in.header.opcode == FUSE_GETATTR &&
327                                 in.header.nodeid == ino);
328                 }, Eq(true)),
329                 _)
330         ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
331                 SET_OUT_HEADER_LEN(out, attr_7_8);
332                 out.body.attr.attr.ino = ino;   // Must match nodeid
333                 out.body.attr.attr.mode = S_IFREG | 0644;
334                 out.body.attr.attr.size = 1;
335                 out.body.attr.attr.blocks = 2;
336                 out.body.attr.attr.atime = 3;
337                 out.body.attr.attr.mtime = 4;
338                 out.body.attr.attr.ctime = 5;
339                 out.body.attr.attr.atimensec = 6;
340                 out.body.attr.attr.mtimensec = 7;
341                 out.body.attr.attr.ctimensec = 8;
342                 out.body.attr.attr.nlink = 9;
343                 out.body.attr.attr.uid = 10;
344                 out.body.attr.attr.gid = 11;
345                 out.body.attr.attr.rdev = 12;
346         })));
347
348         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
349         EXPECT_EQ(1, sb.st_size);
350         EXPECT_EQ(2, sb.st_blocks);
351         EXPECT_EQ(3, sb.st_atim.tv_sec);
352         EXPECT_EQ(6, sb.st_atim.tv_nsec);
353         EXPECT_EQ(4, sb.st_mtim.tv_sec);
354         EXPECT_EQ(7, sb.st_mtim.tv_nsec);
355         EXPECT_EQ(5, sb.st_ctim.tv_sec);
356         EXPECT_EQ(8, sb.st_ctim.tv_nsec);
357         EXPECT_EQ(9ull, sb.st_nlink);
358         EXPECT_EQ(10ul, sb.st_uid);
359         EXPECT_EQ(11ul, sb.st_gid);
360         EXPECT_EQ(12ul, sb.st_rdev);
361         EXPECT_EQ(ino, sb.st_ino);
362         EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
363
364         //st_birthtim and st_flags are not supported by protocol 7.8.  They're
365         //only supported as OS-specific extensions to OSX.
366 }