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 Mkdir: public FuseTest {}; 419821f1d3SAlan Somers 429821f1d3SAlan Somers /* 439821f1d3SAlan Somers * EMLINK is possible on filesystems that limit the number of hard links to a 449821f1d3SAlan Somers * single file, like early versions of BtrFS 459821f1d3SAlan Somers */ 469821f1d3SAlan Somers TEST_F(Mkdir, emlink) 479821f1d3SAlan Somers { 489821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_dir"; 499821f1d3SAlan Somers const char RELPATH[] = "some_dir"; 509821f1d3SAlan Somers mode_t mode = 0755; 519821f1d3SAlan Somers 529821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 539821f1d3SAlan Somers 549821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 559821f1d3SAlan Somers ResultOf([=](auto in) { 569821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 579821f1d3SAlan Somers sizeof(fuse_mkdir_in); 589821f1d3SAlan Somers return (in->header.opcode == FUSE_MKDIR && 599821f1d3SAlan Somers in->body.mkdir.mode == (S_IFDIR | mode) && 609821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 619821f1d3SAlan Somers }, Eq(true)), 629821f1d3SAlan Somers _) 639821f1d3SAlan Somers ).WillOnce(Invoke(ReturnErrno(EMLINK))); 649821f1d3SAlan Somers 659821f1d3SAlan Somers ASSERT_NE(1, mkdir(FULLPATH, mode)); 669821f1d3SAlan Somers ASSERT_EQ(EMLINK, errno); 679821f1d3SAlan Somers } 689821f1d3SAlan Somers 699821f1d3SAlan Somers /* 709821f1d3SAlan Somers * Creating a new directory after FUSE_LOOKUP returned a negative cache entry 719821f1d3SAlan Somers */ 72*6248288eSAlan Somers TEST_F(Mkdir, entry_cache_negative) 739821f1d3SAlan Somers { 749821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 759821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 769821f1d3SAlan Somers mode_t mode = 0755; 779821f1d3SAlan Somers uint64_t ino = 42; 789821f1d3SAlan Somers /* 799821f1d3SAlan Somers * Set entry_valid = 0 because this test isn't concerned with whether 809821f1d3SAlan Somers * or not we actually cache negative entries, only with whether we 819821f1d3SAlan Somers * interpret negative cache responses correctly. 829821f1d3SAlan Somers */ 839821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 849821f1d3SAlan Somers 859821f1d3SAlan Somers /* mkdir will first do a LOOKUP, adding a negative cache entry */ 869821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); 879821f1d3SAlan Somers 889821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 899821f1d3SAlan Somers ResultOf([=](auto in) { 909821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 919821f1d3SAlan Somers sizeof(fuse_open_in); 929821f1d3SAlan Somers return (in->header.opcode == FUSE_MKDIR && 939821f1d3SAlan Somers in->body.mkdir.mode == (S_IFDIR | mode) && 949821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 959821f1d3SAlan Somers }, Eq(true)), 969821f1d3SAlan Somers _) 979821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 989821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry); 999821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFDIR | mode; 1009821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 1019821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 1029821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 1039821f1d3SAlan Somers }))); 1049821f1d3SAlan Somers 1059821f1d3SAlan Somers ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); 1069821f1d3SAlan Somers } 1079821f1d3SAlan Somers 1089821f1d3SAlan Somers /* 1099821f1d3SAlan Somers * Creating a new directory should purge any negative namecache entries 1109821f1d3SAlan Somers */ 111*6248288eSAlan Somers TEST_F(Mkdir, entry_cache_negative_purge) 1129821f1d3SAlan Somers { 1139821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1149821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 1159821f1d3SAlan Somers mode_t mode = 0755; 1169821f1d3SAlan Somers uint64_t ino = 42; 1179821f1d3SAlan Somers struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 1189821f1d3SAlan Somers 1199821f1d3SAlan Somers /* mkdir will first do a LOOKUP, adding a negative cache entry */ 1209821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).Times(1) 1219821f1d3SAlan Somers .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 1229821f1d3SAlan Somers .RetiresOnSaturation(); 1239821f1d3SAlan Somers 1249821f1d3SAlan Somers /* Then the MKDIR should purge the negative cache entry */ 1259821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1269821f1d3SAlan Somers ResultOf([=](auto in) { 1279821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 1289821f1d3SAlan Somers sizeof(fuse_open_in); 1299821f1d3SAlan Somers return (in->header.opcode == FUSE_MKDIR && 1309821f1d3SAlan Somers in->body.mkdir.mode == (S_IFDIR | mode) && 1319821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 1329821f1d3SAlan Somers }, Eq(true)), 1339821f1d3SAlan Somers _) 1349821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 1359821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry); 1369821f1d3SAlan Somers out->body.entry.attr.mode = S_IFDIR | mode; 1379821f1d3SAlan Somers out->body.entry.nodeid = ino; 1389821f1d3SAlan Somers out->body.entry.attr_valid = UINT64_MAX; 1399821f1d3SAlan Somers }))); 1409821f1d3SAlan Somers 1419821f1d3SAlan Somers ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); 1429821f1d3SAlan Somers 1439821f1d3SAlan Somers /* Finally, a subsequent lookup should query the daemon */ 1449821f1d3SAlan Somers expect_lookup(RELPATH, ino, S_IFDIR | mode, 0, 1); 1459821f1d3SAlan Somers 1469821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 1479821f1d3SAlan Somers } 1489821f1d3SAlan Somers 1499821f1d3SAlan Somers TEST_F(Mkdir, ok) 1509821f1d3SAlan Somers { 1519821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_dir"; 1529821f1d3SAlan Somers const char RELPATH[] = "some_dir"; 1539821f1d3SAlan Somers mode_t mode = 0755; 1549821f1d3SAlan Somers uint64_t ino = 42; 1559821f1d3SAlan Somers 1569821f1d3SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 1579821f1d3SAlan Somers 1589821f1d3SAlan Somers EXPECT_CALL(*m_mock, process( 1599821f1d3SAlan Somers ResultOf([=](auto in) { 1609821f1d3SAlan Somers const char *name = (const char*)in->body.bytes + 1619821f1d3SAlan Somers sizeof(fuse_mkdir_in); 1629821f1d3SAlan Somers return (in->header.opcode == FUSE_MKDIR && 1639821f1d3SAlan Somers in->body.mkdir.mode == (S_IFDIR | mode) && 1649821f1d3SAlan Somers (0 == strcmp(RELPATH, name))); 1659821f1d3SAlan Somers }, Eq(true)), 1669821f1d3SAlan Somers _) 1679821f1d3SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 1689821f1d3SAlan Somers SET_OUT_HEADER_LEN(out, entry); 1699821f1d3SAlan Somers out->body.create.entry.attr.mode = S_IFDIR | mode; 1709821f1d3SAlan Somers out->body.create.entry.nodeid = ino; 1719821f1d3SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 1729821f1d3SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 1739821f1d3SAlan Somers }))); 1749821f1d3SAlan Somers 1759821f1d3SAlan Somers ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); 1769821f1d3SAlan Somers } 177