xref: /freebsd/tests/sys/fs/fusefs/unlink.cc (revision 0bf48626aaa33768078f5872b922b1487b3a9296)
1 /*-
2  * Copyright (c) 2019 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by BFF Storage Systems, LLC under sponsorship
6  * from the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 extern "C" {
31 #include <fcntl.h>
32 #include <semaphore.h>
33 }
34 
35 #include "mockfs.hh"
36 #include "utils.hh"
37 
38 using namespace testing;
39 
40 class Unlink: public FuseTest {
41 public:
42 void expect_getattr(uint64_t ino, mode_t mode)
43 {
44 	EXPECT_CALL(*m_mock, process(
45 		ResultOf([=](auto in) {
46 			return (in.header.opcode == FUSE_GETATTR &&
47 				in.header.nodeid == ino);
48 		}, Eq(true)),
49 		_)
50 	).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
51 		SET_OUT_HEADER_LEN(out, attr);
52 		out.body.attr.attr.ino = ino;	// Must match nodeid
53 		out.body.attr.attr.mode = mode;
54 		out.body.attr.attr_valid = UINT64_MAX;
55 	})));
56 }
57 
58 void expect_lookup(const char *relpath, uint64_t ino, int times, int nlink=1)
59 {
60 	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
61 	.Times(times)
62 	.WillRepeatedly(Invoke(
63 		ReturnImmediate([=](auto in __unused, auto& out) {
64 		SET_OUT_HEADER_LEN(out, entry);
65 		out.body.entry.attr.mode = S_IFREG | 0644;
66 		out.body.entry.nodeid = ino;
67 		out.body.entry.attr.nlink = nlink;
68 		out.body.entry.attr_valid = UINT64_MAX;
69 		out.body.entry.attr.size = 0;
70 	})));
71 }
72 
73 };
74 
75 /*
76  * Unlinking a multiply linked file should update its ctime and nlink.  This
77  * could be handled simply by invalidating the attributes, necessitating a new
78  * GETATTR, but we implement it in-kernel for efficiency's sake.
79  */
80 TEST_F(Unlink, attr_cache)
81 {
82 	const char FULLPATH0[] = "mountpoint/some_file.txt";
83 	const char RELPATH0[] = "some_file.txt";
84 	const char FULLPATH1[] = "mountpoint/other_file.txt";
85 	const char RELPATH1[] = "other_file.txt";
86 	uint64_t ino = 42;
87 	struct stat sb_old, sb_new;
88 	int fd1;
89 
90 	expect_getattr(1, S_IFDIR | 0755);
91 	expect_lookup(RELPATH0, ino, 1, 2);
92 	expect_lookup(RELPATH1, ino, 1, 2);
93 	expect_open(ino, 0, 1);
94 	expect_unlink(1, RELPATH0, 0);
95 
96 	fd1 = open(FULLPATH1, O_RDONLY);
97 	ASSERT_LE(0, fd1) << strerror(errno);
98 
99 	ASSERT_EQ(0, fstat(fd1, &sb_old)) << strerror(errno);
100 	ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
101 	ASSERT_EQ(0, fstat(fd1, &sb_new)) << strerror(errno);
102 	EXPECT_NE(sb_old.st_ctime, sb_new.st_ctime);
103 	EXPECT_EQ(1u, sb_new.st_nlink);
104 
105 	leak(fd1);
106 }
107 
108 /*
109  * A successful unlink should clear the parent directory's attribute cache,
110  * because the fuse daemon should update its mtime and ctime
111  */
112 TEST_F(Unlink, parent_attr_cache)
113 {
114 	const char FULLPATH[] = "mountpoint/some_file.txt";
115 	const char RELPATH[] = "some_file.txt";
116 	struct stat sb;
117 	uint64_t ino = 42;
118 
119 	EXPECT_CALL(*m_mock, process(
120 		ResultOf([=](auto in) {
121 			return (in.header.opcode == FUSE_GETATTR &&
122 				in.header.nodeid == FUSE_ROOT_ID);
123 		}, Eq(true)),
124 		_)
125 	).Times(2)
126 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
127 		SET_OUT_HEADER_LEN(out, attr);
128 		out.body.attr.attr.ino = ino;	// Must match nodeid
129 		out.body.attr.attr.mode = S_IFDIR | 0755;
130 		out.body.attr.attr_valid = UINT64_MAX;
131 	})));
132 	/* Use nlink=2 so we don't get a FUSE_FORGET */
133 	expect_lookup(RELPATH, ino, 1, 2);
134 	expect_unlink(1, RELPATH, 0);
135 
136 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
137 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
138 }
139 
140 TEST_F(Unlink, eperm)
141 {
142 	const char FULLPATH[] = "mountpoint/some_file.txt";
143 	const char RELPATH[] = "some_file.txt";
144 	uint64_t ino = 42;
145 
146 	expect_getattr(1, S_IFDIR | 0755);
147 	expect_lookup(RELPATH, ino, 1);
148 	expect_unlink(1, RELPATH, EPERM);
149 
150 	ASSERT_NE(0, unlink(FULLPATH));
151 	ASSERT_EQ(EPERM, errno);
152 }
153 
154 /*
155  * Unlinking a file should expire its entry cache, even if it's multiply linked
156  */
157 TEST_F(Unlink, entry_cache)
158 {
159 	const char FULLPATH[] = "mountpoint/some_file.txt";
160 	const char RELPATH[] = "some_file.txt";
161 	uint64_t ino = 42;
162 
163 	expect_getattr(1, S_IFDIR | 0755);
164 	expect_lookup(RELPATH, ino, 2, 2);
165 	expect_unlink(1, RELPATH, 0);
166 
167 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
168 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
169 }
170 
171 /*
172  * Unlink a multiply-linked file.  There should be no FUSE_FORGET because the
173  * file is still linked.
174  */
175 TEST_F(Unlink, multiply_linked)
176 {
177 	const char FULLPATH0[] = "mountpoint/some_file.txt";
178 	const char RELPATH0[] = "some_file.txt";
179 	const char FULLPATH1[] = "mountpoint/other_file.txt";
180 	const char RELPATH1[] = "other_file.txt";
181 	uint64_t ino = 42;
182 
183 	expect_getattr(1, S_IFDIR | 0755);
184 	expect_lookup(RELPATH0, ino, 1, 2);
185 	expect_unlink(1, RELPATH0, 0);
186 	EXPECT_CALL(*m_mock, process(
187 		ResultOf([=](auto in) {
188 			return (in.header.opcode == FUSE_FORGET &&
189 				in.header.nodeid == ino);
190 		}, Eq(true)),
191 		_)
192 	).Times(0);
193 	expect_lookup(RELPATH1, ino, 1, 1);
194 
195 	ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
196 
197 	/*
198 	 * The final syscall simply ensures that no FUSE_FORGET was ever sent,
199 	 * by scheduling an arbitrary different operation after a FUSE_FORGET
200 	 * would've been sent.
201 	 */
202 	ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);
203 }
204 
205 TEST_F(Unlink, ok)
206 {
207 	const char FULLPATH[] = "mountpoint/some_file.txt";
208 	const char RELPATH[] = "some_file.txt";
209 	uint64_t ino = 42;
210 	sem_t sem;
211 
212 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
213 
214 	expect_getattr(1, S_IFDIR | 0755);
215 	expect_lookup(RELPATH, ino, 1);
216 	expect_unlink(1, RELPATH, 0);
217 	expect_forget(ino, 1, &sem);
218 
219 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
220 	sem_wait(&sem);
221 	sem_destroy(&sem);
222 }
223 
224 /* Unlink an open file */
225 TEST_F(Unlink, open_but_deleted)
226 {
227 	const char FULLPATH0[] = "mountpoint/some_file.txt";
228 	const char RELPATH0[] = "some_file.txt";
229 	const char FULLPATH1[] = "mountpoint/other_file.txt";
230 	const char RELPATH1[] = "other_file.txt";
231 	uint64_t ino = 42;
232 	int fd;
233 
234 	expect_getattr(1, S_IFDIR | 0755);
235 	expect_lookup(RELPATH0, ino, 2);
236 	expect_open(ino, 0, 1);
237 	expect_unlink(1, RELPATH0, 0);
238 	expect_lookup(RELPATH1, ino, 1, 1);
239 
240 	fd = open(FULLPATH0, O_RDWR);
241 	ASSERT_LE(0, fd) << strerror(errno);
242 	ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
243 
244 	/*
245 	 * The final syscall simply ensures that no FUSE_FORGET was ever sent,
246 	 * by scheduling an arbitrary different operation after a FUSE_FORGET
247 	 * would've been sent.
248 	 */
249 	ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);
250 	leak(fd);
251 }
252