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
33extern "C" {
34#include <aio.h>
35#include <fcntl.h>
36#include <unistd.h>
37}
38
39#include "mockfs.hh"
40#include "utils.hh"
41
42using namespace testing;
43
44/*
45 * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28.
46 * This bit was actually part of kernel protocol version 5.2, but never
47 * documented until after 7.28
48 */
49#ifndef FUSE_FSYNC_FDATASYNC
50#define FUSE_FSYNC_FDATASYNC 1
51#endif
52
53class Fsync: public FuseTest {
54public:
55void expect_fsync(uint64_t ino, uint32_t flags, int error, int times = 1)
56{
57	EXPECT_CALL(*m_mock, process(
58		ResultOf([=](auto in) {
59			return (in.header.opcode == FUSE_FSYNC &&
60				in.header.nodeid == ino &&
61				/*
62				 * TODO: reenable pid check after fixing
63				 * bug 236379
64				 */
65				//(pid_t)in.header.pid == getpid() &&
66				in.body.fsync.fh == FH &&
67				in.body.fsync.fsync_flags == flags);
68		}, Eq(true)),
69		_)
70	).Times(times)
71	.WillRepeatedly(Invoke(ReturnErrno(error)));
72}
73
74void expect_lookup(const char *relpath, uint64_t ino, int times = 1)
75{
76	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, times);
77}
78
79void expect_write(uint64_t ino, uint64_t size, const void *contents)
80{
81	FuseTest::expect_write(ino, 0, size, size, 0, 0, contents);
82}
83
84};
85
86class AioFsync: public Fsync {
87virtual void SetUp() {
88	if (!is_unsafe_aio_enabled())
89		GTEST_SKIP() <<
90			"vfs.aio.enable_unsafe must be set for this test";
91	FuseTest::SetUp();
92}
93};
94
95/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
96TEST_F(AioFsync, aio_fsync)
97{
98	const char FULLPATH[] = "mountpoint/some_file.txt";
99	const char RELPATH[] = "some_file.txt";
100	const char *CONTENTS = "abcdefgh";
101	ssize_t bufsize = strlen(CONTENTS);
102	uint64_t ino = 42;
103	struct aiocb iocb, *piocb;
104	int fd;
105
106	expect_lookup(RELPATH, ino);
107	expect_open(ino, 0, 1);
108	expect_write(ino, bufsize, CONTENTS);
109	expect_fsync(ino, 0, 0);
110
111	fd = open(FULLPATH, O_RDWR);
112	ASSERT_LE(0, fd) << strerror(errno);
113	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
114
115	bzero(&iocb, sizeof(iocb));
116	iocb.aio_fildes = fd;
117
118	ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno);
119	ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
120
121	leak(fd);
122}
123
124/*
125 * fuse(4) should NOT fsync during VOP_RELEASE or VOP_INACTIVE
126 *
127 * This test only really make sense in writeback caching mode, but it should
128 * still pass in any cache mode.
129 */
130TEST_F(Fsync, close)
131{
132	const char FULLPATH[] = "mountpoint/some_file.txt";
133	const char RELPATH[] = "some_file.txt";
134	const char *CONTENTS = "abcdefgh";
135	ssize_t bufsize = strlen(CONTENTS);
136	uint64_t ino = 42;
137	int fd;
138
139	expect_lookup(RELPATH, ino);
140	expect_open(ino, 0, 1);
141	expect_write(ino, bufsize, CONTENTS);
142	EXPECT_CALL(*m_mock, process(
143		ResultOf([=](auto in) {
144			return (in.header.opcode == FUSE_SETATTR);
145		}, Eq(true)),
146		_)
147	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
148		SET_OUT_HEADER_LEN(out, attr);
149		out.body.attr.attr.ino = ino;	// Must match nodeid
150	})));
151	EXPECT_CALL(*m_mock, process(
152		ResultOf([=](auto in) {
153			return (in.header.opcode == FUSE_FSYNC);
154		}, Eq(true)),
155		_)
156	).Times(0);
157	expect_flush(ino, 1, ReturnErrno(0));
158	expect_release(ino, FH);
159
160	fd = open(FULLPATH, O_RDWR);
161	ASSERT_LE(0, fd) << strerror(errno);
162	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
163	close(fd);
164}
165
166TEST_F(Fsync, eio)
167{
168	const char FULLPATH[] = "mountpoint/some_file.txt";
169	const char RELPATH[] = "some_file.txt";
170	const char *CONTENTS = "abcdefgh";
171	ssize_t bufsize = strlen(CONTENTS);
172	uint64_t ino = 42;
173	int fd;
174
175	expect_lookup(RELPATH, ino);
176	expect_open(ino, 0, 1);
177	expect_write(ino, bufsize, CONTENTS);
178	expect_fsync(ino, FUSE_FSYNC_FDATASYNC, EIO);
179
180	fd = open(FULLPATH, O_RDWR);
181	ASSERT_LE(0, fd) << strerror(errno);
182	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
183	ASSERT_NE(0, fdatasync(fd));
184	ASSERT_EQ(EIO, errno);
185
186	leak(fd);
187}
188
189/*
190 * If the filesystem returns ENOSYS, it will be treated as success and
191 * subsequent calls to VOP_FSYNC will succeed automatically without being sent
192 * to the filesystem daemon
193 */
194TEST_F(Fsync, enosys)
195{
196	const char FULLPATH[] = "mountpoint/some_file.txt";
197	const char RELPATH[] = "some_file.txt";
198	const char *CONTENTS = "abcdefgh";
199	ssize_t bufsize = strlen(CONTENTS);
200	uint64_t ino = 42;
201	int fd;
202
203	expect_lookup(RELPATH, ino);
204	expect_open(ino, 0, 1);
205	expect_write(ino, bufsize, CONTENTS);
206	expect_fsync(ino, FUSE_FSYNC_FDATASYNC, ENOSYS);
207
208	fd = open(FULLPATH, O_RDWR);
209	ASSERT_LE(0, fd) << strerror(errno);
210	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
211	EXPECT_EQ(0, fdatasync(fd));
212
213	/* Subsequent calls shouldn't query the daemon*/
214	EXPECT_EQ(0, fdatasync(fd));
215	leak(fd);
216}
217
218
219TEST_F(Fsync, fdatasync)
220{
221	const char FULLPATH[] = "mountpoint/some_file.txt";
222	const char RELPATH[] = "some_file.txt";
223	const char *CONTENTS = "abcdefgh";
224	ssize_t bufsize = strlen(CONTENTS);
225	uint64_t ino = 42;
226	int fd;
227
228	expect_lookup(RELPATH, ino);
229	expect_open(ino, 0, 1);
230	expect_write(ino, bufsize, CONTENTS);
231	expect_fsync(ino, FUSE_FSYNC_FDATASYNC, 0);
232
233	fd = open(FULLPATH, O_RDWR);
234	ASSERT_LE(0, fd) << strerror(errno);
235	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
236	ASSERT_EQ(0, fdatasync(fd)) << strerror(errno);
237
238	leak(fd);
239}
240
241TEST_F(Fsync, fsync)
242{
243	const char FULLPATH[] = "mountpoint/some_file.txt";
244	const char RELPATH[] = "some_file.txt";
245	const char *CONTENTS = "abcdefgh";
246	ssize_t bufsize = strlen(CONTENTS);
247	uint64_t ino = 42;
248	int fd;
249
250	expect_lookup(RELPATH, ino);
251	expect_open(ino, 0, 1);
252	expect_write(ino, bufsize, CONTENTS);
253	expect_fsync(ino, 0, 0);
254
255	fd = open(FULLPATH, O_RDWR);
256	ASSERT_LE(0, fd) << strerror(errno);
257	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
258	ASSERT_EQ(0, fsync(fd)) << strerror(errno);
259
260	leak(fd);
261}
262
263/* If multiple FUSE file handles are active, we must fsync them all */
264TEST_F(Fsync, two_handles)
265{
266	const char FULLPATH[] = "mountpoint/some_file.txt";
267	const char RELPATH[] = "some_file.txt";
268	const char *CONTENTS = "abcdefgh";
269	ssize_t bufsize = strlen(CONTENTS);
270	uint64_t ino = 42;
271	int fd1, fd2;
272
273	expect_lookup(RELPATH, ino, 2);
274	expect_open(ino, 0, 2);
275	expect_write(ino, bufsize, CONTENTS);
276	expect_fsync(ino, 0, 0, 2);
277
278	fd1 = open(FULLPATH, O_WRONLY);
279	ASSERT_LE(0, fd1) << strerror(errno);
280	fd2 = open(FULLPATH, O_RDONLY);
281	ASSERT_LE(0, fd2) << strerror(errno);
282	ASSERT_EQ(bufsize, write(fd1, CONTENTS, bufsize)) << strerror(errno);
283	ASSERT_EQ(0, fsync(fd1)) << strerror(errno);
284
285	leak(fd1);
286	leak(fd2);
287}
288