19821f1d3SAlan Somers /*- 29821f1d3SAlan Somers * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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. 291fa8ebfbSAlan Somers * 301fa8ebfbSAlan Somers * $FreeBSD$ 319821f1d3SAlan Somers */ 329821f1d3SAlan Somers 339821f1d3SAlan Somers extern "C" { 34*bfcb817bSAlan Somers #include <sys/types.h> 35*bfcb817bSAlan Somers #include <sys/extattr.h> 36*bfcb817bSAlan Somers 379821f1d3SAlan Somers #include <fcntl.h> 389821f1d3SAlan Somers #include <unistd.h> 399821f1d3SAlan Somers } 409821f1d3SAlan Somers 419821f1d3SAlan Somers #include "mockfs.hh" 429821f1d3SAlan Somers #include "utils.hh" 439821f1d3SAlan Somers 449821f1d3SAlan Somers using namespace testing; 459821f1d3SAlan Somers 469821f1d3SAlan Somers class Access: public FuseTest { 479821f1d3SAlan Somers public: 48*bfcb817bSAlan Somers virtual void SetUp() { 49*bfcb817bSAlan Somers FuseTest::SetUp(); 50*bfcb817bSAlan Somers // Clear the default FUSE_ACCESS expectation 51*bfcb817bSAlan Somers Mock::VerifyAndClearExpectations(m_mock); 52*bfcb817bSAlan Somers } 53*bfcb817bSAlan Somers 549821f1d3SAlan Somers void expect_lookup(const char *relpath, uint64_t ino) 559821f1d3SAlan Somers { 569821f1d3SAlan Somers FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); 579821f1d3SAlan Somers } 58*bfcb817bSAlan Somers 59*bfcb817bSAlan Somers /* 60*bfcb817bSAlan Somers * Expect tha FUSE_ACCESS will never be called for the given inode, with any 61*bfcb817bSAlan Somers * bits in the supplied access_mask set 62*bfcb817bSAlan Somers */ 63*bfcb817bSAlan Somers void expect_noaccess(uint64_t ino, mode_t access_mask) 64*bfcb817bSAlan Somers { 65*bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 66*bfcb817bSAlan Somers ResultOf([=](auto in) { 67*bfcb817bSAlan Somers return (in.header.opcode == FUSE_ACCESS && 68*bfcb817bSAlan Somers in.header.nodeid == ino && 69*bfcb817bSAlan Somers in.body.access.mask & access_mask); 70*bfcb817bSAlan Somers }, Eq(true)), 71*bfcb817bSAlan Somers _) 72*bfcb817bSAlan Somers ).Times(0); 73*bfcb817bSAlan Somers } 74*bfcb817bSAlan Somers 759821f1d3SAlan Somers }; 769821f1d3SAlan Somers 77140bb492SAlan Somers class RofsAccess: public Access { 78140bb492SAlan Somers public: 79140bb492SAlan Somers virtual void SetUp() { 80140bb492SAlan Somers m_ro = true; 81140bb492SAlan Somers Access::SetUp(); 82140bb492SAlan Somers } 83140bb492SAlan Somers }; 84140bb492SAlan Somers 85*bfcb817bSAlan Somers /* 86*bfcb817bSAlan Somers * Change the mode of a file. 87*bfcb817bSAlan Somers * 88*bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 89*bfcb817bSAlan Somers * search permissions on the parent directory. 90*bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 91*bfcb817bSAlan Somers */ 92*bfcb817bSAlan Somers TEST_F(Access, chmod) 93*bfcb817bSAlan Somers { 94*bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 95*bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 96*bfcb817bSAlan Somers const uint64_t ino = 42; 97*bfcb817bSAlan Somers const mode_t newmode = 0644; 98*bfcb817bSAlan Somers 99*bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 100*bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 101*bfcb817bSAlan Somers expect_noaccess(ino, 0); 102*bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 103*bfcb817bSAlan Somers ResultOf([](auto in) { 104*bfcb817bSAlan Somers return (in.header.opcode == FUSE_SETATTR && 105*bfcb817bSAlan Somers in.header.nodeid == ino); 106*bfcb817bSAlan Somers }, Eq(true)), 107*bfcb817bSAlan Somers _) 108*bfcb817bSAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 109*bfcb817bSAlan Somers SET_OUT_HEADER_LEN(out, attr); 110*bfcb817bSAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 111*bfcb817bSAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 112*bfcb817bSAlan Somers }))); 113*bfcb817bSAlan Somers 114*bfcb817bSAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 115*bfcb817bSAlan Somers } 116*bfcb817bSAlan Somers 117*bfcb817bSAlan Somers /* 118*bfcb817bSAlan Somers * Create a new file 119*bfcb817bSAlan Somers * 120*bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 121*bfcb817bSAlan Somers * search permissions on the parent directory. 122*bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 123*bfcb817bSAlan Somers */ 124*bfcb817bSAlan Somers TEST_F(Access, create) 125*bfcb817bSAlan Somers { 126*bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 127*bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 128*bfcb817bSAlan Somers mode_t mode = S_IFREG | 0755; 129*bfcb817bSAlan Somers uint64_t ino = 42; 130*bfcb817bSAlan Somers 131*bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 132*bfcb817bSAlan Somers expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK); 133*bfcb817bSAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 134*bfcb817bSAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 135*bfcb817bSAlan Somers expect_noaccess(ino, 0); 136*bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 137*bfcb817bSAlan Somers ResultOf([=](auto in) { 138*bfcb817bSAlan Somers return (in.header.opcode == FUSE_CREATE); 139*bfcb817bSAlan Somers }, Eq(true)), 140*bfcb817bSAlan Somers _) 141*bfcb817bSAlan Somers ).WillOnce(ReturnErrno(EPERM)); 142*bfcb817bSAlan Somers 143*bfcb817bSAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 144*bfcb817bSAlan Somers EXPECT_EQ(EPERM, errno); 145*bfcb817bSAlan Somers } 146*bfcb817bSAlan Somers 1479821f1d3SAlan Somers /* The error case of FUSE_ACCESS. */ 148caf5f57dSAlan Somers TEST_F(Access, eaccess) 1499821f1d3SAlan Somers { 1509821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1519821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1529821f1d3SAlan Somers uint64_t ino = 42; 1539821f1d3SAlan Somers mode_t access_mode = X_OK; 1549821f1d3SAlan Somers 155a34cdd26SAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 1569821f1d3SAlan Somers expect_lookup(RELPATH, ino); 1579821f1d3SAlan Somers expect_access(ino, access_mode, EACCES); 1589821f1d3SAlan Somers 1599821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 1609821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 1619821f1d3SAlan Somers } 1629821f1d3SAlan Somers 1639821f1d3SAlan Somers /* 1649821f1d3SAlan Somers * If the filesystem returns ENOSYS, then it is treated as a permanent success, 1659821f1d3SAlan Somers * and subsequent VOP_ACCESS calls will succeed automatically without querying 1669821f1d3SAlan Somers * the daemon. 1679821f1d3SAlan Somers */ 168caf5f57dSAlan Somers TEST_F(Access, enosys) 1699821f1d3SAlan Somers { 1709821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1719821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1729821f1d3SAlan Somers uint64_t ino = 42; 1739821f1d3SAlan Somers mode_t access_mode = R_OK; 1749821f1d3SAlan Somers 175a34cdd26SAlan Somers expect_access(FUSE_ROOT_ID, X_OK, ENOSYS); 176caf5f57dSAlan Somers FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 1779821f1d3SAlan Somers 1789821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 1799821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 1809821f1d3SAlan Somers } 1819821f1d3SAlan Somers 182140bb492SAlan Somers TEST_F(RofsAccess, erofs) 183140bb492SAlan Somers { 184140bb492SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 185140bb492SAlan Somers const char RELPATH[] = "some_file.txt"; 186140bb492SAlan Somers uint64_t ino = 42; 187140bb492SAlan Somers mode_t access_mode = W_OK; 188140bb492SAlan Somers 189a34cdd26SAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 190140bb492SAlan Somers expect_lookup(RELPATH, ino); 191140bb492SAlan Somers 192140bb492SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 193140bb492SAlan Somers ASSERT_EQ(EROFS, errno); 194140bb492SAlan Somers } 195140bb492SAlan Somers 196*bfcb817bSAlan Somers 197*bfcb817bSAlan Somers /* 198*bfcb817bSAlan Somers * Lookup an extended attribute 199*bfcb817bSAlan Somers * 200*bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 201*bfcb817bSAlan Somers * search permissions on the parent directory. 202*bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 203*bfcb817bSAlan Somers */ 204*bfcb817bSAlan Somers TEST_F(Access, Getxattr) 205*bfcb817bSAlan Somers { 206*bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 207*bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 208*bfcb817bSAlan Somers uint64_t ino = 42; 209*bfcb817bSAlan Somers char data[80]; 210*bfcb817bSAlan Somers int ns = EXTATTR_NAMESPACE_USER; 211*bfcb817bSAlan Somers ssize_t r; 212*bfcb817bSAlan Somers 213*bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 214*bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 215*bfcb817bSAlan Somers expect_noaccess(ino, 0); 216*bfcb817bSAlan Somers expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); 217*bfcb817bSAlan Somers 218*bfcb817bSAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 219*bfcb817bSAlan Somers ASSERT_EQ(-1, r); 220*bfcb817bSAlan Somers ASSERT_EQ(ENOATTR, errno); 221*bfcb817bSAlan Somers } 222*bfcb817bSAlan Somers 2239821f1d3SAlan Somers /* The successful case of FUSE_ACCESS. */ 224caf5f57dSAlan Somers TEST_F(Access, ok) 2259821f1d3SAlan Somers { 2269821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2279821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2289821f1d3SAlan Somers uint64_t ino = 42; 2299821f1d3SAlan Somers mode_t access_mode = R_OK; 2309821f1d3SAlan Somers 231a34cdd26SAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 2329821f1d3SAlan Somers expect_lookup(RELPATH, ino); 2339821f1d3SAlan Somers expect_access(ino, access_mode, 0); 2349821f1d3SAlan Somers 2359821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 2369821f1d3SAlan Somers } 237*bfcb817bSAlan Somers 238*bfcb817bSAlan Somers /* 239*bfcb817bSAlan Somers * Unlink a file 240*bfcb817bSAlan Somers * 241*bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 242*bfcb817bSAlan Somers * search permissions on the parent directory. 243*bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 244*bfcb817bSAlan Somers */ 245*bfcb817bSAlan Somers TEST_F(Access, unlink) 246*bfcb817bSAlan Somers { 247*bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 248*bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 249*bfcb817bSAlan Somers uint64_t ino = 42; 250*bfcb817bSAlan Somers 251*bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 252*bfcb817bSAlan Somers expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); 253*bfcb817bSAlan Somers expect_noaccess(ino, 0); 254*bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 255*bfcb817bSAlan Somers expect_unlink(1, RELPATH, EPERM); 256*bfcb817bSAlan Somers 257*bfcb817bSAlan Somers ASSERT_NE(0, unlink(FULLPATH)); 258*bfcb817bSAlan Somers ASSERT_EQ(EPERM, errno); 259*bfcb817bSAlan Somers } 260*bfcb817bSAlan Somers 261*bfcb817bSAlan Somers /* 262*bfcb817bSAlan Somers * Unlink a file whose parent diretory's sticky bit is set 263*bfcb817bSAlan Somers * 264*bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 265*bfcb817bSAlan Somers * search permissions on the parent directory. 266*bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 267*bfcb817bSAlan Somers */ 268*bfcb817bSAlan Somers TEST_F(Access, unlink_sticky_directory) 269*bfcb817bSAlan Somers { 270*bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 271*bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 272*bfcb817bSAlan Somers uint64_t ino = 42; 273*bfcb817bSAlan Somers 274*bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 275*bfcb817bSAlan Somers expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); 276*bfcb817bSAlan Somers expect_noaccess(ino, 0); 277*bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 278*bfcb817bSAlan Somers ResultOf([=](auto in) { 279*bfcb817bSAlan Somers return (in.header.opcode == FUSE_GETATTR && 280*bfcb817bSAlan Somers in.header.nodeid == FUSE_ROOT_ID); 281*bfcb817bSAlan Somers }, Eq(true)), 282*bfcb817bSAlan Somers _) 283*bfcb817bSAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) 284*bfcb817bSAlan Somers { 285*bfcb817bSAlan Somers SET_OUT_HEADER_LEN(out, attr); 286*bfcb817bSAlan Somers out.body.attr.attr.ino = FUSE_ROOT_ID; 287*bfcb817bSAlan Somers out.body.attr.attr.mode = S_IFDIR | 01777; 288*bfcb817bSAlan Somers out.body.attr.attr.uid = 0; 289*bfcb817bSAlan Somers out.body.attr.attr_valid = UINT64_MAX; 290*bfcb817bSAlan Somers }))); 291*bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 292*bfcb817bSAlan Somers ResultOf([=](auto in) { 293*bfcb817bSAlan Somers return (in.header.opcode == FUSE_ACCESS && 294*bfcb817bSAlan Somers in.header.nodeid == ino); 295*bfcb817bSAlan Somers }, Eq(true)), 296*bfcb817bSAlan Somers _) 297*bfcb817bSAlan Somers ).Times(0); 298*bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 299*bfcb817bSAlan Somers expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM); 300*bfcb817bSAlan Somers 301*bfcb817bSAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 302*bfcb817bSAlan Somers ASSERT_EQ(EPERM, errno); 303*bfcb817bSAlan Somers } 304