]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/rename.cc
fusefs: clear a dir's attr cache when its contents change
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / rename.cc
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 <stdlib.h>
33 #include <unistd.h>
34 }
35
36 #include "mockfs.hh"
37 #include "utils.hh"
38
39 using namespace testing;
40
41 class Rename: public FuseTest {
42         public:
43         int tmpfd = -1;
44         char tmpfile[80] = "/tmp/fuse.rename.XXXXXX";
45
46         virtual void TearDown() {
47                 if (tmpfd >= 0) {
48                         close(tmpfd);
49                         unlink(tmpfile);
50                 }
51
52                 FuseTest::TearDown();
53         }
54
55         void expect_getattr(uint64_t ino, mode_t mode)
56         {
57                 EXPECT_CALL(*m_mock, process(
58                         ResultOf([=](auto in) {
59                                 return (in->header.opcode == FUSE_GETATTR &&
60                                         in->header.nodeid == ino);
61                         }, Eq(true)),
62                         _)
63                 ).WillOnce(Invoke(
64                         ReturnImmediate([=](auto i __unused, auto out) {
65                         SET_OUT_HEADER_LEN(out, attr);
66                         out->body.attr.attr.ino = ino;  // Must match nodeid
67                         out->body.attr.attr.mode = mode;
68                         out->body.attr.attr_valid = UINT64_MAX;
69                 })));
70         }
71
72 };
73
74 // EINVAL, dst is subdir of src
75 TEST_F(Rename, einval)
76 {
77         const char FULLDST[] = "mountpoint/src/dst";
78         const char RELDST[] = "dst";
79         const char FULLSRC[] = "mountpoint/src";
80         const char RELSRC[] = "src";
81         uint64_t src_ino = 42;
82
83         expect_getattr(1, S_IFDIR | 0755);
84         expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 0, 2);
85         EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
86
87         ASSERT_NE(0, rename(FULLSRC, FULLDST));
88         ASSERT_EQ(EINVAL, errno);
89 }
90
91 // source does not exist
92 TEST_F(Rename, enoent)
93 {
94         const char FULLDST[] = "mountpoint/dst";
95         const char FULLSRC[] = "mountpoint/src";
96         const char RELSRC[] = "src";
97         // FUSE hardcodes the mountpoint to inode 1
98
99         EXPECT_LOOKUP(1, RELSRC).WillOnce(Invoke(ReturnErrno(ENOENT)));
100
101         ASSERT_NE(0, rename(FULLSRC, FULLDST));
102         ASSERT_EQ(ENOENT, errno);
103 }
104
105 /*
106  * Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst
107  */
108 TEST_F(Rename, entry_cache_negative)
109 {
110         const char FULLDST[] = "mountpoint/dst";
111         const char RELDST[] = "dst";
112         const char FULLSRC[] = "mountpoint/src";
113         const char RELSRC[] = "src";
114         // FUSE hardcodes the mountpoint to inode 1
115         uint64_t dst_dir_ino = 1;
116         uint64_t ino = 42;
117         /* 
118          * Set entry_valid = 0 because this test isn't concerned with whether
119          * or not we actually cache negative entries, only with whether we
120          * interpret negative cache responses correctly.
121          */
122         struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
123
124         expect_getattr(1, S_IFDIR | 0755);
125         expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
126         /* LOOKUP returns a negative cache entry for dst */
127         EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid));
128
129         EXPECT_CALL(*m_mock, process(
130                 ResultOf([=](auto in) {
131                         const char *src = (const char*)in->body.bytes +
132                                 sizeof(fuse_rename_in);
133                         const char *dst = src + strlen(src) + 1;
134                         return (in->header.opcode == FUSE_RENAME &&
135                                 in->body.rename.newdir == dst_dir_ino &&
136                                 (0 == strcmp(RELDST, dst)) &&
137                                 (0 == strcmp(RELSRC, src)));
138                 }, Eq(true)),
139                 _)
140         ).WillOnce(Invoke(ReturnErrno(0)));
141
142         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
143 }
144
145 /*
146  * Renaming a file should purge any negative namecache entries for the dst
147  */
148 TEST_F(Rename, entry_cache_negative_purge)
149 {
150         const char FULLDST[] = "mountpoint/dst";
151         const char RELDST[] = "dst";
152         const char FULLSRC[] = "mountpoint/src";
153         const char RELSRC[] = "src";
154         // FUSE hardcodes the mountpoint to inode 1
155         uint64_t dst_dir_ino = 1;
156         uint64_t ino = 42;
157         struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
158
159         expect_getattr(1, S_IFDIR | 0755);
160         expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
161         /* LOOKUP returns a negative cache entry for dst */
162         EXPECT_LOOKUP(1, RELDST).WillOnce(ReturnNegativeCache(&entry_valid))
163         .RetiresOnSaturation();
164
165         EXPECT_CALL(*m_mock, process(
166                 ResultOf([=](auto in) {
167                         const char *src = (const char*)in->body.bytes +
168                                 sizeof(fuse_rename_in);
169                         const char *dst = src + strlen(src) + 1;
170                         return (in->header.opcode == FUSE_RENAME &&
171                                 in->body.rename.newdir == dst_dir_ino &&
172                                 (0 == strcmp(RELDST, dst)) &&
173                                 (0 == strcmp(RELSRC, src)));
174                 }, Eq(true)),
175                 _)
176         ).WillOnce(Invoke(ReturnErrno(0)));
177
178         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
179
180         /* Finally, a subsequent lookup should query the daemon */
181         expect_lookup(RELDST, ino, S_IFREG | 0644, 0, 1);
182
183         ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno);
184 }
185
186 TEST_F(Rename, exdev)
187 {
188         const char FULLB[] = "mountpoint/src";
189         const char RELB[] = "src";
190         // FUSE hardcodes the mountpoint to inode 1
191         uint64_t b_ino = 42;
192
193         tmpfd = mkstemp(tmpfile);
194         ASSERT_LE(0, tmpfd) << strerror(errno);
195
196         expect_getattr(1, S_IFDIR | 0755);
197         expect_lookup(RELB, b_ino, S_IFREG | 0644, 0, 2);
198
199         ASSERT_NE(0, rename(tmpfile, FULLB));
200         ASSERT_EQ(EXDEV, errno);
201
202         ASSERT_NE(0, rename(FULLB, tmpfile));
203         ASSERT_EQ(EXDEV, errno);
204 }
205
206 TEST_F(Rename, ok)
207 {
208         const char FULLDST[] = "mountpoint/dst";
209         const char RELDST[] = "dst";
210         const char FULLSRC[] = "mountpoint/src";
211         const char RELSRC[] = "src";
212         // FUSE hardcodes the mountpoint to inode 1
213         uint64_t dst_dir_ino = 1;
214         uint64_t ino = 42;
215
216         expect_getattr(1, S_IFDIR | 0755);
217         expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
218         EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
219
220         EXPECT_CALL(*m_mock, process(
221                 ResultOf([=](auto in) {
222                         const char *src = (const char*)in->body.bytes +
223                                 sizeof(fuse_rename_in);
224                         const char *dst = src + strlen(src) + 1;
225                         return (in->header.opcode == FUSE_RENAME &&
226                                 in->body.rename.newdir == dst_dir_ino &&
227                                 (0 == strcmp(RELDST, dst)) &&
228                                 (0 == strcmp(RELSRC, src)));
229                 }, Eq(true)),
230                 _)
231         ).WillOnce(Invoke(ReturnErrno(0)));
232
233         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
234 }
235
236 /* When moving a file to a new directory, update its parent */
237 TEST_F(Rename, parent)
238 {
239         const char FULLDST[] = "mountpoint/dstdir/dst";
240         const char RELDSTDIR[] = "dstdir";
241         const char RELDST[] = "dst";
242         const char FULLSRC[] = "mountpoint/src";
243         const char RELSRC[] = "src";
244         const char FULLDSTPARENT[] = "mountpoint/dstdir/dst/..";
245         Sequence seq;
246         uint64_t dst_dir_ino = 43;
247         uint64_t ino = 42;
248         struct stat sb;
249
250         expect_lookup(RELSRC, ino, S_IFDIR | 0755, 0, 1);
251         expect_getattr(1, S_IFDIR | 0755);
252         EXPECT_LOOKUP(1, RELDSTDIR)
253         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
254                 SET_OUT_HEADER_LEN(out, entry);
255                 out->body.entry.nodeid = dst_dir_ino;
256                 out->body.entry.entry_valid = UINT64_MAX;
257                 out->body.entry.attr_valid = UINT64_MAX;
258                 out->body.entry.attr.mode = S_IFDIR | 0755;
259                 out->body.entry.attr.ino = dst_dir_ino;
260         })));
261         EXPECT_LOOKUP(dst_dir_ino, RELDST)
262         .InSequence(seq)
263         .WillOnce(Invoke(ReturnErrno(ENOENT)));
264         EXPECT_CALL(*m_mock, process(
265                 ResultOf([=](auto in) {
266                         const char *src = (const char*)in->body.bytes +
267                                 sizeof(fuse_rename_in);
268                         const char *dst = src + strlen(src) + 1;
269                         return (in->header.opcode == FUSE_RENAME &&
270                                 in->body.rename.newdir == dst_dir_ino &&
271                                 (0 == strcmp(RELDST, dst)) &&
272                                 (0 == strcmp(RELSRC, src)));
273                 }, Eq(true)),
274                 _)
275         ).WillOnce(Invoke(ReturnErrno(0)));
276         EXPECT_LOOKUP(dst_dir_ino, RELDST)
277         .InSequence(seq)
278         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
279                 SET_OUT_HEADER_LEN(out, entry);
280                 out->body.entry.attr.mode = S_IFDIR | 0755;
281                 out->body.entry.nodeid = ino;
282                 out->body.entry.entry_valid = UINT64_MAX;
283                 out->body.entry.attr_valid = UINT64_MAX;
284         })));
285
286         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
287         ASSERT_EQ(0, stat(FULLDSTPARENT, &sb)) << strerror(errno);
288         ASSERT_EQ(dst_dir_ino, sb.st_ino);
289 }
290
291 // Rename overwrites an existing destination file
292 TEST_F(Rename, overwrite)
293 {
294         const char FULLDST[] = "mountpoint/dst";
295         const char RELDST[] = "dst";
296         const char FULLSRC[] = "mountpoint/src";
297         const char RELSRC[] = "src";
298         // The inode of the already-existing destination file
299         uint64_t dst_ino = 2;
300         // FUSE hardcodes the mountpoint to inode 1
301         uint64_t dst_dir_ino = 1;
302         uint64_t ino = 42;
303
304         expect_getattr(1, S_IFDIR | 0755);
305         expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
306         expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 0, 1);
307         EXPECT_CALL(*m_mock, process(
308                 ResultOf([=](auto in) {
309                         const char *src = (const char*)in->body.bytes +
310                                 sizeof(fuse_rename_in);
311                         const char *dst = src + strlen(src) + 1;
312                         return (in->header.opcode == FUSE_RENAME &&
313                                 in->body.rename.newdir == dst_dir_ino &&
314                                 (0 == strcmp(RELDST, dst)) &&
315                                 (0 == strcmp(RELSRC, src)));
316                 }, Eq(true)),
317                 _)
318         ).WillOnce(Invoke(ReturnErrno(0)));
319
320         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
321 }