1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2025 ConnectWise 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 extern "C" { 29 #include <sys/param.h> 30 #include <sys/mount.h> 31 #include <sys/signal.h> 32 #include <sys/wait.h> 33 34 #include <fcntl.h> 35 #include <pthread.h> 36 #include <semaphore.h> 37 #include <signal.h> 38 } 39 40 #include "mockfs.hh" 41 #include "utils.hh" 42 43 using namespace testing; 44 45 /* Tests for behavior that happens before the server responds to FUSE_INIT */ 46 class PreInit: public FuseTest { 47 void SetUp() { 48 m_no_auto_init = true; 49 FuseTest::SetUp(); 50 } 51 }; 52 53 static void* unmount1(void* arg __unused) { 54 ssize_t r; 55 56 r = unmount("mountpoint", 0); 57 if (r >= 0) 58 return 0; 59 else 60 return (void*)(intptr_t)errno; 61 } 62 63 /* 64 * Attempting to unmount the file system before it fully initializes should 65 * work fine. The unmount will complete after initialization does. 66 */ 67 TEST_F(PreInit, unmount_before_init) 68 { 69 sem_t sem0; 70 pthread_t th1; 71 72 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 73 74 EXPECT_CALL(*m_mock, process( 75 ResultOf([](auto in) { 76 return (in.header.opcode == FUSE_INIT); 77 }, Eq(true)), 78 _) 79 ).WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 80 SET_OUT_HEADER_LEN(out, init); 81 out.body.init.major = FUSE_KERNEL_VERSION; 82 out.body.init.minor = FUSE_KERNEL_MINOR_VERSION; 83 out.body.init.flags = in.body.init.flags & m_init_flags; 84 out.body.init.max_write = m_maxwrite; 85 out.body.init.max_readahead = m_maxreadahead; 86 out.body.init.time_gran = m_time_gran; 87 sem_wait(&sem0); 88 }))); 89 expect_destroy(0); 90 91 ASSERT_EQ(0, pthread_create(&th1, NULL, unmount1, NULL)); 92 nap(); /* Wait for th1 to block in unmount() */ 93 sem_post(&sem0); 94 /* The daemon will quit after receiving FUSE_DESTROY */ 95 m_mock->join_daemon(); 96 } 97 98 /* 99 * Don't panic in this very specific scenario: 100 * 101 * The server does not respond to FUSE_INIT in timely fashion. 102 * Some other process tries to do unmount. 103 * That other process gets killed by a signal. 104 * The server finally responds to FUSE_INIT. 105 * 106 * Regression test for bug 287438 107 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=287438 108 */ 109 TEST_F(PreInit, signal_during_unmount_before_init) 110 { 111 sem_t sem0; 112 pid_t child; 113 114 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 115 116 EXPECT_CALL(*m_mock, process( 117 ResultOf([](auto in) { 118 return (in.header.opcode == FUSE_INIT); 119 }, Eq(true)), 120 _) 121 ).WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 122 SET_OUT_HEADER_LEN(out, init); 123 out.body.init.major = FUSE_KERNEL_VERSION; 124 /* 125 * Use protocol 7.19, like libfuse2 does. The server must use 126 * protocol 7.27 or older to trigger the bug. 127 */ 128 out.body.init.minor = 19; 129 out.body.init.flags = in.body.init.flags & m_init_flags; 130 out.body.init.max_write = m_maxwrite; 131 out.body.init.max_readahead = m_maxreadahead; 132 out.body.init.time_gran = m_time_gran; 133 sem_wait(&sem0); 134 }))); 135 136 if ((child = ::fork()) == 0) { 137 /* 138 * In child. This will block waiting for FUSE_INIT to complete 139 * or the receipt of an asynchronous signal. 140 */ 141 (void) unmount("mountpoint", 0); 142 _exit(0); /* Unreachable, unless parent dies after fork */ 143 } else if (child > 0) { 144 /* In parent. Wait for child process to start, then kill it */ 145 nap(); 146 kill(child, SIGINT); 147 waitpid(child, NULL, WEXITED); 148 } else { 149 FAIL() << strerror(errno); 150 } 151 m_mock->m_quit = true; /* Since we are by now unmounted. */ 152 sem_post(&sem0); 153 m_mock->join_daemon(); 154 } 155