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. 299821f1d3SAlan Somers */ 309821f1d3SAlan Somers 319821f1d3SAlan Somers /* 329821f1d3SAlan Somers * Tests for the "default_permissions" mount option. They must be in their own 339821f1d3SAlan Somers * file so they can be run as an unprivileged user 349821f1d3SAlan Somers */ 359821f1d3SAlan Somers 369821f1d3SAlan Somers extern "C" { 37ff4fbdf5SAlan Somers #include <sys/types.h> 38ff4fbdf5SAlan Somers #include <sys/extattr.h> 39ff4fbdf5SAlan Somers 409821f1d3SAlan Somers #include <fcntl.h> 419821f1d3SAlan Somers #include <unistd.h> 429821f1d3SAlan Somers } 439821f1d3SAlan Somers 449821f1d3SAlan Somers #include "mockfs.hh" 459821f1d3SAlan Somers #include "utils.hh" 469821f1d3SAlan Somers 479821f1d3SAlan Somers using namespace testing; 489821f1d3SAlan Somers 499821f1d3SAlan Somers class DefaultPermissions: public FuseTest { 509821f1d3SAlan Somers 519821f1d3SAlan Somers virtual void SetUp() { 52ff4fbdf5SAlan Somers m_default_permissions = true; 539821f1d3SAlan Somers FuseTest::SetUp(); 54ff4fbdf5SAlan Somers if (HasFatalFailure() || IsSkipped()) 55ff4fbdf5SAlan Somers return; 569821f1d3SAlan Somers 579821f1d3SAlan Somers if (geteuid() == 0) { 589821f1d3SAlan Somers GTEST_SKIP() << "This test requires an unprivileged user"; 599821f1d3SAlan Somers } 60ff4fbdf5SAlan Somers 61ff4fbdf5SAlan Somers /* With -o default_permissions, FUSE_ACCESS should never be called */ 62ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 63ff4fbdf5SAlan Somers ResultOf([=](auto in) { 64ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_ACCESS); 65ff4fbdf5SAlan Somers }, Eq(true)), 66ff4fbdf5SAlan Somers _) 67ff4fbdf5SAlan Somers ).Times(0); 689821f1d3SAlan Somers } 699821f1d3SAlan Somers 709821f1d3SAlan Somers public: 71*a90e32deSAlan Somers void expect_chmod(uint64_t ino, mode_t mode) 72*a90e32deSAlan Somers { 73*a90e32deSAlan Somers EXPECT_CALL(*m_mock, process( 74*a90e32deSAlan Somers ResultOf([=](auto in) { 75*a90e32deSAlan Somers return (in->header.opcode == FUSE_SETATTR && 76*a90e32deSAlan Somers in->header.nodeid == ino && 77*a90e32deSAlan Somers in->body.setattr.valid == FATTR_MODE && 78*a90e32deSAlan Somers in->body.setattr.mode == mode); 79*a90e32deSAlan Somers }, Eq(true)), 80*a90e32deSAlan Somers _) 81*a90e32deSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 82*a90e32deSAlan Somers SET_OUT_HEADER_LEN(out, attr); 83*a90e32deSAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 84*a90e32deSAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 85*a90e32deSAlan Somers out->body.attr.attr_valid = UINT64_MAX; 86*a90e32deSAlan Somers }))); 87*a90e32deSAlan Somers } 88*a90e32deSAlan Somers 89ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times, 90474ba6faSAlan Somers uid_t uid = 0, gid_t gid = 0) 919821f1d3SAlan Somers { 92ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 93ff4fbdf5SAlan Somers ResultOf([=](auto in) { 94ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_GETATTR && 95ff4fbdf5SAlan Somers in->header.nodeid == ino); 96ff4fbdf5SAlan Somers }, Eq(true)), 97ff4fbdf5SAlan Somers _) 98ff4fbdf5SAlan Somers ).Times(times) 99ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 100ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 101ff4fbdf5SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 102ff4fbdf5SAlan Somers out->body.attr.attr.mode = mode; 103ff4fbdf5SAlan Somers out->body.attr.attr.size = 0; 104ff4fbdf5SAlan Somers out->body.attr.attr.uid = uid; 105474ba6faSAlan Somers out->body.attr.attr.uid = gid; 106ff4fbdf5SAlan Somers out->body.attr.attr_valid = attr_valid; 107ff4fbdf5SAlan Somers }))); 108ff4fbdf5SAlan Somers } 109ff4fbdf5SAlan Somers 110ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 111474ba6faSAlan Somers uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0) 112ff4fbdf5SAlan Somers { 113474ba6faSAlan Somers FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid); 1149821f1d3SAlan Somers } 1159821f1d3SAlan Somers 1169821f1d3SAlan Somers }; 1179821f1d3SAlan Somers 1189821f1d3SAlan Somers class Access: public DefaultPermissions {}; 119474ba6faSAlan Somers class Chown: public DefaultPermissions {}; 120474ba6faSAlan Somers class Chgrp: public DefaultPermissions {}; 121ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {}; 1229821f1d3SAlan Somers class Open: public DefaultPermissions {}; 123ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {}; 124ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {}; 125*a90e32deSAlan Somers class Write: public DefaultPermissions {}; 1269821f1d3SAlan Somers 127ff4fbdf5SAlan Somers /* 128ff4fbdf5SAlan Somers * Test permission handling during create, mkdir, mknod, link, symlink, and 129ff4fbdf5SAlan Somers * rename vops (they all share a common path for permission checks in 130ff4fbdf5SAlan Somers * VOP_LOOKUP) 131ff4fbdf5SAlan Somers */ 132ff4fbdf5SAlan Somers class Create: public DefaultPermissions { 133ff4fbdf5SAlan Somers public: 134ff4fbdf5SAlan Somers 135ff4fbdf5SAlan Somers void expect_create(const char *relpath, uint64_t ino) 136ff4fbdf5SAlan Somers { 137ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 138ff4fbdf5SAlan Somers ResultOf([=](auto in) { 139ff4fbdf5SAlan Somers const char *name = (const char*)in->body.bytes + 140ff4fbdf5SAlan Somers sizeof(fuse_open_in); 141ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_CREATE && 142ff4fbdf5SAlan Somers (0 == strcmp(relpath, name))); 143ff4fbdf5SAlan Somers }, Eq(true)), 144ff4fbdf5SAlan Somers _) 145ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 146ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, create); 147ff4fbdf5SAlan Somers out->body.create.entry.attr.mode = S_IFREG | 0644; 148ff4fbdf5SAlan Somers out->body.create.entry.nodeid = ino; 149ff4fbdf5SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 150ff4fbdf5SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 151ff4fbdf5SAlan Somers }))); 152ff4fbdf5SAlan Somers } 153ff4fbdf5SAlan Somers 154ff4fbdf5SAlan Somers }; 155ff4fbdf5SAlan Somers 156ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions { 157ff4fbdf5SAlan Somers public: 158ff4fbdf5SAlan Somers void expect_removexattr() 159ff4fbdf5SAlan Somers { 160ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 161ff4fbdf5SAlan Somers ResultOf([=](auto in) { 162ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_REMOVEXATTR); 163ff4fbdf5SAlan Somers }, Eq(true)), 164ff4fbdf5SAlan Somers _) 165ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(0))); 166ff4fbdf5SAlan Somers } 167ff4fbdf5SAlan Somers }; 168ff4fbdf5SAlan Somers 169ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions { 170ff4fbdf5SAlan Somers public: 171ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r) 172ff4fbdf5SAlan Somers { 173ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 174ff4fbdf5SAlan Somers ResultOf([=](auto in) { 175ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_GETXATTR); 176ff4fbdf5SAlan Somers }, Eq(true)), 177ff4fbdf5SAlan Somers _) 178ff4fbdf5SAlan Somers ).WillOnce(Invoke(r)); 179ff4fbdf5SAlan Somers } 180ff4fbdf5SAlan Somers }; 181ff4fbdf5SAlan Somers 182ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions { 183ff4fbdf5SAlan Somers public: 184ff4fbdf5SAlan Somers void expect_listxattr() 185ff4fbdf5SAlan Somers { 186ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 187ff4fbdf5SAlan Somers ResultOf([=](auto in) { 188ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_LISTXATTR); 189ff4fbdf5SAlan Somers }, Eq(true)), 190ff4fbdf5SAlan Somers _) 191ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) { 192ff4fbdf5SAlan Somers out->body.listxattr.size = 0; 193ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, listxattr); 194ff4fbdf5SAlan Somers }))); 195ff4fbdf5SAlan Somers } 196ff4fbdf5SAlan Somers }; 197ff4fbdf5SAlan Somers 198ff4fbdf5SAlan Somers class Rename: public DefaultPermissions { 199ff4fbdf5SAlan Somers public: 200ff4fbdf5SAlan Somers /* 201ff4fbdf5SAlan Somers * Expect a rename and respond with the given error. Don't both to 202ff4fbdf5SAlan Somers * validate arguments; the tests in rename.cc do that. 203ff4fbdf5SAlan Somers */ 204ff4fbdf5SAlan Somers void expect_rename(int error) 205ff4fbdf5SAlan Somers { 206ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 207ff4fbdf5SAlan Somers ResultOf([=](auto in) { 208ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_RENAME); 209ff4fbdf5SAlan Somers }, Eq(true)), 210ff4fbdf5SAlan Somers _) 211ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 212ff4fbdf5SAlan Somers } 213ff4fbdf5SAlan Somers }; 214ff4fbdf5SAlan Somers 215ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions { 216ff4fbdf5SAlan Somers public: 217ff4fbdf5SAlan Somers void expect_setxattr(int error) 218ff4fbdf5SAlan Somers { 219ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 220ff4fbdf5SAlan Somers ResultOf([=](auto in) { 221ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETXATTR); 222ff4fbdf5SAlan Somers }, Eq(true)), 223ff4fbdf5SAlan Somers _) 224ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 225ff4fbdf5SAlan Somers } 226ff4fbdf5SAlan Somers }; 227ff4fbdf5SAlan Somers 228ff4fbdf5SAlan Somers TEST_F(Access, eacces) 2299821f1d3SAlan Somers { 2309821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2319821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2329821f1d3SAlan Somers uint64_t ino = 42; 2339821f1d3SAlan Somers mode_t access_mode = X_OK; 2349821f1d3SAlan Somers 235ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 236ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 237ff4fbdf5SAlan Somers 238ff4fbdf5SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 239ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 240ff4fbdf5SAlan Somers } 241ff4fbdf5SAlan Somers 242ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs) 243ff4fbdf5SAlan Somers { 244ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 245ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 246ff4fbdf5SAlan Somers uint64_t ino = 42; 247ff4fbdf5SAlan Somers mode_t access_mode = X_OK; 248ff4fbdf5SAlan Somers 249ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, 0, 1); 250ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 251ff4fbdf5SAlan Somers expect_getattr(ino, S_IFREG | 0644, 0, 1); 2529821f1d3SAlan Somers /* 2539821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 2549821f1d3SAlan Somers * another FUSE_GETATTR or something in here. But there should not be 2559821f1d3SAlan Somers * a FUSE_ACCESS 2569821f1d3SAlan Somers */ 2579821f1d3SAlan Somers 2589821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 2599821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 2609821f1d3SAlan Somers } 2619821f1d3SAlan Somers 262ff4fbdf5SAlan Somers TEST_F(Access, ok) 2639821f1d3SAlan Somers { 2649821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2659821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2669821f1d3SAlan Somers uint64_t ino = 42; 2679821f1d3SAlan Somers mode_t access_mode = R_OK; 2689821f1d3SAlan Somers 269ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 270ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 2719821f1d3SAlan Somers /* 2729821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 27391ff3a0dSAlan Somers * another FUSE_GETATTR or something in here. 2749821f1d3SAlan Somers */ 2759821f1d3SAlan Somers 2769821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 2779821f1d3SAlan Somers } 2789821f1d3SAlan Somers 279474ba6faSAlan Somers /* Only root may change a file's owner */ 280474ba6faSAlan Somers TEST_F(Chown, eperm) 281474ba6faSAlan Somers { 282474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 283474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 284474ba6faSAlan Somers const uint64_t ino = 42; 285474ba6faSAlan Somers const mode_t mode = 0755; 286474ba6faSAlan Somers 287474ba6faSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 288474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 289474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 290474ba6faSAlan Somers ResultOf([](auto in) { 291474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR); 292474ba6faSAlan Somers }, Eq(true)), 293474ba6faSAlan Somers _) 294474ba6faSAlan Somers ).Times(0); 295474ba6faSAlan Somers 296474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, 0, -1)); 297474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 298474ba6faSAlan Somers } 299474ba6faSAlan Somers 300474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */ 301474ba6faSAlan Somers TEST_F(Chgrp, eperm) 302474ba6faSAlan Somers { 303474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 304474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 305474ba6faSAlan Somers const uint64_t ino = 42; 306474ba6faSAlan Somers const mode_t mode = 0755; 307474ba6faSAlan Somers int ngroups = 64; 308474ba6faSAlan Somers gid_t groups[ngroups]; 309474ba6faSAlan Somers uid_t uid; 310474ba6faSAlan Somers gid_t gid, newgid; 311474ba6faSAlan Somers int i; 312474ba6faSAlan Somers 313474ba6faSAlan Somers uid = geteuid(); 314474ba6faSAlan Somers gid = getegid(); 315474ba6faSAlan Somers getgrouplist(getlogin(), getegid(), groups, &ngroups); 316474ba6faSAlan Somers for (newgid = 0; newgid >= 0; newgid++) { 317474ba6faSAlan Somers bool belongs = false; 318474ba6faSAlan Somers 319474ba6faSAlan Somers for (i = 0; i < ngroups; i++) { 320474ba6faSAlan Somers if (groups[i] == newgid) 321474ba6faSAlan Somers belongs = true; 322474ba6faSAlan Somers } 323474ba6faSAlan Somers if (!belongs) 324474ba6faSAlan Somers break; 325474ba6faSAlan Somers } 326474ba6faSAlan Somers /* newgid is now a group to which the current user does not belong */ 327474ba6faSAlan Somers 328474ba6faSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 329474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 330474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 331474ba6faSAlan Somers ResultOf([](auto in) { 332474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR); 333474ba6faSAlan Somers }, Eq(true)), 334474ba6faSAlan Somers _) 335474ba6faSAlan Somers ).Times(0); 336474ba6faSAlan Somers 337474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 338474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 339474ba6faSAlan Somers } 340474ba6faSAlan Somers 341474ba6faSAlan Somers TEST_F(Chgrp, ok) 342474ba6faSAlan Somers { 343474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 344474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 345474ba6faSAlan Somers const uint64_t ino = 42; 346474ba6faSAlan Somers const mode_t mode = 0755; 347474ba6faSAlan Somers uid_t uid; 348474ba6faSAlan Somers gid_t gid, newgid; 349474ba6faSAlan Somers 350474ba6faSAlan Somers uid = geteuid(); 351474ba6faSAlan Somers gid = 0; 352474ba6faSAlan Somers newgid = getegid(); 353474ba6faSAlan Somers 354474ba6faSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 355474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 356474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 357474ba6faSAlan Somers ResultOf([](auto in) { 358474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR); 359474ba6faSAlan Somers }, Eq(true)), 360474ba6faSAlan Somers _) 361474ba6faSAlan Somers ).Times(0); 362474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 363474ba6faSAlan Somers ResultOf([](auto in) { 364474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR && 365474ba6faSAlan Somers in->header.nodeid == ino); 366474ba6faSAlan Somers }, Eq(true)), 367474ba6faSAlan Somers _) 368474ba6faSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 369474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 370474ba6faSAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 371474ba6faSAlan Somers out->body.attr.attr.uid = uid; 372474ba6faSAlan Somers out->body.attr.attr.gid = newgid; 373474ba6faSAlan Somers }))); 374474ba6faSAlan Somers 375474ba6faSAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 376474ba6faSAlan Somers } 377474ba6faSAlan Somers 378ff4fbdf5SAlan Somers TEST_F(Create, ok) 379ff4fbdf5SAlan Somers { 380ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 381ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 382ff4fbdf5SAlan Somers uint64_t ino = 42; 383ff4fbdf5SAlan Somers int fd; 384ff4fbdf5SAlan Somers 385ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 386ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 387ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 388ff4fbdf5SAlan Somers 389ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 390ff4fbdf5SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 391ff4fbdf5SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 392ff4fbdf5SAlan Somers } 393ff4fbdf5SAlan Somers 394ff4fbdf5SAlan Somers TEST_F(Create, eacces) 395ff4fbdf5SAlan Somers { 396ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 397ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 398ff4fbdf5SAlan Somers 399ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 400ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 401ff4fbdf5SAlan Somers 402ff4fbdf5SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 403ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 404ff4fbdf5SAlan Somers } 405ff4fbdf5SAlan Somers 406ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 407ff4fbdf5SAlan Somers { 408ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 409ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 410ff4fbdf5SAlan Somers uint64_t ino = 42; 411ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 412ff4fbdf5SAlan Somers 413ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 414ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 415ff4fbdf5SAlan Somers 416ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 417ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 418ff4fbdf5SAlan Somers } 419ff4fbdf5SAlan Somers 420ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 421ff4fbdf5SAlan Somers { 422ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 423ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 424ff4fbdf5SAlan Somers uint64_t ino = 42; 425ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 426ff4fbdf5SAlan Somers 427ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 428ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 429ff4fbdf5SAlan Somers expect_removexattr(); 430ff4fbdf5SAlan Somers 431ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 432ff4fbdf5SAlan Somers << strerror(errno); 433ff4fbdf5SAlan Somers } 434ff4fbdf5SAlan Somers 435ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 436ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 437ff4fbdf5SAlan Somers { 438ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 439ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 440ff4fbdf5SAlan Somers uint64_t ino = 42; 441ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 442ff4fbdf5SAlan Somers 443ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 444ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 445ff4fbdf5SAlan Somers 446ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 447ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 448ff4fbdf5SAlan Somers } 449ff4fbdf5SAlan Somers 450ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 451ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 452ff4fbdf5SAlan Somers { 453ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 454ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 455ff4fbdf5SAlan Somers uint64_t ino = 42; 456ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 457ff4fbdf5SAlan Somers 458ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 459ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 460ff4fbdf5SAlan Somers expect_removexattr(); 461ff4fbdf5SAlan Somers 462ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 463ff4fbdf5SAlan Somers << strerror(errno); 464ff4fbdf5SAlan Somers } 465ff4fbdf5SAlan Somers 466ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 467ff4fbdf5SAlan Somers { 468ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 469ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 470ff4fbdf5SAlan Somers uint64_t ino = 42; 471ff4fbdf5SAlan Somers char data[80]; 472ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 473ff4fbdf5SAlan Somers 474ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 475ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 476ff4fbdf5SAlan Somers 477ff4fbdf5SAlan Somers ASSERT_EQ(-1, 478ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 479ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 480ff4fbdf5SAlan Somers } 481ff4fbdf5SAlan Somers 482ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 483ff4fbdf5SAlan Somers { 484ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 485ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 486ff4fbdf5SAlan Somers uint64_t ino = 42; 487ff4fbdf5SAlan Somers char data[80]; 488ff4fbdf5SAlan Somers const char value[] = "whatever"; 489ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 490ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 491ff4fbdf5SAlan Somers ssize_t r; 492ff4fbdf5SAlan Somers 493ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 494ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 495ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 496ff4fbdf5SAlan Somers expect_getxattr( 497ff4fbdf5SAlan Somers ReturnImmediate([&](auto in __unused, auto out) { 498ff4fbdf5SAlan Somers memcpy((void*)out->body.bytes, value, value_len); 499ff4fbdf5SAlan Somers out->header.len = sizeof(out->header) + value_len; 500ff4fbdf5SAlan Somers }) 501ff4fbdf5SAlan Somers ); 502ff4fbdf5SAlan Somers 503ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 504ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 505ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 506ff4fbdf5SAlan Somers } 507ff4fbdf5SAlan Somers 508ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 509ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 510ff4fbdf5SAlan Somers { 511ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 512ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 513ff4fbdf5SAlan Somers uint64_t ino = 42; 514ff4fbdf5SAlan Somers char data[80]; 515ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 516ff4fbdf5SAlan Somers 517ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 518ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 519ff4fbdf5SAlan Somers 520ff4fbdf5SAlan Somers ASSERT_EQ(-1, 521ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 522ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 523ff4fbdf5SAlan Somers } 524ff4fbdf5SAlan Somers 525ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 526ff4fbdf5SAlan Somers { 527ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 528ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 529ff4fbdf5SAlan Somers uint64_t ino = 42; 530ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 531ff4fbdf5SAlan Somers 532ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 533ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 534ff4fbdf5SAlan Somers 535ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 536ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 537ff4fbdf5SAlan Somers } 538ff4fbdf5SAlan Somers 539ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 540ff4fbdf5SAlan Somers { 541ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 542ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 543ff4fbdf5SAlan Somers uint64_t ino = 42; 544ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 545ff4fbdf5SAlan Somers 546ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 547ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 548ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 549ff4fbdf5SAlan Somers expect_listxattr(); 550ff4fbdf5SAlan Somers 551ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 552ff4fbdf5SAlan Somers << strerror(errno); 553ff4fbdf5SAlan Somers } 554ff4fbdf5SAlan Somers 555ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 556ff4fbdf5SAlan Somers TEST_F(Listextattr, system) 557ff4fbdf5SAlan Somers { 558ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 559ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 560ff4fbdf5SAlan Somers uint64_t ino = 42; 561ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 562ff4fbdf5SAlan Somers 563ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 564ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 565ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 566ff4fbdf5SAlan Somers 567ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 568ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 569ff4fbdf5SAlan Somers } 570ff4fbdf5SAlan Somers 571ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 572ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 573ff4fbdf5SAlan Somers { 574ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 575ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 576ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 577ff4fbdf5SAlan Somers 578ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 579ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 580ff4fbdf5SAlan Somers 581ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 582ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 583ff4fbdf5SAlan Somers } 584ff4fbdf5SAlan Somers 585ff4fbdf5SAlan Somers TEST_F(Open, eacces) 586ff4fbdf5SAlan Somers { 587ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 588ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 589ff4fbdf5SAlan Somers uint64_t ino = 42; 590ff4fbdf5SAlan Somers 591ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 592ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 593ff4fbdf5SAlan Somers 594ff4fbdf5SAlan Somers EXPECT_NE(0, open(FULLPATH, O_RDWR)); 595ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 596ff4fbdf5SAlan Somers } 597ff4fbdf5SAlan Somers 5989821f1d3SAlan Somers TEST_F(Open, ok) 5999821f1d3SAlan Somers { 6009821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 6019821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 6029821f1d3SAlan Somers uint64_t ino = 42; 6039821f1d3SAlan Somers int fd; 6049821f1d3SAlan Somers 605ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 606ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 6079821f1d3SAlan Somers expect_open(ino, 0, 1); 6089821f1d3SAlan Somers 6099821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 6109821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 6119821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 6129821f1d3SAlan Somers } 6139821f1d3SAlan Somers 614ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 615ff4fbdf5SAlan Somers { 616ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 617ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 618ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 619ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 620ff4fbdf5SAlan Somers uint64_t ino = 42; 621ff4fbdf5SAlan Somers 622ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0); 623ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 624ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELDST) 625ff4fbdf5SAlan Somers .Times(AnyNumber()) 626ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 627ff4fbdf5SAlan Somers 628ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 629ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 630ff4fbdf5SAlan Somers } 631ff4fbdf5SAlan Somers 632ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 633ff4fbdf5SAlan Somers { 634ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 635ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 636ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 637ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 638ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 639ff4fbdf5SAlan Somers uint64_t src_ino = 42; 640ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 641ff4fbdf5SAlan Somers 642ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 643ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 644ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 645ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 646ff4fbdf5SAlan Somers 647ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 648ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 649ff4fbdf5SAlan Somers } 650ff4fbdf5SAlan Somers 651ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 652ff4fbdf5SAlan Somers { 653ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 654ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 655ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 656ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 657ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 658ff4fbdf5SAlan Somers uint64_t src_ino = 42; 659ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 660ff4fbdf5SAlan Somers 661ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 662ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 663ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 664ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 665ff4fbdf5SAlan Somers 666ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 667ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 668ff4fbdf5SAlan Somers } 669ff4fbdf5SAlan Somers 6706124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 671ff4fbdf5SAlan Somers { 672ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 673ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 674ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 675ff4fbdf5SAlan Somers uint64_t ino = 42; 676ff4fbdf5SAlan Somers 677ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0); 678ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 679ff4fbdf5SAlan Somers 680ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 681ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 682ff4fbdf5SAlan Somers } 683ff4fbdf5SAlan Somers 6846124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 685ff4fbdf5SAlan Somers { 686ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 687ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 6886124fd71SAlan Somers const char RELDST[] = "dst"; 689ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 690ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 691ff4fbdf5SAlan Somers uint64_t src_ino = 42; 692ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 693ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 694ff4fbdf5SAlan Somers 695ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 696ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 697ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 6986124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 6996124fd71SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 7006124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 7016124fd71SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 7026124fd71SAlan Somers out->body.entry.nodeid = dst_ino; 7036124fd71SAlan Somers out->body.entry.attr_valid = UINT64_MAX; 7046124fd71SAlan Somers out->body.entry.entry_valid = UINT64_MAX; 7056124fd71SAlan Somers out->body.entry.attr.uid = 0; 7066124fd71SAlan Somers }))); 707ff4fbdf5SAlan Somers 708ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 709ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 710ff4fbdf5SAlan Somers } 711ff4fbdf5SAlan Somers 712ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 713ff4fbdf5SAlan Somers TEST_F(Rename, ok) 714ff4fbdf5SAlan Somers { 715ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 716ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 717ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 718ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 719ff4fbdf5SAlan Somers // The inode of the already-existing destination file 720ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 721ff4fbdf5SAlan Somers uint64_t ino = 42; 722ff4fbdf5SAlan Somers 723ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 724ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 725ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 726ff4fbdf5SAlan Somers expect_rename(0); 727ff4fbdf5SAlan Somers 728ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 729ff4fbdf5SAlan Somers } 730ff4fbdf5SAlan Somers 731ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 732ff4fbdf5SAlan Somers { 733ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 734ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 735ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 736ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 737ff4fbdf5SAlan Somers uint64_t ino = 42; 738ff4fbdf5SAlan Somers 739ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0); 740ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 741ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 742ff4fbdf5SAlan Somers expect_rename(0); 743ff4fbdf5SAlan Somers 744ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 745ff4fbdf5SAlan Somers } 746ff4fbdf5SAlan Somers 747ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 748ff4fbdf5SAlan Somers { 749ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 750ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 751ff4fbdf5SAlan Somers const uint64_t ino = 42; 752ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 753ff4fbdf5SAlan Somers const mode_t newmode = 0644; 754ff4fbdf5SAlan Somers 755ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 756ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 757ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 758ff4fbdf5SAlan Somers ResultOf([](auto in) { 759ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETATTR && 760ff4fbdf5SAlan Somers in->header.nodeid == ino && 761ff4fbdf5SAlan Somers in->body.setattr.mode == newmode); 762ff4fbdf5SAlan Somers }, Eq(true)), 763ff4fbdf5SAlan Somers _) 764ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 765ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 766ff4fbdf5SAlan Somers out->body.attr.attr.mode = S_IFREG | newmode; 767ff4fbdf5SAlan Somers }))); 768ff4fbdf5SAlan Somers 769ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 770ff4fbdf5SAlan Somers } 771ff4fbdf5SAlan Somers 772ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 773ff4fbdf5SAlan Somers { 774ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 775ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 776ff4fbdf5SAlan Somers const uint64_t ino = 42; 777ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 778ff4fbdf5SAlan Somers const mode_t newmode = 0644; 779ff4fbdf5SAlan Somers 780ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 781ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 782ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 783ff4fbdf5SAlan Somers ResultOf([](auto in) { 784ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETATTR); 785ff4fbdf5SAlan Somers }, Eq(true)), 786ff4fbdf5SAlan Somers _) 787ff4fbdf5SAlan Somers ).Times(0); 788ff4fbdf5SAlan Somers 789ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 790ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 791ff4fbdf5SAlan Somers } 792ff4fbdf5SAlan Somers 793e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */ 794e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file) 795e5ff3a7eSAlan Somers { 796e5ff3a7eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 797e5ff3a7eSAlan Somers const char RELPATH[] = "some_file.txt"; 798e5ff3a7eSAlan Somers const uint64_t ino = 42; 799e5ff3a7eSAlan Somers const mode_t oldmode = 0644; 800e5ff3a7eSAlan Somers const mode_t newmode = 01644; 801e5ff3a7eSAlan Somers 802e5ff3a7eSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 803e5ff3a7eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 804e5ff3a7eSAlan Somers EXPECT_CALL(*m_mock, process( 805e5ff3a7eSAlan Somers ResultOf([](auto in) { 806e5ff3a7eSAlan Somers return (in->header.opcode == FUSE_SETATTR); 807e5ff3a7eSAlan Somers }, Eq(true)), 808e5ff3a7eSAlan Somers _) 809e5ff3a7eSAlan Somers ).Times(0); 810e5ff3a7eSAlan Somers 811e5ff3a7eSAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 812e5ff3a7eSAlan Somers EXPECT_EQ(EFTYPE, errno); 813e5ff3a7eSAlan Somers } 814e5ff3a7eSAlan Somers 815ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 816ff4fbdf5SAlan Somers { 817ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 818ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 819ff4fbdf5SAlan Somers uint64_t ino = 42; 820ff4fbdf5SAlan Somers const char value[] = "whatever"; 821ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 822ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 823ff4fbdf5SAlan Somers ssize_t r; 824ff4fbdf5SAlan Somers 825ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 826ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 827ff4fbdf5SAlan Somers expect_setxattr(0); 828ff4fbdf5SAlan Somers 829ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 830ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 831ff4fbdf5SAlan Somers } 832ff4fbdf5SAlan Somers 833ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 834ff4fbdf5SAlan Somers { 835ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 836ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 837ff4fbdf5SAlan Somers uint64_t ino = 42; 838ff4fbdf5SAlan Somers const char value[] = "whatever"; 839ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 840ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 841ff4fbdf5SAlan Somers 842ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 843ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 844ff4fbdf5SAlan Somers 845ff4fbdf5SAlan Somers ASSERT_EQ(-1, 846ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 847ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 848ff4fbdf5SAlan Somers } 849ff4fbdf5SAlan Somers 850ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 851ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 852ff4fbdf5SAlan Somers { 853ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 854ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 855ff4fbdf5SAlan Somers uint64_t ino = 42; 856ff4fbdf5SAlan Somers const char value[] = "whatever"; 857ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 858ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 859ff4fbdf5SAlan Somers 860ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 861ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 862ff4fbdf5SAlan Somers 863ff4fbdf5SAlan Somers ASSERT_EQ(-1, 864ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 865ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 866ff4fbdf5SAlan Somers } 867ff4fbdf5SAlan Somers 868ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 869ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 870ff4fbdf5SAlan Somers { 871ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 872ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 873ff4fbdf5SAlan Somers uint64_t ino = 42; 874ff4fbdf5SAlan Somers const char value[] = "whatever"; 875ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 876ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 877ff4fbdf5SAlan Somers ssize_t r; 878ff4fbdf5SAlan Somers 879ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 880ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 881ff4fbdf5SAlan Somers expect_setxattr(0); 882ff4fbdf5SAlan Somers 883ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 884ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 885ff4fbdf5SAlan Somers } 886ff4fbdf5SAlan Somers 887ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 8889821f1d3SAlan Somers { 8899821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 8909821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 8919821f1d3SAlan Somers uint64_t ino = 42; 8929821f1d3SAlan Somers 893ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 894ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 895ff4fbdf5SAlan Somers expect_unlink(1, RELPATH, 0); 8969821f1d3SAlan Somers 897ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 898ff4fbdf5SAlan Somers } 899ff4fbdf5SAlan Somers 9006124fd71SAlan Somers /* 9016124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 9026124fd71SAlan Somers * in VOP_LOOKUP. 9036124fd71SAlan Somers * 9046124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 9056124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 9066124fd71SAlan Somers */ 9076124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 9086124fd71SAlan Somers { 9096124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 9106124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 9116124fd71SAlan Somers uint64_t ino = 42; 9126124fd71SAlan Somers 9136124fd71SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 9146124fd71SAlan Somers EXPECT_LOOKUP(1, RELPATH) 9156124fd71SAlan Somers .Times(AnyNumber()) 9166124fd71SAlan Somers .WillRepeatedly(Invoke( 9176124fd71SAlan Somers ReturnImmediate([=](auto i __unused, auto out) { 9186124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 9196124fd71SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 9206124fd71SAlan Somers out->body.entry.nodeid = ino; 9216124fd71SAlan Somers out->body.entry.entry_valid = UINT64_MAX; 9226124fd71SAlan Somers })) 9236124fd71SAlan Somers ); 9246124fd71SAlan Somers 9256124fd71SAlan Somers /* Fill name cache */ 9266124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 9276124fd71SAlan Somers /* Despite cached name , unlink should fail */ 9286124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 9296124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 9306124fd71SAlan Somers } 9316124fd71SAlan Somers 932ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 933ff4fbdf5SAlan Somers { 934ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 935ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 936ff4fbdf5SAlan Somers uint64_t ino = 42; 937ff4fbdf5SAlan Somers 938ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 939ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 940ff4fbdf5SAlan Somers 941ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 942ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 943ff4fbdf5SAlan Somers } 944ff4fbdf5SAlan Somers 9456124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 946ff4fbdf5SAlan Somers { 947ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 948ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 949ff4fbdf5SAlan Somers uint64_t ino = 42; 950ff4fbdf5SAlan Somers 951ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1); 952ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 953ff4fbdf5SAlan Somers 954ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 955ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 9569821f1d3SAlan Somers } 957*a90e32deSAlan Somers 958*a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 959*a90e32deSAlan Somers TEST_F(Write, clear_suid) 960*a90e32deSAlan Somers { 961*a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 962*a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 963*a90e32deSAlan Somers struct stat sb; 964*a90e32deSAlan Somers uint64_t ino = 42; 965*a90e32deSAlan Somers mode_t oldmode = 04777; 966*a90e32deSAlan Somers mode_t newmode = 0777; 967*a90e32deSAlan Somers char wbuf[1] = {'x'}; 968*a90e32deSAlan Somers int fd; 969*a90e32deSAlan Somers 970*a90e32deSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 971*a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 972*a90e32deSAlan Somers expect_open(ino, 0, 1); 973*a90e32deSAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf); 974*a90e32deSAlan Somers expect_chmod(ino, newmode); 975*a90e32deSAlan Somers 976*a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 977*a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 978*a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 979*a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 980*a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 981*a90e32deSAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 982*a90e32deSAlan Somers } 983*a90e32deSAlan Somers 984*a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 985*a90e32deSAlan Somers TEST_F(Write, clear_sgid) 986*a90e32deSAlan Somers { 987*a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 988*a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 989*a90e32deSAlan Somers struct stat sb; 990*a90e32deSAlan Somers uint64_t ino = 42; 991*a90e32deSAlan Somers mode_t oldmode = 02777; 992*a90e32deSAlan Somers mode_t newmode = 0777; 993*a90e32deSAlan Somers char wbuf[1] = {'x'}; 994*a90e32deSAlan Somers int fd; 995*a90e32deSAlan Somers 996*a90e32deSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 997*a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 998*a90e32deSAlan Somers expect_open(ino, 0, 1); 999*a90e32deSAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf); 1000*a90e32deSAlan Somers expect_chmod(ino, newmode); 1001*a90e32deSAlan Somers 1002*a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1003*a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1004*a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1005*a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1006*a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1007*a90e32deSAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 1008*a90e32deSAlan Somers } 1009