1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2019 The FreeBSD Foundation 5 * 6 * This software was developed by BFF Storage Systems, LLC under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 * $FreeBSD$ 31 */ 32 33 extern "C" { 34 #include <fcntl.h> 35 #include <pthread.h> 36 #include <semaphore.h> 37 } 38 39 #include "mockfs.hh" 40 #include "utils.hh" 41 42 using namespace testing; 43 44 /* Tests for orderly unmounts */ 45 class Destroy: public FuseTest {}; 46 47 /* Tests for unexpected deaths of the server */ 48 class Death: public FuseTest{}; 49 50 static void* open_th(void* arg) { 51 int fd; 52 const char *path = (const char*)arg; 53 54 fd = open(path, O_RDONLY); 55 EXPECT_EQ(-1, fd); 56 EXPECT_EQ(ENOTCONN, errno); 57 return 0; 58 } 59 60 /* 61 * The server dies with unsent operations still on the message queue. 62 * Check for any memory leaks like this: 63 * 1) kldunload fusefs, if necessary 64 * 2) kldload fusefs 65 * 3) ./destroy --gtest_filter=Destroy.unsent_operations 66 * 4) kldunload fusefs 67 * 5) check /var/log/messages for anything like this: 68 Freed UMA keg (fuse_ticket) was not empty (31 items). Lost 2 pages of memory. 69 Warning: memory type fuse_msgbuf leaked memory on destroy (68 allocations, 428800 bytes leaked). 70 */ 71 TEST_F(Death, unsent_operations) 72 { 73 const char FULLPATH0[] = "mountpoint/some_file.txt"; 74 const char FULLPATH1[] = "mountpoint/other_file.txt"; 75 const char RELPATH0[] = "some_file.txt"; 76 const char RELPATH1[] = "other_file.txt"; 77 pthread_t th0, th1; 78 ino_t ino0 = 42, ino1 = 43; 79 sem_t sem; 80 mode_t mode = S_IFREG | 0644; 81 82 sem_init(&sem, 0, 0); 83 84 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0) 85 .WillRepeatedly(Invoke( 86 ReturnImmediate([=](auto in __unused, auto& out) { 87 SET_OUT_HEADER_LEN(out, entry); 88 out.body.entry.attr.mode = mode; 89 out.body.entry.nodeid = ino0; 90 out.body.entry.attr.nlink = 1; 91 }))); 92 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1) 93 .WillRepeatedly(Invoke( 94 ReturnImmediate([=](auto in __unused, auto& out) { 95 SET_OUT_HEADER_LEN(out, entry); 96 out.body.entry.attr.mode = mode; 97 out.body.entry.nodeid = ino1; 98 out.body.entry.attr.nlink = 1; 99 }))); 100 101 EXPECT_CALL(*m_mock, process( 102 ResultOf([&](auto in) { 103 return (in.header.opcode == FUSE_OPEN); 104 }, Eq(true)), 105 _) 106 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 107 sem_post(&sem); 108 pause(); 109 })); 110 111 /* 112 * One thread's operation will be sent to the daemon and block, and the 113 * other's will be stuck in the message queue. 114 */ 115 ASSERT_EQ(0, pthread_create(&th0, NULL, open_th, 116 __DECONST(void*, FULLPATH0))) << strerror(errno); 117 ASSERT_EQ(0, pthread_create(&th1, NULL, open_th, 118 __DECONST(void*, FULLPATH1))) << strerror(errno); 119 120 /* Wait for the first thread to block */ 121 sem_wait(&sem); 122 /* Give the second thread time to block */ 123 nap(); 124 125 m_mock->kill_daemon(); 126 127 pthread_join(th0, NULL); 128 pthread_join(th1, NULL); 129 130 sem_destroy(&sem); 131 } 132 133 /* 134 * On unmount the kernel should send a FUSE_DESTROY operation. It should also 135 * send FUSE_FORGET operations for all inodes with lookup_count > 0. 136 */ 137 TEST_F(Destroy, ok) 138 { 139 const char FULLPATH[] = "mountpoint/some_file.txt"; 140 const char RELPATH[] = "some_file.txt"; 141 uint64_t ino = 42; 142 143 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 144 expect_forget(ino, 2); 145 expect_destroy(0); 146 147 /* 148 * access(2) the file to force a lookup. Access it twice to double its 149 * lookup count. 150 */ 151 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 152 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 153 154 /* 155 * Unmount, triggering a FUSE_DESTROY and also causing a VOP_RECLAIM 156 * for every vnode on this mp, triggering FUSE_FORGET for each of them. 157 */ 158 m_mock->unmount(); 159 } 160