Last active
January 11, 2025 04:28
-
-
Save saxbophone/772a881d66592e61d38bf3a1d06f89c7 to your computer and use it in GitHub Desktop.
A shim using a healthy dose of C++20 concepts and preprocessor macros to allow creating a FUSE filesystem driver using public static class methods
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstddef> | |
// #include <cstdint> | |
#include <concepts> | |
#include <type_traits> | |
#define FUSE_USE_VERSION 29 | |
#define _FILE_OFFSET_BITS 64 | |
#include <fuse.h> | |
// // NOTE: Rather than sort out actually linking to libfuse, instead we just provide | |
// // definitions of the minimum number of fuse types required for this demo to work | |
// extern "C" { | |
// // XXX: dummy definitions | |
// struct fuse_file_info; | |
// struct stat; | |
// using mode_t = int; | |
// using dev_t = int; | |
// using uid_t = int; | |
// using gid_t = int; | |
// using off_t = int; | |
// struct statvfs; | |
// struct fuse_fill_dir_t; | |
// enum fuse_readdir_flags : int; | |
// struct fuse_conn_info; | |
// struct fuse_config; | |
// struct timespec; | |
// struct fuse_pollhandle; | |
// struct fuse_bufvec; | |
// using ssize_t = std::make_signed<size_t>; | |
// struct fuse_operations { | |
// int(* getattr )(const char *, struct stat *, struct fuse_file_info *fi); | |
// int(* readlink )(const char *, char *, size_t); | |
// int(* mknod )(const char *, mode_t, dev_t); | |
// int(* mkdir )(const char *, mode_t); | |
// int(* unlink )(const char *); | |
// int(* rmdir )(const char *); | |
// int(* symlink )(const char *, const char *); | |
// int(* rename )(const char *, const char *, unsigned int flags); | |
// int(* link )(const char *, const char *); | |
// int(* chmod )(const char *, mode_t, struct fuse_file_info *fi); | |
// int(* chown )(const char *, uid_t, gid_t, struct fuse_file_info *fi); | |
// int(* truncate )(const char *, off_t, struct fuse_file_info *fi); | |
// int(* open )(const char *, struct fuse_file_info *); | |
// int(* read )(const char *, char *, size_t, off_t, struct fuse_file_info *); | |
// int(* write )(const char *, const char *, size_t, off_t, struct fuse_file_info *); | |
// int(* statfs )(const char *, struct statvfs *); | |
// int(* flush )(const char *, struct fuse_file_info *); | |
// int(* release )(const char *, struct fuse_file_info *); | |
// int(* fsync )(const char *, int, struct fuse_file_info *); | |
// int(* setxattr )(const char *, const char *, const char *, size_t, int); | |
// int(* getxattr )(const char *, const char *, char *, size_t); | |
// int(* listxattr )(const char *, char *, size_t); | |
// int(* removexattr )(const char *, const char *); | |
// int(* opendir )(const char *, struct fuse_file_info *); | |
// int(* readdir )(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags); | |
// int(* releasedir )(const char *, struct fuse_file_info *); | |
// int(* fsyncdir )(const char *, int, struct fuse_file_info *); | |
// void*(* init )(struct fuse_conn_info *conn, struct fuse_config *cfg); | |
// void(* destroy )(void *private_data); | |
// int(* access )(const char *, int); | |
// int(* create )(const char *, mode_t, struct fuse_file_info *); | |
// int(* lock )(const char *, struct fuse_file_info *, int cmd, struct flock *); | |
// int(* utimens )(const char *, const struct timespec tv[2], struct fuse_file_info *fi); | |
// int(* bmap )(const char *, size_t blocksize, uint64_t *idx); | |
// int(* ioctl )(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data); | |
// int(* poll )(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp); | |
// int(* write_buf )(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *); | |
// int(* read_buf )(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *); | |
// int(* flock )(const char *, struct fuse_file_info *, int op); | |
// int(* fallocate )(const char *, int, off_t, off_t, struct fuse_file_info *); | |
// ssize_t(* copy_file_range )(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags); | |
// off_t(* lseek )(const char *, off_t off, int whence, struct fuse_file_info *); | |
// }; | |
// } | |
// TODO: add concept and overload for detecting functions that libfuse doesn't define, | |
// so we can not hard fail when building on an older libfuse API (e.g. macFuse), but | |
// diagnose it with a warning instead | |
#define MAKE_CONCEPT_FOR_IMPLEMENTS(operation) \ | |
template <class C> \ | |
concept Has_ ## operation = std::is_function_v<decltype(C::operation)>; \ | |
template <class C> \ | |
concept Implements_ ## operation = \ | |
std::same_as<decltype(&C::operation), decltype(fuse_operations::operation)>; \ | |
template <class C> \ | |
concept DoesntMatchSignature_ ## operation = \ | |
Has_ ## operation<C> and !Implements_ ## operation<C>; \ | |
template <class C> \ | |
concept DoesntImplement_ ## operation = \ | |
!Has_ ## operation<C> and !Implements_ ## operation<C>; \ | |
template <Implements_ ## operation C> \ | |
constexpr decltype(fuse_operations::operation) GetImplementation_ ## operation () { \ | |
return &C::operation; \ | |
} \ | |
template <DoesntMatchSignature_ ## operation C> \ | |
/*NOTE: This warning only triggers if a public static member function of the same name is found in the class */ \ | |
[[deprecated("Warning: are you trying to implement fuse_operations::" #operation "? (Signature doesn't match)")]] \ | |
constexpr decltype(fuse_operations::operation) GetImplementation_ ## operation () { \ | |
return nullptr; \ | |
} \ | |
template <DoesntImplement_ ## operation C> \ | |
constexpr decltype(fuse_operations::operation) GetImplementation_ ## operation () { \ | |
return nullptr; \ | |
} | |
#define GET_IMPLEMENTATION(fusefs_provider, operation) .operation = GetImplementation_ ## operation <fusefs_provider>() | |
// XXX: X-Macro shenanigans... | |
#define FUSESHIM_LIST_OF_FUSE_OPERATIONS \ | |
X(getattr) \ | |
X(readlink) \ | |
X(mknod) \ | |
X(mkdir) \ | |
X(unlink) \ | |
X(rmdir) \ | |
X(symlink) \ | |
X(rename) \ | |
X(link) \ | |
X(chmod) \ | |
X(chown) \ | |
X(truncate) \ | |
X(open) \ | |
X(read) \ | |
X(write) \ | |
X(statfs) \ | |
X(flush) \ | |
X(release) \ | |
X(fsync) \ | |
X(setxattr) \ | |
X(getxattr) \ | |
X(listxattr) \ | |
X(removexattr) \ | |
X(opendir) \ | |
X(readdir) \ | |
X(releasedir) \ | |
X(fsyncdir) \ | |
X(init) \ | |
X(destroy) \ | |
X(access) \ | |
X(create) \ | |
X(lock) \ | |
X(utimens) \ | |
X(bmap) \ | |
X(ioctl) \ | |
X(poll) \ | |
X(write_buf) \ | |
X(read_buf) \ | |
X(flock) \ | |
X(fallocate) \ | |
// X(copy_file_range) \ | |
// X(lseek) | |
#define X(name) MAKE_CONCEPT_FOR_IMPLEMENTS(name); | |
FUSESHIM_LIST_OF_FUSE_OPERATIONS | |
#undef X | |
template <class FuseFsProvider> | |
constexpr fuse_operations FUSEFS_BINDING = { | |
#define X(name) GET_IMPLEMENTATION(FuseFsProvider, name), | |
FUSESHIM_LIST_OF_FUSE_OPERATIONS | |
#undef X | |
}; | |
// TODO: put everything above this comment block into a namespace | |
/////////////////////////////////////////////////////////////////////////////// | |
class MyFuseFS_A { | |
public: | |
// unfortunately, macFUSE supports an older version of the libFUSE API | |
// UNFORTUNATELY unfortunately, overloading them to take this into account doesn't work --the wrong overload can be selected! | |
static int getattr(const char *, struct stat *) { return -1; } // Pre-v3.0.0 version | |
static int getattr(const char *, struct stat *, struct fuse_file_info *fi) { return -1; } // v3.0.0+ | |
static int readlink(const char *, char *, size_t) { return -1; } | |
}; | |
class MyFuseFS_B : public MyFuseFS_A { | |
public: | |
static int mkdir(const char *, mode_t) { return -1; } | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
int main() { | |
constexpr fuse_operations FUSE_OPERATIONS_A = FUSEFS_BINDING<MyFuseFS_A>; | |
constexpr fuse_operations FUSE_OPERATIONS_B = FUSEFS_BINDING<MyFuseFS_B>; | |
static_assert(FUSE_OPERATIONS_A.getattr != nullptr); // can fail due to failed overload selection! | |
static_assert(FUSE_OPERATIONS_A.mkdir == nullptr); | |
static_assert(FUSE_OPERATIONS_B.mkdir != nullptr); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment