xref: /freebsd/tests/sys/fs/fusefs/rmdir.cc (revision 0bf48626aaa33768078f5872b922b1487b3a9296)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 extern "C" {
32 #include <fcntl.h>
33 #include <semaphore.h>
34 }
35 
36 #include "mockfs.hh"
37 #include "utils.hh"
38 
39 using namespace testing;
40 
41 class Rmdir: public FuseTest {
42 public:
43 void expect_getattr(uint64_t ino, mode_t mode)
44 {
45 	EXPECT_CALL(*m_mock, process(
46 		ResultOf([=](auto in) {
47 			return (in.header.opcode == FUSE_GETATTR &&
48 				in.header.nodeid == ino);
49 		}, Eq(true)),
50 		_)
51 	).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
52 		SET_OUT_HEADER_LEN(out, attr);
53 		out.body.attr.attr.ino = ino;	// Must match nodeid
54 		out.body.attr.attr.mode = mode;
55 		out.body.attr.attr_valid = UINT64_MAX;
56 	})));
57 }
58 
59 void expect_lookup(const char *relpath, uint64_t ino, int times=1)
60 {
61 	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
62 	.Times(times)
63 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
64 		SET_OUT_HEADER_LEN(out, entry);
65 		out.body.entry.attr_valid = UINT64_MAX;
66 		out.body.entry.attr.mode = S_IFDIR | 0755;
67 		out.body.entry.nodeid = ino;
68 		out.body.entry.attr.nlink = 2;
69 	})));
70 }
71 
72 void expect_rmdir(uint64_t parent, const char *relpath, int error)
73 {
74 	EXPECT_CALL(*m_mock, process(
75 		ResultOf([=](auto in) {
76 			return (in.header.opcode == FUSE_RMDIR &&
77 				0 == strcmp(relpath, in.body.rmdir) &&
78 				in.header.nodeid == parent);
79 		}, Eq(true)),
80 		_)
81 	).WillOnce(Invoke(ReturnErrno(error)));
82 }
83 };
84 
85 /*
86  * A successful rmdir should clear the parent directory's attribute cache,
87  * because the fuse daemon should update its mtime and ctime
88  */
89 TEST_F(Rmdir, parent_attr_cache)
90 {
91 	const char FULLPATH[] = "mountpoint/some_dir";
92 	const char RELPATH[] = "some_dir";
93 	struct stat sb;
94 	sem_t sem;
95 	uint64_t ino = 42;
96 
97 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
98 
99 	EXPECT_CALL(*m_mock, process(
100 		ResultOf([=](auto in) {
101 			return (in.header.opcode == FUSE_GETATTR &&
102 				in.header.nodeid == FUSE_ROOT_ID);
103 		}, Eq(true)),
104 		_)
105 	).Times(2)
106 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
107 		SET_OUT_HEADER_LEN(out, attr);
108 		out.body.attr.attr.ino = ino;	// Must match nodeid
109 		out.body.attr.attr.mode = S_IFDIR | 0755;
110 		out.body.attr.attr_valid = UINT64_MAX;
111 	})));
112 	expect_lookup(RELPATH, ino);
113 	expect_rmdir(FUSE_ROOT_ID, RELPATH, 0);
114 	expect_forget(ino, 1, &sem);
115 
116 	ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno);
117 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
118 	sem_wait(&sem);
119 	sem_destroy(&sem);
120 }
121 
122 TEST_F(Rmdir, enotempty)
123 {
124 	const char FULLPATH[] = "mountpoint/some_dir";
125 	const char RELPATH[] = "some_dir";
126 	uint64_t ino = 42;
127 
128 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755);
129 	expect_lookup(RELPATH, ino);
130 	expect_rmdir(FUSE_ROOT_ID, RELPATH, ENOTEMPTY);
131 
132 	ASSERT_NE(0, rmdir(FULLPATH));
133 	ASSERT_EQ(ENOTEMPTY, errno);
134 }
135 
136 /* Removing a directory should expire its entry cache */
137 TEST_F(Rmdir, entry_cache)
138 {
139 	const char FULLPATH[] = "mountpoint/some_dir";
140 	const char RELPATH[] = "some_dir";
141 	sem_t sem;
142 	uint64_t ino = 42;
143 
144 	expect_getattr(1, S_IFDIR | 0755);
145 	expect_lookup(RELPATH, ino, 2);
146 	expect_rmdir(FUSE_ROOT_ID, RELPATH, 0);
147 	expect_forget(ino, 1, &sem);
148 
149 	ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno);
150 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
151 	sem_wait(&sem);
152 	sem_destroy(&sem);
153 }
154 
155 TEST_F(Rmdir, ok)
156 {
157 	const char FULLPATH[] = "mountpoint/some_dir";
158 	const char RELPATH[] = "some_dir";
159 	sem_t sem;
160 	uint64_t ino = 42;
161 
162 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
163 
164 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755);
165 	expect_lookup(RELPATH, ino);
166 	expect_rmdir(FUSE_ROOT_ID, RELPATH, 0);
167 	expect_forget(ino, 1, &sem);
168 
169 	ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno);
170 	sem_wait(&sem);
171 	sem_destroy(&sem);
172 }
173