2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
41 using namespace testing;
43 class Rename: public FuseTest {
46 char tmpfile[80] = "/tmp/fuse.rename.XXXXXX";
48 virtual void TearDown() {
58 // EINVAL, dst is subdir of src
59 TEST_F(Rename, einval)
61 const char FULLDST[] = "mountpoint/src/dst";
62 const char RELDST[] = "dst";
63 const char FULLSRC[] = "mountpoint/src";
64 const char RELSRC[] = "src";
65 uint64_t src_ino = 42;
67 expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 0, 2);
68 EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
70 ASSERT_NE(0, rename(FULLSRC, FULLDST));
71 ASSERT_EQ(EINVAL, errno);
74 // source does not exist
75 TEST_F(Rename, enoent)
77 const char FULLDST[] = "mountpoint/dst";
78 const char FULLSRC[] = "mountpoint/src";
79 const char RELSRC[] = "src";
80 // FUSE hardcodes the mountpoint to inode 1
82 EXPECT_LOOKUP(FUSE_ROOT_ID, RELSRC)
83 .WillOnce(Invoke(ReturnErrno(ENOENT)));
85 ASSERT_NE(0, rename(FULLSRC, FULLDST));
86 ASSERT_EQ(ENOENT, errno);
90 * Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst
92 TEST_F(Rename, entry_cache_negative)
94 const char FULLDST[] = "mountpoint/dst";
95 const char RELDST[] = "dst";
96 const char FULLSRC[] = "mountpoint/src";
97 const char RELSRC[] = "src";
98 uint64_t dst_dir_ino = FUSE_ROOT_ID;
101 * Set entry_valid = 0 because this test isn't concerned with whether
102 * or not we actually cache negative entries, only with whether we
103 * interpret negative cache responses correctly.
105 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
107 expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
108 /* LOOKUP returns a negative cache entry for dst */
109 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
110 .WillOnce(ReturnNegativeCache(&entry_valid));
112 EXPECT_CALL(*m_mock, process(
113 ResultOf([=](auto in) {
114 const char *src = (const char*)in.body.bytes +
115 sizeof(fuse_rename_in);
116 const char *dst = src + strlen(src) + 1;
117 return (in.header.opcode == FUSE_RENAME &&
118 in.body.rename.newdir == dst_dir_ino &&
119 (0 == strcmp(RELDST, dst)) &&
120 (0 == strcmp(RELSRC, src)));
123 ).WillOnce(Invoke(ReturnErrno(0)));
125 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
129 * Renaming a file should purge any negative namecache entries for the dst
131 TEST_F(Rename, entry_cache_negative_purge)
133 const char FULLDST[] = "mountpoint/dst";
134 const char RELDST[] = "dst";
135 const char FULLSRC[] = "mountpoint/src";
136 const char RELSRC[] = "src";
137 uint64_t dst_dir_ino = FUSE_ROOT_ID;
139 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
141 expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
142 /* LOOKUP returns a negative cache entry for dst */
143 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
144 .WillOnce(ReturnNegativeCache(&entry_valid))
145 .RetiresOnSaturation();
147 EXPECT_CALL(*m_mock, process(
148 ResultOf([=](auto in) {
149 const char *src = (const char*)in.body.bytes +
150 sizeof(fuse_rename_in);
151 const char *dst = src + strlen(src) + 1;
152 return (in.header.opcode == FUSE_RENAME &&
153 in.body.rename.newdir == dst_dir_ino &&
154 (0 == strcmp(RELDST, dst)) &&
155 (0 == strcmp(RELSRC, src)));
158 ).WillOnce(Invoke(ReturnErrno(0)));
160 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
162 /* Finally, a subsequent lookup should query the daemon */
163 expect_lookup(RELDST, ino, S_IFREG | 0644, 0, 1);
165 ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno);
168 TEST_F(Rename, exdev)
170 const char FULLB[] = "mountpoint/src";
171 const char RELB[] = "src";
172 // FUSE hardcodes the mountpoint to inode 1
175 tmpfd = mkstemp(tmpfile);
176 ASSERT_LE(0, tmpfd) << strerror(errno);
178 expect_lookup(RELB, b_ino, S_IFREG | 0644, 0, 2);
180 ASSERT_NE(0, rename(tmpfile, FULLB));
181 ASSERT_EQ(EXDEV, errno);
183 ASSERT_NE(0, rename(FULLB, tmpfile));
184 ASSERT_EQ(EXDEV, errno);
189 const char FULLDST[] = "mountpoint/dst";
190 const char RELDST[] = "dst";
191 const char FULLSRC[] = "mountpoint/src";
192 const char RELSRC[] = "src";
193 uint64_t dst_dir_ino = FUSE_ROOT_ID;
196 expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
197 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
198 .WillOnce(Invoke(ReturnErrno(ENOENT)));
200 EXPECT_CALL(*m_mock, process(
201 ResultOf([=](auto in) {
202 const char *src = (const char*)in.body.bytes +
203 sizeof(fuse_rename_in);
204 const char *dst = src + strlen(src) + 1;
205 return (in.header.opcode == FUSE_RENAME &&
206 in.body.rename.newdir == dst_dir_ino &&
207 (0 == strcmp(RELDST, dst)) &&
208 (0 == strcmp(RELSRC, src)));
211 ).WillOnce(Invoke(ReturnErrno(0)));
213 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
216 /* When moving a file to a new directory, update its parent */
217 TEST_F(Rename, parent)
219 const char FULLDST[] = "mountpoint/dstdir/dst";
220 const char RELDSTDIR[] = "dstdir";
221 const char RELDST[] = "dst";
222 const char FULLSRC[] = "mountpoint/src";
223 const char RELSRC[] = "src";
224 const char FULLDSTPARENT[] = "mountpoint/dstdir";
225 const char FULLDSTDOTDOT[] = "mountpoint/dstdir/dst/..";
227 uint64_t dst_dir_ino = 43;
231 expect_lookup(RELSRC, ino, S_IFDIR | 0755, 0, 1);
232 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR)
233 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
234 SET_OUT_HEADER_LEN(out, entry);
235 out.body.entry.nodeid = dst_dir_ino;
236 out.body.entry.entry_valid = UINT64_MAX;
237 out.body.entry.attr_valid = UINT64_MAX;
238 out.body.entry.attr.mode = S_IFDIR | 0755;
239 out.body.entry.attr.ino = dst_dir_ino;
240 out.body.entry.attr.nlink = 2;
242 EXPECT_LOOKUP(dst_dir_ino, RELDST)
244 .WillOnce(Invoke(ReturnErrno(ENOENT)));
245 EXPECT_CALL(*m_mock, process(
246 ResultOf([=](auto in) {
247 const char *src = (const char*)in.body.bytes +
248 sizeof(fuse_rename_in);
249 const char *dst = src + strlen(src) + 1;
250 return (in.header.opcode == FUSE_RENAME &&
251 in.body.rename.newdir == dst_dir_ino &&
252 (0 == strcmp(RELDST, dst)) &&
253 (0 == strcmp(RELSRC, src)));
256 ).WillOnce(Invoke(ReturnErrno(0)));
257 EXPECT_CALL(*m_mock, process(
258 ResultOf([](auto in) {
259 return (in.header.opcode == FUSE_GETATTR &&
260 in.header.nodeid == 1);
264 .WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
265 SET_OUT_HEADER_LEN(out, attr);
266 out.body.attr.attr_valid = UINT64_MAX;
267 out.body.attr.attr.ino = 1;
268 out.body.attr.attr.mode = S_IFDIR | 0755;
269 out.body.attr.attr.nlink = 2;
271 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR)
273 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
274 SET_OUT_HEADER_LEN(out, entry);
275 out.body.entry.nodeid = dst_dir_ino;
276 out.body.entry.entry_valid = UINT64_MAX;
277 out.body.entry.attr_valid = UINT64_MAX;
278 out.body.entry.attr.mode = S_IFDIR | 0755;
279 out.body.entry.attr.ino = dst_dir_ino;
280 out.body.entry.attr.nlink = 3;
282 EXPECT_LOOKUP(dst_dir_ino, RELDST)
284 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
285 SET_OUT_HEADER_LEN(out, entry);
286 out.body.entry.attr.mode = S_IFDIR | 0755;
287 out.body.entry.nodeid = ino;
288 out.body.entry.entry_valid = UINT64_MAX;
289 out.body.entry.attr_valid = UINT64_MAX;
292 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
294 ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
295 EXPECT_EQ(2ul, sb.st_nlink);
297 ASSERT_EQ(0, stat(FULLDSTPARENT, &sb)) << strerror(errno);
298 EXPECT_EQ(3ul, sb.st_nlink);
300 ASSERT_EQ(0, stat(FULLDSTDOTDOT, &sb)) << strerror(errno);
301 ASSERT_EQ(dst_dir_ino, sb.st_ino);
304 // Rename overwrites an existing destination file
305 TEST_F(Rename, overwrite)
307 const char FULLDST[] = "mountpoint/dst";
308 const char RELDST[] = "dst";
309 const char FULLSRC[] = "mountpoint/src";
310 const char RELSRC[] = "src";
311 // The inode of the already-existing destination file
312 uint64_t dst_ino = 2;
313 uint64_t dst_dir_ino = FUSE_ROOT_ID;
316 expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
317 expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 0, 1);
318 EXPECT_CALL(*m_mock, process(
319 ResultOf([=](auto in) {
320 const char *src = (const char*)in.body.bytes +
321 sizeof(fuse_rename_in);
322 const char *dst = src + strlen(src) + 1;
323 return (in.header.opcode == FUSE_RENAME &&
324 in.body.rename.newdir == dst_dir_ino &&
325 (0 == strcmp(RELDST, dst)) &&
326 (0 == strcmp(RELSRC, src)));
329 ).WillOnce(Invoke(ReturnErrno(0)));
331 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);