1 /* 2 * Copyright (C) Eugene 'Vindex' Stulin, 2023 3 * 4 * Distributed under 5 * the Boost Software License 1.0 or (at your option) 6 * the GNU Lesser General Public License 3.0 or later. 7 * This file is offered as-is, without any warranty. 8 * 9 * Function descriptions were copied (and partially modified) 10 * from some header files of the FUSE library (licensed by GNU LGPL v2.1): 11 * 12 * This library was inspired by the dfuse library and uses some ideas from it: 13 * https://github.com/dlang-community/dfuse (licensed by BSL-1.0). 14 */ 15 16 /** 17 * Main module. 18 * Contains the FileSystem class and some useful functions. 19 */ 20 21 module oxfuse.oxfuse; 22 23 public import oxfuse.fusellw; 24 25 import std.algorithm : map; 26 import std.array : array; 27 import std.exception; 28 import std.format : format; 29 import std.string : toStringz, fromStringz; 30 import std.stdio; 31 32 import core.stdc.errno; 33 import core.stdc.stdio; 34 import core.stdc.stdlib; 35 import core.stdc.string : strcmp, memset, strerror, strncpy; 36 37 import core.thread : thread_attachThis, thread_detachThis; 38 39 alias ffi_t = fuse_file_info; 40 41 42 /******************************************************************************* 43 * Class with basic file operations for inheritance. 44 * Operation descriptions are taken from libfuse documentation 45 * with small modifications. 46 * 47 * File operations are marked with `@(null)` attribute. It means that they are 48 * not implemented and the final operation functions will be null pointers. 49 * When overriding file operations, you should not use these attributes 50 * (see examples). 51 */ 52 abstract class FileSystem { 53 54 /*************************************************************************** 55 * Initialize filesystem. 56 */ 57 void initialize(fuse_conn_info* conn, fuse_config* cfg) {} 58 59 /*************************************************************************** 60 * Clean up filesystem. 61 * 62 * Called on filesystem exit. 63 */ 64 void destroy() { 65 core.stdc.stdlib.exit(0); 66 } 67 68 /*************************************************************************** 69 * Get file attributes. 70 * 71 * Similar to stat(). The 'st_dev' and 'st_blksize' fields are 72 * ignored. The 'st_ino' field is ignored except if the 'use_ino' 73 * mount option is given. In that case it is passed to userspace, 74 * but libfuse and the kernel will still assign a different 75 * inode for internal use (called the "nodeid"). 76 * 77 * `fi` will always be null if the file is not currently open, but 78 * may also be null if the file is open. 79 */ 80 @(null) 81 stat_t getattr(string path, ffi_t* fi) { 82 throw new FuseException(ENOTSUP); 83 } 84 85 /*************************************************************************** 86 * Create a directory. 87 * 88 * Note that the mode argument may not have the type specification 89 * bits set, i.e. S_ISDIR(mode) can be false. 90 * To obtain the correct directory type bits use mode|S_IFDIR 91 */ 92 @(null) 93 void mkdir(string path, mode_t mode) {} 94 95 /*************************************************************************** 96 * Create a file node. 97 * 98 * This is called for creation of all non-directory, non-symlink nodes. 99 * If the filesystem defines a create() method, 100 * then for regular files that will be called instead. 101 */ 102 @(null) 103 void mknod(string path, mode_t mode, dev_t dev) {} 104 105 /*************************************************************************** 106 * Create and open a file. 107 * 108 * If the file does not exist, first create it with the specified 109 * mode, and then open it. If the file exists, open() is called. 110 * 111 * If this method is not implemented or under Linux kernel 112 * versions earlier than 2.6.15, the mknod() and open() methods 113 * will be called instead. 114 */ 115 @(null) 116 void create(string path, mode_t mode, ffi_t* fi) {} 117 118 /*************************************************************************** 119 * Create a symbolic link. 120 */ 121 @(null) 122 void symlink(string src, string dst) {} 123 124 /*************************************************************************** 125 * Read the target of a symbolic link. 126 * 127 * The buffer should be filled with a null terminated string. 128 * The buffer size argument includes the space for the terminating 129 * null character. If the linkname is too long to fit in the buffer, 130 * it should be truncated. 131 */ 132 @(null) 133 string readlink(string path) { 134 throw new FuseException(ENOTSUP); 135 } 136 137 /*************************************************************************** 138 * Create a hard link to a file. 139 */ 140 @(null) 141 void link(string src, string dst) {} 142 143 /*************************************************************************** 144 * Check file access permissions. 145 * 146 * This will be called for the access() system call. 147 * If the 'default_permissions' mount option is given, 148 * this method is not called. 149 * 150 * This method is not called under Linux kernel versions 2.4.x. 151 */ 152 @(null) 153 bool access(string path, int mode) { 154 throw new FuseException(ENOTSUP); 155 } 156 157 /*************************************************************************** 158 * Rename a file. 159 * 160 * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. 161 * If `RENAME_NOREPLACE` is specified, the filesystem must not 162 * overwrite *dst* if it exists and throw an error instead. 163 * If `RENAME_EXCHANGE` is specified, the filesystem must atomically 164 * exchange the two files, i.e. both must exist and neither may be deleted. 165 */ 166 @(null) 167 void rename(string src, string dst, uint flags) {} 168 169 /*************************************************************************** 170 * Remove a directory. 171 */ 172 @(null) 173 void rmdir(string path) {} 174 175 /*************************************************************************** 176 * Remove a file (hard link). 177 */ 178 @(null) 179 void unlink(string path) {} 180 181 /*************************************************************************** 182 * Open a file. 183 * 184 * Open flags are available in fi.flags. The following rules apply. 185 * 186 * - If O_CREAT is used and the file doesn't exist, create() is called. 187 * 188 * - Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) 189 * should be used by the filesystem to check if the operation is 190 * permitted. If the `-o default_permissions` mount option is 191 * given, this check is already done by the kernel before calling 192 * open() and may thus be omitted by the filesystem. 193 * 194 * Filesystem may store an arbitrary file handle (pointer, 195 * index, etc.) in fi.fh, and use this in other all other file 196 * operations (read, write, flush, release, fsync). 197 * 198 * Filesystem may also implement stateless file I/O and not store 199 * anything in fi.fh. 200 * 201 * There are also some flags (direct_io, keep_cache) which the 202 * filesystem may set in fi, to change the way the file is opened. 203 * See fuse_file_info (ffi_t) structure for more details. 204 * 205 * If this request is answered with an error code of ENOTSUP 206 * and FUSE_CAP_NO_OPEN_SUPPORT is set in 207 * `fuse_conn_info.capable`, this is treated as success and 208 * future calls to open will also succeed without being send 209 * to the filesystem process. 210 */ 211 @(null) 212 void open(string path, ffi_t* fi) {} 213 214 /*************************************************************************** 215 * Write data to an open file. 216 * 217 * Write should return exactly the number of bytes requested 218 * except on error. An exception to this is when the 'direct_io' 219 * mount option is specified (see read operation). 220 * 221 * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is 222 * expected to reset the setuid and setgid bits. 223 */ 224 @(null) 225 int write(string path, const ubyte* data, size_t size, off_t off, ffi_t* fi) 226 do { 227 throw new FuseException(ENOTSUP); 228 } 229 230 /*************************************************************************** 231 * Read data from an open file. 232 * 233 * Read should return exactly the number of bytes requested except 234 * on EOF or error, otherwise the rest of the data will be 235 * substituted with zeroes. An exception to this is when the 236 * 'direct_io' mount option is specified, in which case the return 237 * value of the read system call will reflect the return value of 238 * this operation. 239 */ 240 @(null) 241 int read(string path, ubyte* buff, size_t, off_t off, ffi_t* fi) { 242 throw new FuseException(ENOTSUP); 243 } 244 245 /*************************************************************************** 246 * Find next data or hole after the specified offset. 247 */ 248 @(null) 249 off_t lseek(string path, off_t off, int whence, ffi_t* fi) { 250 throw new FuseException(ENOTSUP); 251 } 252 253 /*************************************************************************** 254 * Change the size of a file. 255 * 256 * `fi` will always be NULL if the file is not currenlty open, but 257 * may also be NULL if the file is open. 258 * 259 * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is 260 * expected to reset the setuid and setgid bits. 261 */ 262 @(null) 263 void truncate(string path, off_t off, ffi_t* fi) {} 264 265 /*************************************************************************** 266 * Possibly flush cached data. 267 * 268 * This is not equivalent to fsync(). It's not a request to sync dirty data. 269 * 270 * Flush is called on each close() of a file descriptor, as opposed to 271 * release which is called on the close of the last file descriptor for 272 * a file. Under Linux, errors returned by flush() will be passed to 273 * userspace as errors from close(), so flush() is a good place to write 274 * back any cached dirty data. However, many applications ignore errors 275 * on close(), and on non-Linux systems, close() may succeed even if flush() 276 * returns an error. For these reasons, filesystems should not assume 277 * that errors returned by flush will ever be noticed or even 278 * delivered. 279 * 280 * Note: 281 * The flush() method may be called more than once for each 282 * open(). This happens if more than one file descriptor refers to an 283 * open file handle, e.g. due to dup(), dup2() or fork() calls. It is 284 * not possible to determine if a flush is final, so each flush should 285 * be treated equally. Multiple write-flush sequences are relatively 286 * rare, so this shouldn't be a problem. 287 * 288 * Filesystems shouldn't assume that flush will be called at any 289 * particular point. It may be called more times than expected, or not 290 * at all. 291 * 292 * See_Also: 293 * `http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html` 294 */ 295 @(null) 296 void flush(string path, ffi_t* fi) {} 297 298 /*************************************************************************** 299 * Release an open file. 300 * 301 * Release is called when there are no more references to an open file: 302 * all file descriptors are closed and all memory mappings are unmapped. 303 * 304 * For every open() call there will be exactly one release() call with the 305 * same flags and file handle. It is possible to have a fiel opened more 306 * than once, in which case only the last release will mean, that no more 307 * reads/writes will happen on the file. 308 */ 309 @(null) 310 void release(string path, ffi_t* fi) {} 311 312 /*************************************************************************** 313 * Open directory. 314 * 315 * Unless the 'default_permissions' mount option is given, 316 * this method should check if opendir is permitted for this 317 * directory. Optionally opendir may also return an arbitrary 318 * filehandle in the ffi_t structure (fi.fh), which will be 319 * passed to readdir, releasedir and fsyncdir. 320 */ 321 @(null) 322 void opendir(string path, ffi_t* fi) {} 323 324 /*************************************************************************** 325 * Read directory. 326 */ 327 @(null) 328 string[] readdir(string path, ffi_t* fi) { 329 throw new FuseException(ENOTSUP); 330 } 331 332 /*************************************************************************** 333 * Release directory. Corrensponds to 'closedir' system call. 334 */ 335 @(null) 336 void releasedir(string path, ffi_t* fi) {} 337 338 /*************************************************************************** 339 * Change the permission bits of a file. 340 * 341 * `fi` will always be NULL if the file is not currenlty open, but 342 * may also be null if the file is open. 343 */ 344 @(null) 345 void chmod(string path, mode_t mode, ffi_t* fi) {} 346 347 /*************************************************************************** 348 * Change the owner and group of a file. 349 * 350 * `fi` will always be null if the file is not currenlty open, but 351 * may also be null if the file is open. 352 * 353 * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is 354 * expected to reset the setuid and setgid bits. 355 */ 356 @(null) 357 void chown(string path, uid_t uid, gid_t gid, ffi_t* fi) {} 358 359 /*************************************************************************** 360 * Synchronize file contents. 361 * 362 * If the datasync parameter is non-zero, then only the user data 363 * should be flushed, not the meta data. 364 */ 365 @(null) 366 void fsync(string path, int sync, ffi_t* fi) {} 367 368 /*************************************************************************** 369 * Synchronize directory contents. 370 * 371 * If the datasync parameter is non-zero, then only the user data 372 * should be flushed, not the meta data. 373 */ 374 @(null) 375 void fsyncdir(string path, int sync, ffi_t* fi) {} 376 377 /*************************************************************************** 378 * Change the access and modification times of a file with 379 * nanosecond resolution. 380 * 381 * This supersedes the old utime() interface. New applications 382 * should use this. 383 * 384 * `fi` will always be NULL if the file is not currenlty open, but 385 * may also be NULL if the file is open. 386 * 387 * See the utimensat(2) man page for details. 388 */ 389 @(null) 390 void utimens(string path, ref const(timespec)[2] tv, ffi_t* fi) {} 391 392 /*************************************************************************** 393 * Perform BSD file locking operation. 394 * 395 * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN. 396 * 397 * Nonblocking requests will be indicated by ORing LOCK_NB to 398 * the above operations. 399 * 400 * For more information see the flock(2) manual page. 401 * 402 * Additionally fi.owner will be set to a value unique to 403 * this open file. This same value will be supplied to 404 * release() when the file is released. 405 * 406 * Note: if this method is not implemented, the kernel will still 407 * allow file locking to work locally. Hence it is only 408 * interesting for network filesystems and similar. 409 */ 410 @(null) 411 void flock(string path, ffi_t* fi, int op) {} 412 413 /*************************************************************************** 414 * Allocates space for an open file. 415 * 416 * This function ensures that required space is allocated for specified 417 * file. If this function succeeds then any subsequent write 418 * request to specified range is guaranteed not to fail because of lack 419 * of space on the file system media. 420 */ 421 @(null) 422 void fallocate(string path, int mode, off_t off, off_t len, ffi_t* fi) {} 423 424 /*************************************************************************** 425 * Get file system statistics. 426 */ 427 @(null) 428 statvfs_t statfs(string path) { 429 throw new FuseException(ENOTSUP); 430 } 431 432 /*************************************************************************** 433 * Ioctl. 434 * 435 * flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in 436 * 64bit environment. The size and direction of data is 437 * determined by _IOC_*() decoding of cmd. For _IOC_NONE, 438 * data will be NULL, for _IOC_WRITE data is out area, for 439 * _IOC_READ in area and if both are set in/out area. 440 * In all non-NULL cases, the area is of _IOC_SIZE(cmd) bytes. 441 * 442 * If flags has FUSE_IOCTL_DIR then the ffi_t refers to a 443 * directory file handle. 444 * 445 * Note: the unsigned long request submitted by the application 446 * is truncated to 32 bits. 447 */ 448 @(null) 449 void ioctl(string p, uint cmd, void* arg, ffi_t* fi, uint flags, void* data) 450 do {} 451 452 /*************************************************************************** 453 * Set extended attributes. 454 */ 455 @(null) 456 void setxattr(string path, string k, string v, int flags) {} 457 458 /*************************************************************************** 459 * Get extended attributes. 460 */ 461 @(null) 462 string getxattr(string p, string k) { 463 throw new FuseException(ENOTSUP); 464 } 465 466 /*************************************************************************** 467 * List extended attributes. 468 */ 469 @(null) 470 string[] listxattr(string path) { 471 throw new FuseException(ENOTSUP); 472 } 473 474 475 /*************************************************************************** 476 * Remove extended attributes. 477 */ 478 @(null) 479 void removexattr(string path, string key) {} 480 481 /*************************************************************************** 482 * Perform POSIX file locking operation. 483 * 484 * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW. 485 * 486 * For the meaning of fields in 'struct flock' see the man page 487 * for fcntl(2). The l_whence field will always be set to 488 * SEEK_SET. 489 * 490 * For checking lock ownership, the 'fi.owner' argument must be used. 491 * 492 * For F_GETLK operation, the library will first check currently 493 * held locks, and if a conflicting lock is found it will return 494 * information without calling this method. 495 * This ensures, that for local locks the l_pid field is correctly 496 * filled in. The results may not be accurate in case of race conditions 497 * and in the presence of hard links, but it's unlikely that an 498 * application would rely on accurate GETLK results in these 499 * cases. If a conflicting lock is not found, this method will be 500 * called, and the filesystem may fill out l_pid by a meaningful 501 * value, or it may leave this field zero. 502 * 503 * For F_SETLK and F_SETLKW the l_pid field will be set to the pid 504 * of the process performing the locking operation. 505 * 506 * Note: if this method is not implemented, the kernel will still 507 * allow file locking to work locally. Hence it is only 508 * interesting for network filesystems and similar. 509 */ 510 @(null) 511 void lock(string path, ffi_t* fi, int cmd, flock_t* flock) {} 512 513 /*************************************************************************** 514 * Map block index within file to block index within device. 515 * 516 * Note: This makes sense only for block device backed filesystems 517 * mounted with the 'blkdev' option. 518 */ 519 @(null) 520 void bmap(string path, size_t blocksize, ulong* idx) {} 521 522 /*************************************************************************** 523 * Poll for IO readiness events. 524 * 525 * Note: If ph is non-NULL, the client should notify 526 * when IO readiness events occur by calling 527 * fuse_notify_poll() with the specified ph. 528 * 529 * Regardless of the number of times poll with a non-NULL ph 530 * is received, single notification is enough to clear all. 531 * Notifying more times incurs overhead but doesn't harm 532 * correctness. 533 * 534 * The callee is responsible for destroying ph with 535 * $(LLW_REF fuse_pollhandle_destroy) when no longer in use. 536 */ 537 @(null) 538 void poll(string path, ffi_t* fi, fuse_pollhandle* ph, uint* reventsp) {} 539 540 /*************************************************************************** 541 * Copy a range of data from one file to another. 542 * 543 * Performs an optimized copy between two file descriptors without the 544 * additional cost of transferring data through the FUSE kernel module 545 * to user space (glibc) and then back into the FUSE filesystem again. 546 * 547 * In case this method is not implemented, applications are expected to 548 * fall back to a regular file copy. 549 */ 550 @(null) 551 ssize_t copy_file_range( 552 string path_in, 553 ffi_t* fi_in, 554 off_t offset_in, 555 string path_out, 556 ffi_t* fi_out, 557 off_t offset_out, 558 size_t size, 559 int flags 560 ) { 561 throw new FuseException(ENOTSUP); 562 } 563 564 /*************************************************************************** 565 * Called for FuseException containg errno. 566 * It can be used for additional logging. 567 */ 568 void handleFuseException(FuseException e) nothrow {} 569 570 /*************************************************************************** 571 * Called for unknown exception cases (if errno is 0) 572 * during the execution of any file operation. 573 * The best thing to do is print the error. 574 */ 575 void handleException(Exception e) nothrow {} 576 577 /*************************************************************************** 578 * Mount point is a part of an instance of a class that needs to be set 579 * before mounting. 580 */ 581 void setMountPoint(string mountPoint) { 582 this.mountPoint = mountPoint; 583 } 584 585 /*************************************************************************** 586 * Returns current value of mount point path. 587 */ 588 string getMountPoint() { 589 return this.mountPoint; 590 } 591 592 protected string mountPoint; 593 } 594 595 596 /******************************************************************************* 597 * Mounts user file system to the designated location. 598 * Template parameter `FS` is a file system class (real, not abstract). 599 * Params: 600 * fsName = File system name. 601 * fs = Object of file system. For the file system must first be 602 * assigned a mount point with setMountPoint(). 603 * fuseArgs = Options for FUSE (without FS name and without mount point), 604 * like -d, -f, -o autounmount, -o uid=UID, etc. 605 * The complete list is available by calling showFuseHelp(). 606 */ 607 int mount(FS)(string fsName, FileSystem fs, string[] fuseArgs = null) 608 if (is(FS : FileSystem)) { 609 string[] args = [fsName, fs.getMountPoint()] ~ fuseArgs; 610 auto argv = map!(a => a.toStringz)(args).array; 611 int argc = cast(int)(argv.length); 612 auto operations = getFileOperations!FS(); 613 return fuse_main(argc, cast(char**)argv.ptr, &operations, &fs); 614 } 615 616 617 /******************************************************************************* 618 * Prints help for FUSE. 619 */ 620 void showFuseHelp() { 621 string[] args = ["", ".", "--help"]; 622 auto argv = map!(a => a.toStringz)(args).array; 623 int argc = cast(int)(argv.length); 624 int ret = fuse_main(argc, cast(char**)argv.ptr, null, null); 625 assert(ret == 0); 626 } 627 628 629 /******************************************************************************* 630 * Exception to pass errno. 631 * This exception is the main way to convey error information 632 * in any file operation. 633 */ 634 class FuseException : Exception { 635 int err; 636 637 this(int err, 638 string message = "", 639 string file = __FILE__, 640 size_t line = __LINE__) 641 do { 642 super(message, file, line); 643 this.err = err; 644 errno = this.err; 645 } 646 } 647 648 /// enforce for FuseException 649 pragma(inline, true) 650 T fe(T)(T value, int code, string message = "") { 651 if (!value) { 652 throw new FuseException(code, message); 653 } 654 return value; 655 } 656 pragma(inline, true) 657 void fe(int code, string message = "") { 658 throw new FuseException(code, message); 659 } 660 661 662 private: 663 664 665 static bool threadAttached = false; 666 667 void detach() nothrow { 668 thread_detachThis(); 669 threadAttached = false; 670 } 671 672 673 void attach() nothrow { 674 if (threadAttached) return; 675 collectException(thread_attachThis()); 676 threadAttached = true; 677 } 678 679 680 immutable errFmt = "\nException: %s:%s '%s'. %s"; 681 682 extern(C) { 683 void* call_init(fuse_conn_info* conn, fuse_config* cfg) 684 nothrow { 685 attach(); 686 scope(exit) detach(); 687 auto o = cast(FileSystem*) fuse_get_context().private_data; 688 try { 689 (*o).initialize(conn, cfg); 690 } catch (FuseException e) { 691 string errnoMsg = (e.err != 0) ? getInfoAboutError(e.err) : "?"; 692 string msg; 693 try { 694 msg = format!errFmt(e.file, e.line, errnoMsg, e.msg); 695 } catch (Exception) {} 696 assert(false, msg); 697 } catch (Exception e) { 698 string msg; 699 try { 700 msg = format!"\nError: %s:%s > '%s'"(e.file, e.line, e.msg); 701 } catch (Exception) {} 702 assert(false, msg); 703 } 704 return o; 705 } 706 707 int call_getattr(const char* path, stat_t* st, ffi_t* fi) 708 nothrow { 709 return callOperation!( 710 (FileSystem o) { 711 string strPath = path.fromStringz.idup; 712 stat_t statInfo = o.getattr(strPath, fi); 713 *st = statInfo; 714 return 0; 715 } 716 )(); 717 } 718 719 int call_mkdir(const char* path, mode_t mode) 720 nothrow { 721 return callOperation!( 722 (FileSystem o) { 723 o.mkdir(path.fromStringz.idup, mode); 724 return 0; 725 } 726 )(); 727 } 728 729 int call_mknod(const char* path, mode_t mode, dev_t dev) 730 nothrow { 731 return callOperation!( 732 (FileSystem o) { 733 o.mknod(path.fromStringz.idup, mode, dev); 734 return 0; 735 } 736 )(); 737 } 738 739 int call_create(const char* path, mode_t mode, ffi_t* fi) 740 nothrow { 741 return callOperation!( 742 (FileSystem o) { 743 o.create(path.fromStringz.idup, mode, fi); 744 return 0; 745 } 746 )(); 747 } 748 749 int call_symlink(const char* src, const char* dst) 750 nothrow { 751 return callOperation!( 752 (FileSystem o) { 753 o.symlink(src.fromStringz.idup, dst.fromStringz.idup); 754 return 0; 755 } 756 )(); 757 } 758 759 int call_readlink(const char* path, char* buff, size_t size) 760 nothrow { 761 return callOperation!( 762 (FileSystem o) { 763 if (size == 0) { 764 return 0; 765 } 766 import core.stdc.string : strncpy; 767 string destination = o.readlink(path.fromStringz.idup); 768 strncpy(buff, destination.toStringz, size); 769 return 0; 770 } 771 )(); 772 } 773 774 int call_link(const char* src, const char* dst) 775 nothrow { 776 return callOperation!( 777 (FileSystem o) { 778 o.link(src.fromStringz.idup, dst.fromStringz.idup); 779 return 0; 780 } 781 )(); 782 } 783 784 int call_access(const char* path, int mode) 785 nothrow { 786 return callOperation!( 787 (FileSystem o) { 788 if (o.access(path.fromStringz.idup, mode)) { 789 return 0; 790 } 791 return -errno; 792 } 793 )(); 794 } 795 796 int call_rename(const char* src, const char* dst, uint flags) 797 nothrow { 798 return callOperation!( 799 (FileSystem o) { 800 o.rename(src.fromStringz.idup, dst.fromStringz.idup, flags); 801 return 0; 802 } 803 )(); 804 } 805 806 int call_rmdir(const char* path) 807 nothrow { 808 return callOperation!( 809 (FileSystem o) { 810 o.rmdir(path.fromStringz.idup); 811 return 0; 812 } 813 )(); 814 } 815 816 int call_unlink(const char* path) 817 nothrow { 818 return callOperation!( 819 (FileSystem o) { 820 o.unlink(path.fromStringz.idup); 821 return 0; 822 } 823 )(); 824 } 825 826 int call_open(const char* path, ffi_t* fi) 827 nothrow { 828 return callOperation!( 829 (FileSystem o) { 830 o.open(path.fromStringz.idup, fi); 831 return 0; 832 } 833 )(); 834 } 835 836 int call_write( 837 const char* path, const ubyte* data, size_t size, off_t off, ffi_t* fi 838 ) 839 nothrow { 840 return callOperation!( 841 (FileSystem o) { 842 return o.write(path.fromStringz.idup, data, size, off, fi); 843 } 844 )(); 845 } 846 847 int call_read( 848 const char* path, ubyte* buff, size_t size, off_t off, ffi_t* fi 849 ) 850 nothrow { 851 return callOperation!( 852 (FileSystem o) { 853 return o.read(path.fromStringz.idup, buff, size, off, fi); 854 } 855 )(); 856 } 857 858 off_t call_lseek(const char* path, off_t off, int whence, ffi_t* fi) 859 nothrow { 860 return callOperation!( 861 (FileSystem o) { 862 return o.lseek(path.fromStringz.idup, off, whence, fi); 863 } 864 )(); 865 } 866 867 int call_truncate(const char* path, off_t off, ffi_t* fi) nothrow { 868 return callOperation!( 869 (FileSystem o) { 870 o.truncate(path.fromStringz.idup, off, fi); 871 return 0; 872 } 873 )(); 874 } 875 876 int call_flush(const char* path, ffi_t* fi) nothrow { 877 return callOperation!( 878 (FileSystem o) { 879 o.flush(path.fromStringz.idup, fi); 880 return 0; 881 } 882 )(); 883 } 884 885 int call_release(const char* path, ffi_t* fi) nothrow { 886 return callOperation!( 887 (FileSystem o) { 888 o.release(path.fromStringz.idup, fi); 889 return 0; 890 } 891 )(); 892 } 893 894 int call_opendir(const char* path, ffi_t* fi) nothrow { 895 return callOperation!( 896 (FileSystem o) { 897 o.opendir(path.fromStringz.idup, fi); 898 return 0; 899 } 900 )(); 901 } 902 903 int call_readdir( 904 const char* path, 905 void* buf, 906 fuse_fill_dir_t filler, 907 off_t offset, 908 ffi_t* fi, 909 fuse_readdir_flags flags 910 ) 911 nothrow { 912 return callOperation!( 913 (FileSystem o) { 914 string strPath = path.fromStringz.idup; 915 string[] files = o.readdir(path.fromStringz.idup, fi); 916 foreach(file; files) { 917 filler(buf, cast(char*)file.toStringz, null, 0, 0); 918 } 919 return 0; 920 } 921 )(); 922 } 923 924 int call_releasedir(const char* path, ffi_t* fi) nothrow { 925 return callOperation!( 926 (FileSystem o) { 927 o.releasedir(path.fromStringz.idup, fi); 928 return 0; 929 } 930 )(); 931 } 932 933 int call_chmod(const char* path, mode_t mode, ffi_t* fi) nothrow { 934 return callOperation!( 935 (FileSystem o) { 936 o.chmod(path.fromStringz.idup, mode, fi); 937 return 0; 938 } 939 )(); 940 } 941 942 int call_chown(const char* path, uid_t uid, gid_t gid, ffi_t* fi) nothrow { 943 return callOperation!( 944 (FileSystem o) { 945 o.chown(path.fromStringz.idup, uid, gid, fi); 946 return 0; 947 } 948 )(); 949 } 950 951 int call_fsync(const char* path, int sync, ffi_t* fi) nothrow { 952 return callOperation!( 953 (FileSystem o) { 954 o.fsync(path.fromStringz.idup, sync, fi); 955 return 0; 956 } 957 )(); 958 } 959 960 int call_fsyncdir(const char* path, int sync, ffi_t* fi) nothrow { 961 return callOperation!( 962 (FileSystem o) { 963 o.fsyncdir(path.fromStringz.idup, sync, fi); 964 return 0; 965 } 966 )(); 967 } 968 969 int call_utimens(const char* path, ref const(timespec)[2] tv, ffi_t* fi) 970 nothrow { 971 return callOperation!( 972 (FileSystem o) { 973 o.utimens(path.fromStringz.idup, tv, fi); 974 return 0; 975 } 976 )(); 977 } 978 979 int call_flock(const char* path, ffi_t* fi, int op) nothrow { 980 return callOperation!( 981 (FileSystem o) { 982 o.flock(path.fromStringz.idup, fi, op); 983 return 0; 984 } 985 )(); 986 } 987 988 int call_fallocate( 989 const char* path, int mode, off_t off, off_t len, ffi_t* fi 990 ) 991 nothrow { 992 return callOperation!( 993 (FileSystem o) { 994 o.fallocate(path.fromStringz.idup, mode, off, len, fi); 995 return 0; 996 } 997 )(); 998 } 999 1000 int call_statfs(const char* path, statvfs_t* statvfs) 1001 nothrow { 1002 return callOperation!( 1003 (FileSystem o) { 1004 *statvfs = o.statfs(path.fromStringz.idup); 1005 return 0; 1006 } 1007 )(); 1008 } 1009 1010 int call_ioctl( 1011 const char* path, uint cmd, void* arg, ffi_t* fi, uint flags, void* data 1012 ) 1013 nothrow { 1014 return callOperation!( 1015 (FileSystem o) { 1016 o.ioctl(path.fromStringz.idup, cmd, arg, fi, flags, data); 1017 return 0; 1018 } 1019 )(); 1020 } 1021 1022 int call_setxattr( 1023 const char* path, const char* k, const char* v, size_t vsize, int flags 1024 ) 1025 nothrow { 1026 return callOperation!( 1027 (FileSystem o) { 1028 string key = k.fromStringz.idup; 1029 string value = v[0 .. vsize].idup; 1030 o.setxattr(path.fromStringz.idup, key, value, flags); 1031 return 0; 1032 } 1033 )(); 1034 } 1035 1036 int call_getxattr(const char* p, const char* k, char* buff, size_t size) 1037 nothrow { 1038 return callOperation!( 1039 (FileSystem o) { 1040 string path = p.fromStringz.idup; 1041 string key = k.fromStringz.idup; 1042 string value = o.getxattr(path, key); 1043 memset(buff, '\0', size); 1044 strncpy(buff, value.ptr, size-1); 1045 return 0; 1046 } 1047 )(); 1048 } 1049 1050 int call_listxattr(const char* path, char* list, size_t size) 1051 nothrow { 1052 return callOperation!( 1053 (FileSystem o) { 1054 string p = path.fromStringz.idup; 1055 string[] attrs = o.listxattr(p); 1056 size_t pos = 0; 1057 foreach(attr; attrs) { 1058 char[] chars = attr.dup ~ '\0'; 1059 foreach(ch; chars) { 1060 if (size == pos) { 1061 return 0; 1062 } 1063 list[pos] = ch; 1064 pos++; 1065 } 1066 } 1067 return 0; 1068 } 1069 )(); 1070 } 1071 1072 int call_removexattr(const char* path, const char* key) 1073 nothrow { 1074 return callOperation!( 1075 (FileSystem o) { 1076 string p = path.fromStringz.idup; 1077 string k = key.fromStringz.idup; 1078 o.removexattr(p, k); 1079 return 0; 1080 } 1081 )(); 1082 } 1083 1084 int call_lock(const char* path, ffi_t* fi, int cmd, flock_t* flock) 1085 nothrow { 1086 return callOperation!( 1087 (FileSystem o) { 1088 o.lock(path.fromStringz.idup, fi, cmd, flock); 1089 return 0; 1090 } 1091 )(); 1092 } 1093 1094 int call_bmap(const char* path, size_t blocksize, ulong* idx) 1095 nothrow { 1096 return callOperation!( 1097 (FileSystem o) { 1098 o.bmap(path.fromStringz.idup, blocksize, idx); 1099 return 0; 1100 } 1101 )(); 1102 } 1103 1104 int call_poll( 1105 const char* path, ffi_t* fi, fuse_pollhandle* ph, uint* reventsp 1106 ) 1107 nothrow { 1108 return callOperation!( 1109 (FileSystem o) { 1110 o.poll(path.fromStringz.idup, fi, ph, reventsp); 1111 return 0; 1112 } 1113 )(); 1114 } 1115 1116 ssize_t call_copy_file_range( 1117 const char* path_in, 1118 ffi_t* fi_in, 1119 off_t offset_in, 1120 const char* path_out, 1121 ffi_t* fi_out, 1122 off_t offset_out, 1123 size_t size, 1124 int flags 1125 ) 1126 nothrow { 1127 return callOperation!( 1128 (FileSystem o) { 1129 return o.copy_file_range( 1130 path_in.fromStringz.idup, 1131 fi_in, 1132 offset_in, 1133 path_out.fromStringz.idup, 1134 fi_out, 1135 offset_out, 1136 size, 1137 flags 1138 ); 1139 } 1140 )(); 1141 } 1142 1143 void call_destroy(void* private_data) 1144 nothrow { 1145 perf!((FileSystem o) => o.destroy())(); 1146 } 1147 1148 } 1149 1150 1151 void assignOperationPointer(FS, alias opname)(ref fuse_operations operations) { 1152 enum fnUDA = __traits(getAttributes, mixin("FS." ~ opname)); 1153 static if (fnUDA.length == 1) { 1154 static if (fnUDA[0] == null) { 1155 mixin("operations." ~ opname) = null; 1156 } else { 1157 mixin("operations." ~ opname) = mixin("&call_" ~ opname); 1158 } 1159 } else { 1160 mixin("operations." ~ opname) = mixin("&call_" ~ opname); 1161 } 1162 } 1163 1164 1165 fuse_operations getFileOperations(FS)() if (is(FS : FileSystem)) { 1166 fuse_operations operations; 1167 operations.init = &call_init; 1168 operations.destroy = &call_destroy; 1169 assignOperationPointer!(FS, "getattr")(operations); 1170 assignOperationPointer!(FS, "readlink")(operations); 1171 assignOperationPointer!(FS, "mknod")(operations); 1172 assignOperationPointer!(FS, "mkdir")(operations); 1173 assignOperationPointer!(FS, "unlink")(operations); 1174 assignOperationPointer!(FS, "rmdir")(operations); 1175 assignOperationPointer!(FS, "symlink")(operations); 1176 assignOperationPointer!(FS, "rename")(operations); 1177 assignOperationPointer!(FS, "link")(operations); 1178 assignOperationPointer!(FS, "chmod")(operations); 1179 assignOperationPointer!(FS, "chown")(operations); 1180 assignOperationPointer!(FS, "truncate")(operations); 1181 assignOperationPointer!(FS, "open")(operations); 1182 assignOperationPointer!(FS, "read")(operations); 1183 assignOperationPointer!(FS, "write")(operations); 1184 assignOperationPointer!(FS, "statfs")(operations); 1185 assignOperationPointer!(FS, "flush")(operations); 1186 assignOperationPointer!(FS, "release")(operations); 1187 assignOperationPointer!(FS, "fsync")(operations); 1188 assignOperationPointer!(FS, "setxattr")(operations); 1189 assignOperationPointer!(FS, "getxattr")(operations); 1190 assignOperationPointer!(FS, "listxattr")(operations); 1191 assignOperationPointer!(FS, "removexattr")(operations); 1192 assignOperationPointer!(FS, "opendir")(operations); 1193 assignOperationPointer!(FS, "readdir")(operations); 1194 assignOperationPointer!(FS, "releasedir")(operations); 1195 assignOperationPointer!(FS, "fsyncdir")(operations); 1196 assignOperationPointer!(FS, "access")(operations); 1197 assignOperationPointer!(FS, "create")(operations); 1198 assignOperationPointer!(FS, "lock")(operations); 1199 assignOperationPointer!(FS, "utimens")(operations); 1200 assignOperationPointer!(FS, "bmap")(operations); 1201 assignOperationPointer!(FS, "ioctl")(operations); 1202 assignOperationPointer!(FS, "poll")(operations); 1203 assignOperationPointer!(FS, "flock")(operations); 1204 assignOperationPointer!(FS, "fallocate")(operations); 1205 assignOperationPointer!(FS, "copy_file_range")(operations); 1206 assignOperationPointer!(FS, "lseek")(operations); 1207 operations.write_buf = null; // not used in oxfuse 1208 operations.read_buf = null; // not used in oxfuse 1209 return operations; 1210 } 1211 1212 1213 auto callOperation(alias fn)() nothrow { 1214 attach(); 1215 scope(exit) detach(); 1216 auto o = cast(FileSystem*)fuse_get_context().private_data; 1217 try { 1218 return fn(*o); 1219 } catch (FuseException fuseExc) { 1220 (*o).handleFuseException(fuseExc); 1221 return -fuseExc.err; 1222 } catch (Exception e) { 1223 if (errno != 0) { 1224 return -errno; 1225 } 1226 (*o).handleException(e); 1227 return -EIO; 1228 } 1229 } 1230 1231 1232 /// Function for performing operations of a filesystem object. 1233 auto perf(alias fn)() nothrow { 1234 attach(); 1235 scope(exit) detach(); 1236 auto o = cast(FileSystem*)fuse_get_context().private_data; 1237 static if (is(typeof(fn(*o)) == void)) { 1238 try fn(*o); 1239 catch (FuseException fuseExc) (*o).handleFuseException(fuseExc); 1240 catch (Exception e) (*o).handleException(e); 1241 } else { 1242 try { 1243 return fn(*o); 1244 } catch (FuseException fuseExc) { 1245 (*o).handleFuseException(fuseExc); 1246 return -fuseExc.err; 1247 } catch (Exception e) { 1248 if (errno != 0) { 1249 return -errno; 1250 } 1251 (*o).handleException(e); 1252 return -EIO; 1253 } 1254 } 1255 } 1256 1257 1258 1259 /******************************************************************************* 1260 * Returns string containing text view of an errno value. 1261 * (Copied from amalthea.libcore, see: 1262 * $(EXT_LINK https://gitlab.com/os-18/amalthea) 1263 */ 1264 string getInfoAboutError(int err) nothrow { 1265 import core.stdc.string : strerror; 1266 import std.string : fromStringz; 1267 return strerror(err).fromStringz.idup; 1268 } 1269