xref: /freebsd/tests/sys/fs/fusefs/rmdir.cc (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
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  * $FreeBSD$
31  */
32 
33 extern "C" {
34 #include <fcntl.h>
35 #include <semaphore.h>
36 }
37 
38 #include "mockfs.hh"
39 #include "utils.hh"
40 
41 using namespace testing;
42 
43 class Rmdir: public FuseTest {
44 public:
45 void expect_getattr(uint64_t ino, mode_t mode)
46 {
47 	EXPECT_CALL(*m_mock, process(
48 		ResultOf([=](auto in) {
49 			return (in.header.opcode == FUSE_GETATTR &&
50 				in.header.nodeid == ino);
51 		}, Eq(true)),
52 		_)
53 	).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
54 		SET_OUT_HEADER_LEN(out, attr);
55 		out.body.attr.attr.ino = ino;	// Must match nodeid
56 		out.body.attr.attr.mode = mode;
57 		out.body.attr.attr_valid = UINT64_MAX;
58 	})));
59 }
60 
61 void expect_lookup(const char *relpath, uint64_t ino, int times=1)
62 {
63 	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
64 	.Times(times)
65 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
66 		SET_OUT_HEADER_LEN(out, entry);
67 		out.body.entry.attr_valid = UINT64_MAX;
68 		out.body.entry.attr.mode = S_IFDIR | 0755;
69 		out.body.entry.nodeid = ino;
70 		out.body.entry.attr.nlink = 2;
71 	})));
72 }
73 
74 void expect_rmdir(uint64_t parent, const char *relpath, int error)
75 {
76 	EXPECT_CALL(*m_mock, process(
77 		ResultOf([=](auto in) {
78 			return (in.header.opcode == FUSE_RMDIR &&
79 				0 == strcmp(relpath, in.body.rmdir) &&
80 				in.header.nodeid == parent);
81 		}, Eq(true)),
82 		_)
83 	).WillOnce(Invoke(ReturnErrno(error)));
84 }
85 };
86 
87 /*
88  * A successful rmdir should clear the parent directory's attribute cache,
89  * because the fuse daemon should update its mtime and ctime
90  */
91 TEST_F(Rmdir, parent_attr_cache)
92 {
93 	const char FULLPATH[] = "mountpoint/some_dir";
94 	const char RELPATH[] = "some_dir";
95 	struct stat sb;
96 	sem_t sem;
97 	uint64_t ino = 42;
98 
99 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
100 
101 	EXPECT_CALL(*m_mock, process(
102 		ResultOf([=](auto in) {
103 			return (in.header.opcode == FUSE_GETATTR &&
104 				in.header.nodeid == FUSE_ROOT_ID);
105 		}, Eq(true)),
106 		_)
107 	).Times(2)
108 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
109 		SET_OUT_HEADER_LEN(out, attr);
110 		out.body.attr.attr.ino = ino;	// Must match nodeid
111 		out.body.attr.attr.mode = S_IFDIR | 0755;
112 		out.body.attr.attr_valid = UINT64_MAX;
113 	})));
114 	expect_lookup(RELPATH, ino);
115 	expect_rmdir(FUSE_ROOT_ID, RELPATH, 0);
116 	expect_forget(ino, 1, &sem);
117 
118 	ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno);
119 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
120 	sem_wait(&sem);
121 	sem_destroy(&sem);
122 }
123 
124 TEST_F(Rmdir, enotempty)
125 {
126 	const char FULLPATH[] = "mountpoint/some_dir";
127 	const char RELPATH[] = "some_dir";
128 	uint64_t ino = 42;
129 
130 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755);
131 	expect_lookup(RELPATH, ino);
132 	expect_rmdir(FUSE_ROOT_ID, RELPATH, ENOTEMPTY);
133 
134 	ASSERT_NE(0, rmdir(FULLPATH));
135 	ASSERT_EQ(ENOTEMPTY, errno);
136 }
137 
138 /* Removing a directory should expire its entry cache */
139 TEST_F(Rmdir, entry_cache)
140 {
141 	const char FULLPATH[] = "mountpoint/some_dir";
142 	const char RELPATH[] = "some_dir";
143 	sem_t sem;
144 	uint64_t ino = 42;
145 
146 	expect_getattr(1, S_IFDIR | 0755);
147 	expect_lookup(RELPATH, ino, 2);
148 	expect_rmdir(FUSE_ROOT_ID, RELPATH, 0);
149 	expect_forget(ino, 1, &sem);
150 
151 	ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno);
152 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
153 	sem_wait(&sem);
154 	sem_destroy(&sem);
155 }
156 
157 TEST_F(Rmdir, ok)
158 {
159 	const char FULLPATH[] = "mountpoint/some_dir";
160 	const char RELPATH[] = "some_dir";
161 	sem_t sem;
162 	uint64_t ino = 42;
163 
164 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
165 
166 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755);
167 	expect_lookup(RELPATH, ino);
168 	expect_rmdir(FUSE_ROOT_ID, RELPATH, 0);
169 	expect_forget(ino, 1, &sem);
170 
171 	ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno);
172 	sem_wait(&sem);
173 	sem_destroy(&sem);
174 }
175