19821f1d3SAlan Somers /*-
24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
39821f1d3SAlan Somers *
49821f1d3SAlan Somers * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers *
69821f1d3SAlan Somers * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers * from the FreeBSD Foundation.
89821f1d3SAlan Somers *
99821f1d3SAlan Somers * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers * are met:
129821f1d3SAlan Somers * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers * notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers * notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers * documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers *
189821f1d3SAlan Somers * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers * SUCH DAMAGE.
299821f1d3SAlan Somers */
309821f1d3SAlan Somers
319821f1d3SAlan Somers extern "C" {
320a192b3aSAlan Somers #include <sys/types.h>
330a192b3aSAlan Somers #include <sys/resource.h>
349821f1d3SAlan Somers #include <sys/stat.h>
350a192b3aSAlan Somers #include <sys/time.h>
369821f1d3SAlan Somers
379821f1d3SAlan Somers #include <fcntl.h>
38c9cabf9aSDimitry Andric #include <semaphore.h>
390a192b3aSAlan Somers #include <signal.h>
409821f1d3SAlan Somers }
419821f1d3SAlan Somers
429821f1d3SAlan Somers #include "mockfs.hh"
439821f1d3SAlan Somers #include "utils.hh"
449821f1d3SAlan Somers
459821f1d3SAlan Somers using namespace testing;
469821f1d3SAlan Somers
470a192b3aSAlan Somers class Setattr : public FuseTest {
480a192b3aSAlan Somers public:
490a192b3aSAlan Somers static sig_atomic_t s_sigxfsz;
500a192b3aSAlan Somers };
519821f1d3SAlan Somers
52666f8543SAlan Somers class RofsSetattr: public Setattr {
53666f8543SAlan Somers public:
SetUp()54666f8543SAlan Somers virtual void SetUp() {
550a192b3aSAlan Somers s_sigxfsz = 0;
56666f8543SAlan Somers m_ro = true;
57666f8543SAlan Somers Setattr::SetUp();
58666f8543SAlan Somers }
59666f8543SAlan Somers };
60666f8543SAlan Somers
6116bd2d47SAlan Somers class Setattr_7_8: public Setattr {
6216bd2d47SAlan Somers public:
SetUp()6316bd2d47SAlan Somers virtual void SetUp() {
6416bd2d47SAlan Somers m_kernel_minor_version = 8;
6516bd2d47SAlan Somers Setattr::SetUp();
6616bd2d47SAlan Somers }
6716bd2d47SAlan Somers };
6816bd2d47SAlan Somers
699821f1d3SAlan Somers
700a192b3aSAlan Somers sig_atomic_t Setattr::s_sigxfsz = 0;
710a192b3aSAlan Somers
sigxfsz_handler(int __unused sig)720a192b3aSAlan Somers void sigxfsz_handler(int __unused sig) {
730a192b3aSAlan Somers Setattr::s_sigxfsz = 1;
740a192b3aSAlan Somers }
750a192b3aSAlan Somers
769821f1d3SAlan Somers /*
779821f1d3SAlan Somers * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
789821f1d3SAlan Somers * should use the cached attributes, rather than query the daemon
799821f1d3SAlan Somers */
TEST_F(Setattr,attr_cache)80cad67791SAlan Somers TEST_F(Setattr, attr_cache)
819821f1d3SAlan Somers {
829821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
839821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
849821f1d3SAlan Somers const uint64_t ino = 42;
859821f1d3SAlan Somers struct stat sb;
869821f1d3SAlan Somers const mode_t newmode = 0644;
879821f1d3SAlan Somers
88a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
8929edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
909821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
9129edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
9229edc611SAlan Somers out.body.entry.nodeid = ino;
9329edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
949821f1d3SAlan Somers })));
959821f1d3SAlan Somers
969821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
979821f1d3SAlan Somers ResultOf([](auto in) {
9829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
9929edc611SAlan Somers in.header.nodeid == ino);
1009821f1d3SAlan Somers }, Eq(true)),
1019821f1d3SAlan Somers _)
10229edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1039821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
10429edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
10529edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode;
10629edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX;
1079821f1d3SAlan Somers })));
1089821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
1099821f1d3SAlan Somers ResultOf([](auto in) {
11029edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR);
1119821f1d3SAlan Somers }, Eq(true)),
1129821f1d3SAlan Somers _)
1139821f1d3SAlan Somers ).Times(0);
1149821f1d3SAlan Somers
1159821f1d3SAlan Somers /* Set an attribute with SETATTR */
1169821f1d3SAlan Somers ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1179821f1d3SAlan Somers
1189821f1d3SAlan Somers /* The stat(2) should use cached attributes */
1199821f1d3SAlan Somers ASSERT_EQ(0, stat(FULLPATH, &sb));
1209821f1d3SAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1219821f1d3SAlan Somers }
1229821f1d3SAlan Somers
1239821f1d3SAlan Somers /* Change the mode of a file */
TEST_F(Setattr,chmod)1249821f1d3SAlan Somers TEST_F(Setattr, chmod)
1259821f1d3SAlan Somers {
1269821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
1279821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
1289821f1d3SAlan Somers const uint64_t ino = 42;
1299821f1d3SAlan Somers const mode_t oldmode = 0755;
1309821f1d3SAlan Somers const mode_t newmode = 0644;
1319821f1d3SAlan Somers
132a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
13329edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1349821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
13529edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | oldmode;
13629edc611SAlan Somers out.body.entry.nodeid = ino;
1379821f1d3SAlan Somers })));
1389821f1d3SAlan Somers
1399821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
1409821f1d3SAlan Somers ResultOf([](auto in) {
1419821f1d3SAlan Somers uint32_t valid = FATTR_MODE;
14229edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
14329edc611SAlan Somers in.header.nodeid == ino &&
14429edc611SAlan Somers in.body.setattr.valid == valid &&
14529edc611SAlan Somers in.body.setattr.mode == newmode);
1469821f1d3SAlan Somers }, Eq(true)),
1479821f1d3SAlan Somers _)
14829edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1499821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
15029edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
15129edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode;
1529821f1d3SAlan Somers })));
1539821f1d3SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1549821f1d3SAlan Somers }
1559821f1d3SAlan Somers
1564ae3a56cSAlan Somers /*
1574ae3a56cSAlan Somers * Chmod a multiply-linked file with cached attributes. Check that both files'
1584ae3a56cSAlan Somers * attributes have changed.
1594ae3a56cSAlan Somers */
TEST_F(Setattr,chmod_multiply_linked)1604ae3a56cSAlan Somers TEST_F(Setattr, chmod_multiply_linked)
1614ae3a56cSAlan Somers {
1624ae3a56cSAlan Somers const char FULLPATH0[] = "mountpoint/some_file.txt";
1634ae3a56cSAlan Somers const char RELPATH0[] = "some_file.txt";
1644ae3a56cSAlan Somers const char FULLPATH1[] = "mountpoint/other_file.txt";
1654ae3a56cSAlan Somers const char RELPATH1[] = "other_file.txt";
1664ae3a56cSAlan Somers struct stat sb;
1674ae3a56cSAlan Somers const uint64_t ino = 42;
1684ae3a56cSAlan Somers const mode_t oldmode = 0777;
1694ae3a56cSAlan Somers const mode_t newmode = 0666;
1704ae3a56cSAlan Somers
171a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)
17229edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1734ae3a56cSAlan Somers SET_OUT_HEADER_LEN(out, entry);
17429edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | oldmode;
17529edc611SAlan Somers out.body.entry.nodeid = ino;
17629edc611SAlan Somers out.body.entry.attr.nlink = 2;
17729edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
17829edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
1794ae3a56cSAlan Somers })));
1804ae3a56cSAlan Somers
181a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1)
18229edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1834ae3a56cSAlan Somers SET_OUT_HEADER_LEN(out, entry);
18429edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | oldmode;
18529edc611SAlan Somers out.body.entry.nodeid = ino;
18629edc611SAlan Somers out.body.entry.attr.nlink = 2;
18729edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
18829edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
1894ae3a56cSAlan Somers })));
1904ae3a56cSAlan Somers
1914ae3a56cSAlan Somers EXPECT_CALL(*m_mock, process(
1924ae3a56cSAlan Somers ResultOf([](auto in) {
1934ae3a56cSAlan Somers uint32_t valid = FATTR_MODE;
19429edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
19529edc611SAlan Somers in.header.nodeid == ino &&
19629edc611SAlan Somers in.body.setattr.valid == valid &&
19729edc611SAlan Somers in.body.setattr.mode == newmode);
1984ae3a56cSAlan Somers }, Eq(true)),
1994ae3a56cSAlan Somers _)
20029edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
2014ae3a56cSAlan Somers SET_OUT_HEADER_LEN(out, attr);
20229edc611SAlan Somers out.body.attr.attr.ino = ino;
20329edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode;
20429edc611SAlan Somers out.body.attr.attr.nlink = 2;
20529edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX;
2064ae3a56cSAlan Somers })));
2074ae3a56cSAlan Somers
2084ae3a56cSAlan Somers /* For a lookup of the 2nd file to get it into the cache*/
2094ae3a56cSAlan Somers ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
2104ae3a56cSAlan Somers EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
2114ae3a56cSAlan Somers
2124ae3a56cSAlan Somers ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
2134ae3a56cSAlan Somers ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
2144ae3a56cSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
2154ae3a56cSAlan Somers ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
2164ae3a56cSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
2174ae3a56cSAlan Somers }
2184ae3a56cSAlan Somers
2194ae3a56cSAlan Somers
2209821f1d3SAlan Somers /* Change the owner and group of a file */
TEST_F(Setattr,chown)2219821f1d3SAlan Somers TEST_F(Setattr, chown)
2229821f1d3SAlan Somers {
2239821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
2249821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
2259821f1d3SAlan Somers const uint64_t ino = 42;
2269821f1d3SAlan Somers const gid_t oldgroup = 66;
2279821f1d3SAlan Somers const gid_t newgroup = 99;
2289821f1d3SAlan Somers const uid_t olduser = 33;
2299821f1d3SAlan Somers const uid_t newuser = 44;
2309821f1d3SAlan Somers
231a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
23229edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
2339821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
23429edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
23529edc611SAlan Somers out.body.entry.nodeid = ino;
23629edc611SAlan Somers out.body.entry.attr.gid = oldgroup;
23729edc611SAlan Somers out.body.entry.attr.uid = olduser;
2389821f1d3SAlan Somers })));
2399821f1d3SAlan Somers
2409821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
2419821f1d3SAlan Somers ResultOf([](auto in) {
2429821f1d3SAlan Somers uint32_t valid = FATTR_GID | FATTR_UID;
24329edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
24429edc611SAlan Somers in.header.nodeid == ino &&
24529edc611SAlan Somers in.body.setattr.valid == valid &&
24629edc611SAlan Somers in.body.setattr.uid == newuser &&
24729edc611SAlan Somers in.body.setattr.gid == newgroup);
2489821f1d3SAlan Somers }, Eq(true)),
2499821f1d3SAlan Somers _)
25029edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
2519821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
25229edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
25329edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
25429edc611SAlan Somers out.body.attr.attr.uid = newuser;
25529edc611SAlan Somers out.body.attr.attr.gid = newgroup;
2569821f1d3SAlan Somers })));
2579821f1d3SAlan Somers EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
2589821f1d3SAlan Somers }
2599821f1d3SAlan Somers
2609821f1d3SAlan Somers
2619821f1d3SAlan Somers
2629821f1d3SAlan Somers /*
2639821f1d3SAlan Somers * FUSE daemons are allowed to check permissions however they like. If the
2649821f1d3SAlan Somers * daemon returns EPERM, even if the file permissions "should" grant access,
2659821f1d3SAlan Somers * then fuse(4) should return EPERM too.
2669821f1d3SAlan Somers */
TEST_F(Setattr,eperm)2679821f1d3SAlan Somers TEST_F(Setattr, eperm)
2689821f1d3SAlan Somers {
2699821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
2709821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
2719821f1d3SAlan Somers const uint64_t ino = 42;
2729821f1d3SAlan Somers
273a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
27429edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
2759821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
27629edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0777;
27729edc611SAlan Somers out.body.entry.nodeid = ino;
27829edc611SAlan Somers out.body.entry.attr.uid = in.header.uid;
27929edc611SAlan Somers out.body.entry.attr.gid = in.header.gid;
2809821f1d3SAlan Somers })));
2819821f1d3SAlan Somers
2829821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
2839821f1d3SAlan Somers ResultOf([](auto in) {
28429edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
28529edc611SAlan Somers in.header.nodeid == ino);
2869821f1d3SAlan Somers }, Eq(true)),
2879821f1d3SAlan Somers _)
2889821f1d3SAlan Somers ).WillOnce(Invoke(ReturnErrno(EPERM)));
2899821f1d3SAlan Somers EXPECT_NE(0, truncate(FULLPATH, 10));
2909821f1d3SAlan Somers EXPECT_EQ(EPERM, errno);
2919821f1d3SAlan Somers }
2929821f1d3SAlan Somers
2939821f1d3SAlan Somers /* Change the mode of an open file, by its file descriptor */
TEST_F(Setattr,fchmod)2949821f1d3SAlan Somers TEST_F(Setattr, fchmod)
2959821f1d3SAlan Somers {
2969821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
2979821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
2989821f1d3SAlan Somers uint64_t ino = 42;
2999821f1d3SAlan Somers int fd;
3009821f1d3SAlan Somers const mode_t oldmode = 0755;
3019821f1d3SAlan Somers const mode_t newmode = 0644;
3029821f1d3SAlan Somers
303a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
30429edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3059821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
30629edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | oldmode;
30729edc611SAlan Somers out.body.entry.nodeid = ino;
30829edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
3099821f1d3SAlan Somers })));
3109821f1d3SAlan Somers
3119821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
3129821f1d3SAlan Somers ResultOf([=](auto in) {
31329edc611SAlan Somers return (in.header.opcode == FUSE_OPEN &&
31429edc611SAlan Somers in.header.nodeid == ino);
3159821f1d3SAlan Somers }, Eq(true)),
3169821f1d3SAlan Somers _)
31729edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
31829edc611SAlan Somers out.header.len = sizeof(out.header);
3199821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, open);
3209821f1d3SAlan Somers })));
3219821f1d3SAlan Somers
3229821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
3239821f1d3SAlan Somers ResultOf([=](auto in) {
3249821f1d3SAlan Somers uint32_t valid = FATTR_MODE;
32529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
32629edc611SAlan Somers in.header.nodeid == ino &&
32729edc611SAlan Somers in.body.setattr.valid == valid &&
32829edc611SAlan Somers in.body.setattr.mode == newmode);
3299821f1d3SAlan Somers }, Eq(true)),
3309821f1d3SAlan Somers _)
33129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3329821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
33329edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
33429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode;
3359821f1d3SAlan Somers })));
3369821f1d3SAlan Somers
3379821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY);
3389821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno);
3399821f1d3SAlan Somers ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
3407fc0921dSAlan Somers leak(fd);
3419821f1d3SAlan Somers }
3429821f1d3SAlan Somers
3439821f1d3SAlan Somers /* Change the size of an open file, by its file descriptor */
TEST_F(Setattr,ftruncate)3449821f1d3SAlan Somers TEST_F(Setattr, ftruncate)
3459821f1d3SAlan Somers {
3469821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
3479821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
3489821f1d3SAlan Somers uint64_t ino = 42;
3499821f1d3SAlan Somers int fd;
3509821f1d3SAlan Somers uint64_t fh = 0xdeadbeef1a7ebabe;
3519821f1d3SAlan Somers const off_t oldsize = 99;
3529821f1d3SAlan Somers const off_t newsize = 12345;
3539821f1d3SAlan Somers
354a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
35529edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3569821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
35729edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0755;
35829edc611SAlan Somers out.body.entry.nodeid = ino;
35929edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
36029edc611SAlan Somers out.body.entry.attr.size = oldsize;
3619821f1d3SAlan Somers })));
3629821f1d3SAlan Somers
3639821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
3649821f1d3SAlan Somers ResultOf([=](auto in) {
36529edc611SAlan Somers return (in.header.opcode == FUSE_OPEN &&
36629edc611SAlan Somers in.header.nodeid == ino);
3679821f1d3SAlan Somers }, Eq(true)),
3689821f1d3SAlan Somers _)
36929edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
37029edc611SAlan Somers out.header.len = sizeof(out.header);
3719821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, open);
37229edc611SAlan Somers out.body.open.fh = fh;
3739821f1d3SAlan Somers })));
3749821f1d3SAlan Somers
3759821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
3769821f1d3SAlan Somers ResultOf([=](auto in) {
3779821f1d3SAlan Somers uint32_t valid = FATTR_SIZE | FATTR_FH;
37829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
37929edc611SAlan Somers in.header.nodeid == ino &&
38029edc611SAlan Somers in.body.setattr.valid == valid &&
38129edc611SAlan Somers in.body.setattr.fh == fh);
3829821f1d3SAlan Somers }, Eq(true)),
3839821f1d3SAlan Somers _)
38429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3859821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
38629edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
38729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0755;
38829edc611SAlan Somers out.body.attr.attr.size = newsize;
3899821f1d3SAlan Somers })));
3909821f1d3SAlan Somers
3919821f1d3SAlan Somers fd = open(FULLPATH, O_RDWR);
3929821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno);
3939821f1d3SAlan Somers ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
3947fc0921dSAlan Somers leak(fd);
3959821f1d3SAlan Somers }
3969821f1d3SAlan Somers
3979821f1d3SAlan Somers /* Change the size of the file */
TEST_F(Setattr,truncate)3989821f1d3SAlan Somers TEST_F(Setattr, truncate) {
3999821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
4009821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
4019821f1d3SAlan Somers const uint64_t ino = 42;
4029821f1d3SAlan Somers const uint64_t oldsize = 100'000'000;
4039821f1d3SAlan Somers const uint64_t newsize = 20'000'000;
4049821f1d3SAlan Somers
405a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
40629edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
4079821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
40829edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
40929edc611SAlan Somers out.body.entry.nodeid = ino;
41029edc611SAlan Somers out.body.entry.attr.size = oldsize;
4119821f1d3SAlan Somers })));
4129821f1d3SAlan Somers
4139821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
4149821f1d3SAlan Somers ResultOf([](auto in) {
4159821f1d3SAlan Somers uint32_t valid = FATTR_SIZE;
41629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
41729edc611SAlan Somers in.header.nodeid == ino &&
41829edc611SAlan Somers in.body.setattr.valid == valid &&
41929edc611SAlan Somers in.body.setattr.size == newsize);
4209821f1d3SAlan Somers }, Eq(true)),
4219821f1d3SAlan Somers _)
42229edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
4239821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
42429edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
42529edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
42629edc611SAlan Somers out.body.attr.attr.size = newsize;
4279821f1d3SAlan Somers })));
4289821f1d3SAlan Somers EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
4299821f1d3SAlan Somers }
4309821f1d3SAlan Somers
431e312493bSAlan Somers /*
432e312493bSAlan Somers * Truncating a file should discard cached data past the truncation point.
433f8ebf1cdSAlan Somers * This is a regression test for bug 233783.
43477b82478SAlan Somers *
43577b82478SAlan Somers * There are two distinct failure modes. The first one is a failure to zero
43677b82478SAlan Somers * the portion of the file's final buffer past EOF. It can be reproduced by
43777b82478SAlan Somers * fsx -WR -P /tmp -S10 fsx.bin
43877b82478SAlan Somers *
43977b82478SAlan Somers * The second is a failure to drop buffers beyond that. It can be reproduced by
44077b82478SAlan Somers * fsx -WR -P /tmp -S18 -n fsx.bin
44177b82478SAlan Somers * Also reproducible in sh with:
44277b82478SAlan Somers * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt
44377b82478SAlan Somers * $> cd /tmp/mnt/tmp
44477b82478SAlan Somers * $> dd if=/dev/random of=randfile bs=1k count=192
44577b82478SAlan Somers * $> truncate -s 1k randfile && truncate -s 192k randfile
44677b82478SAlan Somers * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000
447e312493bSAlan Somers */
TEST_F(Setattr,truncate_discards_cached_data)448e312493bSAlan Somers TEST_F(Setattr, truncate_discards_cached_data) {
449e312493bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
450e312493bSAlan Somers const char RELPATH[] = "some_file.txt";
451*8bae22bbSAlan Somers char *w0buf, *r0buf, *r1buf, *expected;
45277b82478SAlan Somers off_t w0_offset = 0;
45377b82478SAlan Somers size_t w0_size = 0x30000;
45477b82478SAlan Somers off_t r0_offset = 0;
45577b82478SAlan Somers off_t r0_size = w0_size;
45677b82478SAlan Somers size_t trunc0_size = 0x400;
45777b82478SAlan Somers size_t trunc1_size = w0_size;
45877b82478SAlan Somers off_t r1_offset = trunc0_size;
45977b82478SAlan Somers off_t r1_size = w0_size - trunc0_size;
460e312493bSAlan Somers size_t cur_size = 0;
461e312493bSAlan Somers const uint64_t ino = 42;
462e312493bSAlan Somers mode_t mode = S_IFREG | 0644;
46377b82478SAlan Somers int fd, r;
46477b82478SAlan Somers bool should_have_data = false;
465e312493bSAlan Somers
466*8bae22bbSAlan Somers w0buf = new char[w0_size];
467e312493bSAlan Somers memset(w0buf, 'X', w0_size);
468e312493bSAlan Somers
469*8bae22bbSAlan Somers r0buf = new char[r0_size];
470*8bae22bbSAlan Somers r1buf = new char[r1_size];
471e312493bSAlan Somers
472*8bae22bbSAlan Somers expected = new char[r1_size]();
473e312493bSAlan Somers
474e312493bSAlan Somers expect_lookup(RELPATH, ino, mode, 0, 1);
475e312493bSAlan Somers expect_open(ino, O_RDWR, 1);
476e312493bSAlan Somers EXPECT_CALL(*m_mock, process(
477e312493bSAlan Somers ResultOf([=](auto in) {
47829edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR &&
47929edc611SAlan Somers in.header.nodeid == ino);
480e312493bSAlan Somers }, Eq(true)),
481e312493bSAlan Somers _)
48229edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) {
483e312493bSAlan Somers SET_OUT_HEADER_LEN(out, attr);
48429edc611SAlan Somers out.body.attr.attr.ino = ino;
48529edc611SAlan Somers out.body.attr.attr.mode = mode;
48629edc611SAlan Somers out.body.attr.attr.size = cur_size;
487e312493bSAlan Somers })));
488e312493bSAlan Somers EXPECT_CALL(*m_mock, process(
489e312493bSAlan Somers ResultOf([=](auto in) {
49029edc611SAlan Somers return (in.header.opcode == FUSE_WRITE);
491e312493bSAlan Somers }, Eq(true)),
492e312493bSAlan Somers _)
49329edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
494e312493bSAlan Somers SET_OUT_HEADER_LEN(out, write);
49529edc611SAlan Somers out.body.attr.attr.ino = ino;
49629edc611SAlan Somers out.body.write.size = in.body.write.size;
49729edc611SAlan Somers cur_size = std::max(static_cast<uint64_t>(cur_size),
49829edc611SAlan Somers in.body.write.size + in.body.write.offset);
499e312493bSAlan Somers })));
500e312493bSAlan Somers
501e312493bSAlan Somers EXPECT_CALL(*m_mock, process(
502e312493bSAlan Somers ResultOf([=](auto in) {
50329edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
50429edc611SAlan Somers in.header.nodeid == ino &&
50529edc611SAlan Somers (in.body.setattr.valid & FATTR_SIZE));
506e312493bSAlan Somers }, Eq(true)),
507e312493bSAlan Somers _)
50829edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
50929edc611SAlan Somers auto trunc_size = in.body.setattr.size;
510e312493bSAlan Somers SET_OUT_HEADER_LEN(out, attr);
51129edc611SAlan Somers out.body.attr.attr.ino = ino;
51229edc611SAlan Somers out.body.attr.attr.mode = mode;
51329edc611SAlan Somers out.body.attr.attr.size = trunc_size;
514e312493bSAlan Somers cur_size = trunc_size;
515e312493bSAlan Somers })));
516e312493bSAlan Somers
517e312493bSAlan Somers EXPECT_CALL(*m_mock, process(
518e312493bSAlan Somers ResultOf([=](auto in) {
51929edc611SAlan Somers return (in.header.opcode == FUSE_READ);
520e312493bSAlan Somers }, Eq(true)),
521e312493bSAlan Somers _)
52229edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
52329edc611SAlan Somers auto osize = std::min(
52429edc611SAlan Somers static_cast<uint64_t>(cur_size) - in.body.read.offset,
52529edc611SAlan Somers static_cast<uint64_t>(in.body.read.size));
5260c9df4afSAlan Somers assert(osize <= sizeof(out.body.bytes));
52729edc611SAlan Somers out.header.len = sizeof(struct fuse_out_header) + osize;
52877b82478SAlan Somers if (should_have_data)
52929edc611SAlan Somers memset(out.body.bytes, 'X', osize);
53077b82478SAlan Somers else
53129edc611SAlan Somers bzero(out.body.bytes, osize);
532e312493bSAlan Somers })));
533e312493bSAlan Somers
534e312493bSAlan Somers fd = open(FULLPATH, O_RDWR, 0644);
535e312493bSAlan Somers ASSERT_LE(0, fd) << strerror(errno);
536e312493bSAlan Somers
53777b82478SAlan Somers /* Fill the file with Xs */
53829edc611SAlan Somers ASSERT_EQ(static_cast<ssize_t>(w0_size),
53929edc611SAlan Somers pwrite(fd, w0buf, w0_size, w0_offset));
54077b82478SAlan Somers should_have_data = true;
541f8ebf1cdSAlan Somers /* Fill the cache */
54229edc611SAlan Somers ASSERT_EQ(static_cast<ssize_t>(r0_size),
54329edc611SAlan Somers pread(fd, r0buf, r0_size, r0_offset));
544e312493bSAlan Somers /* 1st truncate should discard cached data */
545e312493bSAlan Somers EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
54677b82478SAlan Somers should_have_data = false;
547e312493bSAlan Somers /* 2nd truncate extends file into previously cached data */
548e312493bSAlan Somers EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
549e312493bSAlan Somers /* Read should return all zeros */
55029edc611SAlan Somers ASSERT_EQ(static_cast<ssize_t>(r1_size),
55129edc611SAlan Somers pread(fd, r1buf, r1_size, r1_offset));
552e312493bSAlan Somers
55377b82478SAlan Somers r = memcmp(expected, r1buf, r1_size);
55477b82478SAlan Somers ASSERT_EQ(0, r);
555e312493bSAlan Somers
556*8bae22bbSAlan Somers delete[] expected;
557*8bae22bbSAlan Somers delete[] r1buf;
558*8bae22bbSAlan Somers delete[] r0buf;
559*8bae22bbSAlan Somers delete[] w0buf;
5608e765737SAlan Somers
5618e765737SAlan Somers leak(fd);
562e312493bSAlan Somers }
563e312493bSAlan Somers
5640a192b3aSAlan Somers /* truncate should fail if it would cause the file to exceed RLIMIT_FSIZE */
TEST_F(Setattr,truncate_rlimit_rsize)5650a192b3aSAlan Somers TEST_F(Setattr, truncate_rlimit_rsize)
5660a192b3aSAlan Somers {
5670a192b3aSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
5680a192b3aSAlan Somers const char RELPATH[] = "some_file.txt";
5690a192b3aSAlan Somers struct rlimit rl;
5700a192b3aSAlan Somers const uint64_t ino = 42;
5710a192b3aSAlan Somers const uint64_t oldsize = 0;
5720a192b3aSAlan Somers const uint64_t newsize = 100'000'000;
5730a192b3aSAlan Somers
5740a192b3aSAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
5750a192b3aSAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
5760a192b3aSAlan Somers SET_OUT_HEADER_LEN(out, entry);
5770a192b3aSAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
5780a192b3aSAlan Somers out.body.entry.nodeid = ino;
5790a192b3aSAlan Somers out.body.entry.attr.size = oldsize;
5800a192b3aSAlan Somers })));
5810a192b3aSAlan Somers
5820a192b3aSAlan Somers rl.rlim_cur = newsize / 2;
5830a192b3aSAlan Somers rl.rlim_max = 10 * newsize;
5840a192b3aSAlan Somers ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
5850a192b3aSAlan Somers ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
5860a192b3aSAlan Somers
5870a192b3aSAlan Somers EXPECT_EQ(-1, truncate(FULLPATH, newsize));
5880a192b3aSAlan Somers EXPECT_EQ(EFBIG, errno);
5890a192b3aSAlan Somers EXPECT_EQ(1, s_sigxfsz);
5900a192b3aSAlan Somers }
5910a192b3aSAlan Somers
5929821f1d3SAlan Somers /* Change a file's timestamps */
TEST_F(Setattr,utimensat)5939821f1d3SAlan Somers TEST_F(Setattr, utimensat) {
5949821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
5959821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
5969821f1d3SAlan Somers const uint64_t ino = 42;
5979821f1d3SAlan Somers const timespec oldtimes[2] = {
5989821f1d3SAlan Somers {.tv_sec = 1, .tv_nsec = 2},
5999821f1d3SAlan Somers {.tv_sec = 3, .tv_nsec = 4},
6009821f1d3SAlan Somers };
6019821f1d3SAlan Somers const timespec newtimes[2] = {
6029821f1d3SAlan Somers {.tv_sec = 5, .tv_nsec = 6},
6039821f1d3SAlan Somers {.tv_sec = 7, .tv_nsec = 8},
6049821f1d3SAlan Somers };
6059821f1d3SAlan Somers
606a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
60729edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6089821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
60929edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
61029edc611SAlan Somers out.body.entry.nodeid = ino;
61129edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
61229edc611SAlan Somers out.body.entry.attr.atime = oldtimes[0].tv_sec;
61329edc611SAlan Somers out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
61429edc611SAlan Somers out.body.entry.attr.mtime = oldtimes[1].tv_sec;
61529edc611SAlan Somers out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
6169821f1d3SAlan Somers })));
6179821f1d3SAlan Somers
6189821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
6199821f1d3SAlan Somers ResultOf([=](auto in) {
6209821f1d3SAlan Somers uint32_t valid = FATTR_ATIME | FATTR_MTIME;
62129edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
62229edc611SAlan Somers in.header.nodeid == ino &&
62329edc611SAlan Somers in.body.setattr.valid == valid &&
6245a0b9a27SAlan Somers (time_t)in.body.setattr.atime ==
6255a0b9a27SAlan Somers newtimes[0].tv_sec &&
62653f82128SKyle Evans (long)in.body.setattr.atimensec ==
6279821f1d3SAlan Somers newtimes[0].tv_nsec &&
6285a0b9a27SAlan Somers (time_t)in.body.setattr.mtime ==
6295a0b9a27SAlan Somers newtimes[1].tv_sec &&
63053f82128SKyle Evans (long)in.body.setattr.mtimensec ==
6319821f1d3SAlan Somers newtimes[1].tv_nsec);
6329821f1d3SAlan Somers }, Eq(true)),
6339821f1d3SAlan Somers _)
63429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6359821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
63629edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
63729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
63829edc611SAlan Somers out.body.attr.attr.atime = newtimes[0].tv_sec;
63929edc611SAlan Somers out.body.attr.attr.atimensec = newtimes[0].tv_nsec;
64029edc611SAlan Somers out.body.attr.attr.mtime = newtimes[1].tv_sec;
64129edc611SAlan Somers out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
6429821f1d3SAlan Somers })));
6439821f1d3SAlan Somers EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
6449821f1d3SAlan Somers << strerror(errno);
6459821f1d3SAlan Somers }
6469821f1d3SAlan Somers
6479821f1d3SAlan Somers /* Change a file mtime but not its atime */
TEST_F(Setattr,utimensat_mtime_only)6489821f1d3SAlan Somers TEST_F(Setattr, utimensat_mtime_only) {
6499821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
6509821f1d3SAlan Somers const char RELPATH[] = "some_file.txt";
6519821f1d3SAlan Somers const uint64_t ino = 42;
6529821f1d3SAlan Somers const timespec oldtimes[2] = {
6539821f1d3SAlan Somers {.tv_sec = 1, .tv_nsec = 2},
6549821f1d3SAlan Somers {.tv_sec = 3, .tv_nsec = 4},
6559821f1d3SAlan Somers };
6569821f1d3SAlan Somers const timespec newtimes[2] = {
6579821f1d3SAlan Somers {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
6589821f1d3SAlan Somers {.tv_sec = 7, .tv_nsec = 8},
6599821f1d3SAlan Somers };
6609821f1d3SAlan Somers
661a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
66229edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6639821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry);
66429edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
66529edc611SAlan Somers out.body.entry.nodeid = ino;
66629edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
66729edc611SAlan Somers out.body.entry.attr.atime = oldtimes[0].tv_sec;
66829edc611SAlan Somers out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
66929edc611SAlan Somers out.body.entry.attr.mtime = oldtimes[1].tv_sec;
67029edc611SAlan Somers out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
6719821f1d3SAlan Somers })));
6729821f1d3SAlan Somers
6739821f1d3SAlan Somers EXPECT_CALL(*m_mock, process(
6749821f1d3SAlan Somers ResultOf([=](auto in) {
6759821f1d3SAlan Somers uint32_t valid = FATTR_MTIME;
67629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
67729edc611SAlan Somers in.header.nodeid == ino &&
67829edc611SAlan Somers in.body.setattr.valid == valid &&
6795a0b9a27SAlan Somers (time_t)in.body.setattr.mtime ==
6805a0b9a27SAlan Somers newtimes[1].tv_sec &&
68153f82128SKyle Evans (long)in.body.setattr.mtimensec ==
6829821f1d3SAlan Somers newtimes[1].tv_nsec);
6839821f1d3SAlan Somers }, Eq(true)),
6849821f1d3SAlan Somers _)
68529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6869821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr);
68729edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
68829edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
68929edc611SAlan Somers out.body.attr.attr.atime = oldtimes[0].tv_sec;
69029edc611SAlan Somers out.body.attr.attr.atimensec = oldtimes[0].tv_nsec;
69129edc611SAlan Somers out.body.attr.attr.mtime = newtimes[1].tv_sec;
69229edc611SAlan Somers out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
6939821f1d3SAlan Somers })));
6949821f1d3SAlan Somers EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
6959821f1d3SAlan Somers << strerror(errno);
6969821f1d3SAlan Somers }
697ff4fbdf5SAlan Somers
698fe221e01SAlan Somers /*
699fe221e01SAlan Somers * Set a file's mtime and atime to now
700fe221e01SAlan Somers *
701fe221e01SAlan Somers * The design of FreeBSD's VFS does not allow fusefs to set just one of atime
702fe221e01SAlan Somers * or mtime to UTIME_NOW; it's both or neither.
703fe221e01SAlan Somers */
TEST_F(Setattr,utimensat_utime_now)704b349700aSAlan Somers TEST_F(Setattr, utimensat_utime_now) {
705b349700aSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
706b349700aSAlan Somers const char RELPATH[] = "some_file.txt";
707b349700aSAlan Somers const uint64_t ino = 42;
708b349700aSAlan Somers const timespec oldtimes[2] = {
709b349700aSAlan Somers {.tv_sec = 1, .tv_nsec = 2},
710b349700aSAlan Somers {.tv_sec = 3, .tv_nsec = 4},
711b349700aSAlan Somers };
712b349700aSAlan Somers const timespec newtimes[2] = {
713b349700aSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW},
714b349700aSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW},
715b349700aSAlan Somers };
716b349700aSAlan Somers /* "now" is whatever the server says it is */
717b349700aSAlan Somers const timespec now[2] = {
718b349700aSAlan Somers {.tv_sec = 5, .tv_nsec = 7},
719b349700aSAlan Somers {.tv_sec = 6, .tv_nsec = 8},
720b349700aSAlan Somers };
721b349700aSAlan Somers struct stat sb;
722b349700aSAlan Somers
723a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
72429edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
725b349700aSAlan Somers SET_OUT_HEADER_LEN(out, entry);
72629edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644;
72729edc611SAlan Somers out.body.entry.nodeid = ino;
72829edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX;
72929edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
73029edc611SAlan Somers out.body.entry.attr.atime = oldtimes[0].tv_sec;
73129edc611SAlan Somers out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
73229edc611SAlan Somers out.body.entry.attr.mtime = oldtimes[1].tv_sec;
73329edc611SAlan Somers out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
734b349700aSAlan Somers })));
735b349700aSAlan Somers
736b349700aSAlan Somers EXPECT_CALL(*m_mock, process(
737b349700aSAlan Somers ResultOf([=](auto in) {
738b349700aSAlan Somers uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
739b349700aSAlan Somers FATTR_MTIME | FATTR_MTIME_NOW;
74029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
74129edc611SAlan Somers in.header.nodeid == ino &&
74229edc611SAlan Somers in.body.setattr.valid == valid);
743b349700aSAlan Somers }, Eq(true)),
744b349700aSAlan Somers _)
74529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
746b349700aSAlan Somers SET_OUT_HEADER_LEN(out, attr);
74729edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
74829edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | 0644;
74929edc611SAlan Somers out.body.attr.attr.atime = now[0].tv_sec;
75029edc611SAlan Somers out.body.attr.attr.atimensec = now[0].tv_nsec;
75129edc611SAlan Somers out.body.attr.attr.mtime = now[1].tv_sec;
75229edc611SAlan Somers out.body.attr.attr.mtimensec = now[1].tv_nsec;
75329edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX;
754b349700aSAlan Somers })));
755b349700aSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
756b349700aSAlan Somers << strerror(errno);
757b349700aSAlan Somers ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
758b349700aSAlan Somers EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
759b349700aSAlan Somers EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
760b349700aSAlan Somers EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
761b349700aSAlan Somers EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
762b349700aSAlan Somers }
763b349700aSAlan Somers
76425927e06SAlan Somers /*
76525927e06SAlan Somers * FUSE_SETATTR returns a different file type, even though the entry cache
76625927e06SAlan Somers * hasn't expired. This is a server bug! It probably means that the server
76725927e06SAlan Somers * removed the file and recreated it with the same inode but a different vtyp.
76825927e06SAlan Somers * The best thing fusefs can do is return ENOENT to the caller. After all, the
76925927e06SAlan Somers * entry must not have existed recently.
77025927e06SAlan Somers */
TEST_F(Setattr,vtyp_conflict)77125927e06SAlan Somers TEST_F(Setattr, vtyp_conflict)
77225927e06SAlan Somers {
77325927e06SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
77425927e06SAlan Somers const char RELPATH[] = "some_file.txt";
77525927e06SAlan Somers const uint64_t ino = 42;
77625927e06SAlan Somers uid_t newuser = 12345;
77725927e06SAlan Somers sem_t sem;
77825927e06SAlan Somers
77925927e06SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
78025927e06SAlan Somers
78125927e06SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
78225927e06SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
78325927e06SAlan Somers SET_OUT_HEADER_LEN(out, entry);
78425927e06SAlan Somers out.body.entry.attr.mode = S_IFREG | 0777;
78525927e06SAlan Somers out.body.entry.nodeid = ino;
78625927e06SAlan Somers out.body.entry.entry_valid = UINT64_MAX;
78725927e06SAlan Somers })));
78825927e06SAlan Somers
78925927e06SAlan Somers EXPECT_CALL(*m_mock, process(
79025927e06SAlan Somers ResultOf([](auto in) {
79125927e06SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
79225927e06SAlan Somers in.header.nodeid == ino);
79325927e06SAlan Somers }, Eq(true)),
79425927e06SAlan Somers _)
79525927e06SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
79625927e06SAlan Somers SET_OUT_HEADER_LEN(out, attr);
79725927e06SAlan Somers out.body.attr.attr.ino = ino;
79825927e06SAlan Somers out.body.attr.attr.mode = S_IFDIR | 0777; // Changed!
79925927e06SAlan Somers out.body.attr.attr.uid = newuser;
80025927e06SAlan Somers })));
80125927e06SAlan Somers // We should reclaim stale vnodes
80225927e06SAlan Somers expect_forget(ino, 1, &sem);
80325927e06SAlan Somers
80425927e06SAlan Somers EXPECT_NE(0, chown(FULLPATH, newuser, -1));
80525927e06SAlan Somers EXPECT_EQ(ENOENT, errno);
80625927e06SAlan Somers
80725927e06SAlan Somers sem_wait(&sem);
80825927e06SAlan Somers sem_destroy(&sem);
80925927e06SAlan Somers }
81025927e06SAlan Somers
811666f8543SAlan Somers /* On a read-only mount, no attributes may be changed */
TEST_F(RofsSetattr,erofs)812666f8543SAlan Somers TEST_F(RofsSetattr, erofs)
813666f8543SAlan Somers {
814666f8543SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
815666f8543SAlan Somers const char RELPATH[] = "some_file.txt";
816666f8543SAlan Somers const uint64_t ino = 42;
817666f8543SAlan Somers const mode_t oldmode = 0755;
818666f8543SAlan Somers const mode_t newmode = 0644;
819666f8543SAlan Somers
820a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
82129edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
822666f8543SAlan Somers SET_OUT_HEADER_LEN(out, entry);
82329edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | oldmode;
82429edc611SAlan Somers out.body.entry.nodeid = ino;
825666f8543SAlan Somers })));
826666f8543SAlan Somers
827666f8543SAlan Somers ASSERT_EQ(-1, chmod(FULLPATH, newmode));
828666f8543SAlan Somers ASSERT_EQ(EROFS, errno);
829666f8543SAlan Somers }
83016bd2d47SAlan Somers
83116bd2d47SAlan Somers /* Change the mode of a file */
TEST_F(Setattr_7_8,chmod)83216bd2d47SAlan Somers TEST_F(Setattr_7_8, chmod)
83316bd2d47SAlan Somers {
83416bd2d47SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt";
83516bd2d47SAlan Somers const char RELPATH[] = "some_file.txt";
83616bd2d47SAlan Somers const uint64_t ino = 42;
83716bd2d47SAlan Somers const mode_t oldmode = 0755;
83816bd2d47SAlan Somers const mode_t newmode = 0644;
83916bd2d47SAlan Somers
840a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
84129edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
84216bd2d47SAlan Somers SET_OUT_HEADER_LEN(out, entry_7_8);
84329edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | oldmode;
84429edc611SAlan Somers out.body.entry.nodeid = ino;
84516bd2d47SAlan Somers })));
84616bd2d47SAlan Somers
84716bd2d47SAlan Somers EXPECT_CALL(*m_mock, process(
84816bd2d47SAlan Somers ResultOf([](auto in) {
84916bd2d47SAlan Somers uint32_t valid = FATTR_MODE;
85029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR &&
85129edc611SAlan Somers in.header.nodeid == ino &&
85229edc611SAlan Somers in.body.setattr.valid == valid &&
85329edc611SAlan Somers in.body.setattr.mode == newmode);
85416bd2d47SAlan Somers }, Eq(true)),
85516bd2d47SAlan Somers _)
85629edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
85716bd2d47SAlan Somers SET_OUT_HEADER_LEN(out, attr_7_8);
85829edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid
85929edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode;
86016bd2d47SAlan Somers })));
86116bd2d47SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
86216bd2d47SAlan Somers }
863