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 FsyncDir: public FuseTest {
54public:
55void expect_fsyncdir(uint64_t ino, uint32_t flags, int error)
56{
57	EXPECT_CALL(*m_mock, process(
58		ResultOf([=](auto in) {
59			return (in.header.opcode == FUSE_FSYNCDIR &&
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.fsyncdir.fh == FH &&
67				in.body.fsyncdir.fsync_flags == flags);
68		}, Eq(true)),
69		_)
70	).WillOnce(Invoke(ReturnErrno(error)));
71}
72
73void expect_lookup(const char *relpath, uint64_t ino)
74{
75	FuseTest::expect_lookup(relpath, ino, S_IFDIR | 0755, 0, 1);
76}
77
78};
79
80class AioFsyncDir: public FsyncDir {
81virtual void SetUp() {
82	if (!is_unsafe_aio_enabled())
83		GTEST_SKIP() <<
84			"vfs.aio.enable_unsafe must be set for this test";
85	FuseTest::SetUp();
86}
87};
88
89/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
90TEST_F(AioFsyncDir, aio_fsync)
91{
92	const char FULLPATH[] = "mountpoint/some_file.txt";
93	const char RELPATH[] = "some_file.txt";
94	uint64_t ino = 42;
95	struct aiocb iocb, *piocb;
96	int fd;
97
98	expect_lookup(RELPATH, ino);
99	expect_opendir(ino);
100	expect_fsyncdir(ino, 0, 0);
101
102	fd = open(FULLPATH, O_DIRECTORY);
103	ASSERT_LE(0, fd) << strerror(errno);
104
105	bzero(&iocb, sizeof(iocb));
106	iocb.aio_fildes = fd;
107
108	ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno);
109	ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno);
110
111	leak(fd);
112}
113
114TEST_F(FsyncDir, eio)
115{
116	const char FULLPATH[] = "mountpoint/some_dir";
117	const char RELPATH[] = "some_dir";
118	uint64_t ino = 42;
119	int fd;
120
121	expect_lookup(RELPATH, ino);
122	expect_opendir(ino);
123	expect_fsyncdir(ino, 0, EIO);
124
125	fd = open(FULLPATH, O_DIRECTORY);
126	ASSERT_LE(0, fd) << strerror(errno);
127	ASSERT_NE(0, fsync(fd));
128	ASSERT_EQ(EIO, errno);
129
130	leak(fd);
131}
132
133/*
134 * If the filesystem returns ENOSYS, it will be treated as success and
135 * subsequent calls to VOP_FSYNC will succeed automatically without being sent
136 * to the filesystem daemon
137 */
138TEST_F(FsyncDir, enosys)
139{
140	const char FULLPATH[] = "mountpoint/some_dir";
141	const char RELPATH[] = "some_dir";
142	uint64_t ino = 42;
143	int fd;
144
145	expect_lookup(RELPATH, ino);
146	expect_opendir(ino);
147	expect_fsyncdir(ino, 0, ENOSYS);
148
149	fd = open(FULLPATH, O_DIRECTORY);
150	ASSERT_LE(0, fd) << strerror(errno);
151	EXPECT_EQ(0, fsync(fd)) << strerror(errno);
152
153	/* Subsequent calls shouldn't query the daemon*/
154	EXPECT_EQ(0, fsync(fd)) << strerror(errno);
155
156	leak(fd);
157}
158
159TEST_F(FsyncDir, fsyncdata)
160{
161	const char FULLPATH[] = "mountpoint/some_dir";
162	const char RELPATH[] = "some_dir";
163	uint64_t ino = 42;
164	int fd;
165
166	expect_lookup(RELPATH, ino);
167	expect_opendir(ino);
168	expect_fsyncdir(ino, FUSE_FSYNC_FDATASYNC, 0);
169
170	fd = open(FULLPATH, O_DIRECTORY);
171	ASSERT_LE(0, fd) << strerror(errno);
172	ASSERT_EQ(0, fdatasync(fd)) << strerror(errno);
173
174	leak(fd);
175}
176
177/*
178 * Unlike regular files, the kernel doesn't know whether a directory is or
179 * isn't dirty, so fuse(4) should always send FUSE_FSYNCDIR on fsync(2)
180 */
181TEST_F(FsyncDir, fsync)
182{
183	const char FULLPATH[] = "mountpoint/some_dir";
184	const char RELPATH[] = "some_dir";
185	uint64_t ino = 42;
186	int fd;
187
188	expect_lookup(RELPATH, ino);
189	expect_opendir(ino);
190	expect_fsyncdir(ino, 0, 0);
191
192	fd = open(FULLPATH, O_DIRECTORY);
193	ASSERT_LE(0, fd) << strerror(errno);
194	ASSERT_EQ(0, fsync(fd)) << strerror(errno);
195
196	leak(fd);
197}
198