19821f1d3SAlan Somers /*- 2*4d846d26SWarner 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. 291fa8ebfbSAlan Somers * 301fa8ebfbSAlan Somers * $FreeBSD$ 319821f1d3SAlan Somers */ 329821f1d3SAlan Somers 339821f1d3SAlan Somers extern "C" { 34bfcb817bSAlan Somers #include <sys/types.h> 35bfcb817bSAlan Somers #include <sys/extattr.h> 36bfcb817bSAlan 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: 48bfcb817bSAlan Somers virtual void SetUp() { 49bfcb817bSAlan Somers FuseTest::SetUp(); 50bfcb817bSAlan Somers // Clear the default FUSE_ACCESS expectation 51bfcb817bSAlan Somers Mock::VerifyAndClearExpectations(m_mock); 52bfcb817bSAlan Somers } 53bfcb817bSAlan 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 } 58bfcb817bSAlan Somers 59bfcb817bSAlan Somers /* 60bfcb817bSAlan Somers * Expect tha FUSE_ACCESS will never be called for the given inode, with any 61bfcb817bSAlan Somers * bits in the supplied access_mask set 62bfcb817bSAlan Somers */ 63bfcb817bSAlan Somers void expect_noaccess(uint64_t ino, mode_t access_mask) 64bfcb817bSAlan Somers { 65bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 66bfcb817bSAlan Somers ResultOf([=](auto in) { 67bfcb817bSAlan Somers return (in.header.opcode == FUSE_ACCESS && 68bfcb817bSAlan Somers in.header.nodeid == ino && 69bfcb817bSAlan Somers in.body.access.mask & access_mask); 70bfcb817bSAlan Somers }, Eq(true)), 71bfcb817bSAlan Somers _) 72bfcb817bSAlan Somers ).Times(0); 73bfcb817bSAlan Somers } 74bfcb817bSAlan 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 85bfcb817bSAlan Somers /* 86bfcb817bSAlan Somers * Change the mode of a file. 87bfcb817bSAlan Somers * 88bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 89bfcb817bSAlan Somers * search permissions on the parent directory. 90bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 91bfcb817bSAlan Somers */ 92bfcb817bSAlan Somers TEST_F(Access, chmod) 93bfcb817bSAlan Somers { 94bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 95bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 96bfcb817bSAlan Somers const uint64_t ino = 42; 97bfcb817bSAlan Somers const mode_t newmode = 0644; 98bfcb817bSAlan Somers 99bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 100bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 101bfcb817bSAlan Somers expect_noaccess(ino, 0); 102bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 103bfcb817bSAlan Somers ResultOf([](auto in) { 104bfcb817bSAlan Somers return (in.header.opcode == FUSE_SETATTR && 105bfcb817bSAlan Somers in.header.nodeid == ino); 106bfcb817bSAlan Somers }, Eq(true)), 107bfcb817bSAlan Somers _) 108bfcb817bSAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 109bfcb817bSAlan Somers SET_OUT_HEADER_LEN(out, attr); 110bfcb817bSAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 111bfcb817bSAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 112bfcb817bSAlan Somers }))); 113bfcb817bSAlan Somers 114bfcb817bSAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 115bfcb817bSAlan Somers } 116bfcb817bSAlan Somers 117bfcb817bSAlan Somers /* 118bfcb817bSAlan Somers * Create a new file 119bfcb817bSAlan Somers * 120bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 121bfcb817bSAlan Somers * search permissions on the parent directory. 122bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 123bfcb817bSAlan Somers */ 124bfcb817bSAlan Somers TEST_F(Access, create) 125bfcb817bSAlan Somers { 126bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 127bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 128bfcb817bSAlan Somers mode_t mode = S_IFREG | 0755; 129bfcb817bSAlan Somers uint64_t ino = 42; 130bfcb817bSAlan Somers 131bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 132bfcb817bSAlan Somers expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK); 133bfcb817bSAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 134bfcb817bSAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 135bfcb817bSAlan Somers expect_noaccess(ino, 0); 136bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 137bfcb817bSAlan Somers ResultOf([=](auto in) { 138bfcb817bSAlan Somers return (in.header.opcode == FUSE_CREATE); 139bfcb817bSAlan Somers }, Eq(true)), 140bfcb817bSAlan Somers _) 141bfcb817bSAlan Somers ).WillOnce(ReturnErrno(EPERM)); 142bfcb817bSAlan Somers 143bfcb817bSAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 144bfcb817bSAlan Somers EXPECT_EQ(EPERM, errno); 145bfcb817bSAlan Somers } 146bfcb817bSAlan 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 196bfcb817bSAlan Somers 197bfcb817bSAlan Somers /* 198bfcb817bSAlan Somers * Lookup an extended attribute 199bfcb817bSAlan Somers * 200bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 201bfcb817bSAlan Somers * search permissions on the parent directory. 202bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 203bfcb817bSAlan Somers */ 204bfcb817bSAlan Somers TEST_F(Access, Getxattr) 205bfcb817bSAlan Somers { 206bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 207bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 208bfcb817bSAlan Somers uint64_t ino = 42; 209bfcb817bSAlan Somers char data[80]; 210bfcb817bSAlan Somers int ns = EXTATTR_NAMESPACE_USER; 211bfcb817bSAlan Somers ssize_t r; 212bfcb817bSAlan Somers 213bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 214bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 215bfcb817bSAlan Somers expect_noaccess(ino, 0); 216bfcb817bSAlan Somers expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); 217bfcb817bSAlan Somers 218bfcb817bSAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 219bfcb817bSAlan Somers ASSERT_EQ(-1, r); 220bfcb817bSAlan Somers ASSERT_EQ(ENOATTR, errno); 221bfcb817bSAlan Somers } 222bfcb817bSAlan 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 } 237bfcb817bSAlan Somers 238bfcb817bSAlan Somers /* 239bfcb817bSAlan Somers * Unlink a file 240bfcb817bSAlan Somers * 241bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 242bfcb817bSAlan Somers * search permissions on the parent directory. 243bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 244bfcb817bSAlan Somers */ 245bfcb817bSAlan Somers TEST_F(Access, unlink) 246bfcb817bSAlan Somers { 247bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 248bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 249bfcb817bSAlan Somers uint64_t ino = 42; 250bfcb817bSAlan Somers 251bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 252bfcb817bSAlan Somers expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); 253bfcb817bSAlan Somers expect_noaccess(ino, 0); 254bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 255bfcb817bSAlan Somers expect_unlink(1, RELPATH, EPERM); 256bfcb817bSAlan Somers 257bfcb817bSAlan Somers ASSERT_NE(0, unlink(FULLPATH)); 258bfcb817bSAlan Somers ASSERT_EQ(EPERM, errno); 259bfcb817bSAlan Somers } 260bfcb817bSAlan Somers 261bfcb817bSAlan Somers /* 262bfcb817bSAlan Somers * Unlink a file whose parent diretory's sticky bit is set 263bfcb817bSAlan Somers * 264bfcb817bSAlan Somers * There should never be a FUSE_ACCESS sent for this operation, except for 265bfcb817bSAlan Somers * search permissions on the parent directory. 266bfcb817bSAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 267bfcb817bSAlan Somers */ 268bfcb817bSAlan Somers TEST_F(Access, unlink_sticky_directory) 269bfcb817bSAlan Somers { 270bfcb817bSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 271bfcb817bSAlan Somers const char RELPATH[] = "some_file.txt"; 272bfcb817bSAlan Somers uint64_t ino = 42; 273bfcb817bSAlan Somers 274bfcb817bSAlan Somers expect_access(FUSE_ROOT_ID, X_OK, 0); 275bfcb817bSAlan Somers expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); 276bfcb817bSAlan Somers expect_noaccess(ino, 0); 277bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 278bfcb817bSAlan Somers ResultOf([=](auto in) { 279bfcb817bSAlan Somers return (in.header.opcode == FUSE_GETATTR && 280bfcb817bSAlan Somers in.header.nodeid == FUSE_ROOT_ID); 281bfcb817bSAlan Somers }, Eq(true)), 282bfcb817bSAlan Somers _) 283bfcb817bSAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) 284bfcb817bSAlan Somers { 285bfcb817bSAlan Somers SET_OUT_HEADER_LEN(out, attr); 286bfcb817bSAlan Somers out.body.attr.attr.ino = FUSE_ROOT_ID; 287bfcb817bSAlan Somers out.body.attr.attr.mode = S_IFDIR | 01777; 288bfcb817bSAlan Somers out.body.attr.attr.uid = 0; 289bfcb817bSAlan Somers out.body.attr.attr_valid = UINT64_MAX; 290bfcb817bSAlan Somers }))); 291bfcb817bSAlan Somers EXPECT_CALL(*m_mock, process( 292bfcb817bSAlan Somers ResultOf([=](auto in) { 293bfcb817bSAlan Somers return (in.header.opcode == FUSE_ACCESS && 294bfcb817bSAlan Somers in.header.nodeid == ino); 295bfcb817bSAlan Somers }, Eq(true)), 296bfcb817bSAlan Somers _) 297bfcb817bSAlan Somers ).Times(0); 298bfcb817bSAlan Somers expect_lookup(RELPATH, ino); 299bfcb817bSAlan Somers expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM); 300bfcb817bSAlan Somers 301bfcb817bSAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 302bfcb817bSAlan Somers ASSERT_EQ(EPERM, errno); 303bfcb817bSAlan Somers } 304