Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/link.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 <unistd.h>
33
}
34
35
#include "mockfs.hh"
36
#include "utils.hh"
37
38
using namespace testing;
39
40
class Link: public FuseTest {
41
public:
42
void expect_link(uint64_t ino, const char *relpath, mode_t mode, uint32_t nlink)
43
{
44
EXPECT_CALL(*m_mock, process(
45
ResultOf([=](auto in) {
46
const char *name = (const char*)in.body.bytes
47
+ sizeof(struct fuse_link_in);
48
return (in.header.opcode == FUSE_LINK &&
49
in.body.link.oldnodeid == ino &&
50
(0 == strcmp(name, relpath)));
51
}, Eq(true)),
52
_)
53
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
54
SET_OUT_HEADER_LEN(out, entry);
55
out.body.entry.nodeid = ino;
56
out.body.entry.attr.mode = mode;
57
out.body.entry.attr.nlink = nlink;
58
out.body.entry.attr_valid = UINT64_MAX;
59
out.body.entry.entry_valid = UINT64_MAX;
60
})));
61
}
62
63
void expect_lookup(const char *relpath, uint64_t ino)
64
{
65
FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
66
}
67
};
68
69
class Link_7_8: public FuseTest {
70
public:
71
virtual void SetUp() {
72
m_kernel_minor_version = 8;
73
FuseTest::SetUp();
74
}
75
76
void expect_link(uint64_t ino, const char *relpath, mode_t mode, uint32_t nlink)
77
{
78
EXPECT_CALL(*m_mock, process(
79
ResultOf([=](auto in) {
80
const char *name = (const char*)in.body.bytes
81
+ sizeof(struct fuse_link_in);
82
return (in.header.opcode == FUSE_LINK &&
83
in.body.link.oldnodeid == ino &&
84
(0 == strcmp(name, relpath)));
85
}, Eq(true)),
86
_)
87
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
88
SET_OUT_HEADER_LEN(out, entry_7_8);
89
out.body.entry.nodeid = ino;
90
out.body.entry.attr.mode = mode;
91
out.body.entry.attr.nlink = nlink;
92
out.body.entry.attr_valid = UINT64_MAX;
93
out.body.entry.entry_valid = UINT64_MAX;
94
})));
95
}
96
97
void expect_lookup(const char *relpath, uint64_t ino)
98
{
99
FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, 0, 1);
100
}
101
};
102
103
/*
104
* A successful link should clear the parent directory's attribute cache,
105
* because the fuse daemon should update its mtime and ctime
106
*/
107
TEST_F(Link, clear_attr_cache)
108
{
109
const char FULLPATH[] = "mountpoint/src";
110
const char RELPATH[] = "src";
111
const char FULLDST[] = "mountpoint/dst";
112
const char RELDST[] = "dst";
113
const uint64_t ino = 42;
114
mode_t mode = S_IFREG | 0644;
115
struct stat sb;
116
117
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
118
.WillOnce(Invoke(ReturnErrno(ENOENT)));
119
EXPECT_CALL(*m_mock, process(
120
ResultOf([=](auto in) {
121
return (in.header.opcode == FUSE_GETATTR &&
122
in.header.nodeid == FUSE_ROOT_ID);
123
}, Eq(true)),
124
_)
125
).Times(2)
126
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
127
SET_OUT_HEADER_LEN(out, attr);
128
out.body.attr.attr.ino = FUSE_ROOT_ID;
129
out.body.attr.attr.mode = S_IFDIR | 0755;
130
out.body.attr.attr_valid = UINT64_MAX;
131
})));
132
133
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
134
135
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
136
SET_OUT_HEADER_LEN(out, entry);
137
out.body.entry.attr.mode = mode;
138
out.body.entry.nodeid = ino;
139
out.body.entry.attr.nlink = 1;
140
out.body.entry.attr_valid = UINT64_MAX;
141
out.body.entry.entry_valid = UINT64_MAX;
142
})));
143
expect_link(ino, RELPATH, mode, 2);
144
145
EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
146
EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
147
EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
148
}
149
150
TEST_F(Link, emlink)
151
{
152
const char FULLPATH[] = "mountpoint/lnk";
153
const char RELPATH[] = "lnk";
154
const char FULLDST[] = "mountpoint/dst";
155
const char RELDST[] = "dst";
156
uint64_t dst_ino = 42;
157
158
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
159
.WillOnce(Invoke(ReturnErrno(ENOENT)));
160
expect_lookup(RELDST, dst_ino);
161
162
EXPECT_CALL(*m_mock, process(
163
ResultOf([=](auto in) {
164
const char *name = (const char*)in.body.bytes
165
+ sizeof(struct fuse_link_in);
166
return (in.header.opcode == FUSE_LINK &&
167
in.body.link.oldnodeid == dst_ino &&
168
(0 == strcmp(name, RELPATH)));
169
}, Eq(true)),
170
_)
171
).WillOnce(Invoke(ReturnErrno(EMLINK)));
172
173
EXPECT_EQ(-1, link(FULLDST, FULLPATH));
174
EXPECT_EQ(EMLINK, errno);
175
}
176
177
/*
178
* A hard link should always have the same inode as its source. If it doesn't,
179
* then it's not a hard link.
180
*/
181
TEST_F(Link, bad_inode)
182
{
183
const char FULLPATH[] = "mountpoint/src";
184
const char RELPATH[] = "src";
185
const char FULLDST[] = "mountpoint/dst";
186
const char RELDST[] = "dst";
187
const uint64_t src_ino = 42;
188
const uint64_t dst_ino = 43;
189
mode_t mode = S_IFREG | 0644;
190
191
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
192
.WillOnce(Invoke(ReturnErrno(ENOENT)));
193
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
194
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
195
SET_OUT_HEADER_LEN(out, entry);
196
out.body.entry.attr.mode = mode;
197
out.body.entry.nodeid = dst_ino;
198
out.body.entry.attr.nlink = 1;
199
out.body.entry.attr_valid = UINT64_MAX;
200
out.body.entry.entry_valid = UINT64_MAX;
201
})));
202
EXPECT_CALL(*m_mock, process(
203
ResultOf([=](auto in) {
204
const char *name = (const char*)in.body.bytes
205
+ sizeof(struct fuse_link_in);
206
return (in.header.opcode == FUSE_LINK &&
207
in.body.link.oldnodeid == dst_ino &&
208
(0 == strcmp(name, RELPATH)));
209
}, Eq(true)),
210
_)
211
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
212
SET_OUT_HEADER_LEN(out, entry);
213
out.body.entry.nodeid = src_ino;
214
out.body.entry.attr.mode = mode;
215
out.body.entry.attr.nlink = 2;
216
out.body.entry.attr_valid = UINT64_MAX;
217
out.body.entry.entry_valid = UINT64_MAX;
218
})));
219
220
ASSERT_EQ(-1, link(FULLDST, FULLPATH));
221
ASSERT_EQ(EIO, errno);
222
}
223
224
TEST_F(Link, ok)
225
{
226
const char FULLPATH[] = "mountpoint/src";
227
const char RELPATH[] = "src";
228
const char FULLDST[] = "mountpoint/dst";
229
const char RELDST[] = "dst";
230
const uint64_t ino = 42;
231
mode_t mode = S_IFREG | 0644;
232
struct stat sb;
233
234
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
235
.WillOnce(Invoke(ReturnErrno(ENOENT)));
236
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
237
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
238
SET_OUT_HEADER_LEN(out, entry);
239
out.body.entry.attr.mode = mode;
240
out.body.entry.nodeid = ino;
241
out.body.entry.attr.nlink = 1;
242
out.body.entry.attr_valid = UINT64_MAX;
243
out.body.entry.entry_valid = UINT64_MAX;
244
})));
245
expect_link(ino, RELPATH, mode, 2);
246
247
ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
248
// Check that the original file's nlink count has increased.
249
ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno);
250
EXPECT_EQ(2ul, sb.st_nlink);
251
}
252
253
TEST_F(Link_7_8, ok)
254
{
255
const char FULLPATH[] = "mountpoint/src";
256
const char RELPATH[] = "src";
257
const char FULLDST[] = "mountpoint/dst";
258
const char RELDST[] = "dst";
259
const uint64_t ino = 42;
260
mode_t mode = S_IFREG | 0644;
261
struct stat sb;
262
263
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
264
.WillOnce(Invoke(ReturnErrno(ENOENT)));
265
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
266
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
267
SET_OUT_HEADER_LEN(out, entry_7_8);
268
out.body.entry.attr.mode = mode;
269
out.body.entry.nodeid = ino;
270
out.body.entry.attr.nlink = 1;
271
out.body.entry.attr_valid = UINT64_MAX;
272
out.body.entry.entry_valid = UINT64_MAX;
273
})));
274
expect_link(ino, RELPATH, mode, 2);
275
276
ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
277
// Check that the original file's nlink count has increased.
278
ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno);
279
EXPECT_EQ(2ul, sb.st_nlink);
280
}
281
282