2 * Copyright (c) 2019 The FreeBSD Foundation
5 * This software was developed by BFF Storage Systems, LLC under sponsorship
6 * from the FreeBSD Foundation.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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
34 #include <semaphore.h>
40 using namespace testing;
42 class Unlink: public FuseTest {
44 void expect_lookup(const char *relpath, uint64_t ino, int times, int nlink=1)
46 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
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;
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.
66 TEST_F(Unlink, attr_cache)
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";
73 struct stat sb_old, sb_new;
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);
81 fd1 = open(FULLPATH1, O_RDONLY);
82 ASSERT_LE(0, fd1) << strerror(errno);
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);
94 * A successful unlink should clear the parent directory's attribute cache,
95 * because the fuse daemon should update its mtime and ctime
97 TEST_F(Unlink, parent_attr_cache)
99 const char FULLPATH[] = "mountpoint/some_file.txt";
100 const char RELPATH[] = "some_file.txt";
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);
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);
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;
130 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
131 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
134 TEST_F(Unlink, eperm)
136 const char FULLPATH[] = "mountpoint/some_file.txt";
137 const char RELPATH[] = "some_file.txt";
140 expect_lookup(RELPATH, ino, 1);
141 expect_unlink(1, RELPATH, EPERM);
143 ASSERT_NE(0, unlink(FULLPATH));
144 ASSERT_EQ(EPERM, errno);
148 * Unlinking a file should expire its entry cache, even if it's multiply linked
150 TEST_F(Unlink, entry_cache)
152 const char FULLPATH[] = "mountpoint/some_file.txt";
153 const char RELPATH[] = "some_file.txt";
156 expect_lookup(RELPATH, ino, 2, 2);
157 expect_unlink(1, RELPATH, 0);
159 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
160 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
164 * Unlink a multiply-linked file. There should be no FUSE_FORGET because the
165 * file is still linked.
167 TEST_F(Unlink, multiply_linked)
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";
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);
184 expect_lookup(RELPATH1, ino, 1, 1);
186 ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
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.
193 ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);
198 const char FULLPATH[] = "mountpoint/some_file.txt";
199 const char RELPATH[] = "some_file.txt";
203 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
205 expect_lookup(RELPATH, ino, 1);
206 expect_unlink(1, RELPATH, 0);
207 expect_forget(ino, 1, &sem);
209 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
214 /* Unlink an open file */
215 TEST_F(Unlink, open_but_deleted)
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";
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);
229 fd = open(FULLPATH0, O_RDWR);
230 ASSERT_LE(0, fd) << strerror(errno);
231 ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
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.
238 ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);