xref: /freebsd-src/tests/sys/fs/fusefs/link.cc (revision b3e7694832e81d7a904a10f525f8797b753bf0d3)
19821f1d3SAlan Somers /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
319821f1d3SAlan Somers extern "C" {
329821f1d3SAlan Somers #include <unistd.h>
339821f1d3SAlan Somers }
349821f1d3SAlan Somers 
359821f1d3SAlan Somers #include "mockfs.hh"
369821f1d3SAlan Somers #include "utils.hh"
379821f1d3SAlan Somers 
389821f1d3SAlan Somers using namespace testing;
399821f1d3SAlan Somers 
409821f1d3SAlan Somers class Link: public FuseTest {
419821f1d3SAlan Somers public:
expect_link(uint64_t ino,const char * relpath,mode_t mode,uint32_t nlink)42002e54b0SAlan Somers void expect_link(uint64_t ino, const char *relpath, mode_t mode, uint32_t nlink)
43002e54b0SAlan Somers {
44002e54b0SAlan Somers 	EXPECT_CALL(*m_mock, process(
45002e54b0SAlan Somers 		ResultOf([=](auto in) {
4629edc611SAlan Somers 			const char *name = (const char*)in.body.bytes
47002e54b0SAlan Somers 				+ sizeof(struct fuse_link_in);
4829edc611SAlan Somers 			return (in.header.opcode == FUSE_LINK &&
4929edc611SAlan Somers 				in.body.link.oldnodeid == ino &&
50002e54b0SAlan Somers 				(0 == strcmp(name, relpath)));
51002e54b0SAlan Somers 		}, Eq(true)),
52002e54b0SAlan Somers 		_)
5329edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
54002e54b0SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
5529edc611SAlan Somers 		out.body.entry.nodeid = ino;
5629edc611SAlan Somers 		out.body.entry.attr.mode = mode;
5729edc611SAlan Somers 		out.body.entry.attr.nlink = nlink;
5829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
5929edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
60002e54b0SAlan Somers 	})));
61002e54b0SAlan Somers }
62002e54b0SAlan Somers 
expect_lookup(const char * relpath,uint64_t ino)639821f1d3SAlan Somers void expect_lookup(const char *relpath, uint64_t ino)
649821f1d3SAlan Somers {
659821f1d3SAlan Somers 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
669821f1d3SAlan Somers }
679821f1d3SAlan Somers };
689821f1d3SAlan Somers 
6916bd2d47SAlan Somers class Link_7_8: public FuseTest {
7016bd2d47SAlan Somers public:
SetUp()7116bd2d47SAlan Somers virtual void SetUp() {
7216bd2d47SAlan Somers 	m_kernel_minor_version = 8;
7316bd2d47SAlan Somers 	FuseTest::SetUp();
7416bd2d47SAlan Somers }
7516bd2d47SAlan Somers 
expect_link(uint64_t ino,const char * relpath,mode_t mode,uint32_t nlink)7616bd2d47SAlan Somers void expect_link(uint64_t ino, const char *relpath, mode_t mode, uint32_t nlink)
7716bd2d47SAlan Somers {
7816bd2d47SAlan Somers 	EXPECT_CALL(*m_mock, process(
7916bd2d47SAlan Somers 		ResultOf([=](auto in) {
8029edc611SAlan Somers 			const char *name = (const char*)in.body.bytes
8116bd2d47SAlan Somers 				+ sizeof(struct fuse_link_in);
8229edc611SAlan Somers 			return (in.header.opcode == FUSE_LINK &&
8329edc611SAlan Somers 				in.body.link.oldnodeid == ino &&
8416bd2d47SAlan Somers 				(0 == strcmp(name, relpath)));
8516bd2d47SAlan Somers 		}, Eq(true)),
8616bd2d47SAlan Somers 		_)
8729edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
8816bd2d47SAlan Somers 		SET_OUT_HEADER_LEN(out, entry_7_8);
8929edc611SAlan Somers 		out.body.entry.nodeid = ino;
9029edc611SAlan Somers 		out.body.entry.attr.mode = mode;
9129edc611SAlan Somers 		out.body.entry.attr.nlink = nlink;
9229edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
9329edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
9416bd2d47SAlan Somers 	})));
9516bd2d47SAlan Somers }
9616bd2d47SAlan Somers 
expect_lookup(const char * relpath,uint64_t ino)9716bd2d47SAlan Somers void expect_lookup(const char *relpath, uint64_t ino)
9816bd2d47SAlan Somers {
9916bd2d47SAlan Somers 	FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, 0, 1);
10016bd2d47SAlan Somers }
10116bd2d47SAlan Somers };
10216bd2d47SAlan Somers 
103002e54b0SAlan Somers /*
104002e54b0SAlan Somers  * A successful link should clear the parent directory's attribute cache,
105002e54b0SAlan Somers  * because the fuse daemon should update its mtime and ctime
106002e54b0SAlan Somers  */
TEST_F(Link,clear_attr_cache)107002e54b0SAlan Somers TEST_F(Link, clear_attr_cache)
108002e54b0SAlan Somers {
109002e54b0SAlan Somers 	const char FULLPATH[] = "mountpoint/src";
110002e54b0SAlan Somers 	const char RELPATH[] = "src";
111002e54b0SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
112002e54b0SAlan Somers 	const char RELDST[] = "dst";
113002e54b0SAlan Somers 	const uint64_t ino = 42;
114002e54b0SAlan Somers 	mode_t mode = S_IFREG | 0644;
115002e54b0SAlan Somers 	struct stat sb;
116002e54b0SAlan Somers 
117a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
118a34cdd26SAlan Somers 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
119002e54b0SAlan Somers 	EXPECT_CALL(*m_mock, process(
120002e54b0SAlan Somers 		ResultOf([=](auto in) {
12129edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
122a34cdd26SAlan Somers 				in.header.nodeid == FUSE_ROOT_ID);
123002e54b0SAlan Somers 		}, Eq(true)),
124002e54b0SAlan Somers 		_)
125002e54b0SAlan Somers 	).Times(2)
12629edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
127002e54b0SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
128a34cdd26SAlan Somers 		out.body.attr.attr.ino = FUSE_ROOT_ID;
12929edc611SAlan Somers 		out.body.attr.attr.mode = S_IFDIR | 0755;
13029edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
131002e54b0SAlan Somers 	})));
132002e54b0SAlan Somers 
133a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
134a34cdd26SAlan Somers 
13529edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
136002e54b0SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
13729edc611SAlan Somers 		out.body.entry.attr.mode = mode;
13829edc611SAlan Somers 		out.body.entry.nodeid = ino;
13929edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
14029edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
14129edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
142002e54b0SAlan Somers 	})));
143002e54b0SAlan Somers 	expect_link(ino, RELPATH, mode, 2);
144002e54b0SAlan Somers 
145002e54b0SAlan Somers 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
146002e54b0SAlan Somers 	EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
147002e54b0SAlan Somers 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
148002e54b0SAlan Somers }
149002e54b0SAlan Somers 
TEST_F(Link,emlink)1509821f1d3SAlan Somers TEST_F(Link, emlink)
1519821f1d3SAlan Somers {
1529821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/lnk";
1539821f1d3SAlan Somers 	const char RELPATH[] = "lnk";
1549821f1d3SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1559821f1d3SAlan Somers 	const char RELDST[] = "dst";
1569821f1d3SAlan Somers 	uint64_t dst_ino = 42;
1579821f1d3SAlan Somers 
158a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
159a34cdd26SAlan Somers 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
1609821f1d3SAlan Somers 	expect_lookup(RELDST, dst_ino);
1619821f1d3SAlan Somers 
1629821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1639821f1d3SAlan Somers 		ResultOf([=](auto in) {
16429edc611SAlan Somers 			const char *name = (const char*)in.body.bytes
1659821f1d3SAlan Somers 				+ sizeof(struct fuse_link_in);
16629edc611SAlan Somers 			return (in.header.opcode == FUSE_LINK &&
16729edc611SAlan Somers 				in.body.link.oldnodeid == dst_ino &&
1689821f1d3SAlan Somers 				(0 == strcmp(name, RELPATH)));
1699821f1d3SAlan Somers 		}, Eq(true)),
1709821f1d3SAlan Somers 		_)
1719821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnErrno(EMLINK)));
1729821f1d3SAlan Somers 
1739821f1d3SAlan Somers 	EXPECT_EQ(-1, link(FULLDST, FULLPATH));
1749821f1d3SAlan Somers 	EXPECT_EQ(EMLINK, errno);
1759821f1d3SAlan Somers }
1769821f1d3SAlan Somers 
1770bef4927SAlan Somers /*
1780bef4927SAlan Somers  * A hard link should always have the same inode as its source.  If it doesn't,
1790bef4927SAlan Somers  * then it's not a hard link.
1800bef4927SAlan Somers  */
TEST_F(Link,bad_inode)1810bef4927SAlan Somers TEST_F(Link, bad_inode)
1820bef4927SAlan Somers {
1830bef4927SAlan Somers 	const char FULLPATH[] = "mountpoint/src";
1840bef4927SAlan Somers 	const char RELPATH[] = "src";
1850bef4927SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1860bef4927SAlan Somers 	const char RELDST[] = "dst";
1870bef4927SAlan Somers 	const uint64_t src_ino = 42;
1880bef4927SAlan Somers 	const uint64_t dst_ino = 43;
1890bef4927SAlan Somers 	mode_t mode = S_IFREG | 0644;
1900bef4927SAlan Somers 
1910bef4927SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1920bef4927SAlan Somers 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
1930bef4927SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1940bef4927SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1950bef4927SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
1960bef4927SAlan Somers 		out.body.entry.attr.mode = mode;
1970bef4927SAlan Somers 		out.body.entry.nodeid = dst_ino;
1980bef4927SAlan Somers 		out.body.entry.attr.nlink = 1;
1990bef4927SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
2000bef4927SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
2010bef4927SAlan Somers 	})));
2020bef4927SAlan Somers 	EXPECT_CALL(*m_mock, process(
2030bef4927SAlan Somers 		ResultOf([=](auto in) {
2040bef4927SAlan Somers 			const char *name = (const char*)in.body.bytes
2050bef4927SAlan Somers 				+ sizeof(struct fuse_link_in);
2060bef4927SAlan Somers 			return (in.header.opcode == FUSE_LINK &&
2070bef4927SAlan Somers 				in.body.link.oldnodeid == dst_ino &&
2080bef4927SAlan Somers 				(0 == strcmp(name, RELPATH)));
2090bef4927SAlan Somers 		}, Eq(true)),
2100bef4927SAlan Somers 		_)
2110bef4927SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
2120bef4927SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
2130bef4927SAlan Somers 		out.body.entry.nodeid = src_ino;
2140bef4927SAlan Somers 		out.body.entry.attr.mode = mode;
2150bef4927SAlan Somers 		out.body.entry.attr.nlink = 2;
2160bef4927SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
2170bef4927SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
2180bef4927SAlan Somers 	})));
2190bef4927SAlan Somers 
2200bef4927SAlan Somers 	ASSERT_EQ(-1, link(FULLDST, FULLPATH));
2210bef4927SAlan Somers 	ASSERT_EQ(EIO, errno);
2220bef4927SAlan Somers }
2230bef4927SAlan Somers 
TEST_F(Link,ok)2249821f1d3SAlan Somers TEST_F(Link, ok)
2259821f1d3SAlan Somers {
2269821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/src";
2279821f1d3SAlan Somers 	const char RELPATH[] = "src";
2289821f1d3SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
2299821f1d3SAlan Somers 	const char RELDST[] = "dst";
2309821f1d3SAlan Somers 	const uint64_t ino = 42;
2314ae3a56cSAlan Somers 	mode_t mode = S_IFREG | 0644;
2324ae3a56cSAlan Somers 	struct stat sb;
2339821f1d3SAlan Somers 
234a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
235a34cdd26SAlan Somers 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
236a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
23729edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
2384ae3a56cSAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
23929edc611SAlan Somers 		out.body.entry.attr.mode = mode;
24029edc611SAlan Somers 		out.body.entry.nodeid = ino;
24129edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
24229edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
24329edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
2444ae3a56cSAlan Somers 	})));
245002e54b0SAlan Somers 	expect_link(ino, RELPATH, mode, 2);
2469821f1d3SAlan Somers 
2474ae3a56cSAlan Somers 	ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
2484ae3a56cSAlan Somers 	// Check that the original file's nlink count has increased.
2494ae3a56cSAlan Somers 	ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno);
2504ae3a56cSAlan Somers 	EXPECT_EQ(2ul, sb.st_nlink);
2519821f1d3SAlan Somers }
25216bd2d47SAlan Somers 
TEST_F(Link_7_8,ok)25316bd2d47SAlan Somers TEST_F(Link_7_8, ok)
25416bd2d47SAlan Somers {
25516bd2d47SAlan Somers 	const char FULLPATH[] = "mountpoint/src";
25616bd2d47SAlan Somers 	const char RELPATH[] = "src";
25716bd2d47SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
25816bd2d47SAlan Somers 	const char RELDST[] = "dst";
25916bd2d47SAlan Somers 	const uint64_t ino = 42;
26016bd2d47SAlan Somers 	mode_t mode = S_IFREG | 0644;
26116bd2d47SAlan Somers 	struct stat sb;
26216bd2d47SAlan Somers 
263a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
264a34cdd26SAlan Somers 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
265a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
26629edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
26716bd2d47SAlan Somers 		SET_OUT_HEADER_LEN(out, entry_7_8);
26829edc611SAlan Somers 		out.body.entry.attr.mode = mode;
26929edc611SAlan Somers 		out.body.entry.nodeid = ino;
27029edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
27129edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
27229edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
27316bd2d47SAlan Somers 	})));
27416bd2d47SAlan Somers 	expect_link(ino, RELPATH, mode, 2);
27516bd2d47SAlan Somers 
27616bd2d47SAlan Somers 	ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno);
27716bd2d47SAlan Somers 	// Check that the original file's nlink count has increased.
27816bd2d47SAlan Somers 	ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno);
27916bd2d47SAlan Somers 	EXPECT_EQ(2ul, sb.st_nlink);
28016bd2d47SAlan Somers }
281