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