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 extern "C" { 329821f1d3SAlan Somers #include <fcntl.h> 339821f1d3SAlan Somers } 349821f1d3SAlan Somers 359821f1d3SAlan Somers #include "mockfs.hh" 369821f1d3SAlan Somers #include "utils.hh" 379821f1d3SAlan Somers 389821f1d3SAlan Somers using namespace testing; 399821f1d3SAlan Somers 402d445be1SAlan Somers class Create: public FuseTest { 412d445be1SAlan Somers public: 422d445be1SAlan Somers 432d445be1SAlan Somers void expect_create(const char *relpath, ProcessMockerT r) 442d445be1SAlan Somers { 452d445be1SAlan Somers EXPECT_CALL(*m_mock, process( 462d445be1SAlan Somers ResultOf([=](auto in) { 472d445be1SAlan Somers const char *name = (const char*)in->body.bytes + 482d445be1SAlan Somers sizeof(fuse_open_in); 492d445be1SAlan Somers return (in->header.opcode == FUSE_CREATE && 502d445be1SAlan Somers (0 == strcmp(relpath, name))); 512d445be1SAlan Somers }, Eq(true)), 522d445be1SAlan Somers _) 532d445be1SAlan Somers ).WillOnce(Invoke(r)); 542d445be1SAlan Somers } 552d445be1SAlan Somers 562d445be1SAlan Somers }; 579821f1d3SAlan Somers 589821f1d3SAlan Somers /* 599821f1d3SAlan Somers * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the 609821f1d3SAlan Somers * attribute cache 619821f1d3SAlan Somers */ 62*cad67791SAlan Somers TEST_F(Create, attr_cache) 639821f1d3SAlan Somers { 649821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 659821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 669821f1d3SAlan Somers mode_t mode = 0755; 679821f1d3SAlan Somers uint64_t ino = 42; 689821f1d3SAlan Somers int fd; 699821f1d3SAlan Somers 709821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 712d445be1SAlan Somers expect_create(RELPATH, ReturnImmediate([=](auto in __unused, auto out) { 729821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 739821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 749821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 759821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 769821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 772d445be1SAlan Somers })); 789821f1d3SAlan Somers 799821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 809821f1d3SAlan Somers ResultOf([=](auto in) { 819821f1d3SAlan Somers return (in->header.opcode == FUSE_GETATTR && 829821f1d3SAlan Somers in->header.nodeid == ino); 839821f1d3SAlan Somers }, Eq(true)), 849821f1d3SAlan Somers _) 859821f1d3SAlan Somers ).Times(0); 869821f1d3SAlan Somers 879821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 889821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 899821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 909821f1d3SAlan Somers } 919821f1d3SAlan Somers 929821f1d3SAlan Somers /* 939821f1d3SAlan Somers * The fuse daemon fails the request with EEXIST. This usually indicates a 949821f1d3SAlan Somers * race condition: some other FUSE client created the file in between when the 959821f1d3SAlan Somers * kernel checked for it with lookup and tried to create it with create 969821f1d3SAlan Somers */ 979821f1d3SAlan Somers TEST_F(Create, eexist) 989821f1d3SAlan Somers { 999821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1009821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1019821f1d3SAlan Somers mode_t mode = 0755; 1029821f1d3SAlan Somers 1039821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 1042d445be1SAlan Somers expect_create(RELPATH, ReturnErrno(EEXIST)); 1059821f1d3SAlan Somers EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 1069821f1d3SAlan Somers EXPECT_EQ(EEXIST, errno); 1079821f1d3SAlan Somers } 1089821f1d3SAlan Somers 1099821f1d3SAlan Somers /* 1109821f1d3SAlan Somers * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 1119821f1d3SAlan Somers * to FUSE_MKNOD/FUSE_OPEN 1129821f1d3SAlan Somers */ 11319ef317dSAlan Somers TEST_F(Create, Enosys) 1149821f1d3SAlan Somers { 1159821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1169821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1179821f1d3SAlan Somers mode_t mode = 0755; 1189821f1d3SAlan Somers uint64_t ino = 42; 1199821f1d3SAlan Somers int fd; 1209821f1d3SAlan Somers 1219821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 1222d445be1SAlan Somers expect_create(RELPATH, ReturnErrno(ENOSYS)); 1239821f1d3SAlan Somers 1249821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1259821f1d3SAlan Somers ResultOf([=](auto in) { 1269821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 1279821f1d3SAlan Somers sizeof(fuse_mknod_in); 1289821f1d3SAlan Somers return (in->header.opcode == FUSE_MKNOD && 1299821f1d3SAlan Somers in->body.mknod.mode == (S_IFREG | mode) && 1309821f1d3SAlan Somers in->body.mknod.rdev == 0 && 1319821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 1329821f1d3SAlan Somers }, Eq(true)), 1339821f1d3SAlan Somers _) 1349821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 13519ef317dSAlan Somers SET_OUT_HEADER_LEN(out, entry); 13619ef317dSAlan Somers out->body.entry.attr.mode = S_IFREG | mode; 13719ef317dSAlan Somers out->body.entry.nodeid = ino; 13819ef317dSAlan Somers out->body.entry.entry_valid = UINT64_MAX; 13919ef317dSAlan Somers out->body.entry.attr_valid = UINT64_MAX; 1409821f1d3SAlan Somers }))); 1419821f1d3SAlan Somers 1429821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1439821f1d3SAlan Somers ResultOf([=](auto in) { 1449821f1d3SAlan Somers return (in->header.opcode == FUSE_OPEN && 1459821f1d3SAlan Somers in->header.nodeid == ino); 1469821f1d3SAlan Somers }, Eq(true)), 1479821f1d3SAlan Somers _) 1489821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 1499821f1d3SAlan Somers out->header.len = sizeof(out->header); 1509821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, open); 1519821f1d3SAlan Somers }))); 1529821f1d3SAlan Somers 1539821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 1549821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 1559821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 1569821f1d3SAlan Somers } 1579821f1d3SAlan Somers 1589821f1d3SAlan Somers /* 1599821f1d3SAlan Somers * Creating a new file after FUSE_LOOKUP returned a negative cache entry 1609821f1d3SAlan Somers */ 1616248288eSAlan Somers TEST_F(Create, entry_cache_negative) 1629821f1d3SAlan Somers { 1639821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1649821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1659821f1d3SAlan Somers mode_t mode = 0755; 1669821f1d3SAlan Somers uint64_t ino = 42; 1679821f1d3SAlan Somers int fd; 1689821f1d3SAlan Somers /* 1699821f1d3SAlan Somers * Set entry_valid = 0 because this test isn't concerned with whether 1709821f1d3SAlan Somers * or not we actually cache negative entries, only with whether we 1719821f1d3SAlan Somers * interpret negative cache responses correctly. 1729821f1d3SAlan Somers */ 1739821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 1749821f1d3SAlan Somers 1759821f1d3SAlan Somers /* create will first do a LOOKUP, adding a negative cache entry */ 1769821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); 1772d445be1SAlan Somers expect_create(RELPATH, ReturnImmediate([=](auto in __unused, auto out) { 1789821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 1799821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 1809821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 1819821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 1829821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 1832d445be1SAlan Somers })); 1849821f1d3SAlan Somers 1859821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 1869821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1879821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 1889821f1d3SAlan Somers } 1899821f1d3SAlan Somers 1909821f1d3SAlan Somers /* 1919821f1d3SAlan Somers * Creating a new file should purge any negative namecache entries 1929821f1d3SAlan Somers */ 1936248288eSAlan Somers TEST_F(Create, entry_cache_negative_purge) 1949821f1d3SAlan Somers { 1959821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1969821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1979821f1d3SAlan Somers mode_t mode = 0755; 1989821f1d3SAlan Somers uint64_t ino = 42; 1999821f1d3SAlan Somers int fd; 2009821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 2019821f1d3SAlan Somers 2029821f1d3SAlan Somers /* create will first do a LOOKUP, adding a negative cache entry */ 2039821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).Times(1) 2049821f1d3SAlan Somers .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 2059821f1d3SAlan Somers .RetiresOnSaturation(); 2069821f1d3SAlan Somers 2079821f1d3SAlan Somers /* Then the CREATE should purge the negative cache entry */ 2082d445be1SAlan Somers expect_create(RELPATH, ReturnImmediate([=](auto in __unused, auto out) { 2099821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 2109821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 2119821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 2129821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 2132d445be1SAlan Somers })); 2149821f1d3SAlan Somers 2159821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 2169821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 2179821f1d3SAlan Somers 2189821f1d3SAlan Somers /* Finally, a subsequent lookup should query the daemon */ 2199821f1d3SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 2209821f1d3SAlan Somers 2219821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 2229821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 2239821f1d3SAlan Somers } 2249821f1d3SAlan Somers 2259821f1d3SAlan Somers /* 2269821f1d3SAlan Somers * The daemon is responsible for checking file permissions (unless the 2279821f1d3SAlan Somers * default_permissions mount option was used) 2289821f1d3SAlan Somers */ 2299821f1d3SAlan Somers TEST_F(Create, eperm) 2309821f1d3SAlan Somers { 2319821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2329821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2339821f1d3SAlan Somers mode_t mode = 0755; 2349821f1d3SAlan Somers 2359821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 2362d445be1SAlan Somers expect_create(RELPATH, ReturnErrno(EPERM)); 2379821f1d3SAlan Somers 2389821f1d3SAlan Somers EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 2399821f1d3SAlan Somers EXPECT_EQ(EPERM, errno); 2409821f1d3SAlan Somers } 2419821f1d3SAlan Somers 2429821f1d3SAlan Somers TEST_F(Create, ok) 2439821f1d3SAlan Somers { 2449821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2459821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2469821f1d3SAlan Somers mode_t mode = 0755; 2479821f1d3SAlan Somers uint64_t ino = 42; 2489821f1d3SAlan Somers int fd; 2499821f1d3SAlan Somers 2509821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 2512d445be1SAlan Somers expect_create(RELPATH, ReturnImmediate([=](auto in __unused, auto out) { 2529821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 2539821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 2549821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 2559821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 2569821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 2572d445be1SAlan Somers })); 2589821f1d3SAlan Somers 2599821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 2609821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 2619821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 2629821f1d3SAlan Somers } 2632d445be1SAlan Somers 2642d445be1SAlan Somers /* 2652d445be1SAlan Somers * A regression test for a bug that affected old FUSE implementations: 2662d445be1SAlan Somers * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming 2672d445be1SAlan Somers * contradiction between O_WRONLY and 0444 2682d445be1SAlan Somers * 2692d445be1SAlan Somers * For example: 2702d445be1SAlan Somers * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886 2712d445be1SAlan Somers */ 2722d445be1SAlan Somers TEST_F(Create, wronly_0444) 2732d445be1SAlan Somers { 2742d445be1SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2752d445be1SAlan Somers const char RELPATH[] = "some_file.txt"; 2762d445be1SAlan Somers mode_t mode = 0444; 2772d445be1SAlan Somers uint64_t ino = 42; 2782d445be1SAlan Somers int fd; 2792d445be1SAlan Somers 2802d445be1SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 2812d445be1SAlan Somers expect_create(RELPATH, ReturnImmediate([=](auto in __unused, auto out) { 2822d445be1SAlan Somers SET_OUT_HEADER_LEN(out, create); 2832d445be1SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 2842d445be1SAlan Somers out->body.create.entry.nodeid = ino; 2852d445be1SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 2862d445be1SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 2872d445be1SAlan Somers })); 2882d445be1SAlan Somers 2892d445be1SAlan Somers fd = open(FULLPATH, O_CREAT | O_WRONLY, mode); 2902d445be1SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 2912d445be1SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 2922d445be1SAlan Somers } 293