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 {
SetUp()47 void SetUp() {
48 m_no_auto_init = true;
49 FuseTest::SetUp();
50 }
51 };
52
unmount1(void * arg __unused)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 */
TEST_F(PreInit,unmount_before_init)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 */
TEST_F(PreInit,signal_during_unmount_before_init)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