Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/rename.cc
39537 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
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
56
// EINVAL, dst is subdir of src
57
TEST_F(Rename, einval)
58
{
59
const char FULLDST[] = "mountpoint/src/dst";
60
const char RELDST[] = "dst";
61
const char FULLSRC[] = "mountpoint/src";
62
const char RELSRC[] = "src";
63
uint64_t src_ino = 42;
64
65
expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 0, 2);
66
EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
67
68
ASSERT_NE(0, rename(FULLSRC, FULLDST));
69
ASSERT_EQ(EINVAL, errno);
70
}
71
72
// source does not exist
73
TEST_F(Rename, enoent)
74
{
75
const char FULLDST[] = "mountpoint/dst";
76
const char FULLSRC[] = "mountpoint/src";
77
const char RELSRC[] = "src";
78
// FUSE hardcodes the mountpoint to inode 1
79
80
EXPECT_LOOKUP(FUSE_ROOT_ID, RELSRC)
81
.WillOnce(Invoke(ReturnErrno(ENOENT)));
82
83
ASSERT_NE(0, rename(FULLSRC, FULLDST));
84
ASSERT_EQ(ENOENT, errno);
85
}
86
87
/*
88
* Renaming a file after FUSE_LOOKUP returned a negative cache entry for dst
89
*/
90
TEST_F(Rename, entry_cache_negative)
91
{
92
const char FULLDST[] = "mountpoint/dst";
93
const char RELDST[] = "dst";
94
const char FULLSRC[] = "mountpoint/src";
95
const char RELSRC[] = "src";
96
uint64_t dst_dir_ino = FUSE_ROOT_ID;
97
uint64_t ino = 42;
98
/*
99
* Set entry_valid = 0 because this test isn't concerned with whether
100
* or not we actually cache negative entries, only with whether we
101
* interpret negative cache responses correctly.
102
*/
103
struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
104
105
expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
106
/* LOOKUP returns a negative cache entry for dst */
107
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
108
.WillOnce(ReturnNegativeCache(&entry_valid));
109
110
EXPECT_CALL(*m_mock, process(
111
ResultOf([=](auto in) {
112
const char *src = (const char*)in.body.bytes +
113
sizeof(fuse_rename_in);
114
const char *dst = src + strlen(src) + 1;
115
return (in.header.opcode == FUSE_RENAME &&
116
in.body.rename.newdir == dst_dir_ino &&
117
(0 == strcmp(RELDST, dst)) &&
118
(0 == strcmp(RELSRC, src)));
119
}, Eq(true)),
120
_)
121
).WillOnce(Invoke(ReturnErrno(0)));
122
123
ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
124
}
125
126
/*
127
* Renaming a file should purge any negative namecache entries for the dst
128
*/
129
TEST_F(Rename, entry_cache_negative_purge)
130
{
131
const char FULLDST[] = "mountpoint/dst";
132
const char RELDST[] = "dst";
133
const char FULLSRC[] = "mountpoint/src";
134
const char RELSRC[] = "src";
135
uint64_t dst_dir_ino = FUSE_ROOT_ID;
136
uint64_t ino = 42;
137
struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
138
139
expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
140
/* LOOKUP returns a negative cache entry for dst */
141
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
142
.WillOnce(ReturnNegativeCache(&entry_valid))
143
.RetiresOnSaturation();
144
145
EXPECT_CALL(*m_mock, process(
146
ResultOf([=](auto in) {
147
const char *src = (const char*)in.body.bytes +
148
sizeof(fuse_rename_in);
149
const char *dst = src + strlen(src) + 1;
150
return (in.header.opcode == FUSE_RENAME &&
151
in.body.rename.newdir == dst_dir_ino &&
152
(0 == strcmp(RELDST, dst)) &&
153
(0 == strcmp(RELSRC, src)));
154
}, Eq(true)),
155
_)
156
).WillOnce(Invoke(ReturnErrno(0)));
157
158
ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
159
160
/* Finally, a subsequent lookup should query the daemon */
161
expect_lookup(RELDST, ino, S_IFREG | 0644, 0, 1);
162
163
ASSERT_EQ(0, access(FULLDST, F_OK)) << strerror(errno);
164
}
165
166
TEST_F(Rename, exdev)
167
{
168
const char FULLB[] = "mountpoint/src";
169
const char RELB[] = "src";
170
// FUSE hardcodes the mountpoint to inode 1
171
uint64_t b_ino = 42;
172
173
tmpfd = mkstemp(tmpfile);
174
ASSERT_LE(0, tmpfd) << strerror(errno);
175
176
expect_lookup(RELB, b_ino, S_IFREG | 0644, 0, 2);
177
178
ASSERT_NE(0, rename(tmpfile, FULLB));
179
ASSERT_EQ(EXDEV, errno);
180
181
ASSERT_NE(0, rename(FULLB, tmpfile));
182
ASSERT_EQ(EXDEV, errno);
183
}
184
185
TEST_F(Rename, ok)
186
{
187
const char FULLDST[] = "mountpoint/dst";
188
const char RELDST[] = "dst";
189
const char FULLSRC[] = "mountpoint/src";
190
const char RELSRC[] = "src";
191
uint64_t dst_dir_ino = FUSE_ROOT_ID;
192
uint64_t ino = 42;
193
194
expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
195
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
196
.WillOnce(Invoke(ReturnErrno(ENOENT)));
197
198
EXPECT_CALL(*m_mock, process(
199
ResultOf([=](auto in) {
200
const char *src = (const char*)in.body.bytes +
201
sizeof(fuse_rename_in);
202
const char *dst = src + strlen(src) + 1;
203
return (in.header.opcode == FUSE_RENAME &&
204
in.body.rename.newdir == dst_dir_ino &&
205
(0 == strcmp(RELDST, dst)) &&
206
(0 == strcmp(RELSRC, src)));
207
}, Eq(true)),
208
_)
209
).WillOnce(Invoke(ReturnErrno(0)));
210
211
ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
212
}
213
214
/* When moving a file to a new directory, update its parent */
215
TEST_F(Rename, parent)
216
{
217
const char FULLDST[] = "mountpoint/dstdir/dst";
218
const char RELDSTDIR[] = "dstdir";
219
const char RELDST[] = "dst";
220
const char FULLSRC[] = "mountpoint/src";
221
const char RELSRC[] = "src";
222
const char FULLDSTPARENT[] = "mountpoint/dstdir";
223
const char FULLDSTDOTDOT[] = "mountpoint/dstdir/dst/..";
224
Sequence seq;
225
uint64_t dst_dir_ino = 43;
226
uint64_t ino = 42;
227
struct stat sb;
228
229
expect_lookup(RELSRC, ino, S_IFDIR | 0755, 0, 1);
230
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR)
231
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
232
SET_OUT_HEADER_LEN(out, entry);
233
out.body.entry.nodeid = dst_dir_ino;
234
out.body.entry.entry_valid = UINT64_MAX;
235
out.body.entry.attr_valid = UINT64_MAX;
236
out.body.entry.attr.mode = S_IFDIR | 0755;
237
out.body.entry.attr.ino = dst_dir_ino;
238
out.body.entry.attr.nlink = 2;
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_CALL(*m_mock, process(
256
ResultOf([](auto in) {
257
return (in.header.opcode == FUSE_GETATTR &&
258
in.header.nodeid == 1);
259
}, Eq(true)),
260
_)
261
).InSequence(seq)
262
.WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
263
SET_OUT_HEADER_LEN(out, attr);
264
out.body.attr.attr_valid = UINT64_MAX;
265
out.body.attr.attr.ino = 1;
266
out.body.attr.attr.mode = S_IFDIR | 0755;
267
out.body.attr.attr.nlink = 2;
268
})));
269
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR)
270
.InSequence(seq)
271
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
272
SET_OUT_HEADER_LEN(out, entry);
273
out.body.entry.nodeid = dst_dir_ino;
274
out.body.entry.entry_valid = UINT64_MAX;
275
out.body.entry.attr_valid = UINT64_MAX;
276
out.body.entry.attr.mode = S_IFDIR | 0755;
277
out.body.entry.attr.ino = dst_dir_ino;
278
out.body.entry.attr.nlink = 3;
279
})));
280
EXPECT_LOOKUP(dst_dir_ino, RELDST)
281
.InSequence(seq)
282
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
283
SET_OUT_HEADER_LEN(out, entry);
284
out.body.entry.attr.mode = S_IFDIR | 0755;
285
out.body.entry.nodeid = ino;
286
out.body.entry.entry_valid = UINT64_MAX;
287
out.body.entry.attr_valid = UINT64_MAX;
288
})));
289
290
ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
291
292
ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
293
EXPECT_EQ(2ul, sb.st_nlink);
294
295
ASSERT_EQ(0, stat(FULLDSTPARENT, &sb)) << strerror(errno);
296
EXPECT_EQ(3ul, sb.st_nlink);
297
298
ASSERT_EQ(0, stat(FULLDSTDOTDOT, &sb)) << strerror(errno);
299
ASSERT_EQ(dst_dir_ino, sb.st_ino);
300
}
301
302
// Rename overwrites an existing destination file
303
TEST_F(Rename, overwrite)
304
{
305
const char FULLDST[] = "mountpoint/dst";
306
const char RELDST[] = "dst";
307
const char FULLSRC[] = "mountpoint/src";
308
const char RELSRC[] = "src";
309
// The inode of the already-existing destination file
310
uint64_t dst_ino = 2;
311
uint64_t dst_dir_ino = FUSE_ROOT_ID;
312
uint64_t ino = 42;
313
314
expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1);
315
expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 0, 1);
316
EXPECT_CALL(*m_mock, process(
317
ResultOf([=](auto in) {
318
const char *src = (const char*)in.body.bytes +
319
sizeof(fuse_rename_in);
320
const char *dst = src + strlen(src) + 1;
321
return (in.header.opcode == FUSE_RENAME &&
322
in.body.rename.newdir == dst_dir_ino &&
323
(0 == strcmp(RELDST, dst)) &&
324
(0 == strcmp(RELSRC, src)));
325
}, Eq(true)),
326
_)
327
).WillOnce(Invoke(ReturnErrno(0)));
328
329
ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
330
}
331
332