]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/setattr.cc
fusefs: adapt the tests to the fuse => fusefs rename
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / setattr.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 <sys/stat.h>
33
34 #include <fcntl.h>
35 }
36
37 #include "mockfs.hh"
38 #include "utils.hh"
39
40 using namespace testing;
41
42 class Setattr : public FuseTest {};
43
44
45 /*
46  * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
47  * should use the cached attributes, rather than query the daemon
48  */
49 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
50 TEST_F(Setattr, DISABLED_attr_cache)
51 {
52         const char FULLPATH[] = "mountpoint/some_file.txt";
53         const char RELPATH[] = "some_file.txt";
54         const uint64_t ino = 42;
55         struct stat sb;
56         const mode_t newmode = 0644;
57
58         EXPECT_LOOKUP(1, RELPATH)
59         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
60                 SET_OUT_HEADER_LEN(out, entry);
61                 out->body.entry.attr.mode = S_IFREG | 0644;
62                 out->body.entry.nodeid = ino;
63         })));
64
65         EXPECT_CALL(*m_mock, process(
66                 ResultOf([](auto in) {
67                         /* In protocol 7.23, ctime will be changed too */
68                         return (in->header.opcode == FUSE_SETATTR &&
69                                 in->header.nodeid == ino);
70                 }, Eq(true)),
71                 _)
72         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
73                 SET_OUT_HEADER_LEN(out, attr);
74                 out->body.attr.attr.ino = ino;  // Must match nodeid
75                 out->body.attr.attr.mode = S_IFREG | newmode;
76         })));
77         EXPECT_CALL(*m_mock, process(
78                 ResultOf([](auto in) {
79                         return (in->header.opcode == FUSE_GETATTR);
80                 }, Eq(true)),
81                 _)
82         ).Times(0);
83
84         /* Set an attribute with SETATTR */
85         ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
86
87         /* The stat(2) should use cached attributes */
88         ASSERT_EQ(0, stat(FULLPATH, &sb));
89         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
90 }
91
92 /* Change the mode of a file */
93 TEST_F(Setattr, chmod)
94 {
95         const char FULLPATH[] = "mountpoint/some_file.txt";
96         const char RELPATH[] = "some_file.txt";
97         const uint64_t ino = 42;
98         const mode_t oldmode = 0755;
99         const mode_t newmode = 0644;
100
101         EXPECT_LOOKUP(1, RELPATH)
102         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
103                 SET_OUT_HEADER_LEN(out, entry);
104                 out->body.entry.attr.mode = S_IFREG | oldmode;
105                 out->body.entry.nodeid = ino;
106                 out->body.entry.attr.mode = S_IFREG | oldmode;
107         })));
108
109         EXPECT_CALL(*m_mock, process(
110                 ResultOf([](auto in) {
111                         /* In protocol 7.23, ctime will be changed too */
112                         uint32_t valid = FATTR_MODE;
113                         return (in->header.opcode == FUSE_SETATTR &&
114                                 in->header.nodeid == ino &&
115                                 in->body.setattr.valid == valid &&
116                                 in->body.setattr.mode == newmode);
117                 }, Eq(true)),
118                 _)
119         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
120                 SET_OUT_HEADER_LEN(out, attr);
121                 out->body.attr.attr.ino = ino;  // Must match nodeid
122                 out->body.attr.attr.mode = S_IFREG | newmode;
123         })));
124         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
125 }
126
127 /* Change the owner and group of a file */
128 TEST_F(Setattr, chown)
129 {
130         const char FULLPATH[] = "mountpoint/some_file.txt";
131         const char RELPATH[] = "some_file.txt";
132         const uint64_t ino = 42;
133         const gid_t oldgroup = 66;
134         const gid_t newgroup = 99;
135         const uid_t olduser = 33;
136         const uid_t newuser = 44;
137
138         EXPECT_LOOKUP(1, RELPATH)
139         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
140                 SET_OUT_HEADER_LEN(out, entry);
141                 out->body.entry.attr.mode = S_IFREG | 0644;
142                 out->body.entry.nodeid = ino;
143                 out->body.entry.attr.gid = oldgroup;
144                 out->body.entry.attr.uid = olduser;
145         })));
146
147         EXPECT_CALL(*m_mock, process(
148                 ResultOf([](auto in) {
149                         /* In protocol 7.23, ctime will be changed too */
150                         uint32_t valid = FATTR_GID | FATTR_UID;
151                         return (in->header.opcode == FUSE_SETATTR &&
152                                 in->header.nodeid == ino &&
153                                 in->body.setattr.valid == valid &&
154                                 in->body.setattr.uid == newuser &&
155                                 in->body.setattr.gid == newgroup);
156                 }, Eq(true)),
157                 _)
158         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
159                 SET_OUT_HEADER_LEN(out, attr);
160                 out->body.attr.attr.ino = ino;  // Must match nodeid
161                 out->body.attr.attr.mode = S_IFREG | 0644;
162                 out->body.attr.attr.uid = newuser;
163                 out->body.attr.attr.gid = newgroup;
164         })));
165         EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
166 }
167
168
169
170 /* 
171  * FUSE daemons are allowed to check permissions however they like.  If the
172  * daemon returns EPERM, even if the file permissions "should" grant access,
173  * then fuse(4) should return EPERM too.
174  */
175 TEST_F(Setattr, eperm)
176 {
177         const char FULLPATH[] = "mountpoint/some_file.txt";
178         const char RELPATH[] = "some_file.txt";
179         const uint64_t ino = 42;
180
181         EXPECT_LOOKUP(1, RELPATH)
182         .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
183                 SET_OUT_HEADER_LEN(out, entry);
184                 out->body.entry.attr.mode = S_IFREG | 0777;
185                 out->body.entry.nodeid = ino;
186                 out->body.entry.attr.uid = in->header.uid;
187                 out->body.entry.attr.gid = in->header.gid;
188         })));
189
190         EXPECT_CALL(*m_mock, process(
191                 ResultOf([](auto in) {
192                         return (in->header.opcode == FUSE_SETATTR &&
193                                 in->header.nodeid == ino);
194                 }, Eq(true)),
195                 _)
196         ).WillOnce(Invoke(ReturnErrno(EPERM)));
197         EXPECT_NE(0, truncate(FULLPATH, 10));
198         EXPECT_EQ(EPERM, errno);
199 }
200
201 /* Change the mode of an open file, by its file descriptor */
202 TEST_F(Setattr, fchmod)
203 {
204         const char FULLPATH[] = "mountpoint/some_file.txt";
205         const char RELPATH[] = "some_file.txt";
206         uint64_t ino = 42;
207         int fd;
208         const mode_t oldmode = 0755;
209         const mode_t newmode = 0644;
210
211         EXPECT_LOOKUP(1, RELPATH)
212         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
213                 SET_OUT_HEADER_LEN(out, entry);
214                 out->body.entry.attr.mode = S_IFREG | oldmode;
215                 out->body.entry.nodeid = ino;
216                 out->body.entry.attr_valid = UINT64_MAX;
217         })));
218
219         EXPECT_CALL(*m_mock, process(
220                 ResultOf([=](auto in) {
221                         return (in->header.opcode == FUSE_OPEN &&
222                                 in->header.nodeid == ino);
223                 }, Eq(true)),
224                 _)
225         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
226                 out->header.len = sizeof(out->header);
227                 SET_OUT_HEADER_LEN(out, open);
228         })));
229
230         /* Until the attr cache is working, we may send an additional GETATTR */
231         EXPECT_CALL(*m_mock, process(
232                 ResultOf([=](auto in) {
233                         return (in->header.opcode == FUSE_GETATTR &&
234                                 in->header.nodeid == ino);
235                 }, Eq(true)),
236                 _)
237         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
238                 SET_OUT_HEADER_LEN(out, attr);
239                 out->body.attr.attr.ino = ino;  // Must match nodeid
240                 out->body.attr.attr.mode = S_IFREG | oldmode;
241         })));
242
243         EXPECT_CALL(*m_mock, process(
244                 ResultOf([=](auto in) {
245                         /* In protocol 7.23, ctime will be changed too */
246                         uint32_t valid = FATTR_MODE;
247                         return (in->header.opcode == FUSE_SETATTR &&
248                                 in->header.nodeid == ino &&
249                                 in->body.setattr.valid == valid &&
250                                 in->body.setattr.mode == newmode);
251                 }, Eq(true)),
252                 _)
253         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
254                 SET_OUT_HEADER_LEN(out, attr);
255                 out->body.attr.attr.ino = ino;  // Must match nodeid
256                 out->body.attr.attr.mode = S_IFREG | newmode;
257         })));
258
259         fd = open(FULLPATH, O_RDONLY);
260         ASSERT_LE(0, fd) << strerror(errno);
261         ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
262         /* Deliberately leak fd.  close(2) will be tested in release.cc */
263 }
264
265 /* Change the size of an open file, by its file descriptor */
266 TEST_F(Setattr, ftruncate)
267 {
268         const char FULLPATH[] = "mountpoint/some_file.txt";
269         const char RELPATH[] = "some_file.txt";
270         uint64_t ino = 42;
271         int fd;
272         uint64_t fh = 0xdeadbeef1a7ebabe;
273         const off_t oldsize = 99;
274         const off_t newsize = 12345;
275
276         EXPECT_LOOKUP(1, RELPATH)
277         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
278                 SET_OUT_HEADER_LEN(out, entry);
279                 out->body.entry.attr.mode = S_IFREG | 0755;
280                 out->body.entry.nodeid = ino;
281                 out->body.entry.attr_valid = UINT64_MAX;
282                 out->body.entry.attr.size = oldsize;
283         })));
284
285         EXPECT_CALL(*m_mock, process(
286                 ResultOf([=](auto in) {
287                         return (in->header.opcode == FUSE_OPEN &&
288                                 in->header.nodeid == ino);
289                 }, Eq(true)),
290                 _)
291         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
292                 out->header.len = sizeof(out->header);
293                 SET_OUT_HEADER_LEN(out, open);
294                 out->body.open.fh = fh;
295         })));
296
297         /* Until the attr cache is working, we may send an additional GETATTR */
298         EXPECT_CALL(*m_mock, process(
299                 ResultOf([=](auto in) {
300                         return (in->header.opcode == FUSE_GETATTR &&
301                                 in->header.nodeid == ino);
302                 }, Eq(true)),
303                 _)
304         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
305                 SET_OUT_HEADER_LEN(out, attr);
306                 out->body.attr.attr.ino = ino;  // Must match nodeid
307                 out->body.attr.attr.mode = S_IFREG | 0755;
308                 out->body.attr.attr.size = oldsize;
309         })));
310
311         EXPECT_CALL(*m_mock, process(
312                 ResultOf([=](auto in) {
313                         /* In protocol 7.23, ctime will be changed too */
314                         uint32_t valid = FATTR_SIZE | FATTR_FH;
315                         return (in->header.opcode == FUSE_SETATTR &&
316                                 in->header.nodeid == ino &&
317                                 in->body.setattr.valid == valid &&
318                                 in->body.setattr.fh == fh);
319                 }, Eq(true)),
320                 _)
321         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
322                 SET_OUT_HEADER_LEN(out, attr);
323                 out->body.attr.attr.ino = ino;  // Must match nodeid
324                 out->body.attr.attr.mode = S_IFREG | 0755;
325                 out->body.attr.attr.size = newsize;
326         })));
327
328         fd = open(FULLPATH, O_RDWR);
329         ASSERT_LE(0, fd) << strerror(errno);
330         ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
331         /* Deliberately leak fd.  close(2) will be tested in release.cc */
332 }
333
334 /* Change the size of the file */
335 TEST_F(Setattr, truncate) {
336         const char FULLPATH[] = "mountpoint/some_file.txt";
337         const char RELPATH[] = "some_file.txt";
338         const uint64_t ino = 42;
339         const uint64_t oldsize = 100'000'000;
340         const uint64_t newsize = 20'000'000;
341
342         EXPECT_LOOKUP(1, RELPATH)
343         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
344                 SET_OUT_HEADER_LEN(out, entry);
345                 out->body.entry.attr.mode = S_IFREG | 0644;
346                 out->body.entry.nodeid = ino;
347                 out->body.entry.attr.size = oldsize;
348         })));
349
350         EXPECT_CALL(*m_mock, process(
351                 ResultOf([](auto in) {
352                         /* In protocol 7.23, ctime will be changed too */
353                         uint32_t valid = FATTR_SIZE;
354                         return (in->header.opcode == FUSE_SETATTR &&
355                                 in->header.nodeid == ino &&
356                                 in->body.setattr.valid == valid &&
357                                 in->body.setattr.size == newsize);
358                 }, Eq(true)),
359                 _)
360         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
361                 SET_OUT_HEADER_LEN(out, attr);
362                 out->body.attr.attr.ino = ino;  // Must match nodeid
363                 out->body.attr.attr.mode = S_IFREG | 0644;
364                 out->body.attr.attr.size = newsize;
365         })));
366         EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
367 }
368
369 /* Change a file's timestamps */
370 TEST_F(Setattr, utimensat) {
371         const char FULLPATH[] = "mountpoint/some_file.txt";
372         const char RELPATH[] = "some_file.txt";
373         const uint64_t ino = 42;
374         const timespec oldtimes[2] = {
375                 {.tv_sec = 1, .tv_nsec = 2},
376                 {.tv_sec = 3, .tv_nsec = 4},
377         };
378         const timespec newtimes[2] = {
379                 {.tv_sec = 5, .tv_nsec = 6},
380                 {.tv_sec = 7, .tv_nsec = 8},
381         };
382
383         EXPECT_LOOKUP(1, RELPATH)
384         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
385                 SET_OUT_HEADER_LEN(out, entry);
386                 out->body.entry.attr.mode = S_IFREG | 0644;
387                 out->body.entry.nodeid = ino;
388                 out->body.entry.attr_valid = UINT64_MAX;
389                 out->body.entry.attr.atime = oldtimes[0].tv_sec;
390                 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
391                 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
392                 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
393         })));
394
395         /* 
396          * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR
397          * call
398          */ 
399         EXPECT_CALL(*m_mock, process(
400                 ResultOf([](auto in) {
401                         return (in->header.opcode == FUSE_GETATTR &&
402                                 in->header.nodeid == ino);
403                 }, Eq(true)),
404                 _)
405         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
406                 SET_OUT_HEADER_LEN(out, attr);
407                 out->body.attr.attr.ino = ino;  // Must match nodeid
408                 out->body.attr.attr.mode = S_IFREG | 0644;
409                 out->body.attr.attr.atime = oldtimes[0].tv_sec;
410                 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
411                 out->body.attr.attr.mtime = oldtimes[1].tv_sec;
412                 out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec;
413         })));
414
415         EXPECT_CALL(*m_mock, process(
416                 ResultOf([=](auto in) {
417                         /* In protocol 7.23, ctime will be changed too */
418                         uint32_t valid = FATTR_ATIME | FATTR_MTIME;
419                         return (in->header.opcode == FUSE_SETATTR &&
420                                 in->header.nodeid == ino &&
421                                 in->body.setattr.valid == valid &&
422                                 in->body.setattr.atime == newtimes[0].tv_sec &&
423                                 in->body.setattr.atimensec ==
424                                         newtimes[0].tv_nsec &&
425                                 in->body.setattr.mtime == newtimes[1].tv_sec &&
426                                 in->body.setattr.mtimensec ==
427                                         newtimes[1].tv_nsec);
428                 }, Eq(true)),
429                 _)
430         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
431                 SET_OUT_HEADER_LEN(out, attr);
432                 out->body.attr.attr.ino = ino;  // Must match nodeid
433                 out->body.attr.attr.mode = S_IFREG | 0644;
434                 out->body.attr.attr.atime = newtimes[0].tv_sec;
435                 out->body.attr.attr.atimensec = newtimes[0].tv_nsec;
436                 out->body.attr.attr.mtime = newtimes[1].tv_sec;
437                 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
438         })));
439         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
440                 << strerror(errno);
441 }
442
443 /* Change a file mtime but not its atime */
444 TEST_F(Setattr, utimensat_mtime_only) {
445         const char FULLPATH[] = "mountpoint/some_file.txt";
446         const char RELPATH[] = "some_file.txt";
447         const uint64_t ino = 42;
448         const timespec oldtimes[2] = {
449                 {.tv_sec = 1, .tv_nsec = 2},
450                 {.tv_sec = 3, .tv_nsec = 4},
451         };
452         const timespec newtimes[2] = {
453                 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
454                 {.tv_sec = 7, .tv_nsec = 8},
455         };
456
457         EXPECT_LOOKUP(1, RELPATH)
458         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
459                 SET_OUT_HEADER_LEN(out, entry);
460                 out->body.entry.attr.mode = S_IFREG | 0644;
461                 out->body.entry.nodeid = ino;
462                 out->body.entry.attr_valid = UINT64_MAX;
463                 out->body.entry.attr.atime = oldtimes[0].tv_sec;
464                 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
465                 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
466                 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
467         })));
468
469         /* 
470          * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR
471          * call
472          */ 
473         EXPECT_CALL(*m_mock, process(
474                 ResultOf([](auto in) {
475                         return (in->header.opcode == FUSE_GETATTR &&
476                                 in->header.nodeid == ino);
477                 }, Eq(true)),
478                 _)
479         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
480                 SET_OUT_HEADER_LEN(out, attr);
481                 out->body.attr.attr.ino = ino;  // Must match nodeid
482                 out->body.attr.attr.mode = S_IFREG | 0644;
483                 out->body.attr.attr.atime = oldtimes[0].tv_sec;
484                 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
485                 out->body.attr.attr.mtime = oldtimes[1].tv_sec;
486                 out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec;
487         })));
488
489         EXPECT_CALL(*m_mock, process(
490                 ResultOf([=](auto in) {
491                         /* In protocol 7.23, ctime will be changed too */
492                         uint32_t valid = FATTR_MTIME;
493                         return (in->header.opcode == FUSE_SETATTR &&
494                                 in->header.nodeid == ino &&
495                                 in->body.setattr.valid == valid &&
496                                 in->body.setattr.mtime == newtimes[1].tv_sec &&
497                                 in->body.setattr.mtimensec ==
498                                         newtimes[1].tv_nsec);
499                 }, Eq(true)),
500                 _)
501         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
502                 SET_OUT_HEADER_LEN(out, attr);
503                 out->body.attr.attr.ino = ino;  // Must match nodeid
504                 out->body.attr.attr.mode = S_IFREG | 0644;
505                 out->body.attr.attr.atime = oldtimes[0].tv_sec;
506                 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
507                 out->body.attr.attr.mtime = newtimes[1].tv_sec;
508                 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
509         })));
510         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
511                 << strerror(errno);
512 }