]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/rename.cc
Fix issues with FUSE_ACCESS when default_permissions is disabled
[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  * $FreeBSD$
31  */
32
33 extern "C" {
34 #include <stdlib.h>
35 #include <unistd.h>
36 }
37
38 #include "mockfs.hh"
39 #include "utils.hh"
40
41 using namespace testing;
42
43 class Rename: public FuseTest {
44         public:
45         int tmpfd = -1;
46         char tmpfile[80] = "/tmp/fuse.rename.XXXXXX";
47
48         virtual void TearDown() {
49                 if (tmpfd >= 0) {
50                         close(tmpfd);
51                         unlink(tmpfile);
52                 }
53
54                 FuseTest::TearDown();
55         }
56 };
57
58 // EINVAL, dst is subdir of src
59 TEST_F(Rename, einval)
60 {
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;
66
67         expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 0, 2);
68         EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
69
70         ASSERT_NE(0, rename(FULLSRC, FULLDST));
71         ASSERT_EQ(EINVAL, errno);
72 }
73
74 // source does not exist
75 TEST_F(Rename, enoent)
76 {
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
81
82         EXPECT_LOOKUP(FUSE_ROOT_ID, RELSRC)
83         .WillOnce(Invoke(ReturnErrno(ENOENT)));
84
85         ASSERT_NE(0, rename(FULLSRC, FULLDST));
86         ASSERT_EQ(ENOENT, errno);
87 }
88
89 /*
90  * Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst
91  */
92 TEST_F(Rename, entry_cache_negative)
93 {
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;
99         uint64_t ino = 42;
100         /* 
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.
104          */
105         struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
106
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));
111
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)));
121                 }, Eq(true)),
122                 _)
123         ).WillOnce(Invoke(ReturnErrno(0)));
124
125         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
126 }
127
128 /*
129  * Renaming a file should purge any negative namecache entries for the dst
130  */
131 TEST_F(Rename, entry_cache_negative_purge)
132 {
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;
138         uint64_t ino = 42;
139         struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
140
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();
146
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)));
156                 }, Eq(true)),
157                 _)
158         ).WillOnce(Invoke(ReturnErrno(0)));
159
160         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
161
162         /* Finally, a subsequent lookup should query the daemon */
163         expect_lookup(RELDST, ino, S_IFREG | 0644, 0, 1);
164
165         ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno);
166 }
167
168 TEST_F(Rename, exdev)
169 {
170         const char FULLB[] = "mountpoint/src";
171         const char RELB[] = "src";
172         // FUSE hardcodes the mountpoint to inode 1
173         uint64_t b_ino = 42;
174
175         tmpfd = mkstemp(tmpfile);
176         ASSERT_LE(0, tmpfd) << strerror(errno);
177
178         expect_lookup(RELB, b_ino, S_IFREG | 0644, 0, 2);
179
180         ASSERT_NE(0, rename(tmpfile, FULLB));
181         ASSERT_EQ(EXDEV, errno);
182
183         ASSERT_NE(0, rename(FULLB, tmpfile));
184         ASSERT_EQ(EXDEV, errno);
185 }
186
187 TEST_F(Rename, ok)
188 {
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;
194         uint64_t ino = 42;
195
196         expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
197         EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
198         .WillOnce(Invoke(ReturnErrno(ENOENT)));
199
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)));
209                 }, Eq(true)),
210                 _)
211         ).WillOnce(Invoke(ReturnErrno(0)));
212
213         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
214 }
215
216 /* When moving a file to a new directory, update its parent */
217 TEST_F(Rename, parent)
218 {
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/dst/..";
225         Sequence seq;
226         uint64_t dst_dir_ino = 43;
227         uint64_t ino = 42;
228         struct stat sb;
229
230         expect_lookup(RELSRC, ino, S_IFDIR | 0755, 0, 1);
231         EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR)
232         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
233                 SET_OUT_HEADER_LEN(out, entry);
234                 out.body.entry.nodeid = dst_dir_ino;
235                 out.body.entry.entry_valid = UINT64_MAX;
236                 out.body.entry.attr_valid = UINT64_MAX;
237                 out.body.entry.attr.mode = S_IFDIR | 0755;
238                 out.body.entry.attr.ino = dst_dir_ino;
239         })));
240         EXPECT_LOOKUP(dst_dir_ino, RELDST)
241         .InSequence(seq)
242         .WillOnce(Invoke(ReturnErrno(ENOENT)));
243         EXPECT_CALL(*m_mock, process(
244                 ResultOf([=](auto in) {
245                         const char *src = (const char*)in.body.bytes +
246                                 sizeof(fuse_rename_in);
247                         const char *dst = src + strlen(src) + 1;
248                         return (in.header.opcode == FUSE_RENAME &&
249                                 in.body.rename.newdir == dst_dir_ino &&
250                                 (0 == strcmp(RELDST, dst)) &&
251                                 (0 == strcmp(RELSRC, src)));
252                 }, Eq(true)),
253                 _)
254         ).WillOnce(Invoke(ReturnErrno(0)));
255         EXPECT_LOOKUP(dst_dir_ino, RELDST)
256         .InSequence(seq)
257         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
258                 SET_OUT_HEADER_LEN(out, entry);
259                 out.body.entry.attr.mode = S_IFDIR | 0755;
260                 out.body.entry.nodeid = ino;
261                 out.body.entry.entry_valid = UINT64_MAX;
262                 out.body.entry.attr_valid = UINT64_MAX;
263         })));
264
265         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
266         ASSERT_EQ(0, stat(FULLDSTPARENT, &sb)) << strerror(errno);
267         ASSERT_EQ(dst_dir_ino, sb.st_ino);
268 }
269
270 // Rename overwrites an existing destination file
271 TEST_F(Rename, overwrite)
272 {
273         const char FULLDST[] = "mountpoint/dst";
274         const char RELDST[] = "dst";
275         const char FULLSRC[] = "mountpoint/src";
276         const char RELSRC[] = "src";
277         // The inode of the already-existing destination file
278         uint64_t dst_ino = 2;
279         uint64_t dst_dir_ino = FUSE_ROOT_ID;
280         uint64_t ino = 42;
281
282         expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
283         expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 0, 1);
284         EXPECT_CALL(*m_mock, process(
285                 ResultOf([=](auto in) {
286                         const char *src = (const char*)in.body.bytes +
287                                 sizeof(fuse_rename_in);
288                         const char *dst = src + strlen(src) + 1;
289                         return (in.header.opcode == FUSE_RENAME &&
290                                 in.body.rename.newdir == dst_dir_ino &&
291                                 (0 == strcmp(RELDST, dst)) &&
292                                 (0 == strcmp(RELSRC, src)));
293                 }, Eq(true)),
294                 _)
295         ).WillOnce(Invoke(ReturnErrno(0)));
296
297         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
298 }