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 31 extern "C" { 32 #include <sys/types.h> 33 34 #include <pthread.h> 35 36 #include "fuse_kernel.h" 37 } 38 39 #include <gmock/gmock.h> 40 41 #define TIME_T_MAX (std::numeric_limits<time_t>::max()) 42 43 /* 44 * A pseudo-fuse errno used indicate that a fuse operation should have no 45 * response, at least not immediately 46 */ 47 #define FUSE_NORESPONSE 9999 48 49 #define SET_OUT_HEADER_LEN(out, variant) { \ 50 (out).header.len = (sizeof((out).header) + \ 51 sizeof((out).body.variant)); \ 52 } 53 54 /* 55 * Create an expectation on FUSE_LOOKUP and return it so the caller can set 56 * actions. 57 * 58 * This must be a macro instead of a method because EXPECT_CALL returns a type 59 * with a deleted constructor. 60 */ 61 #define EXPECT_LOOKUP(parent, path) \ 62 EXPECT_CALL(*m_mock, process( \ 63 ResultOf([=](auto in) { \ 64 return (in.header.opcode == FUSE_LOOKUP && \ 65 in.header.nodeid == (parent) && \ 66 strcmp(in.body.lookup, (path)) == 0); \ 67 }, Eq(true)), \ 68 _) \ 69 ) 70 71 extern int verbosity; 72 73 /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */ 74 struct fuse_create_out { 75 struct fuse_entry_out entry; 76 struct fuse_open_out open; 77 }; 78 79 /* Protocol 7.8 version of struct fuse_attr */ 80 struct fuse_attr_7_8 81 { 82 uint64_t ino; 83 uint64_t size; 84 uint64_t blocks; 85 uint64_t atime; 86 uint64_t mtime; 87 uint64_t ctime; 88 uint32_t atimensec; 89 uint32_t mtimensec; 90 uint32_t ctimensec; 91 uint32_t mode; 92 uint32_t nlink; 93 uint32_t uid; 94 uint32_t gid; 95 uint32_t rdev; 96 }; 97 98 /* Protocol 7.8 version of struct fuse_attr_out */ 99 struct fuse_attr_out_7_8 100 { 101 uint64_t attr_valid; 102 uint32_t attr_valid_nsec; 103 uint32_t dummy; 104 struct fuse_attr_7_8 attr; 105 }; 106 107 /* Protocol 7.8 version of struct fuse_entry_out */ 108 struct fuse_entry_out_7_8 { 109 uint64_t nodeid; /* Inode ID */ 110 uint64_t generation; /* Inode generation: nodeid:gen must 111 be unique for the fs's lifetime */ 112 uint64_t entry_valid; /* Cache timeout for the name */ 113 uint64_t attr_valid; /* Cache timeout for the attributes */ 114 uint32_t entry_valid_nsec; 115 uint32_t attr_valid_nsec; 116 struct fuse_attr_7_8 attr; 117 }; 118 119 /* Output struct for FUSE_CREATE for protocol 7.8 servers */ 120 struct fuse_create_out_7_8 { 121 struct fuse_entry_out_7_8 entry; 122 struct fuse_open_out open; 123 }; 124 125 /* Output struct for FUSE_INIT for protocol 7.22 and earlier servers */ 126 struct fuse_init_out_7_22 { 127 uint32_t major; 128 uint32_t minor; 129 uint32_t max_readahead; 130 uint32_t flags; 131 uint16_t max_background; 132 uint16_t congestion_threshold; 133 uint32_t max_write; 134 }; 135 136 union fuse_payloads_in { 137 fuse_access_in access; 138 fuse_bmap_in bmap; 139 /* value is from fuse_kern_chan.c in fusefs-libs */ 140 uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; 141 fuse_create_in create; 142 fuse_flush_in flush; 143 fuse_fsync_in fsync; 144 fuse_fsync_in fsyncdir; 145 fuse_forget_in forget; 146 fuse_interrupt_in interrupt; 147 fuse_lk_in getlk; 148 fuse_getxattr_in getxattr; 149 fuse_init_in init; 150 fuse_link_in link; 151 fuse_listxattr_in listxattr; 152 char lookup[0]; 153 fuse_mkdir_in mkdir; 154 fuse_mknod_in mknod; 155 fuse_open_in open; 156 fuse_open_in opendir; 157 fuse_read_in read; 158 fuse_read_in readdir; 159 fuse_release_in release; 160 fuse_release_in releasedir; 161 fuse_rename_in rename; 162 char rmdir[0]; 163 fuse_setattr_in setattr; 164 fuse_setxattr_in setxattr; 165 fuse_lk_in setlk; 166 fuse_lk_in setlkw; 167 char unlink[0]; 168 fuse_write_in write; 169 }; 170 171 struct mockfs_buf_in { 172 fuse_in_header header; 173 union fuse_payloads_in body; 174 }; 175 176 union fuse_payloads_out { 177 fuse_attr_out attr; 178 fuse_attr_out_7_8 attr_7_8; 179 fuse_bmap_out bmap; 180 fuse_create_out create; 181 fuse_create_out_7_8 create_7_8; 182 /* 183 * The protocol places no limits on the size of bytes. Choose 184 * a size big enough for anything we'll test. 185 */ 186 uint8_t bytes[0x20000]; 187 fuse_entry_out entry; 188 fuse_entry_out_7_8 entry_7_8; 189 fuse_lk_out getlk; 190 fuse_getxattr_out getxattr; 191 fuse_init_out init; 192 fuse_init_out_7_22 init_7_22; 193 /* The inval_entry structure should be followed by the entry's name */ 194 fuse_notify_inval_entry_out inval_entry; 195 fuse_notify_inval_inode_out inval_inode; 196 /* The store structure should be followed by the data to store */ 197 fuse_notify_store_out store; 198 fuse_listxattr_out listxattr; 199 fuse_open_out open; 200 fuse_statfs_out statfs; 201 /* 202 * The protocol places no limits on the length of the string. This is 203 * merely convenient for testing. 204 */ 205 char str[80]; 206 fuse_write_out write; 207 }; 208 209 struct mockfs_buf_out { 210 fuse_out_header header; 211 union fuse_payloads_out body; 212 213 /* Default constructor: zero everything */ 214 mockfs_buf_out() { 215 memset(this, 0, sizeof(*this)); 216 } 217 }; 218 219 /* A function that can be invoked in place of MockFS::process */ 220 typedef std::function<void (const mockfs_buf_in& in, 221 std::vector<std::unique_ptr<mockfs_buf_out>> &out)> 222 ProcessMockerT; 223 224 /* 225 * Helper function used for setting an error expectation for any fuse operation. 226 * The operation will return the supplied error 227 */ 228 ProcessMockerT ReturnErrno(int error); 229 230 /* Helper function used for returning negative cache entries for LOOKUP */ 231 ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid); 232 233 /* Helper function used for returning a single immediate response */ 234 ProcessMockerT ReturnImmediate( 235 std::function<void(const mockfs_buf_in& in, 236 struct mockfs_buf_out &out)> f); 237 238 /* How the daemon should check /dev/fuse for readiness */ 239 enum poll_method { 240 BLOCKING, 241 SELECT, 242 POLL, 243 KQ 244 }; 245 246 /* 247 * Fake FUSE filesystem 248 * 249 * "Mounts" a filesystem to a temporary directory and services requests 250 * according to the programmed expectations. 251 * 252 * Operates directly on the fusefs(4) kernel API, not the libfuse(3) user api. 253 */ 254 class MockFS { 255 /* 256 * thread id of the fuse daemon thread 257 * 258 * It must run in a separate thread so it doesn't deadlock with the 259 * client test code. 260 */ 261 pthread_t m_daemon_id; 262 263 /* file descriptor of /dev/fuse control device */ 264 int m_fuse_fd; 265 266 /* The minor version of the kernel API that this mock daemon targets */ 267 uint32_t m_kernel_minor_version; 268 269 int m_kq; 270 271 /* The max_readahead file system option */ 272 uint32_t m_maxreadahead; 273 274 /* pid of the test process */ 275 pid_t m_pid; 276 277 /* Method the daemon should use for I/O to and from /dev/fuse */ 278 enum poll_method m_pm; 279 280 /* Timestamp granularity in nanoseconds */ 281 unsigned m_time_gran; 282 283 void debug_request(const mockfs_buf_in&); 284 void debug_response(const mockfs_buf_out&); 285 286 /* Initialize a session after mounting */ 287 void init(uint32_t flags); 288 289 /* Is pid from a process that might be involved in the test? */ 290 bool pid_ok(pid_t pid); 291 292 /* Default request handler */ 293 void process_default(const mockfs_buf_in&, 294 std::vector<std::unique_ptr<mockfs_buf_out>>&); 295 296 /* Entry point for the daemon thread */ 297 static void* service(void*); 298 299 /* Read, but do not process, a single request from the kernel */ 300 void read_request(mockfs_buf_in& in); 301 302 /* Write a single response back to the kernel */ 303 void write_response(const mockfs_buf_out &out); 304 305 public: 306 /* pid of child process, for two-process test cases */ 307 pid_t m_child_pid; 308 309 /* Maximum size of a FUSE_WRITE write */ 310 uint32_t m_maxwrite; 311 312 /* 313 * Number of events that were available from /dev/fuse after the last 314 * kevent call. Only valid when m_pm = KQ. 315 */ 316 int m_nready; 317 318 /* Tell the daemon to shut down ASAP */ 319 bool m_quit; 320 321 /* Create a new mockfs and mount it to a tempdir */ 322 MockFS(int max_readahead, bool allow_other, 323 bool default_permissions, bool push_symlinks_in, bool ro, 324 enum poll_method pm, uint32_t flags, 325 uint32_t kernel_minor_version, uint32_t max_write, bool async, 326 bool no_clusterr, unsigned time_gran, bool nointr); 327 328 virtual ~MockFS(); 329 330 /* Kill the filesystem daemon without unmounting the filesystem */ 331 void kill_daemon(); 332 333 /* Process FUSE requests endlessly */ 334 void loop(); 335 336 /* 337 * Send an asynchronous notification to invalidate a directory entry. 338 * Similar to libfuse's fuse_lowlevel_notify_inval_entry 339 * 340 * This method will block until the client has responded, so it should 341 * generally be run in a separate thread from request processing. 342 * 343 * @param parent Parent directory's inode number 344 * @param name name of dirent to invalidate 345 * @param namelen size of name, including the NUL 346 */ 347 int notify_inval_entry(ino_t parent, const char *name, size_t namelen); 348 349 /* 350 * Send an asynchronous notification to invalidate an inode's cached 351 * data and/or attributes. Similar to libfuse's 352 * fuse_lowlevel_notify_inval_inode. 353 * 354 * This method will block until the client has responded, so it should 355 * generally be run in a separate thread from request processing. 356 * 357 * @param ino File's inode number 358 * @param off offset at which to begin invalidation. A 359 * negative offset means to invalidate attributes 360 * only. 361 * @param len Size of region of data to invalidate. 0 means 362 * to invalidate all cached data. 363 */ 364 int notify_inval_inode(ino_t ino, off_t off, ssize_t len); 365 366 /* 367 * Send an asynchronous notification to store data directly into an 368 * inode's cache. Similar to libfuse's fuse_lowlevel_notify_store. 369 * 370 * This method will block until the client has responded, so it should 371 * generally be run in a separate thread from request processing. 372 * 373 * @param ino File's inode number 374 * @param off Offset at which to store data 375 * @param data Pointer to the data to cache 376 * @param len Size of data 377 */ 378 int notify_store(ino_t ino, off_t off, const void* data, ssize_t size); 379 380 /* 381 * Request handler 382 * 383 * This method is expected to provide the responses to each FUSE 384 * operation. For an immediate response, push one buffer into out. 385 * For a delayed response, push nothing. For an immediate response 386 * plus a delayed response to an earlier operation, push two bufs. 387 * Test cases must define each response using Googlemock expectations 388 */ 389 MOCK_METHOD2(process, void(const mockfs_buf_in&, 390 std::vector<std::unique_ptr<mockfs_buf_out>>&)); 391 392 /* Gracefully unmount */ 393 void unmount(); 394 }; 395