2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
40 using namespace testing;
42 class Setattr : public FuseTest {};
45 /* Change the mode of a file */
46 TEST_F(Setattr, chmod)
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;
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;
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);
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;
79 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
82 /* Change the owner and group of a file */
83 TEST_F(Setattr, chown)
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;
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;
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);
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;
122 EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
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.
132 TEST_F(Setattr, eperm)
134 const char FULLPATH[] = "mountpoint/some_file.txt";
135 const char RELPATH[] = "some_file.txt";
136 const uint64_t ino = 42;
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;
148 EXPECT_CALL(*m_mock, process(
149 ResultOf([](auto in) {
150 return (in->header.opcode == FUSE_SETATTR &&
151 in->header.nodeid == ino);
154 ).WillOnce(Invoke(ReturnErrno(EPERM)));
155 EXPECT_NE(0, truncate(FULLPATH, 10));
156 EXPECT_EQ(EPERM, errno);
159 /* Change the mode of an open file, by its file descriptor */
160 TEST_F(Setattr, fchmod)
162 const char FULLPATH[] = "mountpoint/some_file.txt";
163 const char RELPATH[] = "some_file.txt";
166 const mode_t oldmode = 0755;
167 const mode_t newmode = 0644;
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;
178 EXPECT_CALL(*m_mock, process(
179 ResultOf([=](auto in) {
180 return (in->header.opcode == FUSE_OPEN &&
181 in->header.nodeid == ino);
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);
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);
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;
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);
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;
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 */
227 /* Change the size of an open file, by its file descriptor */
228 TEST_F(Setattr, ftruncate)
230 const char FULLPATH[] = "mountpoint/some_file.txt";
231 const char RELPATH[] = "some_file.txt";
234 uint64_t fh = 0xdeadbeef1a7ebabe;
235 const off_t oldsize = 99;
236 const off_t newsize = 12345;
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;
248 EXPECT_CALL(*m_mock, process(
249 ResultOf([=](auto in) {
250 return (in->header.opcode == FUSE_OPEN &&
251 in->header.nodeid == ino);
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;
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);
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;
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);
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;
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 */
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;
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;
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);
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;
333 EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
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},
345 const timespec newtimes[2] = {
346 {.tv_sec = 5, .tv_nsec = 6},
347 {.tv_sec = 7, .tv_nsec = 8},
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;
363 * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR
366 EXPECT_CALL(*m_mock, process(
367 ResultOf([](auto in) {
368 return (in->header.opcode == FUSE_GETATTR &&
369 in->header.nodeid == ino);
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;
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);
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;
408 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
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},
421 const timespec newtimes[2] = {
422 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
423 {.tv_sec = 7, .tv_nsec = 8},
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;
439 * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR
442 EXPECT_CALL(*m_mock, process(
443 ResultOf([](auto in) {
444 return (in->header.opcode == FUSE_GETATTR &&
445 in->header.nodeid == ino);
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;
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);
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;
481 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
486 * Writethrough cache: newly changed attributes should be automatically cached,
487 * if the filesystem allows it
489 //TODO TEST_F(Setattr, writethrough_cache){}