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:
expect_link(uint64_t ino,const char * relpath,mode_t mode,uint32_t nlink)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
expect_lookup(const char * relpath,uint64_t ino)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:
SetUp()71 virtual void SetUp() {
72 m_kernel_minor_version = 8;
73 FuseTest::SetUp();
74 }
75
expect_link(uint64_t ino,const char * relpath,mode_t mode,uint32_t nlink)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
expect_lookup(const char * relpath,uint64_t ino)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 */
TEST_F(Link,clear_attr_cache)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
TEST_F(Link,emlink)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 */
TEST_F(Link,bad_inode)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
TEST_F(Link,ok)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
TEST_F(Link_7_8,ok)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