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> 33ede571e4SAlan Somers #include <sys/socket.h> 34ede571e4SAlan Somers #include <sys/un.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 { 472d445be1SAlan Somers EXPECT_CALL(*m_mock, process( 482d445be1SAlan Somers ResultOf([=](auto in) { 492d445be1SAlan Somers const char *name = (const char*)in->body.bytes + 502d445be1SAlan Somers sizeof(fuse_open_in); 512d445be1SAlan Somers return (in->header.opcode == FUSE_CREATE && 52ede571e4SAlan Somers in->body.open.mode == mode && 532d445be1SAlan Somers (0 == strcmp(relpath, name))); 542d445be1SAlan Somers }, Eq(true)), 552d445be1SAlan Somers _) 562d445be1SAlan Somers ).WillOnce(Invoke(r)); 572d445be1SAlan Somers } 582d445be1SAlan Somers 592d445be1SAlan Somers }; 609821f1d3SAlan Somers 619821f1d3SAlan Somers /* 62*002e54b0SAlan Somers * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the 639821f1d3SAlan Somers * attribute cache 649821f1d3SAlan Somers */ 65cad67791SAlan Somers TEST_F(Create, attr_cache) 669821f1d3SAlan Somers { 679821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 689821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 69ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 709821f1d3SAlan Somers uint64_t ino = 42; 719821f1d3SAlan Somers int fd; 729821f1d3SAlan Somers 739821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 74ede571e4SAlan Somers expect_create(RELPATH, mode, 75ede571e4SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 769821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 77ede571e4SAlan Somers out->body.create.entry.attr.mode = mode; 789821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 799821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 809821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 812d445be1SAlan Somers })); 829821f1d3SAlan Somers 839821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 849821f1d3SAlan Somers ResultOf([=](auto in) { 859821f1d3SAlan Somers return (in->header.opcode == FUSE_GETATTR && 869821f1d3SAlan Somers in->header.nodeid == ino); 879821f1d3SAlan Somers }, Eq(true)), 889821f1d3SAlan Somers _) 899821f1d3SAlan Somers ).Times(0); 909821f1d3SAlan Somers 919821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 929821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 939821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 949821f1d3SAlan Somers } 959821f1d3SAlan Somers 96*002e54b0SAlan Somers /* A successful CREATE operation should purge the parent dir's attr cache */ 97*002e54b0SAlan Somers TEST_F(Create, clear_attr_cache) 98*002e54b0SAlan Somers { 99*002e54b0SAlan Somers const char FULLPATH[] = "mountpoint/src"; 100*002e54b0SAlan Somers const char RELPATH[] = "src"; 101*002e54b0SAlan Somers mode_t mode = S_IFREG | 0755; 102*002e54b0SAlan Somers uint64_t ino = 42; 103*002e54b0SAlan Somers int fd; 104*002e54b0SAlan Somers struct stat sb; 105*002e54b0SAlan Somers 106*002e54b0SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 107*002e54b0SAlan Somers EXPECT_CALL(*m_mock, process( 108*002e54b0SAlan Somers ResultOf([=](auto in) { 109*002e54b0SAlan Somers return (in->header.opcode == FUSE_GETATTR && 110*002e54b0SAlan Somers in->header.nodeid == 1); 111*002e54b0SAlan Somers }, Eq(true)), 112*002e54b0SAlan Somers _) 113*002e54b0SAlan Somers ).Times(2) 114*002e54b0SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 115*002e54b0SAlan Somers SET_OUT_HEADER_LEN(out, attr); 116*002e54b0SAlan Somers out->body.attr.attr.ino = 1; 117*002e54b0SAlan Somers out->body.attr.attr.mode = S_IFDIR | 0755; 118*002e54b0SAlan Somers out->body.attr.attr_valid = UINT64_MAX; 119*002e54b0SAlan Somers }))); 120*002e54b0SAlan Somers 121*002e54b0SAlan Somers expect_create(RELPATH, mode, 122*002e54b0SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 123*002e54b0SAlan Somers SET_OUT_HEADER_LEN(out, create); 124*002e54b0SAlan Somers out->body.create.entry.attr.mode = mode; 125*002e54b0SAlan Somers out->body.create.entry.nodeid = ino; 126*002e54b0SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 127*002e54b0SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 128*002e54b0SAlan Somers })); 129*002e54b0SAlan Somers 130*002e54b0SAlan Somers EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 131*002e54b0SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 132*002e54b0SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 133*002e54b0SAlan Somers EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 134*002e54b0SAlan Somers 135*002e54b0SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 136*002e54b0SAlan Somers } 137*002e54b0SAlan Somers 1389821f1d3SAlan Somers /* 1399821f1d3SAlan Somers * The fuse daemon fails the request with EEXIST. This usually indicates a 1409821f1d3SAlan Somers * race condition: some other FUSE client created the file in between when the 1419821f1d3SAlan Somers * kernel checked for it with lookup and tried to create it with create 1429821f1d3SAlan Somers */ 1439821f1d3SAlan Somers TEST_F(Create, eexist) 1449821f1d3SAlan Somers { 1459821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1469821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 147ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 1489821f1d3SAlan Somers 1499821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 150ede571e4SAlan Somers expect_create(RELPATH, mode, ReturnErrno(EEXIST)); 1519821f1d3SAlan Somers EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 1529821f1d3SAlan Somers EXPECT_EQ(EEXIST, errno); 1539821f1d3SAlan Somers } 1549821f1d3SAlan Somers 1559821f1d3SAlan Somers /* 1569821f1d3SAlan Somers * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 1579821f1d3SAlan Somers * to FUSE_MKNOD/FUSE_OPEN 1589821f1d3SAlan Somers */ 15919ef317dSAlan Somers TEST_F(Create, Enosys) 1609821f1d3SAlan Somers { 1619821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1629821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 163ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 1649821f1d3SAlan Somers uint64_t ino = 42; 1659821f1d3SAlan Somers int fd; 1669821f1d3SAlan Somers 1679821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 168ede571e4SAlan Somers expect_create(RELPATH, mode, ReturnErrno(ENOSYS)); 1699821f1d3SAlan Somers 1709821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1719821f1d3SAlan Somers ResultOf([=](auto in) { 1729821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 1739821f1d3SAlan Somers sizeof(fuse_mknod_in); 1749821f1d3SAlan Somers return (in->header.opcode == FUSE_MKNOD && 1759821f1d3SAlan Somers in->body.mknod.mode == (S_IFREG | mode) && 1769821f1d3SAlan Somers in->body.mknod.rdev == 0 && 1779821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 1789821f1d3SAlan Somers }, Eq(true)), 1799821f1d3SAlan Somers _) 1809821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 18119ef317dSAlan Somers SET_OUT_HEADER_LEN(out, entry); 182ede571e4SAlan Somers out->body.entry.attr.mode = mode; 18319ef317dSAlan Somers out->body.entry.nodeid = ino; 18419ef317dSAlan Somers out->body.entry.entry_valid = UINT64_MAX; 18519ef317dSAlan Somers out->body.entry.attr_valid = UINT64_MAX; 1869821f1d3SAlan Somers }))); 1879821f1d3SAlan Somers 1889821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1899821f1d3SAlan Somers ResultOf([=](auto in) { 1909821f1d3SAlan Somers return (in->header.opcode == FUSE_OPEN && 1919821f1d3SAlan Somers in->header.nodeid == ino); 1929821f1d3SAlan Somers }, Eq(true)), 1939821f1d3SAlan Somers _) 1949821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 1959821f1d3SAlan Somers out->header.len = sizeof(out->header); 1969821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, open); 1979821f1d3SAlan Somers }))); 1989821f1d3SAlan Somers 1999821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 2009821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 2019821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 2029821f1d3SAlan Somers } 2039821f1d3SAlan Somers 2049821f1d3SAlan Somers /* 2059821f1d3SAlan Somers * Creating a new file after FUSE_LOOKUP returned a negative cache entry 2069821f1d3SAlan Somers */ 2076248288eSAlan Somers TEST_F(Create, entry_cache_negative) 2089821f1d3SAlan Somers { 2099821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2109821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 211ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 2129821f1d3SAlan Somers uint64_t ino = 42; 2139821f1d3SAlan Somers int fd; 2149821f1d3SAlan Somers /* 2159821f1d3SAlan Somers * Set entry_valid = 0 because this test isn't concerned with whether 2169821f1d3SAlan Somers * or not we actually cache negative entries, only with whether we 2179821f1d3SAlan Somers * interpret negative cache responses correctly. 2189821f1d3SAlan Somers */ 2199821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 2209821f1d3SAlan Somers 2219821f1d3SAlan Somers /* create will first do a LOOKUP, adding a negative cache entry */ 2229821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); 223ede571e4SAlan Somers expect_create(RELPATH, mode, 224ede571e4SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 2259821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 226ede571e4SAlan Somers out->body.create.entry.attr.mode = mode; 2279821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 2289821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 2299821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 2302d445be1SAlan Somers })); 2319821f1d3SAlan Somers 2329821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 2339821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 2349821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 2359821f1d3SAlan Somers } 2369821f1d3SAlan Somers 2379821f1d3SAlan Somers /* 2389821f1d3SAlan Somers * Creating a new file should purge any negative namecache entries 2399821f1d3SAlan Somers */ 2406248288eSAlan Somers TEST_F(Create, entry_cache_negative_purge) 2419821f1d3SAlan Somers { 2429821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2439821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 244ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 2459821f1d3SAlan Somers uint64_t ino = 42; 2469821f1d3SAlan Somers int fd; 2479821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 2489821f1d3SAlan Somers 2499821f1d3SAlan Somers /* create will first do a LOOKUP, adding a negative cache entry */ 2509821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).Times(1) 2519821f1d3SAlan Somers .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 2529821f1d3SAlan Somers .RetiresOnSaturation(); 2539821f1d3SAlan Somers 2549821f1d3SAlan Somers /* Then the CREATE should purge the negative cache entry */ 255ede571e4SAlan Somers expect_create(RELPATH, mode, 256ede571e4SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 2579821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 258ede571e4SAlan Somers out->body.create.entry.attr.mode = mode; 2599821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 2609821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 2612d445be1SAlan Somers })); 2629821f1d3SAlan Somers 2639821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 2649821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 2659821f1d3SAlan Somers 2669821f1d3SAlan Somers /* Finally, a subsequent lookup should query the daemon */ 2679821f1d3SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 2689821f1d3SAlan Somers 2699821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 2709821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 2719821f1d3SAlan Somers } 2729821f1d3SAlan Somers 2739821f1d3SAlan Somers /* 2749821f1d3SAlan Somers * The daemon is responsible for checking file permissions (unless the 2759821f1d3SAlan Somers * default_permissions mount option was used) 2769821f1d3SAlan Somers */ 2779821f1d3SAlan Somers TEST_F(Create, eperm) 2789821f1d3SAlan Somers { 2799821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2809821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 281ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 2829821f1d3SAlan Somers 2839821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 284ede571e4SAlan Somers expect_create(RELPATH, mode, ReturnErrno(EPERM)); 2859821f1d3SAlan Somers 2869821f1d3SAlan Somers EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 2879821f1d3SAlan Somers EXPECT_EQ(EPERM, errno); 2889821f1d3SAlan Somers } 2899821f1d3SAlan Somers 2909821f1d3SAlan Somers TEST_F(Create, ok) 2919821f1d3SAlan Somers { 2929821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2939821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 294ede571e4SAlan Somers mode_t mode = S_IFREG | 0755; 2959821f1d3SAlan Somers uint64_t ino = 42; 2969821f1d3SAlan Somers int fd; 2979821f1d3SAlan Somers 2989821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 299ede571e4SAlan Somers expect_create(RELPATH, mode, 300ede571e4SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 3019821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 302ede571e4SAlan Somers out->body.create.entry.attr.mode = mode; 3039821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 3049821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 3059821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 3062d445be1SAlan Somers })); 3079821f1d3SAlan Somers 3089821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 3099821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 3109821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 3119821f1d3SAlan Somers } 3122d445be1SAlan Somers 313ede571e4SAlan Somers /* Create a unix-domain socket */ 314ede571e4SAlan Somers TEST_F(Create, socket) 315ede571e4SAlan Somers { 316ede571e4SAlan Somers const char FULLPATH[] = "mountpoint/some_sock"; 317ede571e4SAlan Somers const char RELPATH[] = "some_sock"; 318ede571e4SAlan Somers mode_t mode = S_IFSOCK | 0755; 319ede571e4SAlan Somers struct sockaddr_un sa; 320ede571e4SAlan Somers uint64_t ino = 42; 321ede571e4SAlan Somers int fd; 322ede571e4SAlan Somers 323ede571e4SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 324ede571e4SAlan Somers expect_create(RELPATH, mode, 325ede571e4SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 326ede571e4SAlan Somers SET_OUT_HEADER_LEN(out, create); 327ede571e4SAlan Somers out->body.create.entry.attr.mode = mode; 328ede571e4SAlan Somers out->body.create.entry.nodeid = ino; 329ede571e4SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 330ede571e4SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 331ede571e4SAlan Somers })); 332ede571e4SAlan Somers 333ede571e4SAlan Somers fd = socket(AF_UNIX, SOCK_STREAM, 0); 334ede571e4SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 335ede571e4SAlan Somers sa.sun_family = AF_UNIX; 336ede571e4SAlan Somers strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path)); 337ede571e4SAlan Somers ASSERT_EQ(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa))) 338ede571e4SAlan Somers << strerror(errno); 339ede571e4SAlan Somers } 340ede571e4SAlan Somers 3412d445be1SAlan Somers /* 3422d445be1SAlan Somers * A regression test for a bug that affected old FUSE implementations: 3432d445be1SAlan Somers * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming 3442d445be1SAlan Somers * contradiction between O_WRONLY and 0444 3452d445be1SAlan Somers * 3462d445be1SAlan Somers * For example: 3472d445be1SAlan Somers * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886 3482d445be1SAlan Somers */ 3492d445be1SAlan Somers TEST_F(Create, wronly_0444) 3502d445be1SAlan Somers { 3512d445be1SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3522d445be1SAlan Somers const char RELPATH[] = "some_file.txt"; 353ede571e4SAlan Somers mode_t mode = S_IFREG | 0444; 3542d445be1SAlan Somers uint64_t ino = 42; 3552d445be1SAlan Somers int fd; 3562d445be1SAlan Somers 3572d445be1SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 358ede571e4SAlan Somers expect_create(RELPATH, mode, 359ede571e4SAlan Somers ReturnImmediate([=](auto in __unused, auto out) { 3602d445be1SAlan Somers SET_OUT_HEADER_LEN(out, create); 361ede571e4SAlan Somers out->body.create.entry.attr.mode = mode; 3622d445be1SAlan Somers out->body.create.entry.nodeid = ino; 3632d445be1SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 3642d445be1SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 3652d445be1SAlan Somers })); 3662d445be1SAlan Somers 3672d445be1SAlan Somers fd = open(FULLPATH, O_CREAT | O_WRONLY, mode); 3682d445be1SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 3692d445be1SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 3702d445be1SAlan Somers } 371