Skip to content

Instantly share code, notes, and snippets.

@saxbophone
Last active January 11, 2025 04:28
Show Gist options
  • Save saxbophone/772a881d66592e61d38bf3a1d06f89c7 to your computer and use it in GitHub Desktop.
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
#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