]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/mockfs.hh
fuse(4): Add some tests for FUSE_FLUSH
[FreeBSD/FreeBSD.git] / tests / sys / fs / fuse / mockfs.hh
1 /*-
2  * Copyright (c) 2019 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by BFF Storage Systems, LLC under sponsorship
6  * from the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 extern "C" {
31 #include <sys/types.h>
32
33 #include <pthread.h>
34
35 #include "fuse_kernel.h"
36 }
37
38 #include <gmock/gmock.h>
39
40 #define TIME_T_MAX (std::numeric_limits<time_t>::max())
41
42 #define SET_OUT_HEADER_LEN(out, variant) { \
43         (out)->header.len = (sizeof((out)->header) + \
44                              sizeof((out)->body.variant)); \
45 }
46
47 /*
48  * Create an expectation on FUSE_LOOKUP and return it so the caller can set
49  * actions.
50  *
51  * This must be a macro instead of a method because EXPECT_CALL returns a type
52  * with a deleted constructor.
53  */
54 #define EXPECT_LOOKUP(parent, path)                                     \
55         EXPECT_CALL(*m_mock, process(                                   \
56                 ResultOf([=](auto in) {                                 \
57                         return (in->header.opcode == FUSE_LOOKUP &&     \
58                                 in->header.nodeid == (parent) &&        \
59                                 strcmp(in->body.lookup, (path)) == 0);  \
60                 }, Eq(true)),                                           \
61                 _)                                                      \
62         )
63
64 extern int verbosity;
65
66 /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */
67 struct fuse_create_out {
68         struct fuse_entry_out   entry;
69         struct fuse_open_out    open;
70 };
71
72 union fuse_payloads_in {
73         fuse_access_in  access;
74         /* value is from fuse_kern_chan.c in fusefs-libs */
75         uint8_t         bytes[0x21000 - sizeof(struct fuse_in_header)];
76         fuse_flush_in   flush;
77         fuse_forget_in  forget;
78         fuse_init_in    init;
79         fuse_link_in    link;
80         char            lookup[0];
81         fuse_mkdir_in   mkdir;
82         fuse_mknod_in   mknod;
83         fuse_open_in    open;
84         fuse_read_in    read;
85         fuse_release_in release;
86         fuse_rename_in  rename;
87         char            rmdir[0];
88         fuse_setattr_in setattr;
89         char            unlink[0];
90         fuse_write_in   write;
91 };
92
93 struct mockfs_buf_in {
94         fuse_in_header          header;
95         union fuse_payloads_in  body;
96 };
97
98 union fuse_payloads_out {
99         fuse_attr_out           attr;
100         fuse_create_out         create;
101         /* The protocol places no limits on the size of bytes */
102         uint8_t                 bytes[0x2000];
103         fuse_entry_out          entry;
104         fuse_init_out           init;
105         fuse_open_out           open;
106         fuse_statfs_out         statfs;
107         /*
108          * The protocol places no limits on the length of the string.  This is
109          * merely convenient for testing.
110          */
111         char                    str[80];
112         fuse_write_out          write;
113 };
114
115 struct mockfs_buf_out {
116         fuse_out_header         header;
117         union fuse_payloads_out body;
118 };
119
120 /* A function that can be invoked in place of MockFS::process */
121 typedef std::function<void (const struct mockfs_buf_in *in,
122                             struct mockfs_buf_out *out)>
123 ProcessMockerT;
124
125 /*
126  * Helper function used for setting an error expectation for any fuse operation.
127  * The operation will return the supplied error
128  */
129 ProcessMockerT ReturnErrno(int error);
130
131 /* Helper function used for returning negative cache entries for LOOKUP */
132 std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
133 ReturnNegativeCache(const struct timespec *entry_valid);
134
135 /*
136  * Fake FUSE filesystem
137  *
138  * "Mounts" a filesystem to a temporary directory and services requests
139  * according to the programmed expectations.
140  *
141  * Operates directly on the fuse(4) kernel API, not the libfuse(3) user api.
142  */
143 class MockFS {
144         /*
145          * thread id of the fuse daemon thread
146          *
147          * It must run in a separate thread so it doesn't deadlock with the
148          * client test code.
149          */
150         pthread_t m_daemon_id;
151
152         /* file descriptor of /dev/fuse control device */
153         int m_fuse_fd;
154         
155         /* pid of the test process */
156         pid_t m_pid;
157
158         /* Initialize a session after mounting */
159         void init();
160
161         /* Is pid from a process that might be involved in the test? */
162         bool pid_ok(pid_t pid);
163
164         /* Default request handler */
165         void process_default(const mockfs_buf_in*, mockfs_buf_out*);
166
167         /* Entry point for the daemon thread */
168         static void* service(void*);
169
170         /* Read, but do not process, a single request from the kernel */
171         void read_request(mockfs_buf_in*);
172
173         public:
174         /* Maximum size of a FUSE_WRITE write */
175         uint32_t m_max_write;
176
177         /* Create a new mockfs and mount it to a tempdir */
178         MockFS();
179         virtual ~MockFS();
180
181         /* Kill the filesystem daemon without unmounting the filesystem */
182         void kill_daemon();
183
184         /* Process FUSE requests endlessly */
185         void loop();
186
187         /* 
188          * Request handler
189          *
190          * This method is expected to provide the response to each FUSE
191          * operation.  Responses must be immediate (so this method can't be used
192          * for testing a daemon with queue depth > 1).  Test cases must define
193          * each response using Googlemock expectations
194          */
195         MOCK_METHOD2(process, void(const mockfs_buf_in*, mockfs_buf_out*));
196
197         /* Gracefully unmount */
198         void unmount();
199 };