2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2020 Alan Somers
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 #include <sys/param.h>
39 using namespace testing;
41 class Lseek: public FuseTest {};
42 class LseekPathconf: public Lseek {};
43 class LseekPathconf_7_23: public LseekPathconf {
45 virtual void SetUp() {
46 m_kernel_minor_version = 23;
50 class LseekSeekHole: public Lseek {};
51 class LseekSeekData: public Lseek {};
54 * If a previous lseek operation has already returned enosys, then pathconf can
55 * return EINVAL immediately.
57 TEST_F(LseekPathconf, already_enosys)
59 const char FULLPATH[] = "mountpoint/some_file.txt";
60 const char RELPATH[] = "some_file.txt";
61 const uint64_t ino = 42;
62 off_t fsize = 1 << 30; /* 1 GiB */
63 off_t offset_in = 1 << 28;
66 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
67 expect_open(ino, 0, 1);
68 EXPECT_CALL(*m_mock, process(
69 ResultOf([=](auto in) {
70 return (in.header.opcode == FUSE_LSEEK);
73 ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
75 fd = open(FULLPATH, O_RDONLY);
77 EXPECT_EQ(offset_in, lseek(fd, offset_in, SEEK_DATA));
78 EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
79 EXPECT_EQ(EINVAL, errno);
83 * If a previous lseek operation has already returned successfully, then
84 * pathconf can return 1 immediately. 1 means "holes are reported, but size is
87 TEST_F(LseekPathconf, already_seeked)
89 const char FULLPATH[] = "mountpoint/some_file.txt";
90 const char RELPATH[] = "some_file.txt";
91 const uint64_t ino = 42;
92 off_t fsize = 1 << 30; /* 1 GiB */
93 off_t offset = 1 << 28;
96 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
97 expect_open(ino, 0, 1);
98 EXPECT_CALL(*m_mock, process(
99 ResultOf([=](auto in) {
100 return (in.header.opcode == FUSE_LSEEK);
103 ).WillOnce(Invoke(ReturnImmediate([=](auto i, auto& out) {
104 SET_OUT_HEADER_LEN(out, lseek);
105 out.body.lseek.offset = i.body.lseek.offset;
107 fd = open(FULLPATH, O_RDONLY);
108 EXPECT_EQ(offset, lseek(fd, offset, SEEK_DATA));
110 EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
114 * If no FUSE_LSEEK operation has been attempted since mount, try once as soon
115 * as a pathconf request comes in.
117 TEST_F(LseekPathconf, enosys_now)
119 const char FULLPATH[] = "mountpoint/some_file.txt";
120 const char RELPATH[] = "some_file.txt";
121 const uint64_t ino = 42;
122 off_t fsize = 1 << 30; /* 1 GiB */
125 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
126 expect_open(ino, 0, 1);
127 EXPECT_CALL(*m_mock, process(
128 ResultOf([=](auto in) {
129 return (in.header.opcode == FUSE_LSEEK);
132 ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
134 fd = open(FULLPATH, O_RDONLY);
136 EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
137 EXPECT_EQ(EINVAL, errno);
141 * If no FUSE_LSEEK operation has been attempted since mount, try one as soon
142 * as a pathconf request comes in. This is the typical pattern of bsdtar. It
143 * will only try SEEK_HOLE/SEEK_DATA if fpathconf says they're supported.
145 TEST_F(LseekPathconf, seek_now)
147 const char FULLPATH[] = "mountpoint/some_file.txt";
148 const char RELPATH[] = "some_file.txt";
149 const uint64_t ino = 42;
150 off_t fsize = 1 << 30; /* 1 GiB */
151 off_t offset_initial = 1 << 27;
152 off_t offset_out = 1 << 29;
155 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
156 expect_open(ino, 0, 1);
157 EXPECT_CALL(*m_mock, process(
158 ResultOf([=](auto in) {
159 return (in.header.opcode == FUSE_LSEEK);
162 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
163 SET_OUT_HEADER_LEN(out, lseek);
164 out.body.lseek.offset = offset_out;
167 fd = open(FULLPATH, O_RDONLY);
168 EXPECT_EQ(offset_initial, lseek(fd, offset_initial, SEEK_SET));
169 EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
170 /* And check that the file pointer hasn't changed */
171 EXPECT_EQ(offset_initial, lseek(fd, 0, SEEK_CUR));
175 * For servers using older protocol versions, no FUSE_LSEEK should be attempted
177 TEST_F(LseekPathconf_7_23, already_enosys)
179 const char FULLPATH[] = "mountpoint/some_file.txt";
180 const char RELPATH[] = "some_file.txt";
181 const uint64_t ino = 42;
182 off_t fsize = 1 << 30; /* 1 GiB */
185 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
186 expect_open(ino, 0, 1);
187 EXPECT_CALL(*m_mock, process(
188 ResultOf([=](auto in) {
189 return (in.header.opcode == FUSE_LSEEK);
194 fd = open(FULLPATH, O_RDONLY);
195 EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
196 EXPECT_EQ(EINVAL, errno);
199 TEST_F(LseekSeekData, ok)
201 const char FULLPATH[] = "mountpoint/some_file.txt";
202 const char RELPATH[] = "some_file.txt";
203 const uint64_t ino = 42;
204 off_t fsize = 1 << 30; /* 1 GiB */
205 off_t offset_in = 1 << 28;
206 off_t offset_out = 1 << 29;
209 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
210 expect_open(ino, 0, 1);
211 EXPECT_CALL(*m_mock, process(
212 ResultOf([=](auto in) {
213 return (in.header.opcode == FUSE_LSEEK &&
214 in.header.nodeid == ino &&
215 in.body.lseek.fh == FH &&
216 (off_t)in.body.lseek.offset == offset_in &&
217 in.body.lseek.whence == SEEK_DATA);
220 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
221 SET_OUT_HEADER_LEN(out, lseek);
222 out.body.lseek.offset = offset_out;
224 fd = open(FULLPATH, O_RDONLY);
225 EXPECT_EQ(offset_out, lseek(fd, offset_in, SEEK_DATA));
226 EXPECT_EQ(offset_out, lseek(fd, 0, SEEK_CUR));
230 * If the server returns ENOSYS, fusefs should fall back to the default
231 * behavior, and never query the server again.
233 TEST_F(LseekSeekData, enosys)
235 const char FULLPATH[] = "mountpoint/some_file.txt";
236 const char RELPATH[] = "some_file.txt";
237 const uint64_t ino = 42;
238 off_t fsize = 1 << 30; /* 1 GiB */
239 off_t offset_in = 1 << 28;
242 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
243 expect_open(ino, 0, 1);
244 EXPECT_CALL(*m_mock, process(
245 ResultOf([=](auto in) {
246 return (in.header.opcode == FUSE_LSEEK &&
247 in.header.nodeid == ino &&
248 in.body.lseek.fh == FH &&
249 (off_t)in.body.lseek.offset == offset_in &&
250 in.body.lseek.whence == SEEK_DATA);
253 ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
254 fd = open(FULLPATH, O_RDONLY);
257 * Default behavior: ENXIO if offset is < 0 or >= fsize, offset
260 EXPECT_EQ(offset_in, lseek(fd, offset_in, SEEK_DATA));
261 EXPECT_EQ(-1, lseek(fd, -1, SEEK_HOLE));
262 EXPECT_EQ(ENXIO, errno);
263 EXPECT_EQ(-1, lseek(fd, fsize, SEEK_HOLE));
264 EXPECT_EQ(ENXIO, errno);
267 TEST_F(LseekSeekHole, ok)
269 const char FULLPATH[] = "mountpoint/some_file.txt";
270 const char RELPATH[] = "some_file.txt";
271 const uint64_t ino = 42;
272 off_t fsize = 1 << 30; /* 1 GiB */
273 off_t offset_in = 1 << 28;
274 off_t offset_out = 1 << 29;
277 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
278 expect_open(ino, 0, 1);
279 EXPECT_CALL(*m_mock, process(
280 ResultOf([=](auto in) {
281 return (in.header.opcode == FUSE_LSEEK &&
282 in.header.nodeid == ino &&
283 in.body.lseek.fh == FH &&
284 (off_t)in.body.lseek.offset == offset_in &&
285 in.body.lseek.whence == SEEK_HOLE);
288 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
289 SET_OUT_HEADER_LEN(out, lseek);
290 out.body.lseek.offset = offset_out;
292 fd = open(FULLPATH, O_RDONLY);
293 EXPECT_EQ(offset_out, lseek(fd, offset_in, SEEK_HOLE));
294 EXPECT_EQ(offset_out, lseek(fd, 0, SEEK_CUR));
298 * If the server returns ENOSYS, fusefs should fall back to the default
299 * behavior, and never query the server again.
301 TEST_F(LseekSeekHole, enosys)
303 const char FULLPATH[] = "mountpoint/some_file.txt";
304 const char RELPATH[] = "some_file.txt";
305 const uint64_t ino = 42;
306 off_t fsize = 1 << 30; /* 1 GiB */
307 off_t offset_in = 1 << 28;
310 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
311 expect_open(ino, 0, 1);
312 EXPECT_CALL(*m_mock, process(
313 ResultOf([=](auto in) {
314 return (in.header.opcode == FUSE_LSEEK &&
315 in.header.nodeid == ino &&
316 in.body.lseek.fh == FH &&
317 (off_t)in.body.lseek.offset == offset_in &&
318 in.body.lseek.whence == SEEK_HOLE);
321 ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
322 fd = open(FULLPATH, O_RDONLY);
325 * Default behavior: ENXIO if offset is < 0 or >= fsize, fsize
328 EXPECT_EQ(fsize, lseek(fd, offset_in, SEEK_HOLE));
329 EXPECT_EQ(-1, lseek(fd, -1, SEEK_HOLE));
330 EXPECT_EQ(ENXIO, errno);
331 EXPECT_EQ(-1, lseek(fd, fsize, SEEK_HOLE));
332 EXPECT_EQ(ENXIO, errno);
335 /* lseek should return ENXIO when offset points to EOF */
336 TEST_F(LseekSeekHole, enxio)
338 const char FULLPATH[] = "mountpoint/some_file.txt";
339 const char RELPATH[] = "some_file.txt";
340 const uint64_t ino = 42;
341 off_t fsize = 1 << 30; /* 1 GiB */
342 off_t offset_in = fsize;
345 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
346 expect_open(ino, 0, 1);
347 EXPECT_CALL(*m_mock, process(
348 ResultOf([=](auto in) {
349 return (in.header.opcode == FUSE_LSEEK &&
350 in.header.nodeid == ino &&
351 in.body.lseek.fh == FH &&
352 (off_t)in.body.lseek.offset == offset_in &&
353 in.body.lseek.whence == SEEK_HOLE);
356 ).WillOnce(Invoke(ReturnErrno(ENXIO)));
357 fd = open(FULLPATH, O_RDONLY);
358 EXPECT_EQ(-1, lseek(fd, offset_in, SEEK_HOLE));
359 EXPECT_EQ(ENXIO, errno);