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" { 349821f1d3SAlan Somers #include <fcntl.h> 359821f1d3SAlan Somers } 369821f1d3SAlan Somers 379821f1d3SAlan Somers #include "mockfs.hh" 389821f1d3SAlan Somers #include "utils.hh" 399821f1d3SAlan Somers 409821f1d3SAlan Somers using namespace testing; 419821f1d3SAlan Somers 422d445be1SAlan Somers class Create: public FuseTest { 432d445be1SAlan Somers public: 442d445be1SAlan Somers 45ede571e4SAlan Somers void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 462d445be1SAlan Somers { 47a4856c96SAlan Somers mode_t mask = umask(0); 48a4856c96SAlan Somers (void)umask(mask); 49a4856c96SAlan Somers 502d445be1SAlan Somers EXPECT_CALL(*m_mock, process( 512d445be1SAlan Somers ResultOf([=](auto in) { 5229edc611SAlan Somers const char *name = (const char*)in.body.bytes + 53a4856c96SAlan Somers sizeof(fuse_create_in); 5429edc611SAlan Somers return (in.header.opcode == FUSE_CREATE && 55a4856c96SAlan Somers in.body.create.mode == mode && 56a4856c96SAlan Somers in.body.create.umask == mask && 572d445be1SAlan Somers (0 == strcmp(relpath, name))); 582d445be1SAlan Somers }, Eq(true)), 592d445be1SAlan Somers _) 602d445be1SAlan Somers ).WillOnce(Invoke(r)); 612d445be1SAlan Somers } 622d445be1SAlan Somers 632d445be1SAlan Somers }; 649821f1d3SAlan Somers 6516bd2d47SAlan Somers /* FUSE_CREATE operations for a protocol 7.8 server */ 6616bd2d47SAlan Somers class Create_7_8: public Create { 6716bd2d47SAlan Somers public: 6816bd2d47SAlan Somers virtual void SetUp() { 6916bd2d47SAlan Somers m_kernel_minor_version = 8; 7016bd2d47SAlan Somers Create::SetUp(); 7116bd2d47SAlan Somers } 72a4856c96SAlan Somers 73a4856c96SAlan Somers void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 74a4856c96SAlan Somers { 75a4856c96SAlan Somers EXPECT_CALL(*m_mock, process( 76a4856c96SAlan Somers ResultOf([=](auto in) { 77a4856c96SAlan Somers const char *name = (const char*)in.body.bytes + 78a4856c96SAlan Somers sizeof(fuse_open_in); 79a4856c96SAlan Somers return (in.header.opcode == FUSE_CREATE && 80a4856c96SAlan Somers in.body.create.mode == mode && 81a4856c96SAlan Somers (0 == strcmp(relpath, name))); 82a4856c96SAlan Somers }, Eq(true)), 83a4856c96SAlan Somers _) 84a4856c96SAlan Somers ).WillOnce(Invoke(r)); 85a4856c96SAlan Somers } 86a4856c96SAlan Somers 87a4856c96SAlan Somers }; 88a4856c96SAlan Somers 89a4856c96SAlan Somers /* FUSE_CREATE operations for a server built at protocol <= 7.11 */ 90a4856c96SAlan Somers class Create_7_11: public FuseTest { 91a4856c96SAlan Somers public: 92a4856c96SAlan Somers virtual void SetUp() { 93a4856c96SAlan Somers m_kernel_minor_version = 11; 94a4856c96SAlan Somers FuseTest::SetUp(); 95a4856c96SAlan Somers } 96a4856c96SAlan Somers 97a4856c96SAlan Somers void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 98a4856c96SAlan Somers { 99a4856c96SAlan Somers EXPECT_CALL(*m_mock, process( 100a4856c96SAlan Somers ResultOf([=](auto in) { 101a4856c96SAlan Somers const char *name = (const char*)in.body.bytes + 102a4856c96SAlan Somers sizeof(fuse_open_in); 103a4856c96SAlan Somers return (in.header.opcode == FUSE_CREATE && 104a4856c96SAlan Somers in.body.create.mode == mode && 105a4856c96SAlan Somers (0 == strcmp(relpath, name))); 106a4856c96SAlan Somers }, Eq(true)), 107a4856c96SAlan Somers _) 108a4856c96SAlan Somers ).WillOnce(Invoke(r)); 109a4856c96SAlan Somers } 110a4856c96SAlan Somers 11116bd2d47SAlan Somers }; 11216bd2d47SAlan Somers 11316bd2d47SAlan Somers 1149821f1d3SAlan Somers /* 115002e54b0SAlan Somers * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the 1169821f1d3SAlan Somers * attribute cache 1179821f1d3SAlan Somers */ 118cad67791SAlan Somers TEST_F(Create, attr_cache) 1199821f1d3SAlan Somers { 1209821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1219821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 122ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 1239821f1d3SAlan Somers uint64_t ino = 42; 1249821f1d3SAlan Somers int fd; 1259821f1d3SAlan Somers 126a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 127a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 128ede571e4SAlan Somers expect_create(RELPATH, mode, 12929edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 1309821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 13129edc611SAlan Somers out.body.create.entry.attr.mode = mode; 13229edc611SAlan Somers out.body.create.entry.nodeid = ino; 13329edc611SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 13429edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 1352d445be1SAlan Somers })); 1369821f1d3SAlan Somers 1379821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1389821f1d3SAlan Somers ResultOf([=](auto in) { 13929edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR && 14029edc611SAlan Somers in.header.nodeid == ino); 1419821f1d3SAlan Somers }, Eq(true)), 1429821f1d3SAlan Somers _) 1439821f1d3SAlan Somers ).Times(0); 1449821f1d3SAlan Somers 1459821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 146d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1477fc0921dSAlan Somers leak(fd); 1489821f1d3SAlan Somers } 1499821f1d3SAlan Somers 150002e54b0SAlan Somers /* A successful CREATE operation should purge the parent dir's attr cache */ 151002e54b0SAlan Somers TEST_F(Create, clear_attr_cache) 152002e54b0SAlan Somers { 153002e54b0SAlan Somers const char FULLPATH[] = "mountpoint/src"; 154002e54b0SAlan Somers const char RELPATH[] = "src"; 155002e54b0SAlan Somers mode_t mode = S_IFREG | 0755; 156002e54b0SAlan Somers uint64_t ino = 42; 157002e54b0SAlan Somers int fd; 158002e54b0SAlan Somers struct stat sb; 159002e54b0SAlan Somers 160a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 161a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 162002e54b0SAlan Somers EXPECT_CALL(*m_mock, process( 163002e54b0SAlan Somers ResultOf([=](auto in) { 16429edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR && 165a34cdd26SAlan Somers in.header.nodeid == FUSE_ROOT_ID); 166002e54b0SAlan Somers }, Eq(true)), 167002e54b0SAlan Somers _) 168002e54b0SAlan Somers ).Times(2) 16929edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 170002e54b0SAlan Somers SET_OUT_HEADER_LEN(out, attr); 171a34cdd26SAlan Somers out.body.attr.attr.ino = FUSE_ROOT_ID; 17229edc611SAlan Somers out.body.attr.attr.mode = S_IFDIR | 0755; 17329edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 174002e54b0SAlan Somers }))); 175002e54b0SAlan Somers 176002e54b0SAlan Somers expect_create(RELPATH, mode, 17729edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 178002e54b0SAlan Somers SET_OUT_HEADER_LEN(out, create); 17929edc611SAlan Somers out.body.create.entry.attr.mode = mode; 18029edc611SAlan Somers out.body.create.entry.nodeid = ino; 18129edc611SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 18229edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 183002e54b0SAlan Somers })); 184002e54b0SAlan Somers 185002e54b0SAlan Somers EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 186002e54b0SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 187d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 188002e54b0SAlan Somers EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 189002e54b0SAlan Somers 1907fc0921dSAlan Somers leak(fd); 191002e54b0SAlan Somers } 192002e54b0SAlan Somers 1939821f1d3SAlan Somers /* 1949821f1d3SAlan Somers * The fuse daemon fails the request with EEXIST. This usually indicates a 1959821f1d3SAlan Somers * race condition: some other FUSE client created the file in between when the 1969821f1d3SAlan Somers * kernel checked for it with lookup and tried to create it with create 1979821f1d3SAlan Somers */ 1989821f1d3SAlan Somers TEST_F(Create, eexist) 1999821f1d3SAlan Somers { 2009821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2019821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 202ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 2039821f1d3SAlan Somers 204a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 205a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 206ede571e4SAlan Somers expect_create(RELPATH, mode, ReturnErrno(EEXIST)); 2074ca1c0b7SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 2089821f1d3SAlan Somers EXPECT_EQ(EEXIST, errno); 2099821f1d3SAlan Somers } 2109821f1d3SAlan Somers 2119821f1d3SAlan Somers /* 2129821f1d3SAlan Somers * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 2139821f1d3SAlan Somers * to FUSE_MKNOD/FUSE_OPEN 2149821f1d3SAlan Somers */ 21519ef317dSAlan Somers TEST_F(Create, Enosys) 2169821f1d3SAlan Somers { 2179821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2189821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 219ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 2209821f1d3SAlan Somers uint64_t ino = 42; 2219821f1d3SAlan Somers int fd; 2229821f1d3SAlan Somers 223a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 224a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 225ede571e4SAlan Somers expect_create(RELPATH, mode, ReturnErrno(ENOSYS)); 2269821f1d3SAlan Somers 2279821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 2289821f1d3SAlan Somers ResultOf([=](auto in) { 22929edc611SAlan Somers const char *name = (const char*)in.body.bytes + 2309821f1d3SAlan Somers sizeof(fuse_mknod_in); 23129edc611SAlan Somers return (in.header.opcode == FUSE_MKNOD && 23229edc611SAlan Somers in.body.mknod.mode == (S_IFREG | mode) && 23329edc611SAlan Somers in.body.mknod.rdev == 0 && 2349821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 2359821f1d3SAlan Somers }, Eq(true)), 2369821f1d3SAlan Somers _) 23729edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 23819ef317dSAlan Somers SET_OUT_HEADER_LEN(out, entry); 23929edc611SAlan Somers out.body.entry.attr.mode = mode; 24029edc611SAlan Somers out.body.entry.nodeid = ino; 24129edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 24229edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 2439821f1d3SAlan Somers }))); 2449821f1d3SAlan Somers 2459821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 2469821f1d3SAlan Somers ResultOf([=](auto in) { 24729edc611SAlan Somers return (in.header.opcode == FUSE_OPEN && 24829edc611SAlan Somers in.header.nodeid == ino); 2499821f1d3SAlan Somers }, Eq(true)), 2509821f1d3SAlan Somers _) 25129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 25229edc611SAlan Somers out.header.len = sizeof(out.header); 2539821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, open); 2549821f1d3SAlan Somers }))); 2559821f1d3SAlan Somers 2569821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 257d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 2587fc0921dSAlan Somers leak(fd); 2599821f1d3SAlan Somers } 2609821f1d3SAlan Somers 2619821f1d3SAlan Somers /* 2629821f1d3SAlan Somers * Creating a new file after FUSE_LOOKUP returned a negative cache entry 2639821f1d3SAlan Somers */ 2646248288eSAlan Somers TEST_F(Create, entry_cache_negative) 2659821f1d3SAlan Somers { 2669821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2679821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 268ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 2699821f1d3SAlan Somers uint64_t ino = 42; 2709821f1d3SAlan Somers int fd; 2719821f1d3SAlan Somers /* 2729821f1d3SAlan Somers * Set entry_valid = 0 because this test isn't concerned with whether 2739821f1d3SAlan Somers * or not we actually cache negative entries, only with whether we 2749821f1d3SAlan Somers * interpret negative cache responses correctly. 2759821f1d3SAlan Somers */ 2769821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 2779821f1d3SAlan Somers 2789821f1d3SAlan Somers /* create will first do a LOOKUP, adding a negative cache entry */ 279a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 280a34cdd26SAlan Somers .WillOnce(ReturnNegativeCache(&entry_valid)); 281ede571e4SAlan Somers expect_create(RELPATH, mode, 28229edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 2839821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 28429edc611SAlan Somers out.body.create.entry.attr.mode = mode; 28529edc611SAlan Somers out.body.create.entry.nodeid = ino; 28629edc611SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 28729edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 2882d445be1SAlan Somers })); 2899821f1d3SAlan Somers 2909821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 2919821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 2927fc0921dSAlan Somers leak(fd); 2939821f1d3SAlan Somers } 2949821f1d3SAlan Somers 2959821f1d3SAlan Somers /* 2969821f1d3SAlan Somers * Creating a new file should purge any negative namecache entries 2979821f1d3SAlan Somers */ 2986248288eSAlan Somers TEST_F(Create, entry_cache_negative_purge) 2999821f1d3SAlan Somers { 3009821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3019821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 302ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 3039821f1d3SAlan Somers uint64_t ino = 42; 3049821f1d3SAlan Somers int fd; 3059821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 3069821f1d3SAlan Somers 3079821f1d3SAlan Somers /* create will first do a LOOKUP, adding a negative cache entry */ 308a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH).Times(1) 3099821f1d3SAlan Somers .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 3109821f1d3SAlan Somers .RetiresOnSaturation(); 3119821f1d3SAlan Somers 3129821f1d3SAlan Somers /* Then the CREATE should purge the negative cache entry */ 313ede571e4SAlan Somers expect_create(RELPATH, mode, 31429edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 3159821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 31629edc611SAlan Somers out.body.create.entry.attr.mode = mode; 31729edc611SAlan Somers out.body.create.entry.nodeid = ino; 31829edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 3192d445be1SAlan Somers })); 3209821f1d3SAlan Somers 3219821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 3229821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 3239821f1d3SAlan Somers 3249821f1d3SAlan Somers /* Finally, a subsequent lookup should query the daemon */ 3259821f1d3SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 3269821f1d3SAlan Somers 3279821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 3287fc0921dSAlan Somers leak(fd); 3299821f1d3SAlan Somers } 3309821f1d3SAlan Somers 3319821f1d3SAlan Somers /* 3329821f1d3SAlan Somers * The daemon is responsible for checking file permissions (unless the 3339821f1d3SAlan Somers * default_permissions mount option was used) 3349821f1d3SAlan Somers */ 3359821f1d3SAlan Somers TEST_F(Create, eperm) 3369821f1d3SAlan Somers { 3379821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3389821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 339ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 3409821f1d3SAlan Somers 341a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 342a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 343ede571e4SAlan Somers expect_create(RELPATH, mode, ReturnErrno(EPERM)); 3449821f1d3SAlan Somers 3454ca1c0b7SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 3469821f1d3SAlan Somers EXPECT_EQ(EPERM, errno); 3479821f1d3SAlan Somers } 3489821f1d3SAlan Somers 3499821f1d3SAlan Somers TEST_F(Create, ok) 3509821f1d3SAlan Somers { 3519821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3529821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 353ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 3549821f1d3SAlan Somers uint64_t ino = 42; 3559821f1d3SAlan Somers int fd; 3569821f1d3SAlan Somers 357a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 358a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 359ede571e4SAlan Somers expect_create(RELPATH, mode, 36029edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 3619821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 36229edc611SAlan Somers out.body.create.entry.attr.mode = mode; 36329edc611SAlan Somers out.body.create.entry.nodeid = ino; 36429edc611SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 36529edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 3662d445be1SAlan Somers })); 3679821f1d3SAlan Somers 3689821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 369d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 3707fc0921dSAlan Somers leak(fd); 3719821f1d3SAlan Somers } 3722d445be1SAlan Somers 3732d445be1SAlan Somers /* 3740bef4927SAlan Somers * Nothing bad should happen if the server returns the parent's inode number 3750bef4927SAlan Somers * for the newly created file. Regression test for bug 263662 3760bef4927SAlan Somers * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662 3770bef4927SAlan Somers */ 3780bef4927SAlan Somers TEST_F(Create, parent_inode) 3790bef4927SAlan Somers { 3800bef4927SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 3810bef4927SAlan Somers const char RELDIRPATH[] = "some_dir"; 3820bef4927SAlan Somers const char RELPATH[] = "some_file.txt"; 3830bef4927SAlan Somers mode_t mode = 0755; 3840bef4927SAlan Somers uint64_t ino = 42; 3850bef4927SAlan Somers int fd; 3860bef4927SAlan Somers 3870bef4927SAlan Somers expect_lookup(RELDIRPATH, ino, S_IFDIR | mode, 0, 1); 3880bef4927SAlan Somers EXPECT_LOOKUP(ino, RELPATH) 3890bef4927SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 3900bef4927SAlan Somers expect_create(RELPATH, S_IFREG | mode, 3910bef4927SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 3920bef4927SAlan Somers SET_OUT_HEADER_LEN(out, create); 3930bef4927SAlan Somers out.body.create.entry.attr.mode = S_IFREG | mode; 3940bef4927SAlan Somers /* Return the same inode as the parent dir */ 3950bef4927SAlan Somers out.body.create.entry.nodeid = ino; 3960bef4927SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 3970bef4927SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 3980bef4927SAlan Somers })); 3990bef4927SAlan Somers // FUSE_RELEASE happens asynchronously, so it may or may not arrive 4000bef4927SAlan Somers // before the test completes. 4010bef4927SAlan Somers EXPECT_CALL(*m_mock, process( 4020bef4927SAlan Somers ResultOf([=](auto in) { 4030bef4927SAlan Somers return (in.header.opcode == FUSE_RELEASE); 4040bef4927SAlan Somers }, Eq(true)), 4050bef4927SAlan Somers _) 4060bef4927SAlan Somers ).Times(AtMost(1)) 4070bef4927SAlan Somers .WillOnce(Invoke([=](auto in __unused, auto &out __unused) { })); 4080bef4927SAlan Somers 4090bef4927SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 4100bef4927SAlan Somers ASSERT_EQ(-1, fd); 4110bef4927SAlan Somers EXPECT_EQ(EIO, errno); 4120bef4927SAlan Somers } 4130bef4927SAlan Somers 4140bef4927SAlan Somers /* 4152d445be1SAlan Somers * A regression test for a bug that affected old FUSE implementations: 4162d445be1SAlan Somers * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming 4172d445be1SAlan Somers * contradiction between O_WRONLY and 0444 4182d445be1SAlan Somers * 4192d445be1SAlan Somers * For example: 4202d445be1SAlan Somers * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886 4212d445be1SAlan Somers */ 4222d445be1SAlan Somers TEST_F(Create, wronly_0444) 4232d445be1SAlan Somers { 4242d445be1SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 4252d445be1SAlan Somers const char RELPATH[] = "some_file.txt"; 426ede571e4SAlan Somers mode_t mode = S_IFREG | 0444; 4272d445be1SAlan Somers uint64_t ino = 42; 4282d445be1SAlan Somers int fd; 4292d445be1SAlan Somers 430a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 431a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 432ede571e4SAlan Somers expect_create(RELPATH, mode, 43329edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 4342d445be1SAlan Somers SET_OUT_HEADER_LEN(out, create); 43529edc611SAlan Somers out.body.create.entry.attr.mode = mode; 43629edc611SAlan Somers out.body.create.entry.nodeid = ino; 43729edc611SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 43829edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 4392d445be1SAlan Somers })); 4402d445be1SAlan Somers 4412d445be1SAlan Somers fd = open(FULLPATH, O_CREAT | O_WRONLY, mode); 442d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 4437fc0921dSAlan Somers leak(fd); 4442d445be1SAlan Somers } 44516bd2d47SAlan Somers 44616bd2d47SAlan Somers TEST_F(Create_7_8, ok) 44716bd2d47SAlan Somers { 44816bd2d47SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 44916bd2d47SAlan Somers const char RELPATH[] = "some_file.txt"; 45016bd2d47SAlan Somers mode_t mode = S_IFREG | 0755; 45116bd2d47SAlan Somers uint64_t ino = 42; 45216bd2d47SAlan Somers int fd; 45316bd2d47SAlan Somers 454a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 455a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 45616bd2d47SAlan Somers expect_create(RELPATH, mode, 45729edc611SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 45816bd2d47SAlan Somers SET_OUT_HEADER_LEN(out, create_7_8); 45945825a12SAlan Somers out.body.create_7_8.entry.attr.mode = mode; 46045825a12SAlan Somers out.body.create_7_8.entry.nodeid = ino; 46145825a12SAlan Somers out.body.create_7_8.entry.entry_valid = UINT64_MAX; 46245825a12SAlan Somers out.body.create_7_8.entry.attr_valid = UINT64_MAX; 46345825a12SAlan Somers out.body.create_7_8.open.fh = FH; 46416bd2d47SAlan Somers })); 46545825a12SAlan Somers expect_flush(ino, 1, ReturnErrno(0)); 46645825a12SAlan Somers expect_release(ino, FH); 46716bd2d47SAlan Somers 46816bd2d47SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 469d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 47045825a12SAlan Somers close(fd); 47116bd2d47SAlan Somers } 47216bd2d47SAlan Somers 473a4856c96SAlan Somers TEST_F(Create_7_11, ok) 474a4856c96SAlan Somers { 475a4856c96SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 476a4856c96SAlan Somers const char RELPATH[] = "some_file.txt"; 477a4856c96SAlan Somers mode_t mode = S_IFREG | 0755; 478a4856c96SAlan Somers uint64_t ino = 42; 479a4856c96SAlan Somers int fd; 48016bd2d47SAlan Somers 481a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 482a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 483a4856c96SAlan Somers expect_create(RELPATH, mode, 484a4856c96SAlan Somers ReturnImmediate([=](auto in __unused, auto& out) { 485a4856c96SAlan Somers SET_OUT_HEADER_LEN(out, create); 486a4856c96SAlan Somers out.body.create.entry.attr.mode = mode; 487a4856c96SAlan Somers out.body.create.entry.nodeid = ino; 488a4856c96SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 489a4856c96SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 490a4856c96SAlan Somers })); 491a4856c96SAlan Somers 492a4856c96SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 493d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 4947fc0921dSAlan Somers leak(fd); 495a4856c96SAlan Somers } 496