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