diff --git a/README.md b/README.md index 0bdb7966..899ca18b 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,10 @@ These options are the same regardless of whether you use them with the longer need the data and it can drop its cache. Recommended when **cache.files=partial|full|auto-full|per-process** to limit double caching. (default: false) -* **direct-io-allow-mmap=BOOL**: On newer kernels (>= 6.6) it is +* **passthrough=off|ro|wo|rw**: On Linux 6.9 and above passthrough allows for + near native IO performance. See below for more details. (default: + off) +* **direct-io-allow-mmap=BOOL**: On Linux 6.6 and above it is possible to disable file page caching while still allowing for shared mmap support. mergerfs will enable this feature if available but an option is provided to turn it off for testing and debugging @@ -411,6 +414,32 @@ fact it will not be automatically included. **fuse** package. +### passthrough + +With Linux 6.9 and above there is a feature in FUSE called +"passthrough" which can improve performance by allowing a union or +overlay filesystem (like mergerfs) to pass the file descriptor of a +file opened on an underlying filesystem to the kernel to work on +directly rather than calls always having to go to the FUSE server +(mergerfs). As of Linux 6.9 the functions that can be passthroughed +are regular file read/write IO and mmap. Other functions may be added +in the future. + +Passthrough requires `cache.files` to be enabled. `cache.files=off` +(which enables FUSE direct-io feature) will override `passthrough` and +read/write requests will still be sent to mergerfs. + +If `cache.files=off` and `direct-io-allow-mmap=true` (the default) +then regular read/write IO will be handled by mergerfs but mmap will +be passthroughed. + +NOTE: `moveonenospc` will not work when using `passthrough` as it +removes mergerfs entirely from the read/write process. If an ENOSPC +error occurs it will be returned to the caller app immediately. Since +mergerfs won't be handling the IO it therefore won't be able to handle +any error returned by said IO. + + ### inodecalc Inodes (st_ino) are unique identifiers within a filesystem. Each @@ -1664,6 +1693,8 @@ over the suggestions below (including the benchmarking section.) NOTE: be sure to read about these features before changing them to understand what behaviors it may impact +* test theoretical performance using `nullrw` or mounting a ram disk +* enable `passthrough` * disable `security_capability` and/or `xattr` * increase cache timeouts `cache.attr`, `cache.entry`, `cache.negative_entry` * enable (or disable) page caching (`cache.files`) @@ -1675,7 +1706,6 @@ understand what behaviors it may impact * change the number of worker threads * disable `posix_acl` * disable `async_read` -* test theoretical performance using `nullrw` or mounting a ram disk * use `symlinkify` if your data is largely static and read-only * use tiered cache devices * use LVM and LVM cache to place a SSD in front of your HDDs @@ -1879,7 +1909,7 @@ more details. #### rtorrent fails with ENODEV (No such device) Be sure to set -`cache.files=partial|full|auto-full|per-processe`. rtorrent and some +`cache.files=partial|full|auto-full|per-process`. rtorrent and some other applications use [mmap](http://linux.die.net/man/2/mmap) to read and write to files and offer no fallback to traditional methods. FUSE does not currently support mmap while using `direct_io`. There may be diff --git a/libfuse/include/fuse.h b/libfuse/include/fuse.h index 2d615223..7bbcbcbd 100644 --- a/libfuse/include/fuse.h +++ b/libfuse/include/fuse.h @@ -667,6 +667,8 @@ int fuse_loop_mt(struct fuse *f); */ struct fuse_context *fuse_get_context(void); +int fuse_get_dev_fuse_fd(const struct fuse_context *); + /** * Check if the current request has already been interrupted * @@ -779,6 +781,11 @@ void fuse_gc1(); void fuse_gc(); void fuse_invalidate_all_nodes(); +int fuse_passthrough_open(const struct fuse_context *fc, + const int fd); +int fuse_passthrough_close(const struct fuse_context *fc, + const int backing_id); + EXTERN_C_END #endif /* _FUSE_H_ */ diff --git a/libfuse/include/fuse_common.h b/libfuse/include/fuse_common.h index 48607d05..877c5971 100644 --- a/libfuse/include/fuse_common.h +++ b/libfuse/include/fuse_common.h @@ -88,26 +88,18 @@ struct fuse_file_info_t uint32_t noflush:1; + uint32_t passthrough:1; + /** File handle. May be filled in by filesystem in open(). Available in all other file operations */ uint64_t fh; + uint32_t backing_id; + /** Lock owner id. Available in locking operations and flush */ uint64_t lock_owner; }; -/** - * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' - * - * FUSE_CAP_ASYNC_READ: filesystem supports asynchronous read requests - * FUSE_CAP_POSIX_LOCKS: filesystem supports "remote" locking - * FUSE_CAP_ATOMIC_O_TRUNC: filesystem handles the O_TRUNC open flag - * FUSE_CAP_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." - * FUSE_CAP_BIG_WRITES: filesystem can handle write size larger than 4kB - * FUSE_CAP_DONT_MASK: don't apply umask to file mode on create operations - * FUSE_CAP_IOCTL_DIR: ioctl support on directories - * FUSE_CAP_CACHE_SYMLINKS: cache READLINK responses - */ #define FUSE_CAP_ASYNC_READ (1ULL << 0) #define FUSE_CAP_POSIX_LOCKS (1ULL << 1) #define FUSE_CAP_ATOMIC_O_TRUNC (1ULL << 3) @@ -127,6 +119,9 @@ struct fuse_file_info_t #define FUSE_CAP_SETXATTR_EXT (1ULL << 22) #define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1ULL << 23) #define FUSE_CAP_CREATE_SUPP_GROUP (1ULL << 24) +#define FUSE_CAP_PASSTHROUGH (1ULL << 25) +#define FUSE_CAP_HANDLE_KILLPRIV (1ULL << 26) +#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1ULL << 27) /** diff --git a/libfuse/include/fuse_kernel.h b/libfuse/include/fuse_kernel.h index e7418d15..d08b99d6 100644 --- a/libfuse/include/fuse_kernel.h +++ b/libfuse/include/fuse_kernel.h @@ -211,6 +211,12 @@ * 7.39 * - add FUSE_DIRECT_IO_ALLOW_MMAP * - add FUSE_STATX and related structures + * + * 7.40 + * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag + * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag + * - add FUSE_NO_EXPORT_SUPPORT init flag + * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag */ #ifndef _LINUX_FUSE_H @@ -246,7 +252,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 39 +#define FUSE_KERNEL_MINOR_VERSION 40 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -353,6 +359,7 @@ struct fuse_file_lock { * FOPEN_STREAM: the file is stream-like (no file position at all) * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE) * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode + * FOPEN_PASSTHROUGH: passthrough read/write io for this open file */ #define FOPEN_DIRECT_IO (1 << 0) #define FOPEN_KEEP_CACHE (1 << 1) @@ -361,6 +368,7 @@ struct fuse_file_lock { #define FOPEN_STREAM (1 << 4) #define FOPEN_NOFLUSH (1 << 5) #define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6) +#define FOPEN_PASSTHROUGH (1 << 7) /** * INIT request/reply flags @@ -410,6 +418,9 @@ struct fuse_file_lock { * symlink and mknod (single group that matches parent) * FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation * FUSE_DIRECT_IO_ALLOW_MMAP: allow shared mmap in FOPEN_DIRECT_IO mode. + * FUSE_NO_EXPORT_SUPPORT: explicitly disable export support + * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit + * of the request ID indicates resend requests */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -449,6 +460,9 @@ struct fuse_file_lock { #define FUSE_CREATE_SUPP_GROUP (1ULL << 34) #define FUSE_HAS_EXPIRE_ONLY (1ULL << 35) #define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36) +#define FUSE_PASSTHROUGH (1ULL << 37) +#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38) +#define FUSE_HAS_RESEND (1ULL << 39) /* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ #define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP @@ -635,6 +649,7 @@ enum fuse_notify_code { FUSE_NOTIFY_STORE = 4, FUSE_NOTIFY_RETRIEVE = 5, FUSE_NOTIFY_DELETE = 6, + FUSE_NOTIFY_RESEND = 7, FUSE_NOTIFY_CODE_MAX, }; @@ -761,7 +776,7 @@ struct fuse_create_in { struct fuse_open_out { uint64_t fh; uint32_t open_flags; - uint32_t padding; + int32_t backing_id; }; struct fuse_release_in { @@ -877,7 +892,8 @@ struct fuse_init_out { uint16_t max_pages; uint16_t map_alignment; uint32_t flags2; - uint32_t unused[7]; + uint32_t max_stack_depth; + uint32_t unused[6]; }; #define CUSE_INIT_INFO_MAX 4096 @@ -960,6 +976,14 @@ struct fuse_fallocate_in { uint32_t padding; }; +/** + * FUSE request unique ID flag + * + * Indicates whether this is a resend request. The receiver should handle this + * request accordingly. + */ +#define FUSE_UNIQUE_RESEND (1ULL << 63) + struct fuse_in_header { uint32_t len; uint32_t opcode; @@ -1049,9 +1073,18 @@ struct fuse_notify_retrieve_in { uint64_t dummy4; }; +struct fuse_backing_map { + int32_t fd; + uint32_t flags; + uint64_t padding; +}; + /* Device ioctls: */ #define FUSE_DEV_IOC_MAGIC 229 #define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) +#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \ + struct fuse_backing_map) +#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t) struct fuse_lseek_in { uint64_t fh; diff --git a/libfuse/lib/debug.c b/libfuse/lib/debug.c index ddb53155..126130e1 100644 --- a/libfuse/lib/debug.c +++ b/libfuse/lib/debug.c @@ -199,6 +199,7 @@ fuse_flag_to_str(const uint64_t offset_) FUSE_INIT_FLAG_CASE(CREATE_SUPP_GROUP); FUSE_INIT_FLAG_CASE(HAS_EXPIRE_ONLY); FUSE_INIT_FLAG_CASE(DIRECT_IO_ALLOW_MMAP); + FUSE_INIT_FLAG_CASE(PASSTHROUGH); } return NULL; @@ -804,13 +805,15 @@ debug_fuse_init_out(const uint64_t unique_, " time_gran=%u;" " max_pages=%u;" " map_alignment=%u;" + " max_stack_depth=%u;" "\n", arg->max_background, arg->congestion_threshold, arg->max_write, arg->time_gran, arg->max_pages, - arg->map_alignment); + arg->map_alignment, + arg->max_stack_depth); } static diff --git a/libfuse/lib/fuse.c b/libfuse/lib/fuse.c index 3cb3b5ad..8eb41788 100644 --- a/libfuse/lib/fuse.c +++ b/libfuse/lib/fuse.c @@ -47,6 +47,7 @@ #include #include #include +#include #ifdef HAVE_MALLOC_TRIM #include @@ -3682,6 +3683,12 @@ fuse_get_context(void) return &fuse_get_context_internal()->ctx; } +int +fuse_get_dev_fuse_fd(const struct fuse_context *fc_) +{ + return fuse_chan_fd(fc_->fuse->se->ch); +} + enum { KEY_HELP, }; @@ -4156,3 +4163,30 @@ fuse_log_metrics_get(void) { return g_LOG_METRICS; } + +int +fuse_passthrough_open(const struct fuse_context *fc_, + const int fd_) +{ + int rv; + int dev_fuse_fd; + struct fuse_backing_map bm = {0}; + + dev_fuse_fd = fuse_get_dev_fuse_fd(fc_); + bm.fd = fd_; + + rv = ioctl(dev_fuse_fd,FUSE_DEV_IOC_BACKING_OPEN,&bm); + + return rv; +} + +int +fuse_passthrough_close(const struct fuse_context *fc_, + const int backing_id_) +{ + int dev_fuse_fd; + + dev_fuse_fd = fuse_get_dev_fuse_fd(fc_); + + return ioctl(dev_fuse_fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id_); +} diff --git a/libfuse/lib/fuse_lowlevel.c b/libfuse/lib/fuse_lowlevel.c index 9dca4c93..d80b0009 100644 --- a/libfuse/lib/fuse_lowlevel.c +++ b/libfuse/lib/fuse_lowlevel.c @@ -277,6 +277,11 @@ fill_open(struct fuse_open_out *arg_, arg_->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES; if(ffi_->noflush) arg_->open_flags |= FOPEN_NOFLUSH; + if(ffi_->passthrough) + { + arg_->open_flags |= FOPEN_PASSTHROUGH; + arg_->backing_id = ffi_->backing_id; + } } int @@ -1175,6 +1180,12 @@ do_init(fuse_req_t req, f->conn.capable |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP; if(inargflags & FUSE_CREATE_SUPP_GROUP) f->conn.capable |= FUSE_CAP_CREATE_SUPP_GROUP; + if(inargflags & FUSE_PASSTHROUGH) + f->conn.capable |= FUSE_CAP_PASSTHROUGH; + if(inargflags & FUSE_HANDLE_KILLPRIV) + f->conn.capable |= FUSE_CAP_HANDLE_KILLPRIV; + if(inargflags & FUSE_HANDLE_KILLPRIV_V2) + f->conn.capable |= FUSE_CAP_HANDLE_KILLPRIV_V2; } else { @@ -1248,6 +1259,15 @@ do_init(fuse_req_t req, outargflags |= FUSE_CREATE_SUPP_GROUP; if(f->conn.want & FUSE_CAP_DIRECT_IO_ALLOW_MMAP) outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP; + if(f->conn.want & FUSE_CAP_HANDLE_KILLPRIV) + outargflags |= FUSE_HANDLE_KILLPRIV; + if(f->conn.want & FUSE_CAP_HANDLE_KILLPRIV_V2) + outargflags |= FUSE_HANDLE_KILLPRIV_V2; + if(f->conn.want & FUSE_CAP_PASSTHROUGH) + { + outargflags |= FUSE_PASSTHROUGH; + outarg.max_stack_depth = 2; + } if(inargflags & FUSE_INIT_EXT) { diff --git a/src/better_enum.hpp b/src/better_enum.hpp new file mode 100644 index 00000000..c89804bb --- /dev/null +++ b/src/better_enum.hpp @@ -0,0 +1,1328 @@ +// This file is part of Better Enums, released under the BSD 2-clause license. +// See LICENSE.md for details, or visit http://github.com/aantron/better-enums. + +// clang-format off +#pragma once + +#ifndef BETTER_ENUMS_ENUM_H +#define BETTER_ENUMS_ENUM_H + + + +#include +#include +#include +#include + + +// in-line, non-#pragma warning handling +// not supported in very old compilers (namely gcc 4.4 or less) +#ifdef __GNUC__ +# ifdef __clang__ +# define BETTER_ENUMS_IGNORE_OLD_CAST_HEADER _Pragma("clang diagnostic push") +# define BETTER_ENUMS_IGNORE_OLD_CAST_BEGIN _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") +# define BETTER_ENUMS_IGNORE_OLD_CAST_END _Pragma("clang diagnostic pop") +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_HEADER +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_BEGIN +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_END +# else +# define BETTER_ENUMS_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) +# if BETTER_ENUMS_GCC_VERSION > 40400 +# define BETTER_ENUMS_IGNORE_OLD_CAST_HEADER _Pragma("GCC diagnostic push") +# define BETTER_ENUMS_IGNORE_OLD_CAST_BEGIN _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +# define BETTER_ENUMS_IGNORE_OLD_CAST_END _Pragma("GCC diagnostic pop") +# if (BETTER_ENUMS_GCC_VERSION >= 70300) +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_HEADER _Pragma("GCC diagnostic push") +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_BEGIN _Pragma("GCC diagnostic ignored \"-Wattributes\"") +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_END _Pragma("GCC diagnostic pop") +# else +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_HEADER +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_BEGIN +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_END +# endif +# else +# define BETTER_ENUMS_IGNORE_OLD_CAST_HEADER +# define BETTER_ENUMS_IGNORE_OLD_CAST_BEGIN +# define BETTER_ENUMS_IGNORE_OLD_CAST_END +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_HEADER +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_BEGIN +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_END +# endif +# endif +#else // empty definitions for compilers that don't support _Pragma +# define BETTER_ENUMS_IGNORE_OLD_CAST_HEADER +# define BETTER_ENUMS_IGNORE_OLD_CAST_BEGIN +# define BETTER_ENUMS_IGNORE_OLD_CAST_END +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_HEADER +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_BEGIN +# define BETTER_ENUMS_IGNORE_ATTRIBUTES_END +#endif + +// Feature detection. + +#ifdef __GNUC__ +# ifdef __clang__ +# if __has_feature(cxx_constexpr) +# define BETTER_ENUMS_HAVE_CONSTEXPR +# endif +# if !defined(__EXCEPTIONS) || !__has_feature(cxx_exceptions) +# define BETTER_ENUMS_NO_EXCEPTIONS +# endif +# else +# if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L +# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 6)) +# define BETTER_ENUMS_HAVE_CONSTEXPR +# endif +# endif +# ifndef __EXCEPTIONS +# define BETTER_ENUMS_NO_EXCEPTIONS +# endif +# endif +#endif + +#ifdef _MSC_VER +# if _MSC_VER >= 1911 +# define BETTER_ENUMS_HAVE_CONSTEXPR +# endif +# ifdef __clang__ +# if __has_feature(cxx_constexpr) +# define BETTER_ENUMS_HAVE_CONSTEXPR +# endif +# endif +# ifndef _CPPUNWIND +# define BETTER_ENUMS_NO_EXCEPTIONS +# endif +# if _MSC_VER < 1600 +# define BETTER_ENUMS_VC2008_WORKAROUNDS +# endif +#endif + +#ifdef BETTER_ENUMS_CONSTEXPR +# define BETTER_ENUMS_HAVE_CONSTEXPR +#endif + +#ifdef BETTER_ENUMS_NO_CONSTEXPR +# ifdef BETTER_ENUMS_HAVE_CONSTEXPR +# undef BETTER_ENUMS_HAVE_CONSTEXPR +# endif +#endif + +// GCC (and maybe clang) can be made to warn about using 0 or NULL when nullptr +// is available, so Better Enums tries to use nullptr. This passage uses +// availability of constexpr as a proxy for availability of nullptr, i.e. it +// assumes that nullptr is available when compiling on the right versions of gcc +// and clang with the right -std flag. This is actually slightly wrong, because +// nullptr is also available in Visual C++, but constexpr isn't. This +// imprecision doesn't matter, however, because VC++ doesn't have the warnings +// that make using nullptr necessary. +#ifdef BETTER_ENUMS_HAVE_CONSTEXPR +# define BETTER_ENUMS_CONSTEXPR_ constexpr +# define BETTER_ENUMS_NULLPTR nullptr +#else +# define BETTER_ENUMS_CONSTEXPR_ +# define BETTER_ENUMS_NULLPTR NULL +#endif + +#ifndef BETTER_ENUMS_NO_EXCEPTIONS +# define BETTER_ENUMS_IF_EXCEPTIONS(x) x +#else +# define BETTER_ENUMS_IF_EXCEPTIONS(x) +#endif + +#ifdef __GNUC__ +# define BETTER_ENUMS_UNUSED __attribute__((__unused__)) +#else +# define BETTER_ENUMS_UNUSED +#endif + + + +// Higher-order preprocessor macros. + +#ifdef BETTER_ENUMS_MACRO_FILE +# include BETTER_ENUMS_MACRO_FILE +#else + +#define BETTER_ENUMS_PP_MAP(macro, data, ...) \ + BETTER_ENUMS_ID( \ + BETTER_ENUMS_APPLY( \ + BETTER_ENUMS_PP_MAP_VAR_COUNT, \ + BETTER_ENUMS_PP_COUNT(__VA_ARGS__)) \ + (macro, data, __VA_ARGS__)) + +#define BETTER_ENUMS_PP_MAP_VAR_COUNT(count) BETTER_ENUMS_M ## count + +#define BETTER_ENUMS_APPLY(macro, ...) BETTER_ENUMS_ID(macro(__VA_ARGS__)) + +#define BETTER_ENUMS_ID(x) x + +#define BETTER_ENUMS_M1(m, d, x) m(d,0,x) +#define BETTER_ENUMS_M2(m,d,x,...) m(d,1,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M1(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M3(m,d,x,...) m(d,2,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M2(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M4(m,d,x,...) m(d,3,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M3(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M5(m,d,x,...) m(d,4,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M4(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M6(m,d,x,...) m(d,5,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M5(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M7(m,d,x,...) m(d,6,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M6(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M8(m,d,x,...) m(d,7,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M7(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M9(m,d,x,...) m(d,8,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M8(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M10(m,d,x,...) m(d,9,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M9(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M11(m,d,x,...) m(d,10,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M10(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M12(m,d,x,...) m(d,11,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M11(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M13(m,d,x,...) m(d,12,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M12(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M14(m,d,x,...) m(d,13,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M13(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M15(m,d,x,...) m(d,14,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M14(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M16(m,d,x,...) m(d,15,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M15(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M17(m,d,x,...) m(d,16,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M16(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M18(m,d,x,...) m(d,17,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M17(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M19(m,d,x,...) m(d,18,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M18(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M20(m,d,x,...) m(d,19,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M19(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M21(m,d,x,...) m(d,20,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M20(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M22(m,d,x,...) m(d,21,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M21(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M23(m,d,x,...) m(d,22,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M22(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M24(m,d,x,...) m(d,23,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M23(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M25(m,d,x,...) m(d,24,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M24(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M26(m,d,x,...) m(d,25,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M25(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M27(m,d,x,...) m(d,26,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M26(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M28(m,d,x,...) m(d,27,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M27(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M29(m,d,x,...) m(d,28,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M28(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M30(m,d,x,...) m(d,29,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M29(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M31(m,d,x,...) m(d,30,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M30(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M32(m,d,x,...) m(d,31,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M31(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M33(m,d,x,...) m(d,32,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M32(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M34(m,d,x,...) m(d,33,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M33(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M35(m,d,x,...) m(d,34,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M34(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M36(m,d,x,...) m(d,35,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M35(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M37(m,d,x,...) m(d,36,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M36(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M38(m,d,x,...) m(d,37,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M37(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M39(m,d,x,...) m(d,38,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M38(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M40(m,d,x,...) m(d,39,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M39(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M41(m,d,x,...) m(d,40,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M40(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M42(m,d,x,...) m(d,41,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M41(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M43(m,d,x,...) m(d,42,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M42(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M44(m,d,x,...) m(d,43,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M43(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M45(m,d,x,...) m(d,44,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M44(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M46(m,d,x,...) m(d,45,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M45(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M47(m,d,x,...) m(d,46,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M46(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M48(m,d,x,...) m(d,47,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M47(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M49(m,d,x,...) m(d,48,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M48(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M50(m,d,x,...) m(d,49,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M49(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M51(m,d,x,...) m(d,50,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M50(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M52(m,d,x,...) m(d,51,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M51(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M53(m,d,x,...) m(d,52,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M52(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M54(m,d,x,...) m(d,53,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M53(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M55(m,d,x,...) m(d,54,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M54(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M56(m,d,x,...) m(d,55,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M55(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M57(m,d,x,...) m(d,56,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M56(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M58(m,d,x,...) m(d,57,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M57(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M59(m,d,x,...) m(d,58,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M58(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M60(m,d,x,...) m(d,59,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M59(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M61(m,d,x,...) m(d,60,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M60(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M62(m,d,x,...) m(d,61,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M61(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M63(m,d,x,...) m(d,62,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M62(m,d,__VA_ARGS__)) +#define BETTER_ENUMS_M64(m,d,x,...) m(d,63,x) \ + BETTER_ENUMS_ID(BETTER_ENUMS_M63(m,d,__VA_ARGS__)) + +#define BETTER_ENUMS_PP_COUNT_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, \ + _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \ + _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, \ + _56, _57, _58, _59, _60, _61, _62, _63, _64, count, ...) count + +#define BETTER_ENUMS_PP_COUNT(...) \ + BETTER_ENUMS_ID(BETTER_ENUMS_PP_COUNT_IMPL(__VA_ARGS__, 64, 63, 62, 61, 60,\ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42,\ + 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24,\ + 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, \ + 4, 3, 2, 1)) + +#define BETTER_ENUMS_ITERATE(X, f, l) X(f, l, 0) X(f, l, 1) X(f, l, 2) \ + X(f, l, 3) X(f, l, 4) X(f, l, 5) X(f, l, 6) X(f, l, 7) X(f, l, 8) \ + X(f, l, 9) X(f, l, 10) X(f, l, 11) X(f, l, 12) X(f, l, 13) X(f, l, 14) \ + X(f, l, 15) X(f, l, 16) X(f, l, 17) X(f, l, 18) X(f, l, 19) X(f, l, 20) \ + X(f, l, 21) X(f, l, 22) X(f, l, 23) + +#endif // #ifdef BETTER_ENUMS_MACRO_FILE else case + + + +namespace better_enums { + + +// Optional type. + +template +BETTER_ENUMS_CONSTEXPR_ inline T _default() +{ + return static_cast(0); +} + +template <> +BETTER_ENUMS_CONSTEXPR_ inline const char* _default() +{ + return BETTER_ENUMS_NULLPTR; +} + +template <> +BETTER_ENUMS_CONSTEXPR_ inline std::size_t _default() +{ + return 0; +} + +template +struct optional { + BETTER_ENUMS_CONSTEXPR_ optional() : + _valid(false), _value(_default()) { } + + BETTER_ENUMS_CONSTEXPR_ optional(T v) : _valid(true), _value(v) { } + + BETTER_ENUMS_CONSTEXPR_ const T& operator *() const { return _value; } + BETTER_ENUMS_CONSTEXPR_ const T* operator ->() const { return &_value; } + + BETTER_ENUMS_CONSTEXPR_ operator bool() const { return _valid; } + + BETTER_ENUMS_CONSTEXPR_ const T& value() const { return _value; } + + private: + bool _valid; + T _value; +}; + +template +BETTER_ENUMS_CONSTEXPR_ static optional +_map_index(const Element *array, optional index) +{ + return index ? static_cast(array[*index]) : optional(); +} + +#ifdef BETTER_ENUMS_VC2008_WORKAROUNDS + +#define BETTER_ENUMS_OR_THROW \ + if (!maybe) \ + throw std::runtime_error(message); \ + \ + return *maybe; + +#else + +#define BETTER_ENUMS_OR_THROW \ + return maybe ? *maybe : throw std::runtime_error(message); + +#endif + +BETTER_ENUMS_IF_EXCEPTIONS( +template +BETTER_ENUMS_CONSTEXPR_ static T _or_throw(optional maybe, + const char *message) +{ + BETTER_ENUMS_OR_THROW +} +) + +template +BETTER_ENUMS_CONSTEXPR_ static T* _or_null(optional maybe) +{ + return maybe ? *maybe : BETTER_ENUMS_NULLPTR; +} + +template +BETTER_ENUMS_CONSTEXPR_ static T _or_zero(optional maybe) +{ + return maybe ? *maybe : T::_from_integral_unchecked(0); +} + + + +// Functional sequencing. This is essentially a comma operator wrapped in a +// constexpr function. g++ 4.7 doesn't "accept" integral constants in the second +// position for the comma operator, and emits an external symbol, which then +// causes a linking error. + +template +BETTER_ENUMS_CONSTEXPR_ U +continue_with(T, U value) { return value; } + + + +// Values array declaration helper. + +//! Get intrinsic value of an (Enum::value) by taking advantage of +// C-conversion's parentheses priority +template +struct _eat_assign { + explicit BETTER_ENUMS_CONSTEXPR_ _eat_assign(EnumType value) : _value(value) + { } + + template + BETTER_ENUMS_CONSTEXPR_ const _eat_assign& + operator =(Any) const { return *this; } + + BETTER_ENUMS_CONSTEXPR_ operator EnumType () const { return _value; } + + private: + EnumType _value; +}; + + + +// Iterables. + +template +struct _iterable { + typedef const Element* iterator; + + BETTER_ENUMS_CONSTEXPR_ iterator begin() const { return iterator(_array); } + BETTER_ENUMS_CONSTEXPR_ iterator end() const + { return iterator(_array + _size); } + BETTER_ENUMS_CONSTEXPR_ std::size_t size() const { return _size; } + BETTER_ENUMS_CONSTEXPR_ const Element& operator [](std::size_t index) const + { return _array[index]; } + + BETTER_ENUMS_CONSTEXPR_ _iterable(const Element *array, std::size_t s) : + _array(array), _size(s) { } + + private: + const Element * const _array; + const std::size_t _size; +}; + + + +// String routines. + +BETTER_ENUMS_CONSTEXPR_ static const char *_name_enders = "= \t\n"; + +BETTER_ENUMS_CONSTEXPR_ inline bool _ends_name(char c, std::size_t index = 0) +{ + return + c == _name_enders[index] ? true : + _name_enders[index] == '\0' ? false : + _ends_name(c, index + 1); +} + +BETTER_ENUMS_CONSTEXPR_ inline bool _has_initializer(const char *s, + std::size_t index = 0) +{ + return + s[index] == '\0' ? false : + s[index] == '=' ? true : + _has_initializer(s, index + 1); +} + +BETTER_ENUMS_CONSTEXPR_ inline std::size_t +_constant_length(const char *s, std::size_t index = 0) +{ + return _ends_name(s[index]) ? index : _constant_length(s, index + 1); +} + +BETTER_ENUMS_CONSTEXPR_ inline char +_select(const char *from, std::size_t from_length, std::size_t index) +{ + return index >= from_length ? '\0' : from[index]; +} + +BETTER_ENUMS_CONSTEXPR_ inline char _to_lower_ascii(char c) +{ + return c >= 0x41 && c <= 0x5A ? static_cast(c + 0x20) : c; +} + +BETTER_ENUMS_CONSTEXPR_ inline bool _names_match(const char *stringizedName, + const char *referenceName, + std::size_t index = 0) +{ + return + _ends_name(stringizedName[index]) ? referenceName[index] == '\0' : + referenceName[index] == '\0' ? false : + stringizedName[index] != referenceName[index] ? false : + _names_match(stringizedName, referenceName, index + 1); +} + +BETTER_ENUMS_CONSTEXPR_ inline bool +_names_match_nocase(const char *stringizedName, const char *referenceName, + std::size_t index = 0) +{ + return + _ends_name(stringizedName[index]) ? referenceName[index] == '\0' : + referenceName[index] == '\0' ? false : + _to_lower_ascii(stringizedName[index]) != + _to_lower_ascii(referenceName[index]) ? false : + _names_match_nocase(stringizedName, referenceName, index + 1); +} + +inline void _trim_names(const char * const *raw_names, + const char **trimmed_names, + char *storage, std::size_t count) +{ + std::size_t offset = 0; + + for (std::size_t index = 0; index < count; ++index) { + trimmed_names[index] = storage + offset; + + std::size_t trimmed_length = + std::strcspn(raw_names[index], _name_enders); + storage[offset + trimmed_length] = '\0'; + + std::size_t raw_length = std::strlen(raw_names[index]); + offset += raw_length + 1; + } +} + + + +// Eager initialization. +template +struct _initialize_at_program_start { + _initialize_at_program_start() { Enum::initialize(); } +}; + +} // namespace better_enums + + + +// Array generation macros. + +#define BETTER_ENUMS_EAT_ASSIGN_SINGLE(EnumType, index, expression) \ + (EnumType)((::better_enums::_eat_assign)EnumType::expression), + +#define BETTER_ENUMS_EAT_ASSIGN(EnumType, ...) \ + BETTER_ENUMS_ID( \ + BETTER_ENUMS_PP_MAP( \ + BETTER_ENUMS_EAT_ASSIGN_SINGLE, EnumType, __VA_ARGS__)) + + + +#ifdef BETTER_ENUMS_HAVE_CONSTEXPR + + + +#define BETTER_ENUMS_SELECT_SINGLE_CHARACTER(from, from_length, index) \ + ::better_enums::_select(from, from_length, index), + +#define BETTER_ENUMS_SELECT_CHARACTERS(from, from_length) \ + BETTER_ENUMS_ITERATE( \ + BETTER_ENUMS_SELECT_SINGLE_CHARACTER, from, from_length) + + + +#define BETTER_ENUMS_TRIM_SINGLE_STRING(ignored, index, expression) \ +constexpr std::size_t _length_ ## index = \ + ::better_enums::_constant_length(#expression); \ +constexpr const char _trimmed_ ## index [] = \ + { BETTER_ENUMS_SELECT_CHARACTERS(#expression, _length_ ## index) }; \ +constexpr const char *_final_ ## index = \ + ::better_enums::_has_initializer(#expression) ? \ + _trimmed_ ## index : #expression; + +#define BETTER_ENUMS_TRIM_STRINGS(...) \ + BETTER_ENUMS_ID( \ + BETTER_ENUMS_PP_MAP( \ + BETTER_ENUMS_TRIM_SINGLE_STRING, ignored, __VA_ARGS__)) + + + +#define BETTER_ENUMS_REFER_TO_SINGLE_STRING(ignored, index, expression) \ + _final_ ## index, + +#define BETTER_ENUMS_REFER_TO_STRINGS(...) \ + BETTER_ENUMS_ID( \ + BETTER_ENUMS_PP_MAP( \ + BETTER_ENUMS_REFER_TO_SINGLE_STRING, ignored, __VA_ARGS__)) + + + +#endif // #ifdef BETTER_ENUMS_HAVE_CONSTEXPR + + + +#define BETTER_ENUMS_STRINGIZE_SINGLE(ignored, index, expression) #expression, + +#define BETTER_ENUMS_STRINGIZE(...) \ + BETTER_ENUMS_ID( \ + BETTER_ENUMS_PP_MAP( \ + BETTER_ENUMS_STRINGIZE_SINGLE, ignored, __VA_ARGS__)) + +#define BETTER_ENUMS_RESERVE_STORAGE_SINGLE(ignored, index, expression) \ + #expression "," + +#define BETTER_ENUMS_RESERVE_STORAGE(...) \ + BETTER_ENUMS_ID( \ + BETTER_ENUMS_PP_MAP( \ + BETTER_ENUMS_RESERVE_STORAGE_SINGLE, ignored, __VA_ARGS__)) + + + +// The enums proper. + +#define BETTER_ENUMS_NS(EnumType) better_enums_data_ ## EnumType + +#ifdef BETTER_ENUMS_VC2008_WORKAROUNDS + +#define BETTER_ENUMS_COPY_CONSTRUCTOR(Enum) \ + BETTER_ENUMS_CONSTEXPR_ Enum(const Enum &other) : \ + _value(other._value) { } + +#else + +#define BETTER_ENUMS_COPY_CONSTRUCTOR(Enum) + +#endif + +#ifndef BETTER_ENUMS_CLASS_ATTRIBUTE +# define BETTER_ENUMS_CLASS_ATTRIBUTE +#endif + +#define BETTER_ENUMS_TYPE(SetUnderlyingType, SwitchType, GenerateSwitchType, \ + GenerateStrings, ToStringConstexpr, \ + DeclareInitialize, DefineInitialize, CallInitialize, \ + Enum, Underlying, ...) \ + \ +namespace better_enums_data_ ## Enum { \ + \ +BETTER_ENUMS_ID(GenerateSwitchType(Underlying, __VA_ARGS__)) \ + \ +} \ + \ +class BETTER_ENUMS_CLASS_ATTRIBUTE Enum { \ + private: \ + typedef ::better_enums::optional _optional; \ + typedef ::better_enums::optional _optional_index; \ + \ + public: \ + typedef Underlying _integral; \ + \ + enum _enumerated SetUnderlyingType(Underlying) { __VA_ARGS__ }; \ + \ + BETTER_ENUMS_CONSTEXPR_ Enum(_enumerated value) : _value(value) { } \ + \ + BETTER_ENUMS_COPY_CONSTRUCTOR(Enum) \ + \ + BETTER_ENUMS_CONSTEXPR_ operator SwitchType(Enum)() const \ + { \ + return SwitchType(Enum)(_value); \ + } \ + \ + BETTER_ENUMS_CONSTEXPR_ _integral _to_integral() const; \ + BETTER_ENUMS_IF_EXCEPTIONS( \ + BETTER_ENUMS_CONSTEXPR_ static Enum _from_integral(_integral value); \ + ) \ + BETTER_ENUMS_CONSTEXPR_ static Enum \ + _from_integral_unchecked(_integral value); \ + BETTER_ENUMS_CONSTEXPR_ static _optional \ + _from_integral_nothrow(_integral value); \ + \ + BETTER_ENUMS_CONSTEXPR_ std::size_t _to_index() const; \ + BETTER_ENUMS_IF_EXCEPTIONS( \ + BETTER_ENUMS_CONSTEXPR_ static Enum _from_index(std::size_t index); \ + ) \ + BETTER_ENUMS_CONSTEXPR_ static Enum \ + _from_index_unchecked(std::size_t index); \ + BETTER_ENUMS_CONSTEXPR_ static _optional \ + _from_index_nothrow(std::size_t index); \ + \ + ToStringConstexpr const char* _to_string() const; \ + BETTER_ENUMS_IF_EXCEPTIONS( \ + BETTER_ENUMS_CONSTEXPR_ static Enum _from_string(const char *name); \ + ) \ + BETTER_ENUMS_CONSTEXPR_ static _optional \ + _from_string_nothrow(const char *name); \ + \ + BETTER_ENUMS_IF_EXCEPTIONS( \ + BETTER_ENUMS_CONSTEXPR_ static Enum _from_string_nocase(const char *name); \ + ) \ + BETTER_ENUMS_CONSTEXPR_ static _optional \ + _from_string_nocase_nothrow(const char *name); \ + \ + BETTER_ENUMS_CONSTEXPR_ static bool _is_valid(_integral value); \ + BETTER_ENUMS_CONSTEXPR_ static bool _is_valid(const char *name); \ + BETTER_ENUMS_CONSTEXPR_ static bool _is_valid_nocase(const char *name); \ + \ + typedef ::better_enums::_iterable _value_iterable; \ + typedef ::better_enums::_iterable _name_iterable; \ + \ + typedef _value_iterable::iterator _value_iterator; \ + typedef _name_iterable::iterator _name_iterator; \ + \ + BETTER_ENUMS_CONSTEXPR_ static const std::size_t _size_constant = \ + BETTER_ENUMS_ID(BETTER_ENUMS_PP_COUNT(__VA_ARGS__)); \ + BETTER_ENUMS_CONSTEXPR_ static std::size_t _size() \ + { return _size_constant; } \ + \ + BETTER_ENUMS_CONSTEXPR_ static const char* _name(); \ + BETTER_ENUMS_CONSTEXPR_ static _value_iterable _values(); \ + ToStringConstexpr static _name_iterable _names(); \ + \ + _integral _value; \ + \ + BETTER_ENUMS_DEFAULT_CONSTRUCTOR(Enum) \ + \ + private: \ + explicit BETTER_ENUMS_CONSTEXPR_ Enum(const _integral &value) : \ + _value(value) { } \ + \ + DeclareInitialize \ + \ + BETTER_ENUMS_CONSTEXPR_ static _optional_index \ + _from_value_loop(_integral value, std::size_t index = 0); \ + BETTER_ENUMS_CONSTEXPR_ static _optional_index \ + _from_string_loop(const char *name, std::size_t index = 0); \ + BETTER_ENUMS_CONSTEXPR_ static _optional_index \ + _from_string_nocase_loop(const char *name, std::size_t index = 0); \ + \ + friend struct ::better_enums::_initialize_at_program_start; \ +}; \ + \ +namespace better_enums_data_ ## Enum { \ + \ +static ::better_enums::_initialize_at_program_start \ + _force_initialization; \ + \ +enum _putNamesInThisScopeAlso { __VA_ARGS__ }; \ + \ +BETTER_ENUMS_IGNORE_OLD_CAST_HEADER \ +BETTER_ENUMS_IGNORE_OLD_CAST_BEGIN \ +BETTER_ENUMS_CONSTEXPR_ const Enum _value_array[] = \ + { BETTER_ENUMS_ID(BETTER_ENUMS_EAT_ASSIGN(Enum, __VA_ARGS__)) }; \ +BETTER_ENUMS_IGNORE_OLD_CAST_END \ + \ +BETTER_ENUMS_ID(GenerateStrings(Enum, __VA_ARGS__)) \ + \ +} \ + \ +BETTER_ENUMS_IGNORE_ATTRIBUTES_HEADER \ +BETTER_ENUMS_IGNORE_ATTRIBUTES_BEGIN \ +BETTER_ENUMS_UNUSED BETTER_ENUMS_CONSTEXPR_ \ +inline const Enum \ +operator +(Enum::_enumerated enumerated) \ +{ \ + return static_cast(enumerated); \ +} \ +BETTER_ENUMS_IGNORE_ATTRIBUTES_END \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_optional_index \ +Enum::_from_value_loop(Enum::_integral value, std::size_t index) \ +{ \ + return \ + index == _size() ? \ + _optional_index() : \ + BETTER_ENUMS_NS(Enum)::_value_array[index]._value == value ? \ + _optional_index(index) : \ + _from_value_loop(value, index + 1); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_optional_index \ +Enum::_from_string_loop(const char *name, std::size_t index) \ +{ \ + return \ + index == _size() ? _optional_index() : \ + ::better_enums::_names_match( \ + BETTER_ENUMS_NS(Enum)::_raw_names()[index], name) ? \ + _optional_index(index) : \ + _from_string_loop(name, index + 1); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_optional_index \ +Enum::_from_string_nocase_loop(const char *name, std::size_t index) \ +{ \ + return \ + index == _size() ? _optional_index() : \ + ::better_enums::_names_match_nocase( \ + BETTER_ENUMS_NS(Enum)::_raw_names()[index], name) ? \ + _optional_index(index) : \ + _from_string_nocase_loop(name, index + 1); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_integral Enum::_to_integral() const \ +{ \ + return _integral(_value); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline std::size_t Enum::_to_index() const \ +{ \ + return *_from_value_loop(_value); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum \ +Enum::_from_index_unchecked(std::size_t index) \ +{ \ + return \ + ::better_enums::_or_zero(_from_index_nothrow(index)); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_optional \ +Enum::_from_index_nothrow(std::size_t index) \ +{ \ + return \ + index >= _size() ? \ + _optional() : \ + _optional(BETTER_ENUMS_NS(Enum)::_value_array[index]); \ +} \ + \ +BETTER_ENUMS_IF_EXCEPTIONS( \ +BETTER_ENUMS_CONSTEXPR_ inline Enum Enum::_from_index(std::size_t index) \ +{ \ + return \ + ::better_enums::_or_throw(_from_index_nothrow(index), \ + #Enum "::_from_index: invalid argument"); \ +} \ +) \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum \ +Enum::_from_integral_unchecked(_integral value) \ +{ \ + return static_cast<_enumerated>(value); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_optional \ +Enum::_from_integral_nothrow(_integral value) \ +{ \ + return \ + ::better_enums::_map_index(BETTER_ENUMS_NS(Enum)::_value_array, \ + _from_value_loop(value)); \ +} \ + \ +BETTER_ENUMS_IF_EXCEPTIONS( \ +BETTER_ENUMS_CONSTEXPR_ inline Enum Enum::_from_integral(_integral value) \ +{ \ + return \ + ::better_enums::_or_throw(_from_integral_nothrow(value), \ + #Enum "::_from_integral: invalid argument"); \ +} \ +) \ + \ +ToStringConstexpr inline const char* Enum::_to_string() const \ +{ \ + return \ + ::better_enums::_or_null( \ + ::better_enums::_map_index( \ + BETTER_ENUMS_NS(Enum)::_name_array(), \ + _from_value_loop(CallInitialize(_value)))); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_optional \ +Enum::_from_string_nothrow(const char *name) \ +{ \ + return \ + ::better_enums::_map_index( \ + BETTER_ENUMS_NS(Enum)::_value_array, _from_string_loop(name)); \ +} \ + \ +BETTER_ENUMS_IF_EXCEPTIONS( \ +BETTER_ENUMS_CONSTEXPR_ inline Enum Enum::_from_string(const char *name) \ +{ \ + return \ + ::better_enums::_or_throw(_from_string_nothrow(name), \ + #Enum "::_from_string: invalid argument"); \ +} \ +) \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_optional \ +Enum::_from_string_nocase_nothrow(const char *name) \ +{ \ + return \ + ::better_enums::_map_index(BETTER_ENUMS_NS(Enum)::_value_array, \ + _from_string_nocase_loop(name)); \ +} \ + \ +BETTER_ENUMS_IF_EXCEPTIONS( \ +BETTER_ENUMS_CONSTEXPR_ inline Enum Enum::_from_string_nocase(const char *name)\ +{ \ + return \ + ::better_enums::_or_throw( \ + _from_string_nocase_nothrow(name), \ + #Enum "::_from_string_nocase: invalid argument"); \ +} \ +) \ + \ +BETTER_ENUMS_CONSTEXPR_ inline bool Enum::_is_valid(_integral value) \ +{ \ + return _from_value_loop(value); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline bool Enum::_is_valid(const char *name) \ +{ \ + return _from_string_loop(name); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline bool Enum::_is_valid_nocase(const char *name) \ +{ \ + return _from_string_nocase_loop(name); \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline const char* Enum::_name() \ +{ \ + return #Enum; \ +} \ + \ +BETTER_ENUMS_CONSTEXPR_ inline Enum::_value_iterable Enum::_values() \ +{ \ + return _value_iterable(BETTER_ENUMS_NS(Enum)::_value_array, _size()); \ +} \ + \ +ToStringConstexpr inline Enum::_name_iterable Enum::_names() \ +{ \ + return \ + _name_iterable(BETTER_ENUMS_NS(Enum)::_name_array(), \ + CallInitialize(_size())); \ +} \ + \ +DefineInitialize(Enum) \ + \ +BETTER_ENUMS_IGNORE_ATTRIBUTES_HEADER \ +BETTER_ENUMS_IGNORE_ATTRIBUTES_BEGIN \ +BETTER_ENUMS_UNUSED BETTER_ENUMS_CONSTEXPR_ \ +inline bool operator ==(const Enum &a, const Enum &b) \ + { return a._to_integral() == b._to_integral(); } \ + \ +BETTER_ENUMS_UNUSED BETTER_ENUMS_CONSTEXPR_ \ +inline bool operator !=(const Enum &a, const Enum &b) \ + { return a._to_integral() != b._to_integral(); } \ + \ +BETTER_ENUMS_UNUSED BETTER_ENUMS_CONSTEXPR_ \ +inline bool operator <(const Enum &a, const Enum &b) \ + { return a._to_integral() < b._to_integral(); } \ + \ +BETTER_ENUMS_UNUSED BETTER_ENUMS_CONSTEXPR_ \ +inline bool operator <=(const Enum &a, const Enum &b) \ + { return a._to_integral() <= b._to_integral(); } \ + \ +BETTER_ENUMS_UNUSED BETTER_ENUMS_CONSTEXPR_ \ +inline bool operator >(const Enum &a, const Enum &b) \ + { return a._to_integral() > b._to_integral(); } \ + \ +BETTER_ENUMS_UNUSED BETTER_ENUMS_CONSTEXPR_ \ +inline bool operator >=(const Enum &a, const Enum &b) \ + { return a._to_integral() >= b._to_integral(); } \ +BETTER_ENUMS_IGNORE_ATTRIBUTES_END \ + \ + \ +template \ +std::basic_ostream& \ +operator <<(std::basic_ostream& stream, const Enum &value) \ +{ \ + return stream << value._to_string(); \ +} \ + \ +template \ +std::basic_istream& \ +operator >>(std::basic_istream& stream, Enum &value) \ +{ \ + std::basic_string buffer; \ + \ + stream >> buffer; \ + ::better_enums::optional converted = \ + Enum::_from_string_nothrow(buffer.c_str()); \ + \ + if (converted) \ + value = *converted; \ + else \ + stream.setstate(std::basic_istream::failbit); \ + \ + return stream; \ +} + + + +// Enum feature options. + +// C++98, C++11 +#define BETTER_ENUMS_CXX98_UNDERLYING_TYPE(Underlying) + +// C++11 +#define BETTER_ENUMS_CXX11_UNDERLYING_TYPE(Underlying) \ + : Underlying + +#if defined(_MSC_VER) && _MSC_VER >= 1700 +// VS 2012 and above fully support strongly typed enums and will warn about +// incorrect usage. +# define BETTER_ENUMS_LEGACY_UNDERLYING_TYPE(Underlying) \ + BETTER_ENUMS_CXX11_UNDERLYING_TYPE(Underlying) +#else +# define BETTER_ENUMS_LEGACY_UNDERLYING_TYPE(Underlying) \ + BETTER_ENUMS_CXX98_UNDERLYING_TYPE(Underlying) +#endif + +// C++98, C++11 +#define BETTER_ENUMS_REGULAR_ENUM_SWITCH_TYPE(Type) \ + _enumerated + +// C++11 +#define BETTER_ENUMS_ENUM_CLASS_SWITCH_TYPE(Type) \ + BETTER_ENUMS_NS(Type)::_enumClassForSwitchStatements + +// C++98, C++11 +#define BETTER_ENUMS_REGULAR_ENUM_SWITCH_TYPE_GENERATE(Underlying, ...) + +// C++11 +#define BETTER_ENUMS_ENUM_CLASS_SWITCH_TYPE_GENERATE(Underlying, ...) \ + enum class _enumClassForSwitchStatements : Underlying { __VA_ARGS__ }; + +// C++98 +#define BETTER_ENUMS_CXX98_TRIM_STRINGS_ARRAYS(Enum, ...) \ + inline const char** _raw_names() \ + { \ + static const char *value[] = \ + { BETTER_ENUMS_ID(BETTER_ENUMS_STRINGIZE(__VA_ARGS__)) }; \ + return value; \ + } \ + \ + inline char* _name_storage() \ + { \ + static char storage[] = \ + BETTER_ENUMS_ID(BETTER_ENUMS_RESERVE_STORAGE(__VA_ARGS__)); \ + return storage; \ + } \ + \ + inline const char** _name_array() \ + { \ + static const char *value[Enum::_size_constant]; \ + return value; \ + } \ + \ + inline bool& _initialized() \ + { \ + static bool value = false; \ + return value; \ + } + +// C++11 fast version +#define BETTER_ENUMS_CXX11_PARTIAL_CONSTEXPR_TRIM_STRINGS_ARRAYS(Enum, ...) \ + constexpr const char *_the_raw_names[] = \ + { BETTER_ENUMS_ID(BETTER_ENUMS_STRINGIZE(__VA_ARGS__)) }; \ + \ + constexpr const char * const * _raw_names() \ + { \ + return _the_raw_names; \ + } \ + \ + inline char* _name_storage() \ + { \ + static char storage[] = \ + BETTER_ENUMS_ID(BETTER_ENUMS_RESERVE_STORAGE(__VA_ARGS__)); \ + return storage; \ + } \ + \ + inline const char** _name_array() \ + { \ + static const char *value[Enum::_size_constant]; \ + return value; \ + } \ + \ + inline bool& _initialized() \ + { \ + static bool value = false; \ + return value; \ + } + +// C++11 slow all-constexpr version +#define BETTER_ENUMS_CXX11_FULL_CONSTEXPR_TRIM_STRINGS_ARRAYS(Enum, ...) \ + BETTER_ENUMS_ID(BETTER_ENUMS_TRIM_STRINGS(__VA_ARGS__)) \ + \ + constexpr const char * const _the_name_array[] = \ + { BETTER_ENUMS_ID(BETTER_ENUMS_REFER_TO_STRINGS(__VA_ARGS__)) }; \ + \ + constexpr const char * const * _name_array() \ + { \ + return _the_name_array; \ + } \ + \ + constexpr const char * const * _raw_names() \ + { \ + return _the_name_array; \ + } + +// C++98, C++11 fast version +#define BETTER_ENUMS_NO_CONSTEXPR_TO_STRING_KEYWORD + +// C++11 slow all-constexpr version +#define BETTER_ENUMS_CONSTEXPR_TO_STRING_KEYWORD \ + constexpr + +// C++98, C++11 fast version +#define BETTER_ENUMS_DO_DECLARE_INITIALIZE \ + static int initialize(); + +// C++11 slow all-constexpr version +#define BETTER_ENUMS_DECLARE_EMPTY_INITIALIZE \ + static int initialize() { return 0; } + +// C++98, C++11 fast version +#define BETTER_ENUMS_DO_DEFINE_INITIALIZE(Enum) \ + inline int Enum::initialize() \ + { \ + if (BETTER_ENUMS_NS(Enum)::_initialized()) \ + return 0; \ + \ + ::better_enums::_trim_names(BETTER_ENUMS_NS(Enum)::_raw_names(), \ + BETTER_ENUMS_NS(Enum)::_name_array(), \ + BETTER_ENUMS_NS(Enum)::_name_storage(), \ + _size()); \ + \ + BETTER_ENUMS_NS(Enum)::_initialized() = true; \ + \ + return 0; \ + } + +// C++11 slow all-constexpr version +#define BETTER_ENUMS_DO_NOT_DEFINE_INITIALIZE(Enum) + +// C++98, C++11 fast version +#define BETTER_ENUMS_DO_CALL_INITIALIZE(value) \ + ::better_enums::continue_with(initialize(), value) + +// C++11 slow all-constexpr version +#define BETTER_ENUMS_DO_NOT_CALL_INITIALIZE(value) \ + value + + + +// User feature selection. + +#ifdef BETTER_ENUMS_STRICT_CONVERSION +# define BETTER_ENUMS_DEFAULT_SWITCH_TYPE \ + BETTER_ENUMS_ENUM_CLASS_SWITCH_TYPE +# define BETTER_ENUMS_DEFAULT_SWITCH_TYPE_GENERATE \ + BETTER_ENUMS_ENUM_CLASS_SWITCH_TYPE_GENERATE +#else +# define BETTER_ENUMS_DEFAULT_SWITCH_TYPE \ + BETTER_ENUMS_REGULAR_ENUM_SWITCH_TYPE +# define BETTER_ENUMS_DEFAULT_SWITCH_TYPE_GENERATE \ + BETTER_ENUMS_REGULAR_ENUM_SWITCH_TYPE_GENERATE +#endif + + + +#ifndef BETTER_ENUMS_DEFAULT_CONSTRUCTOR +# define BETTER_ENUMS_DEFAULT_CONSTRUCTOR(Enum) \ + private: \ + Enum() : _value(0) { } +#endif + + + +#ifdef BETTER_ENUMS_HAVE_CONSTEXPR + +#ifdef BETTER_ENUMS_CONSTEXPR_TO_STRING +# define BETTER_ENUMS_DEFAULT_TRIM_STRINGS_ARRAYS \ + BETTER_ENUMS_CXX11_FULL_CONSTEXPR_TRIM_STRINGS_ARRAYS +# define BETTER_ENUMS_DEFAULT_TO_STRING_KEYWORD \ + BETTER_ENUMS_CONSTEXPR_TO_STRING_KEYWORD +# define BETTER_ENUMS_DEFAULT_DECLARE_INITIALIZE \ + BETTER_ENUMS_DECLARE_EMPTY_INITIALIZE +# define BETTER_ENUMS_DEFAULT_DEFINE_INITIALIZE \ + BETTER_ENUMS_DO_NOT_DEFINE_INITIALIZE +# define BETTER_ENUMS_DEFAULT_CALL_INITIALIZE \ + BETTER_ENUMS_DO_NOT_CALL_INITIALIZE +#else +# define BETTER_ENUMS_DEFAULT_TRIM_STRINGS_ARRAYS \ + BETTER_ENUMS_CXX11_PARTIAL_CONSTEXPR_TRIM_STRINGS_ARRAYS +# define BETTER_ENUMS_DEFAULT_TO_STRING_KEYWORD \ + BETTER_ENUMS_NO_CONSTEXPR_TO_STRING_KEYWORD +# define BETTER_ENUMS_DEFAULT_DECLARE_INITIALIZE \ + BETTER_ENUMS_DO_DECLARE_INITIALIZE +# define BETTER_ENUMS_DEFAULT_DEFINE_INITIALIZE \ + BETTER_ENUMS_DO_DEFINE_INITIALIZE +# define BETTER_ENUMS_DEFAULT_CALL_INITIALIZE \ + BETTER_ENUMS_DO_CALL_INITIALIZE +#endif + + + +// Top-level macros. + +#define BETTER_ENUM(Enum, Underlying, ...) \ + BETTER_ENUMS_ID(BETTER_ENUMS_TYPE( \ + BETTER_ENUMS_CXX11_UNDERLYING_TYPE, \ + BETTER_ENUMS_DEFAULT_SWITCH_TYPE, \ + BETTER_ENUMS_DEFAULT_SWITCH_TYPE_GENERATE, \ + BETTER_ENUMS_DEFAULT_TRIM_STRINGS_ARRAYS, \ + BETTER_ENUMS_DEFAULT_TO_STRING_KEYWORD, \ + BETTER_ENUMS_DEFAULT_DECLARE_INITIALIZE, \ + BETTER_ENUMS_DEFAULT_DEFINE_INITIALIZE, \ + BETTER_ENUMS_DEFAULT_CALL_INITIALIZE, \ + Enum, Underlying, __VA_ARGS__)) + +#define SLOW_ENUM(Enum, Underlying, ...) \ + BETTER_ENUMS_ID(BETTER_ENUMS_TYPE( \ + BETTER_ENUMS_CXX11_UNDERLYING_TYPE, \ + BETTER_ENUMS_DEFAULT_SWITCH_TYPE, \ + BETTER_ENUMS_DEFAULT_SWITCH_TYPE_GENERATE, \ + BETTER_ENUMS_CXX11_FULL_CONSTEXPR_TRIM_STRINGS_ARRAYS, \ + BETTER_ENUMS_CONSTEXPR_TO_STRING_KEYWORD, \ + BETTER_ENUMS_DECLARE_EMPTY_INITIALIZE, \ + BETTER_ENUMS_DO_NOT_DEFINE_INITIALIZE, \ + BETTER_ENUMS_DO_NOT_CALL_INITIALIZE, \ + Enum, Underlying, __VA_ARGS__)) + +#else + +#define BETTER_ENUM(Enum, Underlying, ...) \ + BETTER_ENUMS_ID(BETTER_ENUMS_TYPE( \ + BETTER_ENUMS_LEGACY_UNDERLYING_TYPE, \ + BETTER_ENUMS_DEFAULT_SWITCH_TYPE, \ + BETTER_ENUMS_DEFAULT_SWITCH_TYPE_GENERATE, \ + BETTER_ENUMS_CXX98_TRIM_STRINGS_ARRAYS, \ + BETTER_ENUMS_NO_CONSTEXPR_TO_STRING_KEYWORD, \ + BETTER_ENUMS_DO_DECLARE_INITIALIZE, \ + BETTER_ENUMS_DO_DEFINE_INITIALIZE, \ + BETTER_ENUMS_DO_CALL_INITIALIZE, \ + Enum, Underlying, __VA_ARGS__)) + +#endif + + + +namespace better_enums { + +// Maps. + +template +struct map_compare { + BETTER_ENUMS_CONSTEXPR_ static bool less(const T& a, const T& b) + { return a < b; } +}; + +template <> +struct map_compare { + BETTER_ENUMS_CONSTEXPR_ static bool less(const char *a, const char *b) + { return less_loop(a, b); } + + private: + BETTER_ENUMS_CONSTEXPR_ static bool + less_loop(const char *a, const char *b, size_t index = 0) + { + return + a[index] != b[index] ? a[index] < b[index] : + a[index] == '\0' ? false : + less_loop(a, b, index + 1); + } +}; + +template <> +struct map_compare { + BETTER_ENUMS_CONSTEXPR_ static bool less(const wchar_t *a, const wchar_t *b) + { return less_loop(a, b); } + + private: + BETTER_ENUMS_CONSTEXPR_ static bool + less_loop(const wchar_t *a, const wchar_t *b, size_t index = 0) + { + return + a[index] != b[index] ? a[index] < b[index] : + a[index] == L'\0' ? false : + less_loop(a, b, index + 1); + } +}; + +template > +struct map { + typedef T (*function)(Enum); + + BETTER_ENUMS_CONSTEXPR_ explicit map(function f) : _f(f) { } + + BETTER_ENUMS_CONSTEXPR_ T from_enum(Enum value) const { return _f(value); } + BETTER_ENUMS_CONSTEXPR_ T operator [](Enum value) const + { return _f(value); } + + BETTER_ENUMS_CONSTEXPR_ Enum to_enum(T value) const + { + return + _or_throw(to_enum_nothrow(value), "map::to_enum: invalid argument"); + } + + BETTER_ENUMS_CONSTEXPR_ optional + to_enum_nothrow(T value, size_t index = 0) const + { + return + index >= Enum::_size() ? optional() : + Compare::less(_f(Enum::_values()[index]), value) || + Compare::less(value, _f(Enum::_values()[index])) ? + to_enum_nothrow(value, index + 1) : + Enum::_values()[index]; + } + + private: + const function _f; +}; + +template +BETTER_ENUMS_CONSTEXPR_ map make_map(T (*f)(Enum)) +{ + return map(f); +} + +} + +#define BETTER_ENUMS_DECLARE_STD_HASH(type) \ + namespace std { \ + template <> struct hash \ + { \ + size_t operator()(const type &x) const \ + { \ + return std::hash()(x._to_integral()); \ + } \ + }; \ + } + +#endif // #ifndef BETTER_ENUMS_ENUM_H +// clang-format on diff --git a/src/config.cpp b/src/config.cpp index 255c9d79..6cdbb07d 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -62,6 +62,8 @@ namespace l IFERT("export-support"); IFERT("fsname"); IFERT("fuse_msg_size"); + IFERT("handle-killpriv"); + IFERT("handle-killpriv-v2"); IFERT("mount"); IFERT("nullrw"); IFERT("pid"); @@ -103,6 +105,8 @@ Config::Config() fsname(), func(), fuse_msg_size(FUSE_MAX_MAX_PAGES), + handle_killpriv(true), + handle_killpriv_v2(true), ignorepponrename(false), inodecalc("hybrid-hash"), lazy_umount_mountpoint(false), @@ -114,6 +118,7 @@ Config::Config() nfsopenhack(NFSOpenHack::ENUM::OFF), nullrw(false), parallel_direct_writes(false), + passthrough(PassthroughEnum::off), posix_acl(false), readahead(0), readdir("seq"), @@ -180,6 +185,8 @@ Config::Config() _map["func.unlink"] = &func.unlink; _map["func.utimens"] = &func.utimens; _map["fuse_msg_size"] = &fuse_msg_size; + _map["handle-killpriv"] = &handle_killpriv; + _map["handle-killpriv-v2"] = &handle_killpriv_v2; _map["ignorepponrename"] = &ignorepponrename; _map["inodecalc"] = &inodecalc; _map["kernel_cache"] = &kernel_cache; @@ -194,6 +201,7 @@ Config::Config() _map["nullrw"] = &nullrw; _map["pid"] = &pid; _map["parallel-direct-writes"] = ¶llel_direct_writes; + _map["passthrough"] = &passthrough; _map["pin-threads"] = &fuse_pin_threads; _map["posix_acl"] = &posix_acl; _map["readahead"] = &readahead; diff --git a/src/config.hpp b/src/config.hpp index c56a303c..e468bb8b 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -21,12 +21,13 @@ #include "config_cachefiles.hpp" #include "config_flushonclose.hpp" #include "config_follow_symlinks.hpp" -#include "config_pid.hpp" #include "config_inodecalc.hpp" #include "config_link_exdev.hpp" #include "config_log_metrics.hpp" #include "config_moveonenospc.hpp" #include "config_nfsopenhack.hpp" +#include "config_passthrough.hpp" +#include "config_pid.hpp" #include "config_rename_exdev.hpp" #include "config_set.hpp" #include "config_statfs.hpp" @@ -125,6 +126,8 @@ public: ConfigSTR fsname; Funcs func; ConfigUINT64 fuse_msg_size; + ConfigBOOL handle_killpriv; + ConfigBOOL handle_killpriv_v2; ConfigBOOL ignorepponrename; InodeCalc inodecalc; ConfigBOOL kernel_cache; @@ -137,6 +140,7 @@ public: NFSOpenHack nfsopenhack; ConfigBOOL nullrw; ConfigBOOL parallel_direct_writes; + Passthrough passthrough; ConfigGetPid pid; ConfigBOOL posix_acl; ConfigUINT64 readahead; diff --git a/src/config_passthrough.cpp b/src/config_passthrough.cpp new file mode 100644 index 00000000..2b106032 --- /dev/null +++ b/src/config_passthrough.cpp @@ -0,0 +1,43 @@ +#/* + ISC License + + Copyright (c) 2024, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "config_passthrough.hpp" +#include "ef.hpp" +#include "errno.hpp" + +template<> +std::string +Passthrough::to_string() const +{ + return _data._to_string(); +} + +template<> +int +Passthrough::from_string(const std::string &s_) +{ + better_enums::optional e; + + e = PassthroughEnum::_from_string_nothrow(s_.c_str()); + if(!e) + return -EINVAL; + + _data = *e; + + return 0; +} diff --git a/src/config_passthrough.hpp b/src/config_passthrough.hpp new file mode 100644 index 00000000..721abcbc --- /dev/null +++ b/src/config_passthrough.hpp @@ -0,0 +1,28 @@ +/* + ISC License + + Copyright (c) 2024, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "better_enum.hpp" + +#include "enum.hpp" + + +BETTER_ENUM(PassthroughEnum, uint8_t, off, r, w, ro, wo, rw); + +typedef Enum Passthrough; diff --git a/src/enum.hpp b/src/enum.hpp index b4792bb9..7df0b990 100644 --- a/src/enum.hpp +++ b/src/enum.hpp @@ -60,6 +60,12 @@ public: return _data; } + ENUM + operator+() const + { + return _data; + } + public: bool operator==(const ENUM data_) const { diff --git a/src/fileinfo.hpp b/src/fileinfo.hpp index 4c8beaba..6230a067 100644 --- a/src/fileinfo.hpp +++ b/src/fileinfo.hpp @@ -31,12 +31,14 @@ public: bool const direct_io_) : FH(fusepath_), fd(fd_), + backing_id(0), direct_io(direct_io_) { } public: int fd; + int backing_id; uint32_t direct_io:1; std::mutex mutex; }; diff --git a/src/fuse_create.cpp b/src/fuse_create.cpp index 45ee5c68..b35c0de0 100644 --- a/src/fuse_create.cpp +++ b/src/fuse_create.cpp @@ -170,6 +170,29 @@ namespace l return 0; } + static + int + passthrough(const fuse_context *fc_, + fuse_file_info_t *ffi_) + { + int backing_id; + FileInfo *fi; + const ugid::SetRootGuard ugid; + + fi = reinterpret_cast(ffi_->fh); + + backing_id = fuse_passthrough_open(fc_,fi->fd); + if(backing_id <= 0) + return 0; + + ffi_->passthrough = true; + ffi_->keep_cache = false; + fi->backing_id = backing_id; + ffi_->backing_id = backing_id; + + return 0; + } + static int create(const Policy::Search &searchFunc_, @@ -208,6 +231,15 @@ namespace l } } +constexpr +const +uint64_t +_(const PassthroughEnum e_, + const uint64_t m_) +{ + return ((((uint64_t)e_) << 32) | (m_ & O_ACCMODE)); +} + namespace FUSE { int @@ -247,6 +279,23 @@ namespace FUSE fc->umask); } + if(rv != 0) + return rv; + + switch(_(cfg->passthrough,ffi_->flags)) + { + case _(PassthroughEnum::r, O_RDONLY): + case _(PassthroughEnum::r, O_RDWR): + case _(PassthroughEnum::w, O_WRONLY): + case _(PassthroughEnum::w, O_RDWR): + case _(PassthroughEnum::ro,O_RDONLY): + case _(PassthroughEnum::wo,O_WRONLY): + case _(PassthroughEnum::rw,O_RDONLY): + case _(PassthroughEnum::rw,O_WRONLY): + case _(PassthroughEnum::rw,O_RDWR): + return l::passthrough(fc,ffi_); + } + return rv; } } diff --git a/src/fuse_init.cpp b/src/fuse_init.cpp index cea9513d..ee825a22 100644 --- a/src/fuse_init.cpp +++ b/src/fuse_init.cpp @@ -147,8 +147,11 @@ namespace FUSE l::want_if_capable(conn_,FUSE_CAP_DIRECT_IO_ALLOW_MMAP,&cfg->direct_io_allow_mmap); l::want_if_capable(conn_,FUSE_CAP_DONT_MASK); l::want_if_capable(conn_,FUSE_CAP_EXPORT_SUPPORT,&cfg->export_support); + l::want_if_capable(conn_,FUSE_CAP_HANDLE_KILLPRIV,&cfg->handle_killpriv); + l::want_if_capable(conn_,FUSE_CAP_HANDLE_KILLPRIV_V2,&cfg->handle_killpriv_v2); l::want_if_capable(conn_,FUSE_CAP_IOCTL_DIR); l::want_if_capable(conn_,FUSE_CAP_PARALLEL_DIROPS); + l::want_if_capable(conn_,FUSE_CAP_PASSTHROUGH); l::want_if_capable(conn_,FUSE_CAP_POSIX_ACL,&cfg->posix_acl); l::want_if_capable(conn_,FUSE_CAP_READDIR_PLUS,&cfg->readdirplus); l::want_if_capable(conn_,FUSE_CAP_WRITEBACK_CACHE,&cfg->writeback_cache); diff --git a/src/fuse_open.cpp b/src/fuse_open.cpp index d49ed797..742e0bfd 100644 --- a/src/fuse_open.cpp +++ b/src/fuse_open.cpp @@ -218,6 +218,29 @@ namespace l return 0; } + static + int + passthrough(const fuse_context *fc_, + fuse_file_info_t *ffi_) + { + int backing_id; + FileInfo *fi; + const ugid::SetRootGuard ugid; + + fi = reinterpret_cast(ffi_->fh); + + backing_id = fuse_passthrough_open(fc_,fi->fd); + if(backing_id <= 0) + return 0; + + ffi_->passthrough = true; + ffi_->keep_cache = true; + fi->backing_id = backing_id; + ffi_->backing_id = backing_id; + + return 0; + } + static int open(const Policy::Search &searchFunc_, @@ -238,6 +261,15 @@ namespace l } } +constexpr +const +uint64_t +_(const PassthroughEnum e_, + const uint64_t m_) +{ + return ((((uint64_t)e_) << 32) | (m_ & O_ACCMODE)); +} + namespace FUSE { int @@ -264,6 +296,23 @@ namespace FUSE cfg->link_cow, cfg->nfsopenhack); + if(rv != 0) + return rv; + + switch(_(cfg->passthrough,ffi_->flags)) + { + case _(PassthroughEnum::r, O_RDONLY): + case _(PassthroughEnum::r, O_RDWR): + case _(PassthroughEnum::w, O_WRONLY): + case _(PassthroughEnum::w, O_RDWR): + case _(PassthroughEnum::ro,O_RDONLY): + case _(PassthroughEnum::wo,O_WRONLY): + case _(PassthroughEnum::rw,O_RDONLY): + case _(PassthroughEnum::rw,O_WRONLY): + case _(PassthroughEnum::rw,O_RDWR): + return l::passthrough(fc,ffi_); + } + return rv; } } diff --git a/src/fuse_release.cpp b/src/fuse_release.cpp index 03b6dcdb..29d9ee20 100644 --- a/src/fuse_release.cpp +++ b/src/fuse_release.cpp @@ -19,6 +19,7 @@ #include "fileinfo.hpp" #include "fs_close.hpp" #include "fs_fadvise.hpp" +#include "ugid.hpp" #include "fuse.h" @@ -40,6 +41,16 @@ namespace l fs::fadvise_dontneed(fi_->fd); } + if(fi_->backing_id) + { + const fuse_context *fc; + const ugid::SetRootGuard ugid; + + fc = fuse_get_context(); + + fuse_passthrough_close(fc,fi_->backing_id); + } + fs::close(fi_->fd); delete fi_;