]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/lseek.cc
Add 'contrib/pnglite/' from commit 'a70c2a23d0d84dfc63a1d9413a7f4aaede7313aa'
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / lseek.cc
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2020 Alan Somers
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
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.
14  *
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
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
28  */
29
30 extern "C" {
31 #include <sys/param.h>
32
33 #include <fcntl.h>
34 }
35
36 #include "mockfs.hh"
37 #include "utils.hh"
38
39 using namespace testing;
40
41 class Lseek: public FuseTest {};
42 class LseekPathconf: public Lseek {};
43 class LseekPathconf_7_23: public LseekPathconf {
44 public:
45 virtual void SetUp() {
46         m_kernel_minor_version = 23;
47         FuseTest::SetUp();
48 }
49 };
50 class LseekSeekHole: public Lseek {};
51 class LseekSeekData: public Lseek {};
52
53 /*
54  * If a previous lseek operation has already returned enosys, then pathconf can
55  * return EINVAL immediately.
56  */
57 TEST_F(LseekPathconf, already_enosys)
58 {
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;
64         int fd;
65
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);
71                 }, Eq(true)),
72                 _)
73         ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
74
75         fd = open(FULLPATH, O_RDONLY);
76
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);
80 }
81
82 /*
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
85  * not specified".
86  */
87 TEST_F(LseekPathconf, already_seeked)
88 {
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;
94         int fd;
95
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);
101                 }, Eq(true)),
102                 _)
103         ).WillOnce(Invoke(ReturnImmediate([=](auto i, auto& out) {
104                 SET_OUT_HEADER_LEN(out, lseek);
105                 out.body.lseek.offset = i.body.lseek.offset;
106         })));
107         fd = open(FULLPATH, O_RDONLY);
108         EXPECT_EQ(offset, lseek(fd, offset, SEEK_DATA));
109
110         EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
111 }
112
113 /*
114  * If no FUSE_LSEEK operation has been attempted since mount, try once as soon
115  * as a pathconf request comes in.
116  */
117 TEST_F(LseekPathconf, enosys_now)
118 {
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 */
123         int fd;
124
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);
130                 }, Eq(true)),
131                 _)
132         ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
133
134         fd = open(FULLPATH, O_RDONLY);
135
136         EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
137         EXPECT_EQ(EINVAL, errno);
138 }
139
140 /*
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.
144  */
145 TEST_F(LseekPathconf, seek_now)
146 {
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;
153         int fd;
154
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);
160                 }, Eq(true)),
161                 _)
162         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
163                 SET_OUT_HEADER_LEN(out, lseek);
164                 out.body.lseek.offset = offset_out;
165         })));
166
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));
172 }
173
174 /*
175  * For servers using older protocol versions, no FUSE_LSEEK should be attempted
176  */
177 TEST_F(LseekPathconf_7_23, already_enosys)
178 {
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 */
183         int fd;
184
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);
190                 }, Eq(true)),
191                 _)
192         ).Times(0);
193
194         fd = open(FULLPATH, O_RDONLY);
195         EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
196         EXPECT_EQ(EINVAL, errno);
197 }
198
199 TEST_F(LseekSeekData, ok)
200 {
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;
207         int fd;
208
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);
218                 }, Eq(true)),
219                 _)
220         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
221                 SET_OUT_HEADER_LEN(out, lseek);
222                 out.body.lseek.offset = offset_out;
223         })));
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));
227 }
228
229 /*
230  * If the server returns ENOSYS, fusefs should fall back to the default
231  * behavior, and never query the server again.
232  */
233 TEST_F(LseekSeekData, enosys)
234 {
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;
240         int fd;
241
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);
251                 }, Eq(true)),
252                 _)
253         ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
254         fd = open(FULLPATH, O_RDONLY);
255
256         /*
257          * Default behavior: ENXIO if offset is < 0 or >= fsize, offset
258          * otherwise.
259          */
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);
265 }
266
267 TEST_F(LseekSeekHole, ok)
268 {
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;
275         int fd;
276
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);
286                 }, Eq(true)),
287                 _)
288         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
289                 SET_OUT_HEADER_LEN(out, lseek);
290                 out.body.lseek.offset = offset_out;
291         })));
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));
295 }
296
297 /*
298  * If the server returns ENOSYS, fusefs should fall back to the default
299  * behavior, and never query the server again.
300  */
301 TEST_F(LseekSeekHole, enosys)
302 {
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;
308         int fd;
309
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);
319                 }, Eq(true)),
320                 _)
321         ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
322         fd = open(FULLPATH, O_RDONLY);
323
324         /*
325          * Default behavior: ENXIO if offset is < 0 or >= fsize, fsize
326          * otherwise.
327          */
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);
333 }
334
335 /* lseek should return ENXIO when offset points to EOF */
336 TEST_F(LseekSeekHole, enxio)
337 {
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;
343         int fd;
344
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);
354                 }, Eq(true)),
355                 _)
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);
360 }