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 * $FreeBSD$
30 */
31
32extern "C" {
33#include <fcntl.h>
34#include <semaphore.h>
35}
36
37#include "mockfs.hh"
38#include "utils.hh"
39
40using namespace testing;
41
42class Unlink: public FuseTest {
43public:
44void expect_lookup(const char *relpath, uint64_t ino, int times, int nlink=1)
45{
46	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
47	.Times(times)
48	.WillRepeatedly(Invoke(
49		ReturnImmediate([=](auto in __unused, auto& out) {
50		SET_OUT_HEADER_LEN(out, entry);
51		out.body.entry.attr.mode = S_IFREG | 0644;
52		out.body.entry.nodeid = ino;
53		out.body.entry.attr.nlink = nlink;
54		out.body.entry.attr_valid = UINT64_MAX;
55		out.body.entry.attr.size = 0;
56	})));
57}
58
59};
60
61/*
62 * Unlinking a multiply linked file should update its ctime and nlink.  This
63 * could be handled simply by invalidating the attributes, necessitating a new
64 * GETATTR, but we implement it in-kernel for efficiency's sake.
65 */
66TEST_F(Unlink, attr_cache)
67{
68	const char FULLPATH0[] = "mountpoint/some_file.txt";
69	const char RELPATH0[] = "some_file.txt";
70	const char FULLPATH1[] = "mountpoint/other_file.txt";
71	const char RELPATH1[] = "other_file.txt";
72	uint64_t ino = 42;
73	struct stat sb_old, sb_new;
74	int fd1;
75
76	expect_lookup(RELPATH0, ino, 1, 2);
77	expect_lookup(RELPATH1, ino, 1, 2);
78	expect_open(ino, 0, 1);
79	expect_unlink(1, RELPATH0, 0);
80
81	fd1 = open(FULLPATH1, O_RDONLY);
82	ASSERT_LE(0, fd1) << strerror(errno);
83
84	ASSERT_EQ(0, fstat(fd1, &sb_old)) << strerror(errno);
85	ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
86	ASSERT_EQ(0, fstat(fd1, &sb_new)) << strerror(errno);
87	EXPECT_NE(sb_old.st_ctime, sb_new.st_ctime);
88	EXPECT_EQ(1u, sb_new.st_nlink);
89
90	leak(fd1);
91}
92
93/*
94 * A successful unlink should clear the parent directory's attribute cache,
95 * because the fuse daemon should update its mtime and ctime
96 */
97TEST_F(Unlink, parent_attr_cache)
98{
99	const char FULLPATH[] = "mountpoint/some_file.txt";
100	const char RELPATH[] = "some_file.txt";
101	struct stat sb;
102	uint64_t ino = 42;
103	Sequence seq;
104
105	/* Use nlink=2 so we don't get a FUSE_FORGET */
106	expect_lookup(RELPATH, ino, 1, 2);
107	EXPECT_CALL(*m_mock, process(
108		ResultOf([=](auto in) {
109			return (in.header.opcode == FUSE_UNLINK &&
110				0 == strcmp(RELPATH, in.body.unlink) &&
111				in.header.nodeid == FUSE_ROOT_ID);
112		}, Eq(true)),
113		_)
114	).InSequence(seq)
115	.WillOnce(Invoke(ReturnErrno(0)));
116	EXPECT_CALL(*m_mock, process(
117		ResultOf([=](auto in) {
118			return (in.header.opcode == FUSE_GETATTR &&
119				in.header.nodeid == FUSE_ROOT_ID);
120		}, Eq(true)),
121		_)
122	).InSequence(seq)
123	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
124		SET_OUT_HEADER_LEN(out, attr);
125		out.body.attr.attr.ino = FUSE_ROOT_ID;
126		out.body.attr.attr.mode = S_IFDIR | 0755;
127		out.body.attr.attr_valid = UINT64_MAX;
128	})));
129
130	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
131	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
132}
133
134TEST_F(Unlink, eperm)
135{
136	const char FULLPATH[] = "mountpoint/some_file.txt";
137	const char RELPATH[] = "some_file.txt";
138	uint64_t ino = 42;
139
140	expect_lookup(RELPATH, ino, 1);
141	expect_unlink(1, RELPATH, EPERM);
142
143	ASSERT_NE(0, unlink(FULLPATH));
144	ASSERT_EQ(EPERM, errno);
145}
146
147/*
148 * Unlinking a file should expire its entry cache, even if it's multiply linked
149 */
150TEST_F(Unlink, entry_cache)
151{
152	const char FULLPATH[] = "mountpoint/some_file.txt";
153	const char RELPATH[] = "some_file.txt";
154	uint64_t ino = 42;
155
156	expect_lookup(RELPATH, ino, 2, 2);
157	expect_unlink(1, RELPATH, 0);
158
159	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
160	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
161}
162
163/*
164 * Unlink a multiply-linked file.  There should be no FUSE_FORGET because the
165 * file is still linked.
166 */
167TEST_F(Unlink, multiply_linked)
168{
169	const char FULLPATH0[] = "mountpoint/some_file.txt";
170	const char RELPATH0[] = "some_file.txt";
171	const char FULLPATH1[] = "mountpoint/other_file.txt";
172	const char RELPATH1[] = "other_file.txt";
173	uint64_t ino = 42;
174
175	expect_lookup(RELPATH0, ino, 1, 2);
176	expect_unlink(1, RELPATH0, 0);
177	EXPECT_CALL(*m_mock, process(
178		ResultOf([=](auto in) {
179			return (in.header.opcode == FUSE_FORGET &&
180				in.header.nodeid == ino);
181		}, Eq(true)),
182		_)
183	).Times(0);
184	expect_lookup(RELPATH1, ino, 1, 1);
185
186	ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
187
188	/*
189	 * The final syscall simply ensures that no FUSE_FORGET was ever sent,
190	 * by scheduling an arbitrary different operation after a FUSE_FORGET
191	 * would've been sent.
192	 */
193	ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);
194}
195
196TEST_F(Unlink, ok)
197{
198	const char FULLPATH[] = "mountpoint/some_file.txt";
199	const char RELPATH[] = "some_file.txt";
200	uint64_t ino = 42;
201	sem_t sem;
202
203	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
204
205	expect_lookup(RELPATH, ino, 1);
206	expect_unlink(1, RELPATH, 0);
207	expect_forget(ino, 1, &sem);
208
209	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
210	sem_wait(&sem);
211	sem_destroy(&sem);
212}
213
214/* Unlink an open file */
215TEST_F(Unlink, open_but_deleted)
216{
217	const char FULLPATH0[] = "mountpoint/some_file.txt";
218	const char RELPATH0[] = "some_file.txt";
219	const char FULLPATH1[] = "mountpoint/other_file.txt";
220	const char RELPATH1[] = "other_file.txt";
221	uint64_t ino = 42;
222	int fd;
223
224	expect_lookup(RELPATH0, ino, 2);
225	expect_open(ino, 0, 1);
226	expect_unlink(1, RELPATH0, 0);
227	expect_lookup(RELPATH1, ino, 1, 1);
228
229	fd = open(FULLPATH0, O_RDWR);
230	ASSERT_LE(0, fd) << strerror(errno);
231	ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
232
233	/*
234	 * The final syscall simply ensures that no FUSE_FORGET was ever sent,
235	 * by scheduling an arbitrary different operation after a FUSE_FORGET
236	 * would've been sent.
237	 */
238	ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);
239	leak(fd);
240}
241