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