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 409821f1d3SAlan Somers class Create: public FuseTest {}; 419821f1d3SAlan Somers 429821f1d3SAlan Somers /* 439821f1d3SAlan Somers * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the 449821f1d3SAlan Somers * attribute cache 459821f1d3SAlan Somers */ 469821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ 479821f1d3SAlan Somers TEST_F(Create, DISABLED_attr_cache) 489821f1d3SAlan Somers { 499821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 509821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 519821f1d3SAlan Somers mode_t mode = 0755; 529821f1d3SAlan Somers uint64_t ino = 42; 539821f1d3SAlan Somers int fd; 549821f1d3SAlan Somers 559821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 569821f1d3SAlan Somers 579821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 589821f1d3SAlan Somers ResultOf([=](auto in) { 599821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 609821f1d3SAlan Somers sizeof(fuse_open_in); 619821f1d3SAlan Somers return (in->header.opcode == FUSE_CREATE && 629821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 639821f1d3SAlan Somers }, Eq(true)), 649821f1d3SAlan Somers _) 659821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 669821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 679821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 689821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 699821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 709821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 719821f1d3SAlan Somers }))); 729821f1d3SAlan Somers 739821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 749821f1d3SAlan Somers ResultOf([=](auto in) { 759821f1d3SAlan Somers return (in->header.opcode == FUSE_GETATTR && 769821f1d3SAlan Somers in->header.nodeid == ino); 779821f1d3SAlan Somers }, Eq(true)), 789821f1d3SAlan Somers _) 799821f1d3SAlan Somers ).Times(0); 809821f1d3SAlan Somers 819821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 829821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 839821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 849821f1d3SAlan Somers } 859821f1d3SAlan Somers 869821f1d3SAlan Somers /* 879821f1d3SAlan Somers * The fuse daemon fails the request with EEXIST. This usually indicates a 889821f1d3SAlan Somers * race condition: some other FUSE client created the file in between when the 899821f1d3SAlan Somers * kernel checked for it with lookup and tried to create it with create 909821f1d3SAlan Somers */ 919821f1d3SAlan Somers TEST_F(Create, eexist) 929821f1d3SAlan Somers { 939821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 949821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 959821f1d3SAlan Somers mode_t mode = 0755; 969821f1d3SAlan Somers 979821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 989821f1d3SAlan Somers 999821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1009821f1d3SAlan Somers ResultOf([=](auto in) { 1019821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 1029821f1d3SAlan Somers sizeof(fuse_open_in); 1039821f1d3SAlan Somers return (in->header.opcode == FUSE_CREATE && 1049821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 1059821f1d3SAlan Somers }, Eq(true)), 1069821f1d3SAlan Somers _) 1079821f1d3SAlan Somers ).WillOnce(Invoke(ReturnErrno(EEXIST))); 1089821f1d3SAlan Somers EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 1099821f1d3SAlan Somers EXPECT_EQ(EEXIST, errno); 1109821f1d3SAlan Somers } 1119821f1d3SAlan Somers 1129821f1d3SAlan Somers /* 1139821f1d3SAlan Somers * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 1149821f1d3SAlan Somers * to FUSE_MKNOD/FUSE_OPEN 1159821f1d3SAlan Somers */ 116*19ef317dSAlan Somers TEST_F(Create, Enosys) 1179821f1d3SAlan Somers { 1189821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1199821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1209821f1d3SAlan Somers mode_t mode = 0755; 1219821f1d3SAlan Somers uint64_t ino = 42; 1229821f1d3SAlan Somers int fd; 1239821f1d3SAlan Somers 1249821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 1259821f1d3SAlan Somers 1269821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1279821f1d3SAlan Somers ResultOf([=](auto in) { 1289821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 1299821f1d3SAlan Somers sizeof(fuse_open_in); 1309821f1d3SAlan Somers return (in->header.opcode == FUSE_CREATE && 1319821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 1329821f1d3SAlan Somers }, Eq(true)), 1339821f1d3SAlan Somers _) 1349821f1d3SAlan Somers ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 1359821f1d3SAlan Somers 1369821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1379821f1d3SAlan Somers ResultOf([=](auto in) { 1389821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 1399821f1d3SAlan Somers sizeof(fuse_mknod_in); 1409821f1d3SAlan Somers return (in->header.opcode == FUSE_MKNOD && 1419821f1d3SAlan Somers in->body.mknod.mode == (S_IFREG | mode) && 1429821f1d3SAlan Somers in->body.mknod.rdev == 0 && 1439821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 1449821f1d3SAlan Somers }, Eq(true)), 1459821f1d3SAlan Somers _) 1469821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 147*19ef317dSAlan Somers SET_OUT_HEADER_LEN(out, entry); 148*19ef317dSAlan Somers out->body.entry.attr.mode = S_IFREG | mode; 149*19ef317dSAlan Somers out->body.entry.nodeid = ino; 150*19ef317dSAlan Somers out->body.entry.entry_valid = UINT64_MAX; 151*19ef317dSAlan Somers out->body.entry.attr_valid = UINT64_MAX; 1529821f1d3SAlan Somers }))); 1539821f1d3SAlan Somers 1549821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1559821f1d3SAlan Somers ResultOf([=](auto in) { 1569821f1d3SAlan Somers return (in->header.opcode == FUSE_OPEN && 1579821f1d3SAlan Somers in->header.nodeid == ino); 1589821f1d3SAlan Somers }, Eq(true)), 1599821f1d3SAlan Somers _) 1609821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 1619821f1d3SAlan Somers out->header.len = sizeof(out->header); 1629821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, open); 1639821f1d3SAlan Somers }))); 1649821f1d3SAlan Somers 1659821f1d3SAlan Somers /* Until the attr cache is working, we may send an additional GETATTR */ 1669821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1679821f1d3SAlan Somers ResultOf([=](auto in) { 1689821f1d3SAlan Somers return (in->header.opcode == FUSE_GETATTR && 1699821f1d3SAlan Somers in->header.nodeid == ino); 1709821f1d3SAlan Somers }, Eq(true)), 1719821f1d3SAlan Somers _) 1729821f1d3SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 1739821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr); 1749821f1d3SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 1759821f1d3SAlan Somers out->body.attr.attr.mode = S_IFREG | 0644; 1769821f1d3SAlan Somers }))); 1779821f1d3SAlan Somers 1789821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 1799821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 1809821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 1819821f1d3SAlan Somers } 1829821f1d3SAlan Somers 1839821f1d3SAlan Somers /* 1849821f1d3SAlan Somers * Creating a new file after FUSE_LOOKUP returned a negative cache entry 1859821f1d3SAlan Somers */ 1866248288eSAlan Somers TEST_F(Create, entry_cache_negative) 1879821f1d3SAlan Somers { 1889821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1899821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1909821f1d3SAlan Somers mode_t mode = 0755; 1919821f1d3SAlan Somers uint64_t ino = 42; 1929821f1d3SAlan Somers int fd; 1939821f1d3SAlan Somers /* 1949821f1d3SAlan Somers * Set entry_valid = 0 because this test isn't concerned with whether 1959821f1d3SAlan Somers * or not we actually cache negative entries, only with whether we 1969821f1d3SAlan Somers * interpret negative cache responses correctly. 1979821f1d3SAlan Somers */ 1989821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 1999821f1d3SAlan Somers 2009821f1d3SAlan Somers /* create will first do a LOOKUP, adding a negative cache entry */ 2019821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); 2029821f1d3SAlan Somers 2039821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 2049821f1d3SAlan Somers ResultOf([=](auto in) { 2059821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 2069821f1d3SAlan Somers sizeof(fuse_open_in); 2079821f1d3SAlan Somers return (in->header.opcode == FUSE_CREATE && 2089821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 2099821f1d3SAlan Somers }, Eq(true)), 2109821f1d3SAlan Somers _) 2119821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 2129821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 2139821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 2149821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 2159821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 2169821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 2179821f1d3SAlan Somers }))); 2189821f1d3SAlan Somers 2199821f1d3SAlan Somers /* Until the attr cache is working, we may send an additional GETATTR */ 2209821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 2219821f1d3SAlan Somers ResultOf([=](auto in) { 2229821f1d3SAlan Somers return (in->header.opcode == FUSE_GETATTR && 2239821f1d3SAlan Somers in->header.nodeid == ino); 2249821f1d3SAlan Somers }, Eq(true)), 2259821f1d3SAlan Somers _) 2269821f1d3SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 2279821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr); 2289821f1d3SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 2299821f1d3SAlan Somers out->body.attr.attr.mode = S_IFREG | 0644; 2309821f1d3SAlan 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"; 2449821f1d3SAlan Somers mode_t mode = 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 */ 2559821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 2569821f1d3SAlan Somers ResultOf([=](auto in) { 2579821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 2589821f1d3SAlan Somers sizeof(fuse_open_in); 2599821f1d3SAlan Somers return (in->header.opcode == FUSE_CREATE && 2609821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 2619821f1d3SAlan Somers }, Eq(true)), 2629821f1d3SAlan Somers _) 2639821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 2649821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 2659821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 2669821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 2679821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 2689821f1d3SAlan Somers }))); 2699821f1d3SAlan Somers 2709821f1d3SAlan Somers /* Until the attr cache is working, we may send an additional GETATTR */ 2719821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 2729821f1d3SAlan Somers ResultOf([=](auto in) { 2739821f1d3SAlan Somers return (in->header.opcode == FUSE_GETATTR && 2749821f1d3SAlan Somers in->header.nodeid == ino); 2759821f1d3SAlan Somers }, Eq(true)), 2769821f1d3SAlan Somers _) 2779821f1d3SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 2789821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr); 2799821f1d3SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 2809821f1d3SAlan Somers out->body.attr.attr.mode = S_IFREG | 0644; 2819821f1d3SAlan Somers }))); 2829821f1d3SAlan Somers 2839821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 2849821f1d3SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 2859821f1d3SAlan Somers 2869821f1d3SAlan Somers /* Finally, a subsequent lookup should query the daemon */ 2879821f1d3SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 2889821f1d3SAlan Somers 2899821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 2909821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 2919821f1d3SAlan Somers } 2929821f1d3SAlan Somers 2939821f1d3SAlan Somers /* 2949821f1d3SAlan Somers * The daemon is responsible for checking file permissions (unless the 2959821f1d3SAlan Somers * default_permissions mount option was used) 2969821f1d3SAlan Somers */ 2979821f1d3SAlan Somers TEST_F(Create, eperm) 2989821f1d3SAlan Somers { 2999821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3009821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 3019821f1d3SAlan Somers mode_t mode = 0755; 3029821f1d3SAlan Somers 3039821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 3049821f1d3SAlan Somers 3059821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 3069821f1d3SAlan Somers ResultOf([=](auto in) { 3079821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 3089821f1d3SAlan Somers sizeof(fuse_open_in); 3099821f1d3SAlan Somers return (in->header.opcode == FUSE_CREATE && 3109821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 3119821f1d3SAlan Somers }, Eq(true)), 3129821f1d3SAlan Somers _) 3139821f1d3SAlan Somers ).WillOnce(Invoke(ReturnErrno(EPERM))); 3149821f1d3SAlan Somers EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 3159821f1d3SAlan Somers EXPECT_EQ(EPERM, errno); 3169821f1d3SAlan Somers } 3179821f1d3SAlan Somers 3189821f1d3SAlan Somers TEST_F(Create, ok) 3199821f1d3SAlan Somers { 3209821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3219821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 3229821f1d3SAlan Somers mode_t mode = 0755; 3239821f1d3SAlan Somers uint64_t ino = 42; 3249821f1d3SAlan Somers int fd; 3259821f1d3SAlan Somers 3269821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 3279821f1d3SAlan Somers 3289821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 3299821f1d3SAlan Somers ResultOf([=](auto in) { 3309821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 3319821f1d3SAlan Somers sizeof(fuse_open_in); 3329821f1d3SAlan Somers return (in->header.opcode == FUSE_CREATE && 3339821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 3349821f1d3SAlan Somers }, Eq(true)), 3359821f1d3SAlan Somers _) 3369821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 3379821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, create); 3389821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFREG | mode; 3399821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 3409821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 3419821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 3429821f1d3SAlan Somers }))); 3439821f1d3SAlan Somers 3449821f1d3SAlan Somers /* Until the attr cache is working, we may send an additional GETATTR */ 3459821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 3469821f1d3SAlan Somers ResultOf([=](auto in) { 3479821f1d3SAlan Somers return (in->header.opcode == FUSE_GETATTR && 3489821f1d3SAlan Somers in->header.nodeid == ino); 3499821f1d3SAlan Somers }, Eq(true)), 3509821f1d3SAlan Somers _) 3519821f1d3SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 3529821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, attr); 3539821f1d3SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 3549821f1d3SAlan Somers out->body.attr.attr.mode = S_IFREG | 0644; 3559821f1d3SAlan Somers }))); 3569821f1d3SAlan Somers 3579821f1d3SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 3589821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 3599821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 3609821f1d3SAlan Somers } 361